← All notes

Inventory

Stock is a ledger, not a number

A quantity column tells you what you think you have. A movement log tells you how you got there, which is the only thing that helps when it is wrong.

  • Invexa Technologies
  • 2 min read

Every inventory system starts with products.quantity. Sell one, decrement. Receive ten, increment. It is the obvious model and it is the same mistake as a mutable account balance.

The question you cannot answer

The count says 47. The shelf says 44. Where did three go?

With a quantity column there is no answer. You cannot tell whether it was a miscount at receiving, a sale that decremented twice, a return that never incremented, or theft. You can only correct the number to 44 and lose the evidence that anything happened. Scanning at the moment stock physically moves is what makes those four cases distinguishable at all; a ledger fed from memory at the end of a shift is a quantity column with extra steps.

Do that a few times and nobody trusts the system, so people keep their own spreadsheets, and now you have two wrong numbers.

Movements, append-only

stock_movements
  sku
  location_id
  delta            -- signed: +10 receipt, -1 sale
  reason           -- receipt | sale | return | adjustment
                   -- | transfer_in | transfer_out | damage
  reference        -- order id, transfer id, count sheet id
  batch_id         -- null unless tracked
  occurred_at
  actor

Quantity on hand is SUM(delta) filtered by sku and location. Never stored, never updated, only derived. It is also the number every sales channel subscribes to, and overselling across four marketplaces is a great deal easier to diagnose when you can replay exactly how the figure they were given came to exist.

Every row is immutable. A mistake is corrected by writing the opposite movement with reason = 'adjustment' and a reference to the count that found it — so the correction is as visible as the error.

The performance objection, answered

Summing millions of rows per lookup is genuinely slow, and this is where teams retreat to a quantity column. The right answer is a periodic snapshot:

stock_snapshots
  sku, location_id, as_of, quantity

Current stock is the latest snapshot plus movements since. Snapshot nightly and the sum covers a day, not a decade. Crucially the snapshot is a cache: delete every row and it rebuilds from movements. A quantity column cannot be rebuilt from anything, which is the whole difference.

What it costs and what it buys

One extra table and a nightly job. In return: “why is this wrong” becomes a query, cycle counts write adjustments instead of overwriting truth, and stock at any past date is answerable — which matters the first time a supplier disputes a delivery from six weeks ago. It is the decision that separates an inventory system people rely on from one they keep a spreadsheet next to.

Next step

Have a project in mind?

A 30-minute call is usually enough to know whether we are the right team for it. If we are not, we will say so.

Start a project

Replies within one working day