/** * Duplicate-PO prefill (issue #142) — map a source PurchaseOrder onto the * initial-value props the New PO form consumes. Pure (no DB / no I/O) so the * mapping is unit-testable; the page just fetches the PO and hands it here. * * Nothing is written: a duplicate is only a prefilled draft. Attachments, * status/dates, payment data and audit history are intentionally NOT copied — * a duplicate starts as a clean draft of the editable order fields. */ import { parsePoTerms, legacyPoTerms } from "@/lib/terms"; import type { PoTerm } from "@/lib/terms"; import type { LineItemInput } from "@/lib/validations/po"; type DecimalLike = { toNumber: () => number } | number | null | undefined; const num = (v: DecimalLike, fallback = 0): number => v == null ? fallback : typeof v === "number" ? v : v.toNumber(); export type DuplicateSourceLineItem = { name: string; description?: string | null; quantity: DecimalLike; unit: string; size?: string | null; unitPrice: DecimalLike; gstRate?: DecimalLike; productId?: string | null; accountId?: string | null; }; export type DuplicateSourcePo = { title: string; vesselId: string; accountId: string; companyId?: string | null; vendorId?: string | null; projectCode?: string | null; placeOfDelivery?: string | null; dateRequired?: Date | null; piQuotationNo?: string | null; piQuotationDate?: Date | null; requisitionNo?: string | null; requisitionDate?: Date | null; terms?: unknown; tcDelivery?: string | null; tcDispatch?: string | null; tcInspection?: string | null; tcTransitInsurance?: string | null; tcPaymentTerms?: string | null; tcOthers?: string | null; lineItems: DuplicateSourceLineItem[]; }; export type DuplicatePrefill = { initialLineItems: LineItemInput[]; initialMultiAccount: boolean; initialVendorId?: string; initialVesselId: string; initialCompanyId?: string; initialTitle: string; initialAccountId: string; initialProjectCode: string | null; initialPlaceOfDelivery: string | null; initialDateRequired?: string; initialPiQuotationNo?: string; initialPiQuotationDate?: string; initialRequisitionNo?: string; initialRequisitionDate?: string; initialTerms: PoTerm[]; }; /** Format a Date to a `yyyy-MM-dd` value for a native date input. */ export const toDateInputValue = (d: Date | null | undefined): string | undefined => d ? new Date(d).toISOString().split("T")[0] : undefined; export function buildDuplicatePrefill(source: DuplicateSourcePo): DuplicatePrefill { const savedTerms = parsePoTerms(source.terms); return { initialLineItems: source.lineItems.map((li) => ({ name: li.name, description: li.description ?? "", quantity: num(li.quantity, 1), unit: li.unit, size: li.size ?? "", unitPrice: num(li.unitPrice, 0), gstRate: li.gstRate != null ? num(li.gstRate, 0.18) : 0.18, productId: li.productId ?? undefined, accountId: li.accountId ?? undefined, })), initialMultiAccount: source.lineItems.some((li) => !!li.accountId), initialVendorId: source.vendorId ?? undefined, initialVesselId: source.vesselId, initialCompanyId: source.companyId ?? undefined, initialTitle: source.title, initialAccountId: source.accountId, initialProjectCode: source.projectCode ?? null, initialPlaceOfDelivery: source.placeOfDelivery ?? null, initialDateRequired: toDateInputValue(source.dateRequired), initialPiQuotationNo: source.piQuotationNo ?? undefined, initialPiQuotationDate: toDateInputValue(source.piQuotationDate), initialRequisitionNo: source.requisitionNo ?? undefined, initialRequisitionDate: toDateInputValue(source.requisitionDate), initialTerms: savedTerms.length > 0 ? savedTerms : legacyPoTerms(source), }; }