Readiness Score
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ff357b8..75f977d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,7 @@ This file is the running release and change log for CODEXIUM. Keep it updated wh
### Added
-- Project cockpit section on project detail pages for commercial, supply, execution, delivery, purchasing, and readiness-risk rollups
+- Project cockpit section on project detail pages for commercial, supply, execution, delivery, purchasing, readiness-risk, and project cost snapshot rollups
- Project milestones with status, due dates, notes, and edit-time sequencing inside the project workflow
- Project-side milestone and work-order rollups surfaced on project list and detail pages
- Inventory SKU master builder with family-level sequence codes, branch-aware taxonomy management, and generated SKU previews on the item form
diff --git a/README.md b/README.md
index 87f7ee1..02f6025 100644
--- a/README.md
+++ b/README.md
@@ -91,7 +91,7 @@ Navigation direction:
## Projects Direction
-Projects are now the long-running program and delivery layer for cross-module execution. The current slice ships project records with customer linkage, owner assignment, priority, due dates, milestones, project-side milestone/work-order rollups, cockpit-style commercial/supply/execution/delivery/purchasing visibility, readiness-risk scoring, notes, commercial document links, shipment links, attachments, and dashboard visibility.
+Projects are now the long-running program and delivery layer for cross-module execution. The current slice ships project records with customer linkage, owner assignment, priority, due dates, milestones, project-side milestone/work-order rollups, cockpit-style commercial/supply/execution/delivery/purchasing visibility, readiness-risk scoring, a cost snapshot from linked purchasing and manufacturing data, notes, commercial document links, shipment links, attachments, and dashboard visibility.
Current interactions:
diff --git a/SHIPPED.md b/SHIPPED.md
index 2c41a17..6606fc8 100644
--- a/SHIPPED.md
+++ b/SHIPPED.md
@@ -34,7 +34,7 @@ This file tracks roadmap phases, slices, and major foundations that have already
- Logistics attachments directly on shipment records
- Projects foundation with customer, quote, sales-order, shipment, owner, due-date, notes, and attachment linkage
- Project milestones and project-side milestone/work-order rollups
-- Project cockpit section on detail pages for commercial, supply, execution, delivery, purchasing, and readiness-risk visibility
+- Project cockpit section on detail pages for commercial, supply, execution, delivery, purchasing, readiness-risk, and cost-snapshot visibility
- Project list/detail/create/edit workflows and dashboard program widgets
- Manufacturing foundation with work orders, project linkage, material issue posting, completion posting, and work-order attachments
- Manufacturing stations, item routing templates, and automatic work-order operation planning for gantt scheduling
diff --git a/client/src/modules/projects/ProjectDetailPage.tsx b/client/src/modules/projects/ProjectDetailPage.tsx
index e61068e..3b78459 100644
--- a/client/src/modules/projects/ProjectDetailPage.tsx
+++ b/client/src/modules/projects/ProjectDetailPage.tsx
@@ -159,6 +159,12 @@ export function ProjectDetailPage() {
Readiness Score Material Spend
Booked Revenue
Purchase Commitment
Planned Material Cost
Build Load
Next Checkpoints
diff --git a/server/src/modules/projects/service.ts b/server/src/modules/projects/service.ts index 426d7aa..e1c0eaa 100644 --- a/server/src/modules/projects/service.ts +++ b/server/src/modules/projects/service.ts @@ -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, diff --git a/shared/src/projects/types.ts b/shared/src/projects/types.ts index 766c817..3f465c9 100644 --- a/shared/src/projects/types.ts +++ b/shared/src/projects/types.ts @@ -129,6 +129,17 @@ export interface ProjectCockpitPurchasingDto { recentReceipts: ProjectCockpitReceiptDto[]; } +export interface ProjectCockpitCostDto { + quotedRevenue: number | null; + bookedRevenue: number | null; + linkedPurchaseCommitment: number; + plannedMaterialCost: number; + issuedMaterialCost: number; + plannedOperationHours: number; + buildQuantity: number; + completedBuildQuantity: number; +} + export interface ProjectCockpitDeliveryDto { shipmentNumber: string | null; shipmentStatus: string | null; @@ -154,6 +165,7 @@ export interface ProjectCockpitRiskDto { export interface ProjectCockpitDto { commercial: ProjectCockpitCommercialDto; purchasing: ProjectCockpitPurchasingDto; + costs: ProjectCockpitCostDto; delivery: ProjectCockpitDeliveryDto; risk: ProjectCockpitRiskDto; }