Skip to content

The Stock Ledger — a journal of moves, not a stored number

What this covers. Every physical change to inventory — goods arriving, a dish sold, a transfer between warehouses, a spillage, a monthly count, a batch of dough produced — is recorded as one immutable stock movement: a quantity going from one location to another, on a business date. On-hand is never a number the system keeps and edits; it is the running total of those movements. A mistake is fixed by posting an equal-and-opposite movement, never by reaching back and changing history. This chapter teaches that one primitive — why stock is kept as a journal rather than a balance, why every movement has two sides like double-entry bookkeeping, the small set of locations that lets one shape describe every operation, the lot-level detail that lives beneath a movement, and why corrections are additive.


1. The one idea: a history of movements, and the level is their sum

A restaurant's flour does not have a "stock level" that the system bumps up and down. It has a history of movements, and the level is whatever those movements add up to. This is the single most important decision in the whole stock domain, so it is worth pinning down precisely. There are two ways to run an inventory, and the difference is everything.

A periodic inventory — what most small food businesses run on paper — keeps no running record. You count what is there, you count it again next month, and the difference is what you used. That is all you can ever know. You cannot answer "how much flour did we have last Tuesday?" or "what did this one dish actually cost to make?" — only "how much disappeared over the whole month," with the receipts, the transfers, the spillage, and the cooking all blurred into one figure.

A perpetual inventory writes down every event that touches stock the moment it happens. On-hand at any instant is the sum of every movement up to that instant. The monthly physical count does not replace the running book — it reconciles it: the count is the truth event that absorbs the theft, spoilage, and over-portioning the book could never see, and the gap between book and count is itself recorded as one more movement (see Period close).

Why this matters — the why, not just the what. A perpetual journal is the substrate everything else in the domain reads from. You cannot compute a moving-average cost without a time-ordered sequence of valued arrivals and issues. You cannot compare what a dish should have used against what it actually used without a per-sale consumption record. You cannot show on-hand as of a closing date, or post a correction into last month, without an immutable, dated history. The journal is not one feature among many; it is the foundation the costing, variance, procurement, and period-close layers all stand on. This pattern has a textbook name — perpetual inventory backed by a stock-move journal — and it is the spine the whole stock domain is built around.


2. The movement — the single primitive

A stock movement is one immutable, append-only record. Every operation in the entire stock domain produces one of these — or, for two-sided operations like a transfer, a pair of them. A movement records:

What it captures Meaning
from-location where the quantity left
to-location where the quantity arrived
the item which inventory item moved
quantity always a positive number, always in the item's base unit (grams, milliliters, pieces)
a reason the business intent that the two locations alone cannot tell apart
business date the date the event belongs to in the restaurant's own local clock — not the wall-clock instant the row was written
its source document the receipt, count, transfer, sale, or production run that created it

One of those fields does quiet, load-bearing work: the business date. It is stamped once, in the restaurant's own local clock, and it is the day all the accounting hangs on — a delivery placed at 23:55 and handed over at 00:10 must land on the day (and therefore in the month) the business counts it, which neither the raw timestamp nor the day the record happened to be imported can be trusted to name.

Two things are deliberately absent from a movement, and their absence is the whole design.

There is no "movement type" field. There is no "sale / supply / waste / transfer" label on the row. The type of an operation is simply which two locations it runs between, plus its reason. A movement from a supplier into a storage location is a receipt. A movement from a storage location to a customer is a sale. A movement from a storage location to the scrap location is waste. You do not record the name of the operation; you record its two endpoints, and the name falls out of them. (An older way of doing this flattened a dozen named movement kinds — sales, supplies, wastage, transfers-in, transfers-out, overages, shortages, and so on — into one signed list. Reading the operation off the two endpoints plus a reason replaces that list with two orthogonal questions: between which two places? and for what reason?)

There is no signed quantity and no stored balance. Quantity is always a positive number. The direction — whether a movement adds to or subtracts from a location's on-hand — is not a plus-or-minus sign on the row; it falls out of the from/to pair. A location that appears as the to gains; a location that appears as the from loses. The sign is structural, so there is no per-row sign to get wrong.

Why "the movement is the event; the meaning is the locations"

