From 16582d3ceace1c486442cec69c9af183f10959b5 Mon Sep 17 00:00:00 2001 From: jason Date: Sun, 15 Mar 2026 12:11:46 -0500 Subject: [PATCH] manufacturing and gantt --- AGENTS.md | 10 +- CHANGELOG.md | 8 +- INSTRUCTIONS.md | 4 +- README.md | 35 +- ROADMAP.md | 16 +- client/src/lib/api.ts | 15 +- .../src/modules/dashboard/DashboardPage.tsx | 23 +- client/src/modules/gantt/GanttPage.tsx | 168 ++++++- .../modules/inventory/InventoryDetailPage.tsx | 47 +- .../modules/inventory/InventoryFormPage.tsx | 117 ++++- client/src/modules/inventory/config.ts | 11 + .../manufacturing/ManufacturingPage.tsx | 117 ++++- .../manufacturing/WorkOrderDetailPage.tsx | 37 +- .../manufacturing/WorkOrderFormPage.tsx | 2 +- .../manufacturing/WorkOrderListPage.tsx | 2 + .../migration.sql | 49 ++ server/prisma/schema.prisma | 54 ++ server/src/modules/gantt/router.ts | 17 +- server/src/modules/gantt/service.ts | 460 ++++++++++++++++++ server/src/modules/inventory/router.ts | 10 + server/src/modules/inventory/service.ts | 117 +++++ server/src/modules/manufacturing/router.ts | 23 + server/src/modules/manufacturing/service.ts | 253 +++++++++- shared/src/gantt/types.ts | 31 ++ shared/src/inventory/types.ts | 24 + shared/src/manufacturing/types.ts | 39 ++ 26 files changed, 1614 insertions(+), 75 deletions(-) create mode 100644 server/prisma/migrations/20260315194500_manufacturing_stations_and_operations/migration.sql create mode 100644 server/src/modules/gantt/service.ts diff --git a/AGENTS.md b/AGENTS.md index d307ece..ad13a2e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,8 @@ MRP Codex is a modular Manufacturing Resource Planning platform intended to be a - purchase-order supporting documents and vendor-side purchasing visibility - shipping shipments, packing-slip PDFs, shipping labels, bills of lading, and logistics attachments - projects with customer/commercial/shipment linkage, owners, due dates, notes, and attachments -- manufacturing work orders with project linkage, material issue posting, completion posting, and attachments +- 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 - Puppeteer PDF foundation - single-container Docker deployment @@ -117,10 +118,9 @@ If implementation changes invalidate those docs, update them in the same change Near-term priorities are: -1. Planning and gantt scheduling with live project/manufacturing data -2. Inventory transfers, reservations, and deeper stock controls -3. Broader audit-trail coverage and operational diagnostics -4. Code-splitting and bundle-size reduction +1. Inventory transfers, reservations, and deeper stock controls +2. Broader audit-trail coverage and operational diagnostics +3. Code-splitting and bundle-size reduction 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 c11bf2c..1f9c2d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This file is the running release and change log for MRP Codex. Keep it updated w ### Added +- Manufacturing stations with queue-day definitions and item-level station/time operation templates +- Automatic work-order operation plans copied from buildable item routing into planning/gantt +- Live planning gantt timelines backed by active projects and open manufacturing work orders +- Planning summary metrics and exception cards for overdue or at-risk project/manufacturing schedule items - Sales approval actions with approved-by/approved-at stamps on quotes and sales orders - Automatic sales-document revision history with authored reasons and per-revision snapshots - Projects domain foundation with customer, owner, due date, priority, notes, and attachment support @@ -22,12 +26,14 @@ This file is the running release and change log for MRP Codex. Keep it updated w - The dashboard now treats Projects as a live first-class module alongside CRM, inventory, sales, and shipping - The dashboard now treats Manufacturing as a live first-class module alongside CRM, inventory, sales, shipping, and projects +- The dashboard now treats Planning as a live first-class module with direct gantt access from the landing page +- Manufacturing and inventory now share a routing-driven workflow where assemblies/manufactured parts define station/time templates and work orders inherit them automatically - Sales quote and sales-order detail pages now surface approval state and revision history directly in the operational workflow - Project editing now uses searchable pickers for customer, owner, quote, sales-order, and shipment linkage instead of static operational dropdowns - Project detail now surfaces linked work orders and can launch pre-seeded manufacturing records - Purchase-order detail now links back to the vendor CRM record and supports direct supporting-document management on the PO itself - Vendor CRM detail now exposes purchasing activity and can launch pre-seeded purchase orders -- Roadmap and project docs now treat planning and gantt scheduling as the next active priority after the sales approval and revision-history slice +- Roadmap and project docs now treat inventory transfers and deeper stock controls as the next active priority after the planning slice ## 2026-03-15 diff --git a/INSTRUCTIONS.md b/INSTRUCTIONS.md index d6b6d06..b32e29d 100644 --- a/INSTRUCTIONS.md +++ b/INSTRUCTIONS.md @@ -24,7 +24,8 @@ This repository implements the platform foundation milestone: - purchase-order supporting documents and vendor-side purchasing visibility - shipping shipments linked to sales orders with packing slips, shipping labels, bills of lading, and logistics attachments - projects with customer/commercial/shipment linkage, owners, due dates, notes, attachments, and dashboard visibility -- manufacturing work orders with project linkage, material issue posting, completion posting, attachments, and dashboard visibility +- 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 - Dockerized single-container deployment - Puppeteer PDF pipeline foundation @@ -60,7 +61,6 @@ This repository implements the platform foundation milestone: ## Next roadmap candidates -- planning and gantt scheduling with live project/manufacturing data - inventory transfers, reservations, and deeper stock controls - broader audit and operations maturity - code-splitting and bundle-size reduction diff --git a/README.md b/README.md index 3298222..fcbe7d2 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@ Current foundation scope includes: - purchase-order supporting documents for vendor invoices, acknowledgements, certifications, and backup files - shipping shipments linked to sales orders with packing slips, shipping labels, bills of lading, and logistics attachments - projects with customer/commercial/shipment linkage, owners, due dates, notes, and attachments -- manufacturing work orders with project linkage, material issue posting, completion posting, and work-order attachments +- 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 - file storage and PDF rendering ## Product Map @@ -37,18 +38,14 @@ Current completed foundation areas: - shipping foundation - projects foundation - manufacturing foundation +- planning foundation - branding, attachments, auth/RBAC, and PDF infrastructure -Planned cross-module execution areas: - -- planning and gantt scheduling - Near-term priorities: -1. Planning and gantt scheduling with live project/manufacturing data -2. Inventory transfers, reservations, and deeper stock controls -3. Broader audit-trail coverage and operational diagnostics -4. Code-splitting and bundle-size reduction +1. Inventory transfers, reservations, and deeper stock controls +2. Broader audit-trail coverage and operational diagnostics +3. Code-splitting and bundle-size reduction Revisit / deferred items: @@ -66,6 +63,7 @@ Dashboard direction: - richer recent-activity widgets and exception queues are a planned QOL follow-up, not a separate landing-page redesign - projects now feed dashboard widgets for active programs, overdue work, and risk - manufacturing now feeds dashboard widgets for released work, overdue orders, and execution load +- planning now feeds live gantt scheduling from project and manufacturing records - future project widgets should deepen milestones, shortages, and shipment readiness Navigation direction: @@ -94,7 +92,7 @@ Next expansion areas: ## Manufacturing Direction -Manufacturing is now a separate execution subsystem rather than being collapsed into Projects. The current slice ships work-order records with build-item linkage, optional project linkage, warehouse/location output posting, BOM-based material requirement visibility, material issue posting, completion posting, work-order attachments, and dashboard visibility. +Manufacturing is now a separate execution subsystem rather than being collapsed into Projects. The current slice ships work-order records with build-item linkage, optional project linkage, warehouse/location output posting, BOM-based material requirement visibility, station master data, item-level operation templates, automatic work-order operation plans, material issue posting, completion posting, work-order attachments, and dashboard visibility. Current interactions: @@ -108,6 +106,22 @@ Next expansion areas: - Shipping: completed manufacturing should feed shipment readiness - Planning: manufacturing orders, routings, and work centers should drive capacity and schedule views +## Planning Direction + +Planning is now the live scheduling and visibility layer over projects and manufacturing instead of a placeholder wrapper. The current slice ships a gantt surface backed by active projects, due-date milestones, linked work orders, standalone manufacturing queue visibility, and exception cards for overdue or at-risk schedule items. + +Current interactions: + +- Projects: project timelines and due dates anchor the top-level planning rows +- Manufacturing: open work orders feed task rows, sequencing links, and execution progress +- Dashboard: planning now appears as a first-class module with schedule visibility links + +Next expansion areas: + +- Purchasing: shortages, late receipts, and vendor risk should surface directly in planning +- Manufacturing: routings, work centers, and capacity should deepen the schedule model +- Projects: richer milestones and dependency editing should extend the project-level timeline + ## Workspace - `client`: React, Vite, Tailwind frontend @@ -318,6 +332,7 @@ As of March 14, 2026, the latest committed domain migrations include: - shipping foundation - projects foundation - manufacturing foundation +- planning foundation Recent roadmap-driving migrations should always be applied before validating new CRM, inventory, sales, shipping, or purchasing features in a running environment. diff --git a/ROADMAP.md b/ROADMAP.md index 849683e..145e7b0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -43,12 +43,13 @@ MRP Codex is being built as a streamlined, modular manufacturing resource planni - Projects foundation with customer, quote, sales-order, shipment, owner, due-date, notes, and attachment linkage - 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 - Vendor invoice/supporting-document attachments directly on purchase orders - Vendor-detail purchasing visibility with recent purchase-order activity - SKU-searchable BOM component selection for inventory-scale datasets - Theme persistence fixes and denser responsive workspace layouts - Full-site density normalization pass across active CRM, inventory, settings, dashboard, and login screens -- SVAR Gantt integration wrapper with demo planning data +- Live planning gantt timelines driven by project and manufacturing data - Multi-stage Docker packaging and migration-aware entrypoint - Docker image validated locally with successful app startup and login flow - Core project documentation in `README.md`, `INSTRUCTIONS.md`, and `STRUCTURE.md` @@ -222,6 +223,12 @@ QOL subfeatures: ### Phase 7: Planning and scheduling +Foundation slice shipped: + +- Live gantt schedule backed by active projects and open manufacturing work orders +- Project due-date milestones, manufacturing sequencing links, and standalone work-queue visibility +- Planning exception queue for overdue or at-risk project/manufacturing schedule items + - Live project-backed SVAR gantt timelines - Task dependencies, milestones, and progress updates - Manufacturing calendar views and bottleneck visibility @@ -275,7 +282,6 @@ QOL subfeatures: ## Near-term priority order -1. Planning and scheduling with live project/manufacturing data -2. Inventory transfers, reservations, and deeper stock controls -3. Broader audit-trail coverage and operational diagnostics -4. Code-splitting and bundle-size reduction +1. Inventory transfers, reservations, and deeper stock controls +2. Broader audit-trail coverage and operational diagnostics +3. Code-splitting and bundle-size reduction diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts index 2b279ea..3e33363 100644 --- a/client/src/lib/api.ts +++ b/client/src/lib/api.ts @@ -3,8 +3,7 @@ import type { CompanyProfileDto, CompanyProfileInput, FileAttachmentDto, - GanttLinkDto, - GanttTaskDto, + PlanningTimelineDto, LoginRequest, LoginResponse, } from "@mrp/shared"; @@ -34,6 +33,8 @@ import type { WarehouseSummaryDto, } from "@mrp/shared/dist/inventory/types.js"; import type { + ManufacturingStationDto, + ManufacturingStationInput, ManufacturingItemOptionDto, ManufacturingProjectOptionDto, WorkOrderCompletionInput, @@ -461,6 +462,12 @@ export const api = { getManufacturingProjectOptions(token: string) { return request("/api/v1/manufacturing/projects/options", undefined, token); }, + getManufacturingStations(token: string) { + return request("/api/v1/manufacturing/stations", undefined, token); + }, + createManufacturingStation(token: string, payload: ManufacturingStationInput) { + return request("/api/v1/manufacturing/stations", { method: "POST", body: JSON.stringify(payload) }, token); + }, getWorkOrders(token: string, filters?: { q?: string; status?: WorkOrderStatus; projectId?: string; itemId?: string }) { return request( `/api/v1/manufacturing/work-orders${buildQueryString({ @@ -503,8 +510,8 @@ export const api = { token ); }, - getGanttDemo(token: string) { - return request<{ tasks: GanttTaskDto[]; links: GanttLinkDto[] }>("/api/v1/gantt/demo", undefined, token); + getPlanningTimeline(token: string) { + return request("/api/v1/gantt/timeline", undefined, token); }, getSalesCustomers(token: string) { return request("/api/v1/sales/customers/options", undefined, token); diff --git a/client/src/modules/dashboard/DashboardPage.tsx b/client/src/modules/dashboard/DashboardPage.tsx index a682576..035b4b7 100644 --- a/client/src/modules/dashboard/DashboardPage.tsx +++ b/client/src/modules/dashboard/DashboardPage.tsx @@ -55,6 +55,7 @@ export function DashboardPage() { const [error, setError] = useState(null); const canWriteManufacturing = hasPermission(user?.permissions, permissions.manufacturingWrite); const canWriteProjects = hasPermission(user?.permissions, permissions.projectsWrite); + const canReadPlanning = hasPermission(user?.permissions, permissions.ganttRead); useEffect(() => { if (!token || !user) { @@ -150,6 +151,7 @@ export function DashboardPage() { snapshot?.quotes !== null || snapshot?.orders !== null, snapshot?.shipments !== null, snapshot?.projects !== null, + canReadPlanning, ].filter(Boolean).length; const customerCount = customers.length; @@ -398,12 +400,24 @@ export function DashboardPage() { ...(canWriteProjects ? [{ label: "New project", to: "/projects/new" }] : []), ], }, + { + 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." + : "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" }, + ], + links: canReadPlanning ? [{ label: "Open gantt", to: "/planning/gantt" }] : [], + }, ]; const futureModules = [ "Stock transfers, allocations, and cycle counts", - "Planning timeline, milestones, and dependency views", - "Sales approvals, revisions, and change history", + "Revision comparison and document restore tooling", "Audit trails, diagnostics, and system health checks", ]; @@ -461,6 +475,11 @@ export function DashboardPage() { Open manufacturing + {canReadPlanning ? ( + + Open planning + + ) : null} {error ?
{error}
: null} diff --git a/client/src/modules/gantt/GanttPage.tsx b/client/src/modules/gantt/GanttPage.tsx index b87a353..3577495 100644 --- a/client/src/modules/gantt/GanttPage.tsx +++ b/client/src/modules/gantt/GanttPage.tsx @@ -1,48 +1,166 @@ import { useEffect, useState } from "react"; import { Gantt } from "@svar-ui/react-gantt"; import "@svar-ui/react-gantt/style.css"; +import { Link } from "react-router-dom"; -import type { GanttLinkDto, GanttTaskDto } from "@mrp/shared"; +import type { GanttTaskDto, PlanningExceptionDto, PlanningTimelineDto } from "@mrp/shared"; import { useAuth } from "../../auth/AuthProvider"; -import { api } from "../../lib/api"; +import { ApiError, api } from "../../lib/api"; import { useTheme } from "../../theme/ThemeProvider"; +function formatDate(value: string | null) { + if (!value) { + return "Unscheduled"; + } + + return new Intl.DateTimeFormat("en-US", { + month: "short", + day: "numeric", + }).format(new Date(value)); +} + export function GanttPage() { const { token } = useAuth(); const { mode } = useTheme(); - const [tasks, setTasks] = useState([]); - const [links, setLinks] = useState([]); + const [timeline, setTimeline] = useState(null); + const [status, setStatus] = useState("Loading live planning timeline..."); useEffect(() => { if (!token) { return; } - api.getGanttDemo(token).then((data) => { - setTasks(data.tasks); - setLinks(data.links); - }); + api + .getPlanningTimeline(token) + .then((data) => { + setTimeline(data); + setStatus("Planning timeline loaded."); + }) + .catch((error: unknown) => { + const message = error instanceof ApiError ? error.message : "Unable to load planning timeline."; + setStatus(message); + }); }, [token]); + const tasks = timeline?.tasks ?? []; + const links = timeline?.links ?? []; + const summary = timeline?.summary; + const exceptions = timeline?.exceptions ?? []; + return ( -
-

Planning

-

SVAR Gantt Preview

-

Theme-aware integration wrapper prepared for future manufacturing schedules and task dependencies.

-
- ({ - ...task, - start: new Date(task.start), - end: new Date(task.end), - }))} - links={links} - /> +
+
+
+
+

