From 59754c76577e777c774ac7a6c5b6e43cbcb8cdbe Mon Sep 17 00:00:00 2001 From: jason Date: Sun, 15 Mar 2026 16:40:25 -0500 Subject: [PATCH] PO logic --- AGENTS.md | 7 +- CHANGELOG.md | 8 +- INSTRUCTIONS.md | 7 +- README.md | 13 +- ROADMAP.md | 11 +- client/src/lib/api.ts | 4 + .../src/modules/dashboard/DashboardPage.tsx | 49 +- client/src/modules/gantt/GanttPage.tsx | 29 +- .../modules/inventory/InventoryDetailPage.tsx | 4 + .../modules/inventory/InventoryFormPage.tsx | 85 ++ client/src/modules/inventory/config.ts | 1 + .../manufacturing/WorkOrderDetailPage.tsx | 7 + .../manufacturing/WorkOrderFormPage.tsx | 31 +- client/src/modules/manufacturing/config.ts | 2 + .../modules/projects/ProjectDetailPage.tsx | 48 ++ .../modules/purchasing/PurchaseDetailPage.tsx | 34 +- .../modules/purchasing/PurchaseFormPage.tsx | 87 +- client/src/modules/sales/SalesDetailPage.tsx | 61 ++ .../migration.sql | 13 + server/prisma/schema.prisma | 21 + server/src/modules/inventory/router.ts | 1 + server/src/modules/inventory/service.ts | 56 ++ server/src/modules/manufacturing/router.ts | 2 + server/src/modules/manufacturing/service.ts | 138 +++- server/src/modules/purchasing/router.ts | 2 + server/src/modules/purchasing/service.ts | 74 ++ server/src/modules/sales/planning.ts | 760 +++++++++++++++++- server/src/modules/sales/router.ts | 6 +- server/tests/sales-planning.test.ts | 51 +- shared/src/inventory/types.ts | 6 + shared/src/manufacturing/types.ts | 9 + shared/src/purchasing/types.ts | 5 + shared/src/sales/types.ts | 37 + 33 files changed, 1620 insertions(+), 49 deletions(-) create mode 100644 server/prisma/migrations/20260315212000_supply_pegging_and_vendor_sourcing/migration.sql diff --git a/AGENTS.md b/AGENTS.md index 6a585ea..007b25d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,6 +24,9 @@ MRP Codex is a modular Manufacturing Resource Planning platform intended to be a - manufacturing work orders with project linkage, station master data, item operation templates, auto-generated work-order operations, and attachments - planning gantt timelines backed by live project and manufacturing schedule data - sales-order demand planning with multi-level BOM explosion, stock/open-supply netting, and build/buy recommendations +- planner-assisted conversion of demand-planning recommendations into prefilled work-order and purchase-order drafts +- pegged work-order and purchase-order supply coverage tied back to sales demand, with preferred-vendor sourcing defaults +- shared shortage and readiness rollups across dashboard, planning, projects, purchasing, and manufacturing - admin diagnostics with runtime footprint, record counts, and persisted audit-trail visibility - admin user management with account creation, activation, role assignment, and role-permission editing - CRM/shipping audit coverage and startup validation surfaced through the admin diagnostics workflow @@ -126,8 +129,8 @@ If implementation changes invalidate those docs, update them in the same change Near-term priorities are: -1. Convert demand-planning recommendations into work orders and purchase orders -2. Shared shortage and readiness rollups across planning, purchasing, manufacturing, and projects +1. Better user and session visibility for operational admins +2. Safer destructive-action confirmations and recovery messaging When adding new modules, preserve the ability to extend the system without refactoring the existing app shell. diff --git a/CHANGELOG.md b/CHANGELOG.md index 54cde5d..fad15d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,14 @@ This file is the running release and change log for MRP Codex. Keep it updated w ### Added +- Shared shortage and readiness rollups across dashboard, planning, project detail, purchasing detail, and manufacturing detail +- Prefilled work-order draft launch for build recommendations and prefilled purchase-order draft launch for buy recommendations from sales-order demand planning - Sales-order demand planning with multi-level BOM explosion across manufactured and assembly children - Netting of sales-order demand against available stock, active reservations, open work orders, and open purchase orders - Build and buy recommendations surfaced directly on sales-order detail pages +- Pegged work-order and purchase-order supply tracking back to sales demand so reopened planning views do not overstate remaining recommendations +- Preferred-vendor sourcing on inventory items, used to preseed buy-side planning workflows +- Demand-planning recommendations now reduce against existing linked WO/PO supply and support safer partial conversion behavior - Support-log capture for startup warnings, HTTP failures, and server errors, surfaced through admin diagnostics - Exportable support bundles that now include backup guidance and recent support logs for support handoff - Deeper startup diagnostics with writable-path checks, database-file validation, startup timing, and pass/warn/fail rollups @@ -57,7 +62,8 @@ This file is the running release and change log for MRP Codex. Keep it updated w - Admin diagnostics now includes structured startup summaries and a dedicated support-log view for faster debugging - Roadmap and project docs now treat demand planning and supply generation as its own phase ahead of the deferred admin QOL work - Roadmap and project docs now treat backup verification checklist and restore drill guidance as the next active priority after the backup/support-tooling slice -- Roadmap and project docs now treat recommendation-to-WO/PO generation and cross-module shortage rollups as the next active priorities after the first demand-planning slice +- Manufacturing work-order material requirements now include live available/shortage visibility instead of only required-versus-issued math +- Roadmap and project docs now move back to admin session visibility and destructive-action safety after the demand-planning rollout ## 2026-03-15 diff --git a/INSTRUCTIONS.md b/INSTRUCTIONS.md index 56b32f5..4525f7c 100644 --- a/INSTRUCTIONS.md +++ b/INSTRUCTIONS.md @@ -28,6 +28,9 @@ This repository implements the platform foundation milestone: - manufacturing work orders with project linkage, station master data, item operation templates, auto-generated work-order operations, attachments, and dashboard visibility - planning gantt timelines backed by live project and manufacturing schedule data - sales-order demand planning with multi-level BOM explosion, net stock/open-supply coverage, and build/buy recommendations +- planner-assisted conversion of demand-planning recommendations into prefilled work-order and purchase-order drafts +- pegged work-order and purchase-order supply coverage tied back to sales demand, with preferred-vendor sourcing defaults +- shared shortage and readiness rollups across dashboard, planning, projects, purchasing, and manufacturing - admin diagnostics with runtime footprint, storage visibility, record counts, and recent audit activity - admin user management with account creation, activation, role assignment, and role-permission editing - CRM/shipping audit coverage and startup validation surfaced through diagnostics @@ -69,5 +72,5 @@ This repository implements the platform foundation milestone: ## Next roadmap candidates -- convert demand-planning recommendations into work orders and purchase orders -- shared shortage and readiness rollups across planning, purchasing, manufacturing, and projects +- better user and session visibility for operational admins +- safer destructive-action confirmations and recovery messaging diff --git a/README.md b/README.md index d4e6e81..0471509 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,9 @@ Current foundation scope includes: - manufacturing work orders with project linkage, station-based operation templates, material issue posting, completion posting, and work-order attachments - planning gantt timelines with live project and manufacturing schedule data - sales-order demand planning with multi-level BOM explosion, stock/open-supply netting, and build/buy recommendations +- planner-assisted conversion of demand-planning recommendations into prefilled work-order and purchase-order drafts +- pegged WO/PO supply tracking back to sales demand with preferred-vendor sourcing on inventory items +- shared shortage and readiness rollups across dashboard, planning, projects, purchasing, and manufacturing - admin diagnostics with runtime footprint, record counts, and recent audit-trail visibility - admin user management with account creation, activation, role assignment, and role-permission editing - CRM and shipping audit coverage plus startup validation surfaced through the admin diagnostics page @@ -54,8 +57,8 @@ Current completed foundation areas: Near-term priorities: -1. Convert demand-planning recommendations into work orders and purchase orders -2. Shared shortage and readiness rollups across planning, purchasing, manufacturing, and projects +1. Better user and session visibility for operational admins +2. Safer destructive-action confirmations and recovery messaging Revisit / deferred items: @@ -353,6 +356,8 @@ The current admin operations slice supports: - persisted audit events for core settings, inventory, purchasing, sales, project, and manufacturing write actions - an admin diagnostics page for runtime footprint, data/storage path visibility, key record counts, and recent audit activity - a sales-order demand-planning view with multi-level BOM netting and build/buy recommendations +- prefilled work-order and purchase-order draft launch paths from sales-order demand-planning recommendations +- shared shortage/readiness rollups across planning, project, purchasing, dashboard, and manufacturing views - a dedicated user-management page for account creation, activation, role assignment, password reset-style updates, and role-permission administration - CRM customer/vendor changes and shipping mutations now flow into the shared audit trail - startup validation now checks storage paths, writable storage readiness, database connectivity, client bundle readiness, Chromium availability, and risky production defaults during server boot @@ -363,8 +368,8 @@ The current admin operations slice supports: Current follow-up direction: -- convert demand-planning recommendations into work orders and purchase orders -- shared shortage and readiness rollups across planning, purchasing, manufacturing, and projects +- better user and session visibility for operational admins +- safer destructive-action confirmations and recovery messaging ## UI Notes diff --git a/ROADMAP.md b/ROADMAP.md index bc2df25..bcc2aa0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -262,6 +262,11 @@ Foundation slice shipped: - Multi-level BOM explosion from sales-order lines through manufactured and assembly children - Netting against available stock, active reservations, open work orders, and open purchase orders - Build and buy recommendations surfaced directly from the sales-order workflow +- Prefilled work-order and purchase-order draft generation launched from demand-planning recommendations +- Shared shortage and readiness rollups surfaced across dashboard, planning, project, purchasing, and manufacturing views +- Preferred-vendor sourcing on inventory items for buy-side planning defaults +- Pegged work-order and purchase-order supply links back to originating sales demand +- Planning recommendations now reduce against already-linked draft/open supply to avoid duplicate WO/PO generation - Shared MRP demand engine across sales, inventory, purchasing, manufacturing, projects, and planning - Planned work-order and purchase-order recommendation generation @@ -274,7 +279,7 @@ QOL subfeatures: - Better shortage and substitute-part guidance during planning review - Saved planning views by customer, project, item family, and shortage state - Planner-focused drilldowns from demand source to buy/build action without re-keying data -- More explicit pegging between parent demand and generated supply actions +- Time-phased supply recommendations with vendor lead times and build timing ### Phase 9: Security, audit, and operations maturity @@ -320,5 +325,5 @@ QOL subfeatures: ## Near-term priority order -1. Convert demand-planning recommendations into work orders and purchase orders -2. Shared shortage and readiness rollups across planning, purchasing, manufacturing, and projects +1. Better user and session visibility for operational admins +2. Safer destructive-action confirmations and recovery messaging diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts index 6ee59ed..f3a676e 100644 --- a/client/src/lib/api.ts +++ b/client/src/lib/api.ts @@ -68,6 +68,7 @@ import type { } from "@mrp/shared/dist/projects/types.js"; import type { SalesCustomerOptionDto, + DemandPlanningRollupDto, SalesDocumentDetailDto, SalesDocumentInput, SalesOrderPlanningDto, @@ -635,6 +636,9 @@ export const api = { getSalesOrderPlanning(token: string, orderId: string) { return request(`/api/v1/sales/orders/${orderId}/planning`, undefined, token); }, + getDemandPlanningRollup(token: string) { + return request("/api/v1/sales/planning-rollup", undefined, token); + }, createSalesOrder(token: string, payload: SalesDocumentInput) { return request("/api/v1/sales/orders", { method: "POST", body: JSON.stringify(payload) }, token); }, diff --git a/client/src/modules/dashboard/DashboardPage.tsx b/client/src/modules/dashboard/DashboardPage.tsx index 035b4b7..99de6a3 100644 --- a/client/src/modules/dashboard/DashboardPage.tsx +++ b/client/src/modules/dashboard/DashboardPage.tsx @@ -1,4 +1,5 @@ import { permissions } from "@mrp/shared"; +import type { DemandPlanningRollupDto } from "@mrp/shared/dist/sales/types.js"; import { useEffect, useState } from "react"; import { Link } from "react-router-dom"; @@ -16,6 +17,7 @@ interface DashboardSnapshot { orders: Awaited> | null; shipments: Awaited> | null; projects: Awaited> | null; + planningRollup: DemandPlanningRollupDto | null; refreshedAt: string; } @@ -87,8 +89,9 @@ export function DashboardPage() { canReadManufacturing ? api.getWorkOrders(authToken) : Promise.resolve(null), canReadSales ? api.getQuotes(authToken) : Promise.resolve(null), canReadSales ? api.getSalesOrders(authToken) : Promise.resolve(null), - canReadShipping ? api.getShipments(authToken) : Promise.resolve(null), - canReadProjects ? api.getProjects(authToken) : Promise.resolve(null), + canReadShipping ? api.getShipments(authToken) : Promise.resolve(null), + canReadProjects ? api.getProjects(authToken) : Promise.resolve(null), + canReadSales ? api.getDemandPlanningRollup(authToken) : Promise.resolve(null), ]); if (!isMounted) { @@ -112,6 +115,7 @@ export function DashboardPage() { orders: results[7].status === "fulfilled" ? results[7].value : null, shipments: results[8].status === "fulfilled" ? results[8].value : null, projects: results[9].status === "fulfilled" ? results[9].value : null, + planningRollup: results[10].status === "fulfilled" ? results[10].value : null, refreshedAt: new Date().toISOString(), }); setIsLoading(false); @@ -142,6 +146,7 @@ export function DashboardPage() { const orders = snapshot?.orders ?? []; const shipments = snapshot?.shipments ?? []; const projects = snapshot?.projects ?? []; + const planningRollup = snapshot?.planningRollup; const accessibleModules = [ snapshot?.customers !== null || snapshot?.vendors !== null, @@ -199,6 +204,10 @@ export function DashboardPage() { return new Date(project.dueDate).getTime() < Date.now(); }).length; + const shortageItemCount = planningRollup?.summary.uncoveredItemCount ?? 0; + const buyRecommendationCount = planningRollup?.summary.purchaseRecommendationCount ?? 0; + const buildRecommendationCount = planningRollup?.summary.buildRecommendationCount ?? 0; + const totalUncoveredQuantity = planningRollup?.summary.totalUncoveredQuantity ?? 0; const lastActivityAt = [ ...customers.map((customer) => customer.updatedAt), @@ -279,6 +288,14 @@ export function DashboardPage() { : "Project metrics are permission-gated.", tone: "border-violet-400/30 bg-violet-500/12 text-violet-700 dark:text-violet-300", }, + { + label: "Material Readiness", + value: planningRollup ? `${shortageItemCount}` : "No access", + detail: planningRollup + ? `${buildRecommendationCount} build and ${buyRecommendationCount} buy recommendations` + : "Sales read permission is required to surface shortage rollups.", + tone: "border-rose-400/30 bg-rose-500/12 text-rose-700 dark:text-rose-300", + }, ]; const modulePanels = [ @@ -404,12 +421,12 @@ export function DashboardPage() { title: "Planning", eyebrow: "Schedule Visibility", summary: canReadPlanning - ? "Live gantt planning now pulls directly from active projects and open manufacturing work orders to show due-date pressure in one schedule view." + ? "Live gantt planning now pulls directly from active projects and open manufacturing work orders, with shared shortage/readiness rollups alongside schedule pressure." : "Planning read permission is required to surface the live gantt schedule.", metrics: [ { label: "At risk projects", value: canReadPlanning ? `${atRiskProjectCount}` : "No access" }, - { label: "Overdue work", value: canReadPlanning ? `${overdueWorkOrderCount}` : "No access" }, - { label: "Schedule links", value: canReadPlanning ? `${activeProjectCount + activeWorkOrderCount}` : "No access" }, + { label: "Shortage items", value: canReadPlanning && planningRollup ? `${shortageItemCount}` : "No access" }, + { label: "Build / buy", value: canReadPlanning && planningRollup ? `${buildRecommendationCount} / ${buyRecommendationCount}` : "No access" }, ], links: canReadPlanning ? [{ label: "Open gantt", to: "/planning/gantt" }] : [], }, @@ -533,6 +550,28 @@ export function DashboardPage() { ))}
+
+

