diff --git a/AGENTS.md b/AGENTS.md index ad13a2e..2534770 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,6 +16,7 @@ MRP Codex is a modular Manufacturing Resource Planning platform intended to be a - filesystem-backed attachments - CRM customers/vendors, hierarchy, contacts, lifecycle metadata, and attachments - inventory items, BOMs, warehouses, locations, transactions, item attachments, and item pricing +- inventory transfers, reservations, available-stock visibility, and work-order reservation automation - sales quotes, sales orders, approvals, revision history, and purchase orders - purchase-order supporting documents and vendor-side purchasing visibility - shipping shipments, packing-slip PDFs, shipping labels, bills of lading, and logistics attachments @@ -118,9 +119,8 @@ If implementation changes invalidate those docs, update them in the same change Near-term priorities are: -1. Inventory transfers, reservations, and deeper stock controls -2. Broader audit-trail coverage and operational diagnostics -3. Code-splitting and bundle-size reduction +1. Broader audit-trail coverage and operational diagnostics +2. 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 1f9c2d8..47e0af2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ This file is the running release and change log for MRP Codex. Keep it updated w ### Added +- Inventory transfers with paired physical stock movement posting between warehouses and locations +- Manual inventory reservations plus automatic work-order-driven component reservations +- Reserved and available stock visibility on inventory item detail and stock-by-location views - 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 @@ -27,13 +30,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 +- Inventory control now distinguishes on-hand, reserved, and available stock instead of treating all positive stock as fully free - 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 inventory transfers and deeper stock controls as the next active priority after the planning slice +- Roadmap and project docs now treat broader audit-trail coverage and operational diagnostics as the next active priority after the inventory-control slice ## 2026-03-15 diff --git a/INSTRUCTIONS.md b/INSTRUCTIONS.md index b32e29d..fccec52 100644 --- a/INSTRUCTIONS.md +++ b/INSTRUCTIONS.md @@ -15,6 +15,7 @@ This repository implements the platform foundation milestone: - file attachment storage - CRM foundation through reseller hierarchy, contacts, attachments, and lifecycle metadata - inventory master data, BOM, warehouse, stock-location, transactions, and item attachments +- inventory transfers, reservations, available-stock visibility, and work-order reservation automation - sales quotes and sales orders with quick actions and quote conversion - sales approvals, approval stamps, and automatic revision history on quotes and sales orders - purchase orders with quick actions and searchable vendor/SKU entry @@ -61,6 +62,5 @@ This repository implements the platform foundation milestone: ## Next roadmap candidates -- 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 fcbe7d2..35b310e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Current foundation scope includes: - CRM search, filtering, status tagging, and reseller hierarchy - CRM contact history, account contacts, and shared attachments - inventory item master, BOM, warehouse, stock-location, and stock-transaction flows +- inventory transfers, reservations, and available-stock visibility - sales quotes and sales orders with searchable customer and SKU entry - sales approvals, approval stamps, and automatic revision history on quotes and sales orders - purchase orders with searchable vendor and SKU entry, restricted to purchasable inventory items @@ -43,9 +44,8 @@ Current completed foundation areas: Near-term priorities: -1. Inventory transfers, reservations, and deeper stock controls -2. Broader audit-trail coverage and operational diagnostics -3. Code-splitting and bundle-size reduction +1. Broader audit-trail coverage and operational diagnostics +2. Code-splitting and bundle-size reduction Revisit / deferred items: @@ -206,15 +206,16 @@ The current inventory foundation supports: - protected warehouse list, detail, create, and edit flows - nested stock-location management inside each warehouse record - inventory transaction posting for receipts, issues, and adjustments +- inventory transfers with paired source/destination movement posting +- manual reservations plus automatic work-order component reservations - item on-hand quantity, stock-by-location balances, and recent stock history +- reserved and available quantity visibility by location - item-level file attachments for drawings and support documents - seeded sample inventory items and a starter assembly BOM during bootstrap - seeded sample warehouse and stock locations during bootstrap QOL direction: -- stock transfers -- reservations and allocations - clearer warehouse dashboards and shortage views - BOM revisions and where-used visibility diff --git a/ROADMAP.md b/ROADMAP.md index 145e7b0..288858a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -32,6 +32,7 @@ MRP Codex is being built as a streamlined, modular manufacturing resource planni - CRM multi-contact records, commercial terms, lifecycle stages, operational flags, and activity rollups - Inventory item master, BOM, warehouse, and stock-location foundation - Inventory transactions, on-hand tracking, and item attachments +- Inventory transfers, reservations, available-stock visibility, and work-order-driven material reservation automation - Sales quotes and sales orders with commercial totals logic - Purchase orders with vendor lookup, item lines, totals, and quick status actions - Purchase-order line selection restricted to inventory items flagged as purchasable @@ -282,6 +283,5 @@ QOL subfeatures: ## Near-term priority order -1. Inventory transfers, reservations, and deeper stock controls -2. Broader audit-trail coverage and operational diagnostics -3. Code-splitting and bundle-size reduction +1. Broader audit-trail coverage and operational diagnostics +2. Code-splitting and bundle-size reduction diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts index 3e33363..b3e4c36 100644 --- a/client/src/lib/api.ts +++ b/client/src/lib/api.ts @@ -23,8 +23,10 @@ import type { InventoryItemDetailDto, InventoryItemInput, InventoryItemOptionDto, + InventoryReservationInput, InventoryItemStatus, InventoryItemSummaryDto, + InventoryTransferInput, InventoryTransactionInput, InventoryItemType, WarehouseDetailDto, @@ -378,6 +380,26 @@ export const api = { token ); }, + createInventoryTransfer(token: string, itemId: string, payload: InventoryTransferInput) { + return request( + `/api/v1/inventory/items/${itemId}/transfers`, + { + method: "POST", + body: JSON.stringify(payload), + }, + token + ); + }, + createInventoryReservation(token: string, itemId: string, payload: InventoryReservationInput) { + return request( + `/api/v1/inventory/items/${itemId}/reservations`, + { + method: "POST", + body: JSON.stringify(payload), + }, + token + ); + }, getWarehouses(token: string) { return request("/api/v1/inventory/warehouses", undefined, token); }, diff --git a/client/src/modules/inventory/InventoryDetailPage.tsx b/client/src/modules/inventory/InventoryDetailPage.tsx index adc478a..584aadc 100644 --- a/client/src/modules/inventory/InventoryDetailPage.tsx +++ b/client/src/modules/inventory/InventoryDetailPage.tsx @@ -1,4 +1,10 @@ -import type { InventoryItemDetailDto, InventoryTransactionInput, WarehouseLocationOptionDto } from "@mrp/shared/dist/inventory/types.js"; +import type { + InventoryItemDetailDto, + InventoryReservationInput, + InventoryTransactionInput, + InventoryTransferInput, + WarehouseLocationOptionDto, +} from "@mrp/shared/dist/inventory/types.js"; import { permissions } from "@mrp/shared"; import { useEffect, useState } from "react"; import { Link, useParams } from "react-router-dom"; @@ -11,14 +17,36 @@ import { InventoryStatusBadge } from "./InventoryStatusBadge"; import { InventoryTransactionTypeBadge } from "./InventoryTransactionTypeBadge"; import { InventoryTypeBadge } from "./InventoryTypeBadge"; +const emptyTransferInput: InventoryTransferInput = { + quantity: 1, + fromWarehouseId: "", + fromLocationId: "", + toWarehouseId: "", + toLocationId: "", + notes: "", +}; + +const emptyReservationInput: InventoryReservationInput = { + quantity: 1, + warehouseId: null, + locationId: null, + notes: "", +}; + export function InventoryDetailPage() { const { token, user } = useAuth(); const { itemId } = useParams(); const [item, setItem] = useState(null); const [locationOptions, setLocationOptions] = useState([]); const [transactionForm, setTransactionForm] = useState(emptyInventoryTransactionInput); + const [transferForm, setTransferForm] = useState(emptyTransferInput); + const [reservationForm, setReservationForm] = useState(emptyReservationInput); const [transactionStatus, setTransactionStatus] = useState("Record receipts, issues, and adjustments against this item."); + const [transferStatus, setTransferStatus] = useState("Move physical stock between warehouses or locations without manual paired entries."); + const [reservationStatus, setReservationStatus] = useState("Reserve stock manually while active work orders reserve component demand automatically."); const [isSavingTransaction, setIsSavingTransaction] = useState(false); + const [isSavingTransfer, setIsSavingTransfer] = useState(false); + const [isSavingReservation, setIsSavingReservation] = useState(false); const [status, setStatus] = useState("Loading inventory item..."); const canManage = user?.permissions.includes(permissions.inventoryWrite) ?? false; @@ -43,20 +71,23 @@ export function InventoryDetailPage() { .getWarehouseLocationOptions(token) .then((options) => { setLocationOptions(options); - setTransactionForm((current) => { - if (current.locationId) { - return current; - } + const firstOption = options[0]; + if (!firstOption) { + return; + } - const firstOption = options[0]; - return firstOption - ? { - ...current, - warehouseId: firstOption.warehouseId, - locationId: firstOption.locationId, - } - : current; - }); + setTransactionForm((current) => ({ + ...current, + warehouseId: current.warehouseId || firstOption.warehouseId, + locationId: current.locationId || firstOption.locationId, + })); + setTransferForm((current) => ({ + ...current, + fromWarehouseId: current.fromWarehouseId || firstOption.warehouseId, + fromLocationId: current.fromLocationId || firstOption.locationId, + toWarehouseId: current.toWarehouseId || firstOption.warehouseId, + toLocationId: current.toLocationId || firstOption.locationId, + })); }) .catch(() => setLocationOptions([])); }, [itemId, token]); @@ -65,6 +96,10 @@ export function InventoryDetailPage() { setTransactionForm((current) => ({ ...current, [key]: value })); } + function updateTransferField(key: Key, value: InventoryTransferInput[Key]) { + setTransferForm((current) => ({ ...current, [key]: value })); + } + async function handleTransactionSubmit(event: React.FormEvent) { event.preventDefault(); if (!token || !itemId) { @@ -92,8 +127,51 @@ export function InventoryDetailPage() { } } + async function handleTransferSubmit(event: React.FormEvent) { + event.preventDefault(); + if (!token || !itemId) { + return; + } + + setIsSavingTransfer(true); + setTransferStatus("Saving transfer..."); + + try { + const nextItem = await api.createInventoryTransfer(token, itemId, transferForm); + setItem(nextItem); + setTransferStatus("Transfer recorded."); + } catch (error: unknown) { + const message = error instanceof ApiError ? error.message : "Unable to save transfer."; + setTransferStatus(message); + } finally { + setIsSavingTransfer(false); + } + } + + async function handleReservationSubmit(event: React.FormEvent) { + event.preventDefault(); + if (!token || !itemId) { + return; + } + + setIsSavingReservation(true); + setReservationStatus("Saving reservation..."); + + try { + const nextItem = await api.createInventoryReservation(token, itemId, reservationForm); + setItem(nextItem); + setReservationStatus("Reservation recorded."); + setReservationForm((current) => ({ ...current, quantity: 1, notes: "" })); + } catch (error: unknown) { + const message = error instanceof ApiError ? error.message : "Unable to save reservation."; + setReservationStatus(message); + } finally { + setIsSavingReservation(false); + } + } + if (!item) { - return
{status}
; + return
{status}
; } return ( @@ -111,7 +189,7 @@ export function InventoryDetailPage() {

Last updated {new Date(item.updatedAt).toLocaleString()}.

- + Back to items {canManage ? ( @@ -122,11 +200,20 @@ export function InventoryDetailPage() {
-
+ +

On Hand

{item.onHandQuantity}
+
+

Reserved

+
{item.reservedQuantity}
+
+
+

Available

+
{item.availableQuantity}
+

Stock Locations

{item.stockBalances.length}
@@ -136,14 +223,15 @@ export function InventoryDetailPage() {
{item.recentTransactions.length}
-

BOM Lines

-
{item.bomLines.length}
+

Transfers

+
{item.transfers.length}
-

Operations

-
{item.operations.length}
+

Reservations

+
{item.reservations.length}
+

Item Definition

@@ -173,128 +261,41 @@ export function InventoryDetailPage() {
-

Internal Notes

-

{item.notes || "No internal notes recorded for this item yet."}

-
- Created {new Date(item.createdAt).toLocaleDateString()} -
-
-

Current stock by location

- {item.stockBalances.length === 0 ? ( -

No stock has been posted for this item yet.

- ) : ( -
- {item.stockBalances.map((balance) => ( -
-
-
- {balance.warehouseCode} / {balance.locationCode} -
-
- {balance.warehouseName} · {balance.locationName} -
-
-
{balance.quantityOnHand}
-
- ))} -
- )} -
-
-
-
-

Bill Of Materials

-

Component structure

- {item.bomLines.length === 0 ? ( -
- No BOM lines are defined for this item yet. -
- ) : ( -
- - - - - - - - - - - - {item.bomLines.map((line) => ( - - - - - - - - ))} - -
PositionComponentQuantityUOMNotes
{line.position} -
{line.componentSku}
-
{line.componentName}
-
{line.quantity}{line.unitOfMeasure}{line.notes || "—"}
-
- )} -
- {(item.type === "ASSEMBLY" || item.type === "MANUFACTURED") ? ( -
-

Manufacturing Routing

-

Station template

- {item.operations.length === 0 ? ( -
- No station operations are defined for this buildable item yet. -
+

Stock By Location

+ {item.stockBalances.length === 0 ? ( +

No stock or reservation balances have been posted for this 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 || "-"}
+
+ {item.stockBalances.map((balance) => ( +
+
+
+ {balance.warehouseCode} / {balance.locationCode} +
+
+ {balance.warehouseName} / {balance.locationName} +
+
+
+
{balance.quantityOnHand} on hand
+
{balance.quantityReserved} reserved / {balance.quantityAvailable} available
+
+
+ ))}
)} -
- ) : null} -
+ + + +
{canManage ? ( -
+

Stock Transactions

-

Record movement

-

Post receipts, issues, and adjustments to update on-hand inventory.

- +