Finished Goods Inventory
Finished-goods inventory is the running record of product stock, kept as a ledger in exactly the same way as materials inventory: movements are appended as entries and the position is summed per product and location. What's different is that finished goods track a demand side (what customers want and what's been set aside), not just what's physically on hand.
How the ledger works
Each entry records one movement: a product, a location, an event type, a quantity,
an optional unit cost, and a ref linking it back to whatever caused it. Entries are never
edited or deleted in normal operation; a movement that needs undoing is offset by a
reversing entry. Two timestamps matter: event date is the business moment the movement
happened, date and time, so same-day movements keep their real order (you can set it),
while created-at is when the row was written. Positions and ordering use the event
date.
Product can arrive three ways: bought on a purchase order, made by a work or production order, or moved in by a transfer. All three arrive as a RECEIVE; the entry's source says which.
Event types
Each entry's event type is drawn from the shared inventory vocabulary, identical on both
ledgers. The single canonical list, with every forward event, its REVERSE_ counterpart,
and the SETTLE_ close forms, lives in the
modules overview.
A SETTLE_ entry trues an order up when it is closed. The first receipt moves an order to
Partial and Received is set by hand, so the booked commitment rarely matches exactly what
arrived. On reaching Received the system writes a settle entry for the gap between ordered
and received: SETTLE_ORDER on the on-order side, SETTLE_ALLOCATE on a job's allocated
stock. The gap is signed, so the one entry trues the figure to zero whether the order came up
short (it clears the leftover) or over (it cancels the excess). It is written automatically,
never by hand, and is undone by deletion: revert an order back out of Received and its settle
entries are removed, restoring the open commitment.
Deriving the position
Finished goods derive four figures, each a signed sum over the entries with every
REVERSE_ form backing out its event:
- On hand = RECEIVE + ADJUST - CONSUME - DISPOSE. A transfer out is a CONSUME, so it is already counted.
- Demand = DEMAND - ALLOCATE.
- Allocated = ALLOCATE - CONSUME - SETTLE_ALLOCATE.
- Available = on hand - allocated.
The supply side also carries on order = ORDER - RECEIVE - SETTLE_ORDER. A finished-goods output is delivered either by a purchase-order delivery or by a work or production order's produced goods coming back, both a RECEIVE, so both draw on-order down as they arrive; SETTLE_ORDER clears whatever is left when the order is closed short. A transfer's arrival never touches on-order, because a transfer never booked an ORDER.
A SETTLE_ term is added only to the figures that do not already reach their closed value
on their own. Closing an order that received 3 of 10:
| Figure | At close | Lands at | Settle |
|---|---|---|---|
| On order | 10 - 3 | 7 | SETTLE_ORDER |
| Allocated | 10 - 3 | 7 | SETTLE_ALLOCATE |
| Demand | 10 - 10 | 0 | none |
Receiving over flips the sign: 12 of 10 leaves on order at -2, and the settle carries that signed gap to bring it back to 0. A settle always holds the signed difference between ordered and received, so it trues the figure to zero either way.
ALLOCATE does double duty: it relieves demand (so demand reaches 0 by itself) and it
creates the reservation (which CONSUME only partly relieves, so allocated still needs a
settle). That is why there is a SETTLE_ORDER and a SETTLE_ALLOCATE but no SETTLE_DEMAND.
Positions are grouped by product and location.
The API serves one complete position per product and location: on hand, on order, allocated, available, in stock (on hand net of allocation, never below zero), demand, wasted (the DISPOSE total), in-transit, and net position (in stock plus on order plus in-transit minus demand). The same shape is served for materials, so the two ledgers cannot drift.
A manual ADJUST corrects a count in either direction: a positive quantity raises on-hand, a negative one lowers it (for example, fixing a count that was entered too high). A DISPOSE is the write-off path for damage, loss, or expiry; it carries waste semantics and stays a positive reduction.
Lots and expirations
An arrival carries the lot and expiration entered on its receipt line; every later movement of that stock derives them from there. When one movement spans several lots (a shipment drawn from two batches, say) the ledger view splits it into one line per lot, each with its own expiration and unit cost, even though only one movement is stored. How that works, and why, is covered in Lots & Expirations.
Where the entries come from
| Source | Events |
|---|---|
| Purchase orders (finished goods) | ORDER, RECEIVE, SETTLE_ORDER |
| Work orders (outputs) | ORDER, RECEIVE, SETTLE_ORDER |
| Work orders (sub-assembly inputs) | DEMAND, ALLOCATE, CONSUME, SETTLE_ALLOCATE |
| Production orders (outputs) | ORDER, RECEIVE, SETTLE_ORDER |
| Sales orders | DEMAND, ALLOCATE, CONSUME |
| Transfers | CONSUME, RECEIVE |
| Manual operations | ADJUST, DISPOSE |
The interplay between the supply side (purchase, work, and production orders) and the demand side (sales orders) is what the Pipelines section traces end to end.
See also
Read summaries and post manual events through the REST API or the MCP server.