MaterialsInventory
Owns the materials inventory ledger (append-only event log) and the derived current-stock view. Every materials movement — receipts, consumption, transfers, adjustments, disposals — passes through this component.
Tables owned
| Table | Purpose |
|---|---|
org.materials_inventory_ledger | Append-only event log. Columns include event_type, material_id, location_id, quantity, ref, event_date, notes. |
Operations
Writes
Each write is a single ledger insert. Event-typed APIs keep callers honest about what they're recording (no generic insertLedgerEntry).
recordReceipt(conn, input)→LedgerEntry.event_type = RECEIVE. Used by PO receipts and to close transfers.input:{ materialId, locationId, quantity, ref, eventDate, notes? }. (For PO receipts,ref= PO number; for transfer completion,ref= the pairedTFR-MI-xxx.)recordOrder(conn, input)→LedgerEntry.event_type = ORDER. Records expected inbound from a PO placement (in-transit / on-order view).ref= PO number.recordDemand(conn, input)→LedgerEntry.event_type = DEMAND. Used when a WO transitions toEntered/Placedand reserves material from BOM inputs.recordAllocation(conn, input)→LedgerEntry.event_type = ALLOCATE. Used when a WO entersProductionor beyond; commits the previously-demanded material.recordConsumption(conn, input)→LedgerEntry.event_type = CONSUME. Used by WO receipts when materials are physically consumed.recordTransfer(conn, input)→{ ledgerEntry: LedgerEntry, ref: string }.event_type = TRANSFER. Storesto_location_id. Auto-generates aTFR-MI-xxxref if none supplied. The receiving leg is created later viarecordReceiptwith the same ref.recordAdjustment(conn, input)→LedgerEntry.event_type = ADJUST.refis null. Used for inventory corrections.recordDisposal(conn, input)→LedgerEntry.event_type = DISPOSE.refis null.
Writes — reversals
Append-only. Reverse ops insert a new row that cancels the effect of an original entry; they never hard-delete. Every reverse op takes the original entryId (or the matching source_id) and copies material_id, location_id, and quantity (positive — direction is in event_type). source_type and source_id on the reverse row match the original so audit pairs are easy to find.
recordReverseOrder(conn, { originalEntryId, eventDate? })→LedgerEntry.event_type = REVERSE_ORDER. Used by PO cancellation.recordReverseReceive(conn, { originalEntryId, eventDate? })→LedgerEntry.event_type = REVERSE_RECEIVE. Used by PO receipt delete and transfer reversal.recordReverseDemand(conn, { originalEntryId, eventDate? })→LedgerEntry.event_type = REVERSE_DEMAND. Used by WO cancellation.recordReverseAllocate(conn, { originalEntryId, eventDate? })→LedgerEntry.event_type = REVERSE_ALLOCATE. Used by WO cancellation. (Was namedrecordReleasein earlier drafts.)recordReverseConsume(conn, { originalEntryId, eventDate? })→LedgerEntry.event_type = REVERSE_CONSUME. Used by WO receipt delete.recordReverseTransfer(conn, { originalEntryId, eventDate? })→LedgerEntry.event_type = REVERSE_TRANSFER. Used when an in-transit transfer is voided before completion.
Reads
getEntryById(conn, entryId)→LedgerEntry | null.listEntries(conn, filters?)→LedgerEntry[]. Filters:{ materialId?, locationId?, eventType?, refLike?, dateFrom?, dateTo? }.refLike: 'TFR-MI-%'is the Transfers page query.listEntriesByRef(conn, ref)→LedgerEntry[]. Returns all entries sharing a ref (e.g., paired TRANSFER + RECEIVE).getSummary(conn, filters?)→Array<{ materialId, locationId, onHand: string, onOrder: string }>. Aggregated from the ledger. Filters:{ materialId?, locationId? }.getInTransitByDestination(conn, destinationLocationId)→Array<{ ref, materialId, quantity, sourceLocationId, sentDate }>. OpenTFR-MI-%transfers landing at this location.
Notes
- Component is the only writer of
materials_inventory_ledger. PO/WO components and the transfer workflows call into this API. - Transfer-ref auto-generation lives here (not in callers).
- Append-only. There is no
deleteEntryop. Cancellations and corrections write reverse rows; the originals stay in the log.getSummarynets reverse pairs by default soonHandreflects the post-reversal state without callers having to handle it. - The summary is computed on read — no materialized cache. If volume ever demands it, a materialized view is an L1 schema change, not an L2 contract change.