Planning

+

Live Project + Manufacturing Gantt

+

+ The planning surface now reads directly from active projects and open work orders so schedule pressure, due-date risk, and standalone manufacturing load are visible in one place. +

+
+
+
Timeline Status
+
{status}
+
+
+
+
+
+

Active Projects

+
{summary?.activeProjects ?? 0}
+
+
+

At Risk

+
{summary?.atRiskProjects ?? 0}
+
+
+

Overdue Projects

+
{summary?.overdueProjects ?? 0}
+
+
+

Active Work Orders

+
{summary?.activeWorkOrders ?? 0}
+
+
+

Overdue Work

+
{summary?.overdueWorkOrders ?? 0}
+
+
+

Unscheduled Work

+
{summary?.unscheduledWorkOrders ?? 0}
+
+
+
+
+
+
+

Schedule Window

+

+ {summary ? `${formatDate(summary.horizonStart)} through ${formatDate(summary.horizonEnd)}` : "Waiting for live schedule data."} +

+
+
+ {tasks.length} schedule rows +
+
+ ({ + ...task, + start: new Date(task.start), + end: new Date(task.end), + parent: task.parentId ?? undefined, + }))} + links={links} + /> +
+
); diff --git a/client/src/modules/inventory/InventoryDetailPage.tsx b/client/src/modules/inventory/InventoryDetailPage.tsx index fe23e41..adc478a 100644 --- a/client/src/modules/inventory/InventoryDetailPage.tsx +++ b/client/src/modules/inventory/InventoryDetailPage.tsx @@ -122,7 +122,7 @@ export function InventoryDetailPage() {
-
+

On Hand

{item.onHandQuantity}
@@ -139,6 +139,10 @@ export function InventoryDetailPage() {

BOM Lines

{item.bomLines.length}
+
+

Operations

+
{item.operations.length}
+
@@ -235,6 +239,47 @@ export function InventoryDetailPage() {
)}
+ {(item.type === "ASSEMBLY" || item.type === "MANUFACTURED") ? ( +
+

Manufacturing Routing

+

Station template

+ {item.operations.length === 0 ? ( +
+ No station operations are defined for this buildable item yet. +
+ ) : ( +
+ + + + + + + + + + + + + {item.operations.map((operation) => ( + + + + + + + + + ))} + +
PositionStationSetupRun / UnitMoveNotes
{operation.position} +
{operation.stationCode}
+
{operation.stationName}
+
{operation.setupMinutes} min{operation.runMinutesPerUnit} min{operation.moveMinutes} min{operation.notes || "-"}
+
+ )} +
+ ) : null}
{canManage ? (
diff --git a/client/src/modules/inventory/InventoryFormPage.tsx b/client/src/modules/inventory/InventoryFormPage.tsx index 228817f..e93471c 100644 --- a/client/src/modules/inventory/InventoryFormPage.tsx +++ b/client/src/modules/inventory/InventoryFormPage.tsx @@ -1,10 +1,11 @@ -import type { InventoryBomLineInput, InventoryItemInput, InventoryItemOptionDto } from "@mrp/shared/dist/inventory/types.js"; +import type { InventoryBomLineInput, InventoryItemInput, InventoryItemOperationInput, InventoryItemOptionDto } from "@mrp/shared/dist/inventory/types.js"; +import type { ManufacturingStationDto } from "@mrp/shared"; import { useEffect, useState } from "react"; import { Link, useNavigate, useParams } from "react-router-dom"; import { useAuth } from "../../auth/AuthProvider"; import { api, ApiError } from "../../lib/api"; -import { emptyInventoryBomLineInput, emptyInventoryItemInput, inventoryStatusOptions, inventoryTypeOptions, inventoryUnitOptions } from "./config"; +import { emptyInventoryBomLineInput, emptyInventoryItemInput, emptyInventoryOperationInput, inventoryStatusOptions, inventoryTypeOptions, inventoryUnitOptions } from "./config"; interface InventoryFormPageProps { mode: "create" | "edit"; @@ -16,6 +17,7 @@ export function InventoryFormPage({ mode }: InventoryFormPageProps) { const { itemId } = useParams(); const [form, setForm] = useState(emptyInventoryItemInput); const [componentOptions, setComponentOptions] = useState([]); + const [stations, setStations] = useState([]); const [componentSearchTerms, setComponentSearchTerms] = useState([]); const [activeComponentPicker, setActiveComponentPicker] = useState(null); const [status, setStatus] = useState(mode === "create" ? "Create a new inventory item." : "Loading inventory item..."); @@ -80,6 +82,14 @@ export function InventoryFormPage({ mode }: InventoryFormPageProps) { notes: line.notes, position: line.position, })), + operations: item.operations.map((operation) => ({ + stationId: operation.stationId, + setupMinutes: operation.setupMinutes, + runMinutesPerUnit: operation.runMinutesPerUnit, + moveMinutes: operation.moveMinutes, + position: operation.position, + notes: operation.notes, + })), }); setComponentSearchTerms(item.bomLines.map((line) => line.componentSku)); setStatus("Inventory item loaded."); @@ -90,6 +100,14 @@ export function InventoryFormPage({ mode }: InventoryFormPageProps) { }); }, [itemId, mode, token]); + useEffect(() => { + if (!token) { + return; + } + + api.getManufacturingStations(token).then(setStations).catch(() => setStations([])); + }, [token]); + function updateField(key: Key, value: InventoryItemInput[Key]) { setForm((current) => ({ ...current, [key]: value })); } @@ -123,6 +141,33 @@ export function InventoryFormPage({ mode }: InventoryFormPageProps) { setComponentSearchTerms((current) => [...current, ""]); } + function updateOperation(index: number, nextOperation: InventoryItemOperationInput) { + setForm((current) => ({ + ...current, + operations: current.operations.map((operation, operationIndex) => (operationIndex === index ? nextOperation : operation)), + })); + } + + function addOperation() { + setForm((current) => ({ + ...current, + operations: [ + ...current.operations, + { + ...emptyInventoryOperationInput, + position: current.operations.length === 0 ? 10 : Math.max(...current.operations.map((operation) => operation.position)) + 10, + }, + ], + })); + } + + function removeOperation(index: number) { + setForm((current) => ({ + ...current, + operations: current.operations.filter((_operation, operationIndex) => operationIndex !== index), + })); + } + function removeBomLine(index: number) { setForm((current) => ({ ...current, @@ -289,6 +334,74 @@ export function InventoryFormPage({ mode }: InventoryFormPageProps) { />
+ {form.type === "ASSEMBLY" || form.type === "MANUFACTURED" ? ( +
+
+
+

Manufacturing Routing

+

Station and time template

+

These operations are copied automatically into work orders and drive gantt scheduling without manual planner task entry.

+
+ +
+ {form.operations.length === 0 ? ( +
+ Add at least one station operation for this buildable item. +
+ ) : ( +
+ {form.operations.map((operation, index) => ( +
+
+ + + + + +
+ +
+
+ +
+ ))} +
+ )} +
+ ) : null}
diff --git a/client/src/modules/inventory/config.ts b/client/src/modules/inventory/config.ts index b0f4a52..76ee472 100644 --- a/client/src/modules/inventory/config.ts +++ b/client/src/modules/inventory/config.ts @@ -5,6 +5,7 @@ import { inventoryUnitsOfMeasure, type InventoryBomLineInput, type InventoryItemInput, + type InventoryItemOperationInput, type WarehouseInput, type WarehouseLocationInput, type InventoryItemStatus, @@ -22,6 +23,15 @@ export const emptyInventoryBomLineInput: InventoryBomLineInput = { position: 10, }; +export const emptyInventoryOperationInput: InventoryItemOperationInput = { + stationId: "", + setupMinutes: 0, + runMinutesPerUnit: 0, + moveMinutes: 0, + position: 10, + notes: "", +}; + export const emptyInventoryItemInput: InventoryItemInput = { sku: "", name: "", @@ -35,6 +45,7 @@ export const emptyInventoryItemInput: InventoryItemInput = { defaultPrice: null, notes: "", bomLines: [], + operations: [], }; export const emptyInventoryTransactionInput: InventoryTransactionInput = { diff --git a/client/src/modules/manufacturing/ManufacturingPage.tsx b/client/src/modules/manufacturing/ManufacturingPage.tsx index 4492e57..ec4e76e 100644 --- a/client/src/modules/manufacturing/ManufacturingPage.tsx +++ b/client/src/modules/manufacturing/ManufacturingPage.tsx @@ -1,5 +1,120 @@ +import { permissions, type ManufacturingStationInput, type ManufacturingStationDto } from "@mrp/shared"; +import { useEffect, useState } from "react"; + +import { useAuth } from "../../auth/AuthProvider"; +import { api, ApiError } from "../../lib/api"; import { WorkOrderListPage } from "./WorkOrderListPage"; +const emptyStationInput: ManufacturingStationInput = { + code: "", + name: "", + description: "", + queueDays: 0, + isActive: true, +}; + export function ManufacturingPage() { - return ; + const { token, user } = useAuth(); + const [stations, setStations] = useState([]); + const [form, setForm] = useState(emptyStationInput); + const [status, setStatus] = useState("Define manufacturing stations once so routings and work orders can schedule automatically."); + const [isSaving, setIsSaving] = useState(false); + const canManage = user?.permissions.includes(permissions.manufacturingWrite) ?? false; + + useEffect(() => { + if (!token) { + return; + } + + api.getManufacturingStations(token).then(setStations).catch(() => setStations([])); + }, [token]); + + async function handleSubmit(event: React.FormEvent) { + event.preventDefault(); + if (!token) { + return; + } + + setIsSaving(true); + setStatus("Saving station..."); + try { + const station = await api.createManufacturingStation(token, form); + setStations((current) => [...current, station].sort((left, right) => left.code.localeCompare(right.code))); + setForm(emptyStationInput); + setStatus("Station saved."); + } catch (error: unknown) { + const message = error instanceof ApiError ? error.message : "Unable to save station."; + setStatus(message); + } finally { + setIsSaving(false); + } + } + + return ( +
+
+
+

Manufacturing Stations

+

Scheduling anchors

+

Stations define where operation time belongs. Buildable items reference them in their routing template, and work orders inherit those steps automatically into planning.

+ {stations.length === 0 ? ( +
+ No stations defined yet. +
+ ) : ( +
+ {stations.map((station) => ( +
+
+
+
{station.code} - {station.name}
+
{station.description || "No description"}
+
+
+
{station.queueDays} queue day(s)
+
{station.isActive ? "Active" : "Inactive"}
+
+
+
+ ))} +
+ )} +
+ {canManage ? ( +
+

New Station

+
+ + + +