Inventory Ledger
Organization-wide parts pool, append-only ledger, manual adjustments, and auto-credits / auto-deducts
Last updated: 2026-05-27
Inventory in S3Suite is a single organization-wide parts pool with an append-only ledger behind it. Every change to a part's on-hand count (manual adjustment, PO delivery, device build) writes a ledger row. The current on-hand number is just the materialised sum, so you can always answer "why does this part show 47 on hand?" by scrolling its history.
There is one inventory per organization. Projects, variants, and lots draw from the same shared pool. Negative stock is allowed by design - the system would rather show you a real shortfall than refuse to record a build.
Why a ledger model
Spreadsheet inventory drifts the moment two people edit it at once. A ledger model fixes that:
- Every change is a row with actor, reason, delta, and the resulting balance
- The on-hand number is a materialised count, not a hand-edited cell
- Concurrent operators cannot overwrite each other; each writes a new row
- "Who removed 50 of this part on Tuesday?" is answerable in two clicks
Concepts
| Term | What it means |
|---|---|
| Inventory Item | A unique part number in your organization's pool. Compound unique on (organization, partNumber). |
| On hand | The current materialised count. Computed from the sum of all ledger deltas. Allowed to go negative. |
| Ledger entry | An append-only row recording one mutation. Fields: source, delta, comment, actor, references. |
| Source | Why the entry exists: manual-add, manual-remove, po-delivered, device-built. |
| Low only filter | The inventory list view's lowOnly toggle, narrowing to rows with onHand <= 0 (negative + exact-zero). |
Quick start (2 minutes)
1. Open the inventory page
- From the sidebar, click Inventory (top-level, organization-scoped)
- The list shows every part in your org with current on-hand, last updated, and a search box
2. Add stock manually
- Click Adjust stock
- Enter the part number (existing or new - new parts are auto-created)
- Enter a positive delta (e.g. 100)
- Enter a comment (required) explaining the source (e.g. "Counted in stockroom 2026-05-27")
- Save
manual-add. The on-hand count goes up by the delta.
3. Remove stock manually
Same flow, but enter a negative delta (e.g. -5). Source is recorded as manual-remove. The comment is required.
4. Inspect history
- Click any row to open the detail panel
- The overview tab shows the 10 most recent ledger entries
- The ledger tab paginates through every entry, newest first
Auto-credits from procurement
When a Purchase Order is marked delivered (Manufacturing → Procurement → PO detail → Mark delivered), every line that received parts auto-credits inventory:
- Source:
po-delivered - Refs: PO id, vendor name, lot id, BOM line index
- One ledger row per delivered line
Note: The credit fires once per line, atomically. If two operators try to mark the same PO delivered at once, one wins; the other sees a 409 telling them the line moved underneath them.
Auto-deducts on device build
When a device is created against a lot (via the production station kiosk, or the Manufacturing → Lot → Add Device flow), the BOM lines for that lot's variant are read and a per-unit quantity is deducted from inventory:
- Source:
device-built - Refs: project id, variant id, lot id, device id and label, BOM line index
- One ledger row per BOM line that contributed
- Fires after the device record is saved; the auto-deduct does not block device creation
-quantity (the deduct is recorded; the shortfall is visible immediately in the low-only filter).
Tip: Open the inventory page with the lowOnly filter on after a long manufacturing run. Anything in red has been over-consumed and needs restocking before the next lot.
Workflow: investigating a shortfall
"BOM line says we need 250 of P-1234, the system shows 12. Where did 238 go?"
- Open Inventory, search for
P-1234 - Click the row, open the Ledger tab
- Scroll the entries newest first. Each row shows source, delta, actor, comment, and timestamp
- Filter by source if you need to (e.g. only
device-builtto see which lots consumed it) - The org-wide ledger view (under Inventory → org ledger) can also be filtered by partNumber, source, projectId, and date range
manual-add adjustment with a comment explaining the recount.
Reference
Permissions
- Read (
/inventorylist, detail, ledger): any authenticated org member - Adjust (
POST /inventory/adjust): writer roles (Org Admin, Project Lead, Manufacturing Lead). Org Viewer cannot. - Super-admin users with no organization see an empty list (intentional - inventory is org-scoped)
Validation
partNumberis required, max 100 charsdeltais a non-zero integer (positive or negative)commentis required, max 500 chars- Validation failures return 400 with
details: string[]so the UI surfaces them viabuildErrorMessage
Offline behaviour
- Inventory mutations are not queueable. If the client is offline, the adjust button fails loudly instead of silently deferring.
- Reason: silent deferral causes "the system says we have 500, build the lot" while the real server state has not moved. The user must retry deliberately when reconnected.
Common pitfalls
- "On-hand shows -47." That is intentional. The system shows the real shortfall. Restock or place a PO; do not "correct" it with a manual add unless you actually have the parts in hand.
- "I cannot delete a ledger entry." Correct. The ledger is append-only. If a row is wrong, add a reversing row with a comment explaining the correction.
- "Two adjusts happened at the same time and the count looks off." Each adjust writes its own row atomically; on-hand reflects the sum. Open the ledger tab to verify both rows landed.
Related docs
- Procurement - PO delivery auto-credits inventory
- Manufacturing Suite - device builds auto-deduct from inventory
- Tooling Registry - separate from parts; tracks reusable tools and jigs
- Roles and Permissions - who can adjust stock