Planning Watch

+

Shared shortage and readiness

+
+
+ Shortage items + {planningRollup ? `${shortageItemCount}` : "No access"} +
+
+ Build recommendations + {planningRollup ? `${buildRecommendationCount}` : "No access"} +
+
+ Buy recommendations + {planningRollup ? `${buyRecommendationCount}` : "No access"} +
+
+ Uncovered qty + {planningRollup ? `${totalUncoveredQuantity}` : "No access"} +
+
+

Inventory Watch

Master data pressure points

diff --git a/client/src/modules/gantt/GanttPage.tsx b/client/src/modules/gantt/GanttPage.tsx index 3577495..b5d6bf4 100644 --- a/client/src/modules/gantt/GanttPage.tsx +++ b/client/src/modules/gantt/GanttPage.tsx @@ -3,7 +3,7 @@ import { Gantt } from "@svar-ui/react-gantt"; import "@svar-ui/react-gantt/style.css"; import { Link } from "react-router-dom"; -import type { GanttTaskDto, PlanningExceptionDto, PlanningTimelineDto } from "@mrp/shared"; +import type { DemandPlanningRollupDto, GanttTaskDto, PlanningExceptionDto, PlanningTimelineDto } from "@mrp/shared"; import { useAuth } from "../../auth/AuthProvider"; import { ApiError, api } from "../../lib/api"; @@ -24,6 +24,7 @@ export function GanttPage() { const { token } = useAuth(); const { mode } = useTheme(); const [timeline, setTimeline] = useState(null); + const [planningRollup, setPlanningRollup] = useState(null); const [status, setStatus] = useState("Loading live planning timeline..."); useEffect(() => { @@ -31,10 +32,10 @@ export function GanttPage() { return; } - api - .getPlanningTimeline(token) - .then((data) => { + Promise.all([api.getPlanningTimeline(token), api.getDemandPlanningRollup(token)]) + .then(([data, rollup]) => { setTimeline(data); + setPlanningRollup(rollup); setStatus("Planning timeline loaded."); }) .catch((error: unknown) => { @@ -90,6 +91,16 @@ export function GanttPage() {

Unscheduled Work

{summary?.unscheduledWorkOrders ?? 0}
+
+

Shortage Items

+
{planningRollup?.summary.uncoveredItemCount ?? 0}
+
+
+

Build / Buy

+
+ {planningRollup ? `${planningRollup.summary.totalBuildQuantity} / ${planningRollup.summary.totalPurchaseQuantity}` : "0 / 0"} +
+

Planner Actions

+
+
+ Uncovered quantity + {planningRollup?.summary.totalUncoveredQuantity ?? 0} +
+
+ Projects with linked demand + {planningRollup?.summary.projectCount ?? 0} +
+
Open projects diff --git a/client/src/modules/inventory/InventoryDetailPage.tsx b/client/src/modules/inventory/InventoryDetailPage.tsx index 584aadc..233e747 100644 --- a/client/src/modules/inventory/InventoryDetailPage.tsx +++ b/client/src/modules/inventory/InventoryDetailPage.tsx @@ -252,6 +252,10 @@ export function InventoryDetailPage() {
Default price
{item.defaultPrice == null ? "Not set" : `$${item.defaultPrice.toFixed(2)}`}
+
+
Preferred vendor
+
{item.preferredVendorName ?? "Not set"}
+
Flags
diff --git a/client/src/modules/inventory/InventoryFormPage.tsx b/client/src/modules/inventory/InventoryFormPage.tsx index e93471c..f4e6749 100644 --- a/client/src/modules/inventory/InventoryFormPage.tsx +++ b/client/src/modules/inventory/InventoryFormPage.tsx @@ -1,3 +1,4 @@ +import type { PurchaseVendorOptionDto } from "@mrp/shared"; import type { InventoryBomLineInput, InventoryItemInput, InventoryItemOperationInput, InventoryItemOptionDto } from "@mrp/shared/dist/inventory/types.js"; import type { ManufacturingStationDto } from "@mrp/shared"; import { useEffect, useState } from "react"; @@ -18,8 +19,11 @@ export function InventoryFormPage({ mode }: InventoryFormPageProps) { const [form, setForm] = useState(emptyInventoryItemInput); const [componentOptions, setComponentOptions] = useState([]); const [stations, setStations] = useState([]); + const [vendorOptions, setVendorOptions] = useState([]); const [componentSearchTerms, setComponentSearchTerms] = useState([]); const [activeComponentPicker, setActiveComponentPicker] = useState(null); + const [vendorSearchTerm, setVendorSearchTerm] = useState(""); + const [vendorPickerOpen, setVendorPickerOpen] = useState(false); const [status, setStatus] = useState(mode === "create" ? "Create a new inventory item." : "Loading inventory item..."); const [isSaving, setIsSaving] = useState(false); @@ -72,6 +76,7 @@ export function InventoryFormPage({ mode }: InventoryFormPageProps) { unitOfMeasure: item.unitOfMeasure, isSellable: item.isSellable, isPurchasable: item.isPurchasable, + preferredVendorId: item.preferredVendorId, defaultCost: item.defaultCost, defaultPrice: item.defaultPrice, notes: item.notes, @@ -93,6 +98,7 @@ export function InventoryFormPage({ mode }: InventoryFormPageProps) { }); setComponentSearchTerms(item.bomLines.map((line) => line.componentSku)); setStatus("Inventory item loaded."); + setVendorSearchTerm(item.preferredVendorName ?? ""); }) .catch((error: unknown) => { const message = error instanceof ApiError ? error.message : "Unable to load inventory item."; @@ -106,12 +112,21 @@ export function InventoryFormPage({ mode }: InventoryFormPageProps) { } api.getManufacturingStations(token).then(setStations).catch(() => setStations([])); + api.getPurchaseVendors(token).then(setVendorOptions).catch(() => setVendorOptions([])); }, [token]); function updateField(key: Key, value: InventoryItemInput[Key]) { setForm((current) => ({ ...current, [key]: value })); } + function getSelectedVendorName(vendorId: string | null) { + if (!vendorId) { + return ""; + } + + return vendorOptions.find((vendor) => vendor.id === vendorId)?.name ?? ""; + } + function updateBomLine(index: number, nextLine: InventoryBomLineInput) { setForm((current) => ({ ...current, @@ -315,6 +330,76 @@ export function InventoryFormPage({ mode }: InventoryFormPageProps) {
+