The movement row answers exactly one question: what physically happened — this much of this item went from here to there, on this date. The interpretation is read off the endpoints. The pair of locations tells you the class of operation; the reason disambiguates within a class. A staff meal and a sold portion both end up at the customer location — the reason is what separates a sale from internal use. This keeps the journal a tiny, stable, append-only table of facts, and pushes all the "what does this mean" judgment into a small, queryable vocabulary instead of a sprawling type list.


3. Locations — the double-entry primitive

Every movement has two ends, and each end is a location. Each location is one of exactly seven kinds of place:

  • storage location — a real place the business manages and counts: a walk-in, a bar, a back room, a dry store. These hold actual countable stock, and on-hand is computed against them. Each one sits inside exactly one warehouse — the contour that values it and closes its books. (See Scope & locations.)
  • transit — an in-between holding place used as the middle leg of a transfer between two warehouses; stock that is provably "on the truck."
  • supplier — where received goods come from.
  • customer — where sold or served goods go to.
  • scrap — where wasted or spoiled goods go.
  • production — the conceptual prep bench that an ingredient passes through when it is consumed into a made item (a batch of dough, a base sauce) and the made item is yielded back.
  • stock-loss — the counterparty for count adjustments: the place a shortage "goes to," and the place an overage "comes from."

The first kind is a real place you can walk up to and count. The other six are virtual — they are not physical places at all. They are the counterparty side of an entry, seeded once per company so that every movement always has a second end to point at.

Why every movement needs two sides — double-entry, applied to food

The bedrock rule is that quantity is globally conserved: every movement decreases somewhere and increases somewhere else by the same amount. Nothing ever appears from nowhere, and nothing ever silently vanishes — even waste and shrinkage have an explicit destination (the scrap location, the stock-loss location). This is double-entry bookkeeping — the centuries-old accounting discipline that every entry has two equal sides — applied to physical stock. It buys three things.

  1. One primitive covers everything. Receipt, sale, transfer, waste, count adjustment, and production are all the same shape — a movement between two locations — distinguished only by which locations and which reason. Transfers and production, which a naive one-sided "in / out" model treats as awkward special cases, are just ordinary movements here.
  2. Auditability. Because nothing is created or destroyed, the books always balance against a known counterparty. Shrinkage is not a silent subtraction; it is a recorded movement into the stock-loss location that you can total, attribute, and investigate. This matters double when entries are written by machines — parsed invoices, automated posting, assistant-driven counts: an automated mistake is an immutable movement with a named source document, reversed by an equally visible contra-movement — never an edit that dissolves into the books. Append-only plus provenance is what makes automated entry safe to trust, and to audit after the fact.
  3. The sign is structural. On-hand for a location is the sum of movements into it minus the sum of movements out of it. There is no per-row sign to get wrong.

Every operation, written as a movement

Operation The movement(s)
Receipt (goods arrive) supplier → storage location
Sale / service storage location → customer
Transfer (warehouse A → warehouse B) a location in A → transit, then transit → a location in B
Waste / spoilage storage location → scrap
Count overage (more counted than the book says) stock-loss → storage location
Count shortage (less counted than the book says) storage location → stock-loss
Production — consume an input storage location → production
Production — yield the made item production → storage location

That small table is the entire operational vocabulary of the stock domain. For the location taxonomy and the warehouse-versus-restaurant scope axes, read Scope & locations.

A write-off names the place it came off

Waste is destructive at a place. Somebody found the broken bottle in the walk-in and not in the bar, and that is the fact being recorded — so every line of a write-off names the storage location the quantity left, exactly as a receipt line names where the goods were put away. Without it the loss would have to be guessed onto some default corner of the warehouse, and every per-location loss report would be fiction.

The naming can wait, but not forever. A cook can write down "two trays of mussels, spoiled" before anyone has decided which storage location they came off, so a draft may sit unplaced. Posting is where the question has to be answered: a write-off with a line that names no location cannot post, and once it has posted the placement never moves. Where a business keeps a single warehouse there is only one possible answer, so the write-off is placed on its default location for you and nobody is asked to state the obvious.

Because the place is stated line by line, one write-off document can reach across warehouses — a broken crate in the dry store and a spoiled tray in the freezer, recorded on one walk. Each line leaves the location it names and is valued at the average cost of the warehouse that owns that location, so each warehouse's books see only its own loss. The document is one piece of paper; it is not one valuation contour.


