Multi-step cart workflows for both shipment and allocation requests: - Ship-to address selection from Epicor portal_CustomerShipToAddresses - Inventory browser dialog (shipment) / coil browser dialog (allocation) - Cart persistence in DB with duplicate detection - Details form (order#, PO#, pickup date, instructions, email recipients) - Review & submit with confirmation dialog - Cancel with permission gate and confirmation - Shared components: stepper, ship-to selector, cart table, header form, review - 16 API routes, 4 services, 13 components, 6 pages - Phase 2 complete: 16/16 tasks done Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
70 lines
2 KiB
TypeScript
70 lines
2 KiB
TypeScript
/**
|
|
* Available Coils Service
|
|
*
|
|
* Queries Epicor for coils available for allocation to a specific job.
|
|
* The legacy system used CoilAllocation.sql with JobNum + DBNAME params.
|
|
* This queries PartLot/PartBin data filtered by job number.
|
|
*/
|
|
|
|
import { execQuery, getPortalDbName } from '@/lib/epicor';
|
|
import type { AvailableCoilItem } from '@/types/requests';
|
|
|
|
type EpicorCoilRow = {
|
|
PartNum: string;
|
|
LotNum: string | null;
|
|
CoilNum: string | null;
|
|
OnHandQty: number;
|
|
Plant: string | null;
|
|
Warehouse: string | null;
|
|
};
|
|
|
|
/**
|
|
* Get available coils for allocation to a specific job.
|
|
*
|
|
* This is a best-effort query — the legacy CoilAllocation.sql may not be available
|
|
* as a view in the current Epicor database. If the query fails, we return an empty
|
|
* array gracefully.
|
|
*/
|
|
export async function getAvailableCoils(
|
|
custId: string,
|
|
dbName: string,
|
|
jobNumber: string
|
|
): Promise<AvailableCoilItem[]> {
|
|
try {
|
|
// Try the portal coil allocation view/query
|
|
// Legacy used: SELECT ... FROM CoilAllocation WHERE JobNum = @JobNum AND DBNAME = @DBNAME
|
|
const query = `
|
|
SELECT
|
|
PartNum,
|
|
LotNum,
|
|
CoilNum,
|
|
OnHandQty,
|
|
Plant,
|
|
WarehouseCode AS Warehouse
|
|
FROM portal_CoilAllocation
|
|
WHERE JobNum = @JobNum
|
|
ORDER BY PartNum, LotNum, CoilNum
|
|
`;
|
|
|
|
const rows = await execQuery<EpicorCoilRow[]>(query, {
|
|
JobNum: jobNumber,
|
|
});
|
|
|
|
return rows.map((row) => ({
|
|
part_num: row.PartNum?.trim() ?? '',
|
|
lot_num: row.LotNum?.trim() || null,
|
|
coil_number: row.CoilNum?.trim() || null,
|
|
on_hand_qty: Number(row.OnHandQty ?? 0),
|
|
plant: row.Plant?.trim() || null,
|
|
warehouse: row.Warehouse?.trim() || null,
|
|
}));
|
|
} catch (error) {
|
|
// The coil allocation view may not exist yet — fail gracefully
|
|
console.warn(
|
|
'[available-coils] Failed to query coil allocation data. ' +
|
|
'The portal_CoilAllocation view may need to be created in Epicor.',
|
|
error instanceof Error ? error.message : error
|
|
);
|
|
return [];
|
|
}
|
|
}
|