import { Prisma } from "@prisma/client"; import type { PurchaseLineInput, PurchaseOrderDetailDto, PurchaseOrderInput, PurchaseOrderRevisionDto, PurchaseOrderRevisionSnapshotDto, PurchaseOrderStatus, PurchaseOrderSummaryDto, PurchaseVendorOptionDto, } from "@mrp/shared"; import type { PurchaseReceiptDto, PurchaseReceiptInput } from "@mrp/shared/dist/purchasing/types.js"; import { logAuditEvent } from "../../lib/audit.js"; import { prisma } from "../../lib/prisma.js"; const purchaseOrderModel = prisma.purchaseOrder; export interface PurchaseOrderPdfData { documentNumber: string; status: PurchaseOrderStatus; issueDate: string; vendor: { name: string; email: string; phone: string; addressLine1: string; addressLine2: string; city: string; state: string; postalCode: string; country: string; paymentTerms: string | null; currencyCode: string | null; }; notes: string; subtotal: number; taxPercent: number; taxAmount: number; freightAmount: number; total: number; lines: Array<{ itemSku: string; itemName: string; description: string; quantity: number; receivedQuantity: number; remainingQuantity: number; unitOfMeasure: string; unitCost: number; lineTotal: number; }>; } type PurchaseLineRecord = { id: string; salesOrderId: string | null; salesOrderLineId: string | null; description: string; quantity: number; unitOfMeasure: string; unitCost: number; position: number; receiptLines?: { quantity: number; }[]; item: { id: string; sku: string; name: string; }; salesOrder: { id: string; documentNumber: string; } | null; }; type PurchaseReceiptLineRecord = { id: string; purchaseOrderLineId: string; quantity: number; purchaseOrderLine: { item: { id: string; sku: string; name: string; }; }; }; type PurchaseReceiptRecord = { id: string; receiptNumber: string; receivedAt: Date; notes: string; createdAt: Date; warehouse: { id: string; code: string; name: string; }; location: { id: string; code: string; name: string; }; createdBy: { firstName: string; lastName: string; } | null; lines: PurchaseReceiptLineRecord[]; }; type PurchaseOrderRevisionRecord = { id: string; revisionNumber: number; reason: string; snapshot: string; createdAt: Date; createdBy: { firstName: string; lastName: string; } | null; }; type PurchaseOrderRecord = { id: string; documentNumber: string; status: string; issueDate: Date; taxPercent: number; freightAmount: number; notes: string; createdAt: Date; updatedAt: Date; vendor: { id: string; name: string; email: string; paymentTerms: string | null; currencyCode: string | null; }; lines: PurchaseLineRecord[]; receipts: PurchaseReceiptRecord[]; revisions: PurchaseOrderRevisionRecord[]; }; function roundMoney(value: number) { return Math.round(value * 100) / 100; } function calculateTotals(subtotal: number, taxPercent: number, freightAmount: number) { const normalizedSubtotal = roundMoney(subtotal); const normalizedTaxPercent = Number.isFinite(taxPercent) ? taxPercent : 0; const normalizedFreight = roundMoney(Number.isFinite(freightAmount) ? freightAmount : 0); const taxAmount = roundMoney(normalizedSubtotal * (normalizedTaxPercent / 100)); const total = roundMoney(normalizedSubtotal + taxAmount + normalizedFreight); return { subtotal: normalizedSubtotal, taxPercent: normalizedTaxPercent, taxAmount, freightAmount: normalizedFreight, total, }; } function getCreatedByName(createdBy: PurchaseReceiptRecord["createdBy"]) { return createdBy ? `${createdBy.firstName} ${createdBy.lastName}`.trim() : "System"; } function getUserDisplayName(user: { firstName: string; lastName: string } | null) { return user ? `${user.firstName} ${user.lastName}`.trim() : null; } function mapPurchaseReceipt(record: PurchaseReceiptRecord, purchaseOrderId: string): PurchaseReceiptDto { const lines = record.lines.map((line: PurchaseReceiptLineRecord) => ({ id: line.id, purchaseOrderLineId: line.purchaseOrderLineId, itemId: line.purchaseOrderLine.item.id, itemSku: line.purchaseOrderLine.item.sku, itemName: line.purchaseOrderLine.item.name, quantity: line.quantity, })); return { id: record.id, receiptNumber: record.receiptNumber, purchaseOrderId, receivedAt: record.receivedAt.toISOString(), notes: record.notes, createdAt: record.createdAt.toISOString(), createdByName: getCreatedByName(record.createdBy), warehouseId: record.warehouse.id, warehouseCode: record.warehouse.code, warehouseName: record.warehouse.name, locationId: record.location.id, locationCode: record.location.code, locationName: record.location.name, totalQuantity: lines.reduce((sum: number, line: PurchaseReceiptDto["lines"][number]) => sum + line.quantity, 0), lineCount: lines.length, lines, }; } function parseRevisionSnapshot(snapshot: string): PurchaseOrderRevisionSnapshotDto { return JSON.parse(snapshot) as PurchaseOrderRevisionSnapshotDto; } function mapPurchaseOrderRevision(record: PurchaseOrderRevisionRecord): PurchaseOrderRevisionDto { return { id: record.id, revisionNumber: record.revisionNumber, reason: record.reason, createdAt: record.createdAt.toISOString(), createdByName: getUserDisplayName(record.createdBy), snapshot: parseRevisionSnapshot(record.snapshot), }; } function normalizeLines(lines: PurchaseLineInput[]) { return lines .map((line, index) => ({ itemId: line.itemId, salesOrderId: line.salesOrderId ?? null, salesOrderLineId: line.salesOrderLineId ?? null, description: line.description.trim(), quantity: Number(line.quantity), unitOfMeasure: line.unitOfMeasure, unitCost: Number(line.unitCost), position: line.position ?? (index + 1) * 10, })) .filter((line) => line.itemId.trim().length > 0); } async function validateLines(lines: PurchaseLineInput[]) { const normalized = normalizeLines(lines); if (normalized.length === 0) { return { ok: false as const, reason: "At least one line item is required." }; } if (normalized.some((line) => !Number.isInteger(line.quantity) || line.quantity <= 0)) { return { ok: false as const, reason: "Line quantity must be a whole number greater than zero." }; } if (normalized.some((line) => Number.isNaN(line.unitCost) || line.unitCost < 0)) { return { ok: false as const, reason: "Unit cost must be zero or greater." }; } const itemIds = [...new Set(normalized.map((line) => line.itemId))]; const items = await prisma.inventoryItem.findMany({ where: { id: { in: itemIds } }, select: { id: true, isPurchasable: true }, }); if (items.length !== itemIds.length) { return { ok: false as const, reason: "One or more purchase lines reference an invalid inventory item." }; } if (items.some((item) => !item.isPurchasable)) { return { ok: false as const, reason: "Purchase orders can only include purchasable inventory items." }; } const salesOrderIds = [...new Set(normalized.flatMap((line) => (line.salesOrderId ? [line.salesOrderId] : [])))]; const salesOrderLineIds = [...new Set(normalized.flatMap((line) => (line.salesOrderLineId ? [line.salesOrderLineId] : [])))]; if (normalized.some((line) => line.salesOrderLineId && !line.salesOrderId)) { return { ok: false as const, reason: "Linked sales-order lines require a linked sales order." }; } if (salesOrderIds.length > 0) { const salesOrders = await prisma.salesOrder.findMany({ where: { id: { in: salesOrderIds } }, select: { id: true }, }); if (salesOrders.length !== salesOrderIds.length) { return { ok: false as const, reason: "One or more linked sales orders do not exist." }; } } if (salesOrderLineIds.length > 0) { const salesOrderLines = await prisma.salesOrderLine.findMany({ where: { id: { in: salesOrderLineIds } }, select: { id: true, orderId: true, itemId: true, }, }); const salesOrderLinesById = new Map( salesOrderLines.map((line) => [ line.id, { orderId: line.orderId, itemId: line.itemId, }, ]) ); if (salesOrderLines.length !== salesOrderLineIds.length) { return { ok: false as const, reason: "One or more linked sales-order lines do not exist." }; } for (const line of normalized) { if (!line.salesOrderLineId) { continue; } const salesOrderLine = salesOrderLinesById.get(line.salesOrderLineId); if (!salesOrderLine || salesOrderLine.orderId !== line.salesOrderId) { return { ok: false as const, reason: "Linked sales-order line does not belong to the selected sales order." }; } if (salesOrderLine.itemId !== line.itemId) { return { ok: false as const, reason: "Linked sales-order line item does not match the purchase item." }; } } } return { ok: true as const, lines: normalized }; } function mapPurchaseOrder(record: PurchaseOrderRecord): PurchaseOrderDetailDto { const receivedByLineId = new Map(); for (const receipt of record.receipts) { for (const line of receipt.lines) { receivedByLineId.set(line.purchaseOrderLineId, (receivedByLineId.get(line.purchaseOrderLineId) ?? 0) + line.quantity); } } const lines = record.lines .slice() .sort((left, right) => left.position - right.position) .map((line) => ({ id: line.id, itemId: line.item.id, itemSku: line.item.sku, itemName: line.item.name, description: line.description, quantity: line.quantity, unitOfMeasure: line.unitOfMeasure as PurchaseOrderDetailDto["lines"][number]["unitOfMeasure"], unitCost: line.unitCost, lineTotal: line.quantity * line.unitCost, receivedQuantity: receivedByLineId.get(line.id) ?? 0, remainingQuantity: Math.max(0, line.quantity - (receivedByLineId.get(line.id) ?? 0)), salesOrderId: line.salesOrderId, salesOrderLineId: line.salesOrderLineId, salesOrderNumber: line.salesOrder?.documentNumber ?? null, position: line.position, })); const totals = calculateTotals( lines.reduce((sum, line) => sum + line.lineTotal, 0), record.taxPercent, record.freightAmount ); const receipts = record.receipts .slice() .sort((left, right) => right.receivedAt.getTime() - left.receivedAt.getTime()) .map((receipt) => mapPurchaseReceipt(receipt, record.id)); const revisions = record.revisions .slice() .sort((left, right) => right.revisionNumber - left.revisionNumber) .map(mapPurchaseOrderRevision); return { id: record.id, documentNumber: record.documentNumber, vendorId: record.vendor.id, vendorName: record.vendor.name, vendorEmail: record.vendor.email, paymentTerms: record.vendor.paymentTerms, currencyCode: record.vendor.currencyCode, status: record.status as PurchaseOrderStatus, subtotal: totals.subtotal, taxPercent: totals.taxPercent, taxAmount: totals.taxAmount, freightAmount: totals.freightAmount, total: totals.total, issueDate: record.issueDate.toISOString(), notes: record.notes, createdAt: record.createdAt.toISOString(), updatedAt: record.updatedAt.toISOString(), lineCount: lines.length, lines, receipts, revisions, }; } function buildPurchaseOrderRevisionSnapshot(document: PurchaseOrderDetailDto) { return JSON.stringify({ documentNumber: document.documentNumber, vendorId: document.vendorId, vendorName: document.vendorName, status: document.status, issueDate: document.issueDate, taxPercent: document.taxPercent, taxAmount: document.taxAmount, freightAmount: document.freightAmount, subtotal: document.subtotal, total: document.total, notes: document.notes, paymentTerms: document.paymentTerms, currencyCode: document.currencyCode, lines: document.lines.map((line) => ({ itemId: line.itemId, itemSku: line.itemSku, itemName: line.itemName, description: line.description, quantity: line.quantity, unitOfMeasure: line.unitOfMeasure, unitCost: line.unitCost, lineTotal: line.lineTotal, receivedQuantity: line.receivedQuantity, remainingQuantity: line.remainingQuantity, salesOrderId: line.salesOrderId, salesOrderLineId: line.salesOrderLineId, salesOrderNumber: line.salesOrderNumber, position: line.position, })), receipts: document.receipts.map((receipt) => ({ id: receipt.id, receiptNumber: receipt.receiptNumber, purchaseOrderId: receipt.purchaseOrderId, receivedAt: receipt.receivedAt, notes: receipt.notes, createdAt: receipt.createdAt, createdByName: receipt.createdByName, warehouseId: receipt.warehouseId, warehouseCode: receipt.warehouseCode, warehouseName: receipt.warehouseName, locationId: receipt.locationId, locationCode: receipt.locationCode, locationName: receipt.locationName, totalQuantity: receipt.totalQuantity, lineCount: receipt.lineCount, lines: receipt.lines.map((line) => ({ id: line.id, purchaseOrderLineId: line.purchaseOrderLineId, itemId: line.itemId, itemSku: line.itemSku, itemName: line.itemName, quantity: line.quantity, })), })), }); } async function createPurchaseOrderRevision(documentId: string, detail: PurchaseOrderDetailDto, reason: string, actorId?: string | null) { const aggregate = await prisma.purchaseOrderRevision.aggregate({ where: { purchaseOrderId: documentId }, _max: { revisionNumber: true }, }); const nextRevisionNumber = (aggregate._max.revisionNumber ?? 0) + 1; await prisma.purchaseOrderRevision.create({ data: { purchaseOrderId: documentId, revisionNumber: nextRevisionNumber, reason, snapshot: buildPurchaseOrderRevisionSnapshot(detail), createdById: actorId ?? null, }, }); } async function nextDocumentNumber() { const next = (await purchaseOrderModel.count()) + 1; return `PO-${String(next).padStart(5, "0")}`; } async function nextReceiptNumber(transaction: Prisma.TransactionClient | typeof prisma = prisma) { const next = (await transaction.purchaseReceipt.count()) + 1; return `PR-${String(next).padStart(5, "0")}`; } const purchaseOrderInclude = Prisma.validator()({ vendor: { select: { id: true, name: true, email: true, paymentTerms: true, currencyCode: true, }, }, lines: { include: { item: { select: { id: true, sku: true, name: true, }, }, salesOrder: { select: { id: true, documentNumber: true, }, }, }, orderBy: [{ position: "asc" }, { createdAt: "asc" }], }, receipts: { include: { warehouse: { select: { id: true, code: true, name: true, }, }, location: { select: { id: true, code: true, name: true, }, }, createdBy: { select: { firstName: true, lastName: true, }, }, lines: { include: { purchaseOrderLine: { include: { item: { select: { id: true, sku: true, name: true, }, }, }, }, }, orderBy: [{ createdAt: "asc" }], }, }, orderBy: [{ receivedAt: "desc" }, { createdAt: "desc" }], }, revisions: { include: { createdBy: { select: { firstName: true, lastName: true, }, }, }, orderBy: [{ revisionNumber: "desc" }], }, }); function normalizeReceiptLines(lines: PurchaseReceiptInput["lines"]) { return lines .map((line) => ({ purchaseOrderLineId: line.purchaseOrderLineId.trim(), quantity: Number(line.quantity), })) .filter((line) => line.purchaseOrderLineId.length > 0 && line.quantity > 0); } async function validateReceipt(orderId: string, payload: PurchaseReceiptInput) { const normalizedLines = normalizeReceiptLines(payload.lines); if (normalizedLines.length === 0) { return { ok: false as const, reason: "At least one receipt line is required." }; } if (normalizedLines.some((line) => !Number.isInteger(line.quantity) || line.quantity <= 0)) { return { ok: false as const, reason: "Receipt quantity must be a whole number greater than zero." }; } const order = await purchaseOrderModel.findUnique({ where: { id: orderId }, select: { id: true, documentNumber: true, status: true, lines: { select: { id: true, quantity: true, itemId: true, item: { select: { sku: true, }, }, receiptLines: { select: { quantity: true, }, }, }, }, }, }); if (!order) { return { ok: false as const, reason: "Purchase order was not found." }; } if (order.status === "CLOSED") { return { ok: false as const, reason: "Closed purchase orders cannot receive additional stock." }; } const location = await prisma.warehouseLocation.findUnique({ where: { id: payload.locationId }, select: { id: true, warehouseId: true, }, }); if (!location || location.warehouseId !== payload.warehouseId) { return { ok: false as const, reason: "Warehouse location is invalid for the selected warehouse." }; } const orderLinesById = new Map( order.lines.map((line: { id: string; quantity: number; itemId: string; item: { sku: string }; receiptLines: { quantity: number }[] }) => [ line.id, { id: line.id, quantity: line.quantity, itemId: line.itemId, itemSku: line.item.sku, receivedQuantity: line.receiptLines.reduce((sum: number, receiptLine: { quantity: number }) => sum + receiptLine.quantity, 0), }, ]) ); const mergedQuantities = new Map(); for (const line of normalizedLines) { const current = mergedQuantities.get(line.purchaseOrderLineId) ?? 0; mergedQuantities.set(line.purchaseOrderLineId, current + line.quantity); } for (const [lineId, quantity] of mergedQuantities.entries()) { const orderLine = orderLinesById.get(lineId); if (!orderLine) { return { ok: false as const, reason: "Receipt lines must reference lines on the selected purchase order." }; } if (orderLine.receivedQuantity + quantity > orderLine.quantity) { return { ok: false as const, reason: `Receipt for ${orderLine.itemSku} exceeds the remaining ordered quantity.`, }; } } return { ok: true as const, order, lines: [...mergedQuantities.entries()].map(([purchaseOrderLineId, quantity]) => ({ purchaseOrderLineId, quantity, itemId: orderLinesById.get(purchaseOrderLineId)!.itemId, })), }; } export async function listPurchaseVendorOptions(): Promise { const vendors = await prisma.vendor.findMany({ where: { status: { not: "INACTIVE", }, }, select: { id: true, name: true, email: true, paymentTerms: true, currencyCode: true, }, orderBy: [{ name: "asc" }], }); return vendors; } export async function listPurchaseOrders(filters: { q?: string; status?: PurchaseOrderStatus; vendorId?: string } = {}) { const query = filters.q?.trim(); const records = await purchaseOrderModel.findMany({ where: { ...(filters.status ? { status: filters.status } : {}), ...(filters.vendorId ? { vendorId: filters.vendorId } : {}), ...(query ? { OR: [ { documentNumber: { contains: query } }, { vendor: { name: { contains: query } } }, ], } : {}), }, include: purchaseOrderInclude, orderBy: [{ issueDate: "desc" }, { createdAt: "desc" }], }); return records.map((record: unknown) => { const detail = mapPurchaseOrder(record as PurchaseOrderRecord); const summary: PurchaseOrderSummaryDto = { id: detail.id, documentNumber: detail.documentNumber, vendorId: detail.vendorId, vendorName: detail.vendorName, status: detail.status, subtotal: detail.subtotal, taxPercent: detail.taxPercent, taxAmount: detail.taxAmount, freightAmount: detail.freightAmount, total: detail.total, issueDate: detail.issueDate, updatedAt: detail.updatedAt, lineCount: detail.lineCount, }; return summary; }); } export async function getPurchaseOrderById(documentId: string) { const record = await purchaseOrderModel.findUnique({ where: { id: documentId }, include: purchaseOrderInclude, }); return record ? mapPurchaseOrder(record as unknown as PurchaseOrderRecord) : null; } export async function listPurchaseOrderRevisions(documentId: string) { const revisions = await prisma.purchaseOrderRevision.findMany({ where: { purchaseOrderId: documentId }, include: { createdBy: { select: { firstName: true, lastName: true, }, }, }, orderBy: [{ revisionNumber: "desc" }], }); return revisions.map((revision) => mapPurchaseOrderRevision(revision as PurchaseOrderRevisionRecord)); } export async function createPurchaseOrder(payload: PurchaseOrderInput, actorId?: string | null) { const validatedLines = await validateLines(payload.lines); if (!validatedLines.ok) { return { ok: false as const, reason: validatedLines.reason }; } const vendor = await prisma.vendor.findUnique({ where: { id: payload.vendorId }, select: { id: true }, }); if (!vendor) { return { ok: false as const, reason: "Vendor was not found." }; } const documentNumber = await nextDocumentNumber(); const created = await purchaseOrderModel.create({ data: { documentNumber, vendorId: payload.vendorId, status: payload.status, issueDate: new Date(payload.issueDate), taxPercent: payload.taxPercent, freightAmount: payload.freightAmount, notes: payload.notes, lines: { create: validatedLines.lines, }, }, select: { id: true }, }); const detail = await getPurchaseOrderById(created.id); if (detail) { await createPurchaseOrderRevision(created.id, detail, payload.revisionReason?.trim() || "Initial issue", actorId); await logAuditEvent({ actorId, entityType: "purchase-order", entityId: created.id, action: "created", summary: `Created purchase order ${detail.documentNumber}.`, metadata: { documentNumber: detail.documentNumber, vendorId: detail.vendorId, status: detail.status, total: detail.total, }, }); } return detail ? { ok: true as const, document: detail } : { ok: false as const, reason: "Unable to load saved purchase order." }; } export async function updatePurchaseOrder(documentId: string, payload: PurchaseOrderInput, actorId?: string | null) { const existing = await purchaseOrderModel.findUnique({ where: { id: documentId }, select: { id: true }, }); if (!existing) { return { ok: false as const, reason: "Purchase order was not found." }; } const validatedLines = await validateLines(payload.lines); if (!validatedLines.ok) { return { ok: false as const, reason: validatedLines.reason }; } const vendor = await prisma.vendor.findUnique({ where: { id: payload.vendorId }, select: { id: true }, }); if (!vendor) { return { ok: false as const, reason: "Vendor was not found." }; } await purchaseOrderModel.update({ where: { id: documentId }, data: { vendorId: payload.vendorId, status: payload.status, issueDate: new Date(payload.issueDate), taxPercent: payload.taxPercent, freightAmount: payload.freightAmount, notes: payload.notes, lines: { deleteMany: {}, create: validatedLines.lines, }, }, select: { id: true }, }); const detail = await getPurchaseOrderById(documentId); if (detail) { await createPurchaseOrderRevision(documentId, detail, payload.revisionReason?.trim() || "Document edited", actorId); await logAuditEvent({ actorId, entityType: "purchase-order", entityId: documentId, action: "updated", summary: `Updated purchase order ${detail.documentNumber}.`, metadata: { documentNumber: detail.documentNumber, vendorId: detail.vendorId, status: detail.status, total: detail.total, }, }); } return detail ? { ok: true as const, document: detail } : { ok: false as const, reason: "Unable to load saved purchase order." }; } export async function updatePurchaseOrderStatus(documentId: string, status: PurchaseOrderStatus, actorId?: string | null) { const existing = await purchaseOrderModel.findUnique({ where: { id: documentId }, select: { id: true }, }); if (!existing) { return { ok: false as const, reason: "Purchase order was not found." }; } await purchaseOrderModel.update({ where: { id: documentId }, data: { status }, select: { id: true }, }); const detail = await getPurchaseOrderById(documentId); if (detail) { await createPurchaseOrderRevision(documentId, detail, `Status changed to ${status}`, actorId); await logAuditEvent({ actorId, entityType: "purchase-order", entityId: documentId, action: "status.updated", summary: `Updated purchase order ${detail.documentNumber} to ${status}.`, metadata: { documentNumber: detail.documentNumber, status, }, }); } return detail ? { ok: true as const, document: detail } : { ok: false as const, reason: "Unable to load updated purchase order." }; } export async function createPurchaseReceipt(orderId: string, payload: PurchaseReceiptInput, createdById?: string | null) { const validated = await validateReceipt(orderId, payload); if (!validated.ok) { return { ok: false as const, reason: validated.reason }; } await prisma.$transaction(async (transaction) => { const receiptNumber = await nextReceiptNumber(transaction); const receipt = await transaction.purchaseReceipt.create({ data: { receiptNumber, purchaseOrderId: orderId, warehouseId: payload.warehouseId, locationId: payload.locationId, receivedAt: new Date(payload.receivedAt), notes: payload.notes, createdById: createdById ?? null, lines: { create: validated.lines.map((line) => ({ purchaseOrderLineId: line.purchaseOrderLineId, quantity: line.quantity, })), }, }, select: { receiptNumber: true, }, }); await transaction.inventoryTransaction.createMany({ data: validated.lines.map((line) => ({ itemId: line.itemId, warehouseId: payload.warehouseId, locationId: payload.locationId, transactionType: "RECEIPT", quantity: line.quantity, reference: receipt.receiptNumber, notes: `Purchase receipt for ${validated.order.documentNumber}`, createdById: createdById ?? null, })), }); }); const detail = await getPurchaseOrderById(orderId); if (detail) { await createPurchaseOrderRevision(orderId, detail, `Receipt posted on ${payload.receivedAt}`, createdById); await logAuditEvent({ actorId: createdById, entityType: "purchase-order", entityId: orderId, action: "receipt.created", summary: `Received material against purchase order ${detail.documentNumber}.`, metadata: { documentNumber: detail.documentNumber, warehouseId: payload.warehouseId, locationId: payload.locationId, receivedAt: payload.receivedAt, lineCount: payload.lines.length, }, }); } return detail ? { ok: true as const, document: detail } : { ok: false as const, reason: "Unable to load updated purchase order." }; } export async function getPurchaseOrderPdfData(orderId: string): Promise { const order = await prisma.purchaseOrder.findUnique({ where: { id: orderId }, include: { vendor: { select: { name: true, email: true, phone: true, addressLine1: true, addressLine2: true, city: true, state: true, postalCode: true, country: true, paymentTerms: true, currencyCode: true, }, }, lines: { include: { item: { select: { sku: true, name: true, }, }, receiptLines: { select: { quantity: true, }, }, }, orderBy: [{ position: "asc" }, { createdAt: "asc" }], }, }, }); if (!order) { return null; } const lines = order.lines.map((line) => { const receivedQuantity = line.receiptLines.reduce((sum, receiptLine) => sum + receiptLine.quantity, 0); return { itemSku: line.item.sku, itemName: line.item.name, description: line.description, quantity: line.quantity, receivedQuantity, remainingQuantity: Math.max(0, line.quantity - receivedQuantity), unitOfMeasure: line.unitOfMeasure, unitCost: line.unitCost, lineTotal: line.quantity * line.unitCost, }; }); const totals = calculateTotals( lines.reduce((sum, line) => sum + line.lineTotal, 0), order.taxPercent, order.freightAmount ); return { documentNumber: order.documentNumber, status: order.status as PurchaseOrderStatus, issueDate: order.issueDate.toISOString(), vendor: order.vendor, notes: order.notes, subtotal: totals.subtotal, taxPercent: totals.taxPercent, taxAmount: totals.taxAmount, freightAmount: totals.freightAmount, total: totals.total, lines, }; }