4. On-hand is derived, never stored

On-hand for an item in a storage location is never kept as a single number. It is computed:

on-hand of an item in a storage location, as of a date = (sum of quantities of movements into that location) − (sum of quantities of movements out of that location), over movements on that item whose business date is on or before that date.

Run the same sum over every storage location a warehouse owns and you have the warehouse's on-hand — the grain the close snapshots and the costing values.

Re-summing all of history on every read would not scale, so the read works as a checkpoint plus a short sum: each period close records a snapshot of on-hand (and value) for each warehouse and item as of the close date, and a live read takes the last closed snapshot and sums only the movements since that checkpoint. The answer is identical to a full-history sum, but the work is bounded by one period of activity rather than all time.

Value is derived the same way, and independently of quantity. On-hand quantity and inventory value are two separate things. A movement can change quantity without changing total value — a transfer moves units between warehouses, but the company's total stock value is unchanged, only the place holding it changed. And the way value is computed can evolve without restating where the units physically sit. Value is quantity times the warehouse's weighted-average cost — the running blended price of everything in that warehouse's pool — and the full costing math (how arrivals re-blend the average, how landed freight folds in, how the close re-spreads value over the counted quantity) lives in Costing & valuation. The journal's only job is to supply the valued, time-ordered stream of arrivals and issues that the costing reads from.

Valuation entries — value-only changes leave artifacts too

Some events change what stock is worth without moving a single gram. The three everyday cases: a supplier invoice arrives after the goods and re-prices the portion still on hand; a cost pool that was briefly driven negative gets its value corrected; the period close re-spreads a warehouse's value over the counted quantity. Each of these is recorded as an explicit, append-only valuation entry, linked to the document that caused it — never as a silent nudge to a stored number. The money trail thus has dated, auditable artifacts exactly as the quantity trail does, and derived value is always a reconstruction from recorded entries — the quantity movements plus the valuation entries, nothing else.

