cost rollups

This commit is contained in:
2026-03-17 19:17:12 -05:00
parent f772ccacc7
commit cdbd54b8cc
6 changed files with 116 additions and 3 deletions

View File

@@ -134,6 +134,28 @@ type ProjectReceiptLineRecord = {
};
};
type ProjectCostWorkOrderRecord = {
quantity: number;
completedQuantity: number;
item: {
bomLines: Array<{
quantity: number;
componentItem: {
defaultCost: number | null;
};
}>;
};
operations: Array<{
plannedMinutes: number;
}>;
materialIssues: Array<{
quantity: number;
componentItem: {
defaultCost: number | null;
};
}>;
};
function roundMoney(value: number) {
return Math.round(value * 100) / 100;
}
@@ -327,6 +349,44 @@ async function buildProjectCockpit(record: ProjectRecord, rollups: ProjectRollup
: Promise.resolve([]),
record.salesOrder ? getSalesOrderPlanningById(record.salesOrder.id) : Promise.resolve(null),
]);
const workOrderCosts = await prisma.workOrder.findMany({
where: {
projectId: record.id,
},
select: {
quantity: true,
completedQuantity: true,
item: {
select: {
bomLines: {
select: {
quantity: true,
componentItem: {
select: {
defaultCost: true,
},
},
},
},
},
},
operations: {
select: {
plannedMinutes: true,
},
},
materialIssues: {
select: {
quantity: true,
componentItem: {
select: {
defaultCost: true,
},
},
},
},
},
});
const typedPurchaseOrders = purchaseOrders as ProjectPurchaseOrderRecord[];
const typedReceiptLines = receiptLines as ProjectReceiptLineRecord[];
@@ -334,6 +394,7 @@ async function buildProjectCockpit(record: ProjectRecord, rollups: ProjectRollup
const typedSalesOrder = salesOrder as ProjectSalesDocumentRecord | null;
const typedShipment = shipment as ProjectShipmentRecord | null;
const typedPlanning = planning as SalesOrderPlanningDto | null;
const typedWorkOrderCosts = workOrderCosts as ProjectCostWorkOrderRecord[];
const purchaseOrdersSummary: ProjectCockpitPurchaseOrderDto[] = typedPurchaseOrders.map((purchaseOrder) => {
const linkedLineCount = purchaseOrder.lines.length;
@@ -427,6 +488,30 @@ async function buildProjectCockpit(record: ProjectRecord, rollups: ProjectRollup
const commercialQuoteTotal = typedQuote ? calculateSalesDocumentTotal(typedQuote) : null;
const commercialOrderTotal = typedSalesOrder ? calculateSalesDocumentTotal(typedSalesOrder) : null;
const plannedMaterialCost = roundMoney(
typedWorkOrderCosts.reduce((sum, workOrder) => (
sum + workOrder.item.bomLines.reduce(
(workOrderSum, bomLine) => workOrderSum + (bomLine.quantity * workOrder.quantity * (bomLine.componentItem.defaultCost ?? 0)),
0
)
), 0)
);
const issuedMaterialCost = roundMoney(
typedWorkOrderCosts.reduce((sum, workOrder) => (
sum + workOrder.materialIssues.reduce(
(workOrderSum, issue) => workOrderSum + (issue.quantity * (issue.componentItem.defaultCost ?? 0)),
0
)
), 0)
);
const plannedOperationHours = roundMoney(
typedWorkOrderCosts.reduce(
(sum, workOrder) => sum + workOrder.operations.reduce((workOrderSum, operation) => workOrderSum + operation.plannedMinutes, 0),
0
) / 60
);
const buildQuantity = typedWorkOrderCosts.reduce((sum, workOrder) => sum + workOrder.quantity, 0);
const completedBuildQuantity = typedWorkOrderCosts.reduce((sum, workOrder) => sum + workOrder.completedQuantity, 0);
return {
commercial: {
@@ -459,6 +544,16 @@ async function buildProjectCockpit(record: ProjectRecord, rollups: ProjectRollup
.sort((left, right) => new Date(right.receivedAt).getTime() - new Date(left.receivedAt).getTime())
.slice(0, 5),
},
costs: {
quotedRevenue: commercialQuoteTotal,
bookedRevenue: commercialOrderTotal,
linkedPurchaseCommitment: linkedLineValue,
plannedMaterialCost,
issuedMaterialCost,
plannedOperationHours,
buildQuantity,
completedBuildQuantity,
},
delivery: {
shipmentNumber: typedShipment?.shipmentNumber ?? null,
shipmentStatus: typedShipment?.status ?? null,