|
- import { ItemLotTraceJoPrelude, ItemLotTraceMaterialInput } from "@/app/api/itemTracing";
- import {
- TraceGraphDetailField,
- TraceGraphDetailLabels,
- TraceGraphNode,
- } from "./buildTraceGraphNodes";
- import {
- buildProductionGraphNodes,
- ProductionGraphLabels,
- } from "./buildProductionGraphNodes";
- import {
- groupQcResultsBySession,
- } from "./traceLabelUtils";
- import { formatQty, formatSignedQty } from "./traceQtyUtils";
- import {
- createMaterialPickNode,
- docLinkFromOriginType,
- docLinkFromRefType,
- field,
- fieldIf,
- detailsOf,
- isJoProducedMaterial,
- parseSortKey,
- } from "./traceNodeFactory";
- import { buildPickOrderTargetDateMapFromPrelude } from "./traceDocLinkUtils";
- import { resolvePutawayPresentation, isTransferInboundPutaway } from "./tracePutawayUtils";
-
- export interface JoPreludeGraphLabels extends TraceGraphDetailLabels {
- nodeMaterialIn: string;
- nodeMaterialPick: string;
- nodePurchase: string;
- nodeReceipt: string;
- nodeJoCreated: string;
- pickOrder: string;
- }
-
- type LotPickRef = {
- pickOrderCode: string;
- pickOrderId: number | null;
- consoCode: string;
- };
-
- type PreludeBuildContext = {
- seenPurchaseLines: Set<number>;
- seenInboundLots: Set<number>;
- seenReceiptLots: Set<number>;
- seenQcKeys: Set<string>;
- seenPutawayKeys: Set<string>;
- pickOrderTargetDateMap: Map<string, string>;
- seq: number;
- };
-
- const materialLotKey = (itemCode: string, lotNo: string) => `${itemCode}::${lotNo}`;
-
- const mergeLotPickMaps = (
- target: Map<string, LotPickRef[]>,
- source: Map<string, LotPickRef[]>,
- ): void => {
- source.forEach((picks, key) => {
- const list = target.get(key) ?? [];
- picks.forEach((p) => {
- if (!list.some((existing) => existing.pickOrderCode === p.pickOrderCode)) {
- list.push(p);
- }
- });
- if (list.length) target.set(key, list);
- });
- };
-
- const buildLotPickOrderMap = (
- materialInputs: ItemLotTraceJoPrelude["materialInputs"],
- ): Map<string, LotPickRef[]> => {
- const map = new Map<string, LotPickRef[]>();
- materialInputs.forEach((m) => {
- const code = m.pickOrderCode?.trim();
- if (!code) return;
- const key = materialLotKey(m.materialItemCode, m.materialLotNo);
- const list = map.get(key) ?? [];
- if (!list.some((p) => p.pickOrderCode === code)) {
- list.push({
- pickOrderCode: code,
- pickOrderId: m.pickOrderId,
- consoCode: m.consoCode,
- });
- }
- map.set(key, list);
- const nestedInputs = m.nestedJoPrelude?.materialInputs ?? [];
- if (nestedInputs.length) {
- mergeLotPickMaps(map, buildLotPickOrderMapRecursive(nestedInputs));
- }
- });
- return map;
- };
-
- const buildLotPickOrderMapRecursive = (
- materialInputs: ItemLotTraceMaterialInput[],
- ): Map<string, LotPickRef[]> => buildLotPickOrderMap(materialInputs);
-
- const pickOrdersForLot = (
- map: Map<string, LotPickRef[]>,
- itemCode: string,
- lotNo: string,
- ): LotPickRef[] => map.get(materialLotKey(itemCode, lotNo)) ?? [];
-
- const pickOrderCodesLabel = (picks: LotPickRef[]) =>
- picks.map((p) => p.pickOrderCode).join(" · ");
-
- const appendPickToSubtitle = (base: string, picks: LotPickRef[], pickLabel: string): string => {
- if (!picks.length) return base;
- const suffix = `${pickLabel}: ${pickOrderCodesLabel(picks)}`;
- return base ? `${base} · ${suffix}` : suffix;
- };
-
- const pickOrderDetailFields = (
- picks: LotPickRef[],
- pickLabel: string,
- ): TraceGraphDetailField[] =>
- picks.map((p) =>
- field(pickLabel, p.pickOrderCode, {
- linkKind: "pick",
- linkCode: p.pickOrderCode,
- linkId: p.pickOrderId,
- consoCode: p.consoCode || p.pickOrderCode,
- }),
- );
-
- const nextSeq = (ctx: PreludeBuildContext): number => ctx.seq++;
-
- const pickCtx = (ctx: PreludeBuildContext) => ({
- nextSeq: () => nextSeq(ctx),
- pickOrderTargetDate: (pickOrderCode: string) =>
- ctx.pickOrderTargetDateMap.get(pickOrderCode?.trim() ?? ""),
- });
-
- const buildMaterialProductionNodes = (
- m: ItemLotTraceMaterialInput,
- labels: JoPreludeGraphLabels & Partial<ProductionGraphLabels>,
- ctx: PreludeBuildContext,
- ): TraceGraphNode[] => {
- const steps = m.productionSteps ?? [];
- if (!steps.length || !labels.nodeProductionStep || !labels.nodeScrap || !labels.nodeDefect) return [];
-
- const matUom = m.materialUom?.trim() || "";
- return buildProductionGraphNodes(steps, [], labels as ProductionGraphLabels, matUom).map((node) => ({
- ...node,
- id: `mat-prod-${m.materialLotNo}-${node.id}`,
- traceLotNo: m.materialLotNo,
- traceItemCode: m.materialItemCode,
- subtitle: [m.materialItemCode, m.materialLotNo, node.subtitle].filter(Boolean).join(" · "),
- }));
- };
-
- const buildMaterialStockInPreludeNodes = (
- m: ItemLotTraceMaterialInput,
- labels: JoPreludeGraphLabels,
- lotPicks: Map<string, LotPickRef[]>,
- ctx: PreludeBuildContext,
- ): TraceGraphNode[] => {
- const nodes: TraceGraphNode[] = [];
- const picks = pickOrdersForLot(lotPicks, m.materialItemCode, m.materialLotNo);
- const lotId = m.materialInventoryLotId;
- const origin = m.stockInOrigin;
- const matUom = m.materialUom?.trim() || "";
-
- (m.purchaseEvents ?? []).forEach((p) => {
- if (ctx.seenPurchaseLines.has(p.purchaseOrderLineId)) return;
- ctx.seenPurchaseLines.add(p.purchaseOrderLineId);
- const purchaseUnit = p.purchaseUnit;
- const supplierLabel = [p.supplierCode, p.supplierName].filter(Boolean).join(" ");
- const subtitle =
- [supplierLabel, p.itemCode, p.itemName].filter(Boolean).join(" · ") || m.materialItemCode;
- const meta = [supplierLabel].filter(Boolean).join(" · ");
- nodes.push({
- id: `mat-purchase-${p.purchaseOrderLineId}-${m.materialLotNo}`,
- kind: "PURCHASE",
- timestamp: p.orderDate,
- sortKey: parseSortKey(p.orderDate, nextSeq(ctx)),
- title: labels.nodePurchase,
- subtitle: appendPickToSubtitle(subtitle, picks, labels.pickOrder),
- qty: p.orderQty,
- uom: purchaseUnit,
- meta: meta || undefined,
- refType: "PO",
- refCode: p.purchaseOrderCode,
- refId: p.purchaseOrderId,
- docLinkKind: "po",
- traceLotNo: m.materialLotNo,
- traceItemCode: m.materialItemCode,
- categoryLabel: labels.categoryPurchase,
- details: [
- field(labels.detailItemCode, p.itemCode),
- field(labels.detailItemName, p.itemName),
- field(labels.detailSupplier, supplierLabel),
- field(labels.detailOrderQty, formatQty(p.orderQty, purchaseUnit)),
- field(labels.detailPurchaseUnit, p.purchaseUnit),
- field(labels.detailPurchaseOrderNo, p.purchaseOrderCode, {
- linkKind: "po",
- linkCode: p.purchaseOrderCode,
- linkId: p.purchaseOrderId,
- }),
- field(labels.detailTime, p.orderDate),
- field(labels.detailLot, m.materialLotNo),
- ...pickOrderDetailFields(picks, labels.pickOrder),
- ],
- });
- });
-
- if (origin && lotId != null) {
- const originType = origin.type.trim().toUpperCase();
- const linkKind = docLinkFromOriginType(origin.type);
- const subtitle = [
- m.materialLotNo,
- [origin.supplierCode, origin.supplierName].filter(Boolean).join(" "),
- ]
- .filter(Boolean)
- .join(" · ");
-
- if (originType === "PO" && !ctx.seenReceiptLots.has(lotId)) {
- ctx.seenReceiptLots.add(lotId);
- nodes.push({
- id: `mat-receipt-${lotId}-${origin.stockInLineId}`,
- kind: "RECEIPT",
- timestamp: origin.receiptDate,
- sortKey: parseSortKey(origin.receiptDate, nextSeq(ctx)),
- title: labels.nodeReceipt,
- subtitle: appendPickToSubtitle(
- [origin.refCode, subtitle].filter(Boolean).join(" · ") || m.materialItemCode,
- picks,
- labels.pickOrder,
- ),
- qty: origin.acceptedQty,
- uom: matUom,
- refType: origin.type,
- refCode: origin.refCode,
- refId: origin.refId,
- docLinkKind: linkKind,
- traceLotNo: m.materialLotNo,
- traceItemCode: m.materialItemCode,
- categoryLabel: labels.categoryReceipt,
- details: [
- field(labels.detailPurchaseOrderNo, origin.refCode, {
- linkKind,
- linkCode: origin.refCode,
- linkId: origin.refId,
- }),
- field(labels.detailSupplier, [origin.supplierCode, origin.supplierName].filter(Boolean).join(" ")),
- field(labels.detailItemCode, m.materialItemCode),
- field(labels.detailLot, m.materialLotNo),
- field(labels.detailQty, formatQty(origin.acceptedQty, matUom)),
- field(labels.detailStatus, labels.tr.stockInStatus(origin.status)),
- field(labels.detailTime, origin.receiptDate),
- ...pickOrderDetailFields(picks, labels.pickOrder),
- ],
- });
- } else if (
- originType !== "PO" &&
- originType !== "ADJ" &&
- !ctx.seenInboundLots.has(lotId)
- ) {
- // Nested JO_CREATED already represents this JO output lot (with 提料 links).
- const skipJoOriginBecauseNested =
- originType === "JO" && Boolean(m.nestedJoPrelude?.jobOrder?.jobOrderCode?.trim());
- if (!skipJoOriginBecauseNested) {
- // ADJ is emitted after putaways as ADJUSTMENT (FG-aligned: 上架 → 庫存調整).
- ctx.seenInboundLots.add(lotId);
- const isJoOrigin = originType === "JO";
- nodes.push({
- id: `mat-in-${lotId}-${origin.stockInLineId}`,
- kind: isJoOrigin ? "JO_CREATED" : "MATERIAL_IN",
- timestamp: origin.receiptDate,
- sortKey: parseSortKey(origin.receiptDate, nextSeq(ctx)),
- title: isJoOrigin
- ? labels.nodeJoCreated
- : `${labels.tr.refType(origin.type)} · ${labels.nodeMaterialIn}`,
- subtitle: appendPickToSubtitle(
- subtitle || m.materialItemCode,
- picks,
- labels.pickOrder,
- ),
- qty: origin.acceptedQty,
- uom: matUom,
- refType: origin.type,
- refCode: origin.refCode,
- refId: origin.refId,
- docLinkKind: linkKind,
- traceLotNo: m.materialLotNo,
- traceItemCode: m.materialItemCode,
- categoryLabel: isJoOrigin ? labels.categoryProduction : labels.categoryPurchase,
- details: [
- field(labels.detailType, isJoOrigin ? labels.nodeJoCreated : labels.tr.refType(origin.type)),
- field(
- isJoOrigin
- ? labels.jobOrder
- : originType === "PO"
- ? labels.detailPurchaseOrderNo
- : labels.detailSourceDoc,
- origin.refCode,
- {
- linkKind,
- linkCode: origin.refCode,
- linkId: origin.refId,
- }),
- field(labels.detailSupplier, [origin.supplierCode, origin.supplierName].filter(Boolean).join(" ")),
- field(labels.detailItemCode, m.materialItemCode),
- field(labels.detailLot, m.materialLotNo),
- field(labels.detailQty, formatQty(origin.acceptedQty, matUom)),
- field(labels.detailStatus, labels.tr.stockInStatus(origin.status)),
- field(labels.detailTime, origin.receiptDate),
- ...pickOrderDetailFields(picks, labels.pickOrder),
- ],
- });
- }
- }
- }
-
- groupQcResultsBySession(m.qcResults).forEach((group) => {
- const first = group[0];
- if (!first) return;
- const key = `${first.stockInLineId}-${first.created}`;
- if (ctx.seenQcKeys.has(key)) return;
- ctx.seenQcKeys.add(key);
- const allPassed = group.every((q) => q.qcPassed);
- const totalFail = group.reduce((s, q) => s + (q.failQty ?? 0), 0);
- const meta = [first.handledBy, ...group.map((q) => q.remarks).filter(Boolean), m.materialLotNo]
- .filter(Boolean)
- .join(" · ");
- const qcTypeRaw = group.map((q) => q.qcType).find((t) => t?.trim()) ?? "";
- nodes.push({
- id: `mat-qc-${key}`,
- kind: "MATERIAL_QC",
- timestamp: first.created,
- sortKey: parseSortKey(first.created, nextSeq(ctx)),
- title: allPassed ? labels.nodeQcPass : labels.nodeQcFail,
- subtitle: "",
- qty: totalFail,
- uom: matUom,
- meta: meta || undefined,
- traceLotNo: m.materialLotNo,
- traceItemCode: m.materialItemCode,
- categoryLabel: labels.categoryQc,
- qcTypeLabel: labels.tr.qcType(qcTypeRaw),
- details: detailsOf(
- field(labels.detailItemCode, m.materialItemCode),
- field(labels.detailLot, m.materialLotNo),
- field(labels.detailStatus, labels.tr.qcPassed(allPassed)),
- field(labels.detailQcType, labels.tr.qcType(qcTypeRaw)),
- field(labels.detailQcCriteria, "", {
- variant: "qcCriteriaList",
- qcCriteriaItems: group.map((q) => ({
- name: q.qcItemName?.trim() || q.qcItemCode?.trim() || labels.detailQcUnknownItem,
- description: q.qcItemDescription?.trim() || undefined,
- passed: q.qcPassed,
- failQty: q.failQty > 0 ? q.failQty : undefined,
- })),
- }),
- field(labels.detailAcceptedQty, formatQty(first.acceptedQty, matUom)),
- field(labels.detailFailQty, formatQty(totalFail, matUom)),
- field(labels.detailHandler, first.handledBy),
- field(labels.detailTime, first.created),
- fieldIf(labels.detailRemarks, group.map((q) => q.remarks).filter(Boolean).join(";")),
- ...pickOrderDetailFields(picks, labels.pickOrder),
- ),
- });
- });
-
- let lastMaterialPutawayWh = "";
- (m.putawayEvents ?? []).forEach((p) => {
- const key = `${p.inventoryLotLineId}-${p.timestamp}`;
- if (ctx.seenPutawayKeys.has(key)) return;
- if (isTransferInboundPutaway(p.refType)) return;
- ctx.seenPutawayKeys.add(key);
- const linkKind = docLinkFromRefType(p.refType);
- const pres = resolvePutawayPresentation(
- labels.tr,
- {
- nodePutaway: labels.nodePutaway,
- nodePutawayTransfer: labels.nodePutawayTransfer,
- putawayTransferDetail: labels.putawayTransferDetail,
- },
- p.status,
- p.refType,
- );
- const meta = [pres.chipLabel, p.handledBy, p.warehouseCode].filter(Boolean).join(" · ");
- if (p.warehouseCode?.trim()) lastMaterialPutawayWh = p.warehouseCode.trim();
- nodes.push({
- id: `mat-putaway-${p.inventoryLotLineId}-${p.timestamp}`,
- kind: "PUTAWAY",
- timestamp: p.timestamp,
- sortKey: parseSortKey(p.timestamp, nextSeq(ctx)),
- title: pres.title,
- subtitle: appendPickToSubtitle(
- [pres.chipLabel, p.refCode, p.warehouseCode, m.materialLotNo].filter(Boolean).join(" · ") ||
- m.materialItemCode,
- picks,
- labels.pickOrder,
- ),
- qty: p.qty,
- uom: matUom,
- meta: meta || undefined,
- refType: p.refType,
- refCode: p.refCode,
- refId: p.refId,
- docLinkKind: linkKind,
- putawayStatusLabel: pres.chipLabel,
- putawayStatusDetail: pres.statusDetail,
- warehouseCode: p.warehouseCode,
- traceLotNo: m.materialLotNo,
- traceItemCode: m.materialItemCode,
- categoryLabel: labels.categoryPutaway,
- details: [
- field(labels.detailStatus, pres.statusDetail),
- field(labels.detailItemCode, m.materialItemCode),
- field(labels.detailLot, m.materialLotNo),
- field(
- isTransferInboundPutaway(p.refType)
- ? labels.detailInboundSiNo
- : (p.refType ?? "").trim().toUpperCase() === "PO"
- ? labels.detailPurchaseOrderNo
- : (p.refType ?? "").trim().toUpperCase() === "JO"
- ? labels.jobOrder
- : labels.detailSourceDoc,
- p.refCode,
- {
- linkKind,
- linkCode: p.refCode,
- linkId: p.refId,
- }),
- field(labels.detailPutawayBin, p.warehouseCode),
- field(labels.detailQty, formatQty(p.qty, matUom)),
- field(labels.detailHandler, p.handledBy),
- field(labels.detailTime, p.timestamp),
- ...pickOrderDetailFields(picks, labels.pickOrder),
- ],
- });
- });
-
- // ADJ stock-in: match FG — 上架 → 庫存調整 (入), not orphaned 「調整 · 原料入庫」.
- if (origin && lotId != null) {
- const originType = origin.type.trim().toUpperCase();
- if (originType === "ADJ" && !ctx.seenInboundLots.has(lotId)) {
- ctx.seenInboundLots.add(lotId);
- const adjWh = lastMaterialPutawayWh;
- const meta = [labels.tr.stockInStatus(origin.status)].filter(Boolean).join(" · ");
- // Place at/after related putaway so chronological X and 上架 → 調整 edges match FG.
- const lastPutawayTs = (m.putawayEvents ?? [])
- .map((p) => p.timestamp)
- .filter((t): t is string => Boolean(t?.trim()))
- .sort()
- .at(-1);
- const adjTimestamp =
- lastPutawayTs &&
- parseSortKey(lastPutawayTs, 0) >= parseSortKey(origin.receiptDate, 0)
- ? lastPutawayTs
- : origin.receiptDate;
- // Keep sortKey strictly after matching putaway so layout X stays 上架 → 調整
- // (same calendar time previously ordered adj left of putaway → back-arrow).
- const putawaySort = lastPutawayTs ? parseSortKey(lastPutawayTs, 0) : null;
- const adjSortBase = parseSortKey(adjTimestamp, nextSeq(ctx));
- nodes.push({
- id: `mat-adj-${lotId}-${origin.stockInLineId}`,
- kind: "ADJUSTMENT",
- timestamp: origin.receiptDate,
- sortKey:
- putawaySort != null && adjSortBase <= putawaySort ? putawaySort + 1 : adjSortBase,
- title: `${labels.nodeAdjustment} (${labels.tr.direction("IN")})`,
- subtitle: appendPickToSubtitle(
- [labels.tr.adjustmentType("ADJ"), origin.refCode || m.materialLotNo]
- .filter(Boolean)
- .join(" · ") || m.materialItemCode,
- picks,
- labels.pickOrder,
- ),
- qty: origin.acceptedQty,
- uom: matUom,
- adjustmentDirection: "IN",
- meta: meta || undefined,
- refType: origin.type,
- refCode: origin.refCode,
- refId: origin.refId,
- warehouseCode: adjWh || undefined,
- traceLotNo: m.materialLotNo,
- traceItemCode: m.materialItemCode,
- categoryLabel: labels.categoryAdjustment,
- details: detailsOf(
- field(labels.detailVariance, formatSignedQty(origin.acceptedQty, "IN", matUom)),
- field(labels.detailAdjustmentRef, origin.refCode),
- field(labels.detailItemCode, m.materialItemCode),
- field(labels.detailLot, m.materialLotNo),
- ...(adjWh ? [field(labels.detailWarehouse, adjWh)] : []),
- field(labels.detailStatus, labels.tr.stockInStatus(origin.status)),
- field(labels.detailTime, origin.receiptDate),
- ...pickOrderDetailFields(picks, labels.pickOrder),
- ),
- });
- }
- }
-
- return nodes;
- };
-
- const buildNestedJoCreatedNode = (
- m: ItemLotTraceMaterialInput,
- labels: JoPreludeGraphLabels,
- picks: LotPickRef[],
- ctx: PreludeBuildContext,
- ): TraceGraphNode | null => {
- const nested = m.nestedJoPrelude;
- const jo = nested?.jobOrder;
- if (!jo?.jobOrderCode?.trim()) return null;
-
- const nestedInputs = nested?.materialInputs ?? [];
- const nestedPickTs = nestedInputs
- .map((input) => input.pickedAt)
- .find((ts) => ts?.trim());
- const nestedProdTs = (m.productionSteps ?? [])
- .map((step) => step.startTime)
- .find((ts) => ts?.trim());
- const origin = m.stockInOrigin;
- const ts =
- jo.createdAt?.trim() ||
- jo.planStart?.trim() ||
- nestedPickTs?.trim() ||
- nestedProdTs?.trim() ||
- origin?.receiptDate?.trim() ||
- null;
- const matUom = m.materialUom?.trim() || "";
- const qty = origin?.acceptedQty ?? jo.reqQty;
-
- return {
- id: `mat-jo-created-${jo.jobOrderId}-${m.materialLotNo}`,
- kind: "JO_CREATED",
- timestamp: ts,
- sortKey: parseSortKey(ts, nextSeq(ctx)),
- title: labels.nodeJoCreated,
- subtitle: appendPickToSubtitle(
- [m.materialLotNo, m.materialItemCode].filter(Boolean).join(" · ") || jo.jobOrderCode,
- picks,
- labels.pickOrder,
- ),
- qty,
- uom: matUom,
- refType: "JO",
- refCode: jo.jobOrderCode,
- refId: jo.jobOrderId,
- docLinkKind: "jo",
- traceLotNo: m.materialLotNo,
- traceItemCode: m.materialItemCode,
- categoryLabel: labels.categoryProduction,
- details: [
- field(labels.detailType, labels.nodeJoCreated),
- field(labels.jobOrder, jo.jobOrderCode, {
- linkKind: "jo",
- linkCode: jo.jobOrderCode,
- linkId: jo.jobOrderId,
- }),
- field(labels.detailItemCode, m.materialItemCode),
- field(labels.detailLot, m.materialLotNo),
- field(labels.detailQty, formatQty(qty, matUom)),
- field(labels.detailTime, ts),
- ...pickOrderDetailFields(picks, labels.pickOrder),
- ],
- };
- };
-
- const buildMaterialInputChainNodes = (
- materialInputs: ItemLotTraceMaterialInput[],
- labels: JoPreludeGraphLabels & Partial<ProductionGraphLabels>,
- lotPicks: Map<string, LotPickRef[]>,
- ctx: PreludeBuildContext,
- ): TraceGraphNode[] => {
- const nodes: TraceGraphNode[] = [];
-
- materialInputs.forEach((m) => {
- const joProduced = isJoProducedMaterial(m);
- const nestedInputs = m.nestedJoPrelude?.materialInputs ?? [];
- const picks = pickOrdersForLot(lotPicks, m.materialItemCode, m.materialLotNo);
-
- if (joProduced && m.nestedJoPrelude) {
- const nestedJoCreated = buildNestedJoCreatedNode(m, labels, picks, ctx);
- if (nestedJoCreated) nodes.push(nestedJoCreated);
- }
-
- if (joProduced && nestedInputs.length > 0) {
- const nestedLotPicks = buildLotPickOrderMap(nestedInputs);
- nodes.push(
- ...buildMaterialInputChainNodes(
- nestedInputs,
- labels,
- nestedLotPicks,
- ctx,
- ),
- );
- nestedInputs.forEach((nm, ni) => {
- nodes.push(createMaterialPickNode(nm, ni, labels, pickCtx(ctx), m.materialLotNo));
- });
- }
-
- if (joProduced && (m.productionSteps?.length ?? 0) > 0) {
- nodes.push(...buildMaterialProductionNodes(m, labels, ctx));
- }
-
- nodes.push(...buildMaterialStockInPreludeNodes(m, labels, lotPicks, ctx));
- });
-
- return nodes;
- };
-
- export const buildJoPreludeGraphNodes = (
- joPrelude: ItemLotTraceJoPrelude,
- labels: JoPreludeGraphLabels & Partial<ProductionGraphLabels>,
- ): TraceGraphNode[] => {
- const ctx: PreludeBuildContext = {
- seenPurchaseLines: new Set(),
- seenInboundLots: new Set(),
- seenReceiptLots: new Set(),
- seenQcKeys: new Set(),
- seenPutawayKeys: new Set(),
- pickOrderTargetDateMap: buildPickOrderTargetDateMapFromPrelude(joPrelude),
- seq: 0,
- };
- const lotPicks = buildLotPickOrderMap(joPrelude.materialInputs);
-
- const nodes: TraceGraphNode[] = [
- ...buildMaterialInputChainNodes(joPrelude.materialInputs, labels, lotPicks, ctx),
- ...joPrelude.materialInputs.map((m, i) => createMaterialPickNode(m, i, labels, pickCtx(ctx))),
- ];
-
- return nodes.sort((a, b) => {
- if (a.sortKey !== b.sortKey) return a.sortKey - b.sortKey;
- return a.id.localeCompare(b.id);
- });
- };
|