Home/Blog/Why we chose ledger-based stock over running balances
EngineeringInventory

Why we chose ledger-based stock over running balances

Running balances break silently. Ledger-based stock — append-only moves with immutable unit costs — gives you an audit trail that survives price changes, reversals and multi-location complexity.

5 min read
EYP Ops Team8 April 2026

The problem with running balances

Most inventory systems store a single number per item per location: the current quantity on hand. When a purchase arrives, the system adds to it. When a recipe consumes stock, it subtracts. Simple, fast, easy to query.

Until it breaks.

Running balances break in ways that are hard to detect and harder to correct. A background job crashes halfway through posting an invoice. A user double-submits a form. A developer runs a migration script that inadvertently adjusts quantities. A supplier return is partially processed. The number on screen becomes fiction, but there is no history to tell you when it diverged from reality or by how much.

What ledger-based stock looks like

A ledger-based system never updates a quantity. It only appends records. Every stock movement — purchase receipt, recipe consumption, waste event, transfer, stock count — becomes a new row in the StockMove table. The current quantity on hand is derived by summing all moves for a given item and location.

SELECT SUM(qty) FROM stock_moves
WHERE item_id = $1 AND location_id = $2

That query is the source of truth. There is no separate "current quantity" field that can drift out of sync. If a move was recorded incorrectly, you correct it by posting a correcting move — never by editing the original.

The Orion mark-to-market problem

We learned from a specific failure mode we observed in a competitor's platform (internal reference: "Orion mark-to-market bug", versions 4.1.19 to 6.1.20). Their system recalculated the unit cost of all historical consumption moves whenever a supplier price updated.

On the surface this seems reasonable: if the ingredient now costs more, shouldn't historical COGS reflect that? No. It destroys the integrity of closed periods. A P&L you signed off on in January should not change in March because a supplier raised prices.

The Orion bug caused reported food cost percentages for closed months to silently shift by 2–4 points every time a bulk price update ran. Finance teams were reconciling against numbers that had already moved.

How EYP Ops solves it: immutable unit costs

In EYP Ops, every StockMove row carries a unitCost field that is written at posting time and never changed. The cost of a recipe consumption is the weighted average cost of the ingredients at the moment the production was posted — not today's price, not the price at month-end.

This means:

  • Closed periods stay closed. A February P&L will show the same food cost in April as it did in February.
  • Price changes are prospective. A new delivery at a higher price affects moves from that delivery forward, not backward.
  • Audit is trivial. Every COGS line can be traced to the specific stock moves that created it, each with its recorded unit cost.

Why this matters for multi-location operations

Restaurants with multiple outlets face a compounding version of this problem. A transfer from the main store to the kitchen moves inventory between locations. If you are storing running balances, a transfer requires updating two rows — and a crash between those two updates leaves the books unbalanced.

With ledger-based stock, a transfer is two moves: a TRANSFER_OUT from the source and a TRANSFER_IN to the destination. They are written in a single database transaction. Either both succeed or neither does. The books are always consistent.

For a group operating across three outlets — main store, kitchen, bar — the ledger approach is the only viable path to accurate outlet-level COGS that can be reconciled to the group P&L.

The trade-off: query complexity

The trade-off is real. Summing all moves on every balance query is slower than reading a single field. For an operation with years of history and hundreds of items, a naive implementation will become slow.

The solution is not to abandon the ledger model — it is to build appropriate materialized views or snapshot tables that cache period totals without sacrificing the audit integrity of the underlying moves. The cache can be wrong; the ledger cannot.

Starting without the infrastructure

You do not need a purpose-built ledger system to benefit from these principles today. Even in a spreadsheet, the practice of recording every movement (rather than updating a total) and preserving the unit cost at time of posting will give you a recoverable audit trail. The discipline comes first; the automation follows.

Enjoyed this post?

Get practical operations content in your inbox monthly.