Four conceptual layers, worth holding in your head. It helps to picture the system in four layers, even though they are often physically folded together: the movement (the event), the lot detail beneath it (§5), the derived on-hand (the running sum), and the value (decoupled from quantity — the movements' values plus the valuation entries). One journal of movements and valuation entries is stored; on-hand and value are derived from it.


5. The movement, and the lot detail beneath it

For most items the movement is the whole story. Quantity and value live at the movement level, and that is enough, because the warehouse runs one blended average cost for the item — so it genuinely does not matter which physical kilogram of flour was issued; they are all worth the same average.

But some items must be tracked at a finer grain than "how many kilograms." A perishable batch has an expiry date; a serial-numbered asset is one specific unit. For these, the item carries a lot-tracking (or serial-tracking) policy — see Item model & policies — and a single movement is fulfilled by one or more lot lines, each naming a specific lot.

Worked example. A 10 kg flour issue under FEFOfirst-expiry-first-out, the rule of consuming the soonest-to-expire lot before later ones — splits into two lot lines: 6 kg from Lot-A (which expires sooner) and 4 kg from Lot-B. It is still one movement — 10 kg left one storage location, one event — but two lot lines carry the lot identity and expiry beneath it.

The division of labor is deliberate:

Lives on the movement Lives on the lot line
quantity, value, business date the lot or serial number
the two locations, the reason the expiry date, per-lot traceability
the costing input (the warehouse average) which physical lot fulfilled this slice

This keeps the costing and on-hand math lot-agnostic — it always works at the movement level on a single blended average — while still supporting expiry-driven issue order, recall traceability, and serial tracking for the items that need them. It deliberately does not force per-lot cost layers onto fungible food, where they would be fiction: a kilo of flour is a kilo of flour. Items that need no lot tracking never generate lot lines at all.

Where a lot's expiry comes from — the food-safety seam. A lot's expiry is not typed in by hand at each receipt. It is the receipt or production date plus the item's shelf-life, and the shelf-life itself is derived from the recipe (see Food attributes). That same expiry date drives two things: the FEFO issue order above, and the expiry write-off — when a lot lapses, it leaves as an ordinary waste movement (storage location → scrap) with the reason expired. The journal owns the lot and its expiry date; the food-attributes layer owns how that date is computed. That is the single seam where food safety touches the ledger.


6. Immutability — fix a mistake with an opposite entry, never an edit

A posted movement is never edited and never deleted. A mistake is corrected by posting an equal-and-opposite movement — a second movement in the reverse direction that cancels the first — followed, if needed, by a fresh correct movement. The original error and its correction both stay in the journal forever.

Worked example. A receipt was posted as supplier → storage location for 12 kg when the invoice was really 10 kg. You do not change the 12 kg row. You post an opposite movement, storage location → supplier for 12 kg, undoing the original; then a new movement, supplier → storage location for 10 kg. The net effect on on-hand is +10 kg, which is correct. The trail shows exactly what happened: a 12 kg receipt, its full reversal, and a 10 kg re-receipt.

Why never edit. The journal is the record of fact, financial and operational. If rows could be edited: (a) the audit trail would lie — there would be no evidence the original error ever happened; (b) any checkpoint snapshots and already-computed period valuations would silently diverge from a freshly re-summed history; and (c) backdating an edit into a closed period would corrupt a period that has already been reconciled and reported. Opposite-entry corrections make every fix additive and dated, so closed periods stay closed and a late correction flows forward as an explicit prior-period adjustment in the next open period.

Immutable ledger ≠ immutable documents

This is a crucial distinction, and a common source of confusion. The ledger — the movements — is append-only and immutable. But the documents that create movements — a goods receipt, a stock count, a transfer, a waste record, a sale's consumption posting, a production run — have a perfectly normal, editable lifecycle:

Draft → Posted → Amend / Cancel

  • Draft — freely editable, with no ledger effect at all. You can correct a receipt draft all day; nothing has moved yet.
  • Posted — posting is the act that emits the immutable movements. From this point the document's lines are frozen as a recorded fact.
  • Amend means reverse the document's movements with opposite entries, then create an editable copy to re-post. Cancel means reverse only.

So the rule is "the ledger is immutable, the documents are mutable." Operators work in an ordinary draft / edit / post world; it is the consequences of posting — the movements — that become permanent. The posting documents themselves are covered in Period close. One of them, the goods receipt, runs the landed-cost waterfall — spreading freight and handling across the lines it received so each arrives at its true delivered cost — and that valued line is exactly what the receipt movement carries into the journal. Posting does not even have to wait for the final price: a delivery often posts from the delivery note, and the supplier invoice attaches later as a price update — the portion still on hand revalues through a valuation entry (§4); see Procurement. A walk-in cash-and-carry purchase, by contrast, posts with its invoice in one step.


7. Worked examples (real numbers)

These trace three everyday operations end to end, as movements. Assume an item Flour with base unit grams, and a business that values each of its sites separately. The first site is the warehouse we'll call Kitchen Store; it holds a single storage location, so one name serves both for the place the flour sits and for the pool it is valued in.

7.1 Receipt — buying 25 kg of flour

A supplier delivers 25 kg of flour, invoiced at €0.80/kg = €20.00 subtotal, plus €2.00 of allocated freight (the landed cost). The goods-receipt document posts one movement:

supplier → Kitchen Store item = Flour · quantity = 25 000 g (25 kg, converted to the base unit at the posting edge) · value = €22.00 (the €20.00 subtotal plus €2.00 landed freight, net of any recoverable tax) · reason = supply · business date = the delivery day · source = the goods receipt, its flour line.

Effect: Kitchen Store flour on-hand goes up by 25 000 g; the €22.00 net value re-blends into Kitchen Store's flour weighted-average. The movement carries the net, capitalized amount — what the stock is truly worth — not the gross payable; the distinction belongs to Costing & valuation. Notice the supplier location's running balance goes negative, which is correct and meaningful: the virtual supplier location is the running counterparty of everything the company has ever received from suppliers.

7.2 Transfer — moving 5 kg to the bar store

5 kg of flour moves from Kitchen Store to the Bar Store (a second warehouse). This is two movements, through the transit location:

Movement A — Kitchen Store → transit · Flour · 5 000 g · reason = transfer · source = the transfer, leg 1 Movement B — transit → Bar Store · Flour · 5 000 g · reason = transfer · source = the transfer, leg 2

Effect: Kitchen Store −5 000 g, Bar Store +5 000 g, and the transit location nets to zero once both legs are posted. Value is conserved across the transfer: the 5 kg leaves Kitchen Store at Kitchen Store's current average and arrives at Bar Store, where it re-blends into Bar Store's own average. The company's total stock value is unchanged — only the place holding it changed. The two-leg shape (rather than a single direct movement) is what makes in-transit stock visible: between leg A and leg B, the 5 kg is in neither warehouse's on-hand — it is provably on the truck.

7.3 Count adjustment — the monthly count finds a shortage

The perpetual book says Kitchen Store should have 20 000 g of flour (25 000 received − 5 000 transferred). The physical count finds only 19 200 g — 800 g unaccounted for (spillage, over-portioning, the bag that tore). The count itself moves nothing — it freezes the observation — and when whoever reconciles accepts the gap, the count wins: the difference posts as one adjustment movement:

Kitchen Store → stock-loss item = Flour · quantity = 800 g (book 20 000 − counted 19 200) · reason = shortage · business date = the count's day (at a close, the period's last day) · source = the stock count.

Effect: Kitchen Store flour on-hand drops from 20 000 g to 19 200 g, now matching reality, and the 800 g is recorded as a quantified, attributable loss in the stock-loss counterparty — not silently zeroed away. The closing average re-spreads Kitchen Store's flour value over the counted 19 200 g; that re-spread is exactly how shrinkage finds its way into the cost of goods sold. Had the count found more than the book — an overage — the movement would simply run the other way: stock-loss → Kitchen Store.

Periodic vs perpetual, made concrete. A purely periodic system would only ever know "we bought 25 kg and 19.2 kg is left, so 5.8 kg was used" — with the transfer, the shortage, and any per-dish consumption all blurred into that one number. The perpetual journal separates them into distinct, dated, valued movements: a 25 kg receipt, a 5 kg transfer, an 800 g shortage, and — once sales post — the recipe-driven consumption of each dish. That separation is precisely what makes per-warehouse on-hand, per-restaurant cost of goods, and theoretical-versus-actual variance computable at all.


8. What the ledger does not do

The journal is intentionally narrow. It records valued movements between locations and derives on-hand and value from them. It does not decide:

  • what to buy — replenishment nets on-hand and open orders against par levels; see Procurement.
  • how quantities are expressed — units are normalized to base units at the posting edge; nothing enters the journal except in base units (see Units of measure).
  • how a sale becomes consumption — exploding a sold dish down to its leaf-ingredient storage location → customer movements is the sales and recipe layers (see Sales & consumption and Recipes & production).
  • how value blends — the weighted-average math and the landed-cost waterfall are Costing & valuation.
  • how the book is reconciled to truth — the physical count, its overage and shortage adjustments, and the per-warehouse close are Period close.
  • what's safe to eat or how nutritious it is — shelf-life, nutrition, and allergens are food attributes that roll up the recipe tree, not ledger facts (see Food attributes); the journal carries only the lot expiry date and issues FEFO from it (§5).

All of those layers write to and read from the journal; the journal's contract is just this — an immutable, double-entry, base-unit, valued stream of location movements.


See also

  • Scope & locations — the seven kinds of location, the storage locations a business manages, and the warehouse-as-valuation-grain versus restaurant-as-profit-center axes every movement is stamped against.
  • Item model & policies — the tracking policy that decides whether a movement generates lot lines, and the policy that decides whether an item is journaled at all.
  • Period close — the posting documents (receipt, count, transfer, waste, production) and how the perpetual book is reconciled to the physical count.
  • Costing & valuation — the weighted-average cost each movement is valued at, and how the close re-spreads value over counted quantity.
  • Sales & consumption — how a sale becomes the consumption movements the journal records.
  • Procurement — how on-hand (a sum of movements) feeds the replenishment plan, and the delivery-note-now, invoice-later receipt flow.
  • Food attributes — where a lot's expiry comes from (shelf-life), the FEFO issue order, and the expiry write-off — the one seam between food safety and this journal.
  • Financial boundary — the conceptual accounts the journal's movements and valuation entries roll up into for export to the operator's accounting system.
Last updated · History