This commit is contained in:
2026-03-15 16:40:25 -05:00
parent 15116807ce
commit 59754c7657
33 changed files with 1620 additions and 49 deletions

View File

@@ -45,6 +45,8 @@ export interface PurchaseOrderPdfData {
type PurchaseLineRecord = {
id: string;
salesOrderId: string | null;
salesOrderLineId: string | null;
description: string;
quantity: number;
unitOfMeasure: string;
@@ -58,6 +60,10 @@ type PurchaseLineRecord = {
sku: string;
name: string;
};
salesOrder: {
id: string;
documentNumber: string;
} | null;
};
type PurchaseReceiptLineRecord = {
@@ -175,6 +181,8 @@ 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,
@@ -213,6 +221,63 @@ async function validateLines(lines: PurchaseLineInput[]) {
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 };
}
@@ -240,6 +305,9 @@ function mapPurchaseOrder(record: PurchaseOrderRecord): PurchaseOrderDetailDto {
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(
@@ -305,6 +373,12 @@ const purchaseOrderInclude = Prisma.validator<Prisma.PurchaseOrderInclude>()({
name: true,
},
},
salesOrder: {
select: {
id: true,
documentNumber: true,
},
},
},
orderBy: [{ position: "asc" }, { createdAt: "asc" }],
},