Warehouse inventory ledger demo

Forty parts across twelve locations, with up to 50,000 stock movements generated in front of you. On-hand is written down nowhere. It is a fold over the ledger, and one button re-folds the whole thing and reports the difference.

Open the demo

Opens warehouse.taiphamsolutions.com in a new tab. No account, no signup, and nothing you do in it leaves your browser.

What it does

A stock figure opens into the movements behind it, with a running balance whose last row is the number on the screen before. The ledger has no edit control and no delete control anywhere, because the only correction available is a reversal that points back at what it reverses. Every row carries actor, role, timestamp and reason, which is what makes the audit page a query rather than a feature.

Folding 50,000 rows on every render is not viable, so there is a stored balances projection, and it is handled in the open rather than hidden. It is written inside the same transaction as the append that produced it, it records the sequence it is valid up to, and Rebuild and verify re-folds the entire ledger from the movements store rather than from any cache, then reports rows, milliseconds and any disagreement. The disagreement is zero, and the test suite asserts the same equivalence for random streams written in interrupted batches.

Four roles are enforced in the command layer rather than in template conditions. An operator posting an issue larger than the bin holds is blocked and told what the resulting balance would have been. A supervisor may override that with a written reason, and the override lands in the audit trail. A counter counting blind never receives the expected quantity at all, because it is stripped before the data reaches the screen rather than hidden once it is there.

In Admin, tamper with a row behind the application's back, then verify the ledger chain. It goes red and names the sequence where the chain breaks.

Features

40 items across 12 locations, 8 people in four roles, and a year of activity at 1,000, 10,000 or 50,000 movements, generated on first open.

Derivation you can open

Every on-hand figure drills into the movements that produced it, with a running balance column that ends on exactly that number.

Immutable, and checkable

No edit and no delete. Each row stores a digest of the row before it, so a record changed outside the application is both detectable and locatable.

Blind stock takes

Count without expected quantities, submit, review as a supervisor, accept or send back for a recount, and post only accepted lines as adjustment movements.

Variance and audit

Accuracy, valuation and worst offenders, with net and absolute variance reported apart, because plus forty in one bin and minus forty in the next nets to zero and is still a problem.

The flow it closes, end to end

Every step below happens inside the demo. There is no point in it where the work leaves the browser and comes back.

  1. Generate a year of history

    1,000, 10,000 or 50,000 movements, built in a worker so the page stays interactive while the progress bar moves.

  2. Post a movement

    A receipt, an issue or a transfer. A transfer is two legs that sum to zero, and a refused one leaves neither leg behind.

  3. The projection moves with it

    The append and the balance it changes are one transaction, so the ledger and the cache cannot diverge across a crash.

  4. The block, and the override

    An issue larger than the bin holds is refused. Only a supervisor may proceed, only with a reason, and the reason is part of the record.

  5. Count, review, post

    A blind count is submitted, reviewed line by line, and posted as adjustment movements referencing the session. A line nobody accepted stays visible as an unresolved variance.

  6. Rebuild and verify

    The whole ledger is re-folded from the movements store and diffed against the projection, with the row count, the time it took, and any disagreement on screen.

What is real, and what is not

There is no server behind any of this, which decides both columns. The left one is what that constraint made worth building properly. The right one is what it rules out.

Actually handled

  • Incremental balances matching a full rebuild, for random streams written in interrupted batches
  • An edited row breaking the chain at the expected sequence, and a deleted row reported as a gap
  • A refused transfer leaving neither leg, and a reversal that negates exactly without touching the original
  • 50,000 rows scrolled, rebuilt in about 325 ms and chain verified in about a second

Deliberately not real

  • The role switcher is identity by declaration. The enforcement is real, the sign-in is not
  • The chain digest is FNV-1a rather than SHA-256, which catches accidental corruption and casual tampering and nothing stronger
  • There are no purchase or sales order lifecycles, no barcode hardware, and no costing beyond one unit cost per movement
  • Nothing leaves the browser, so moving the data to another machine means exporting it and importing it

How it is built

The write-up sells harder than the application does, so it is on the page rather than in a repository nobody can open.

Stack
Svelte 5 and SvelteKit as a static build, TypeScript and Vite, with two runtime dependencies: Svelte and idb. The charts, the virtual list and the translation layer are a few dozen lines each rather than a package each.
Boundary
One function is the only writer to the movements store. Routes never touch the database, commands go through a pure domain layer, and the projections read the ledger back.
Budget
42.9 KB gzip on the initial route against a 45 KB budget, 98.1 KB of client JavaScript against 110 KB, and 7.0 KB of CSS against 8 KB. Vietnamese is a 4.5 KB chunk an English reader never fetches.
Tests
25 tests against a real IndexedDB rather than a mock of it: projection against full rebuild, chain integrity, permission denials, the negative stock block and its override, blind counts, and an export and import round trip.

Want the version of this with your rules in it?

This one was built to a constraint nobody would choose. Yours would have a server, real accounts, and the parts of the domain that are actually hard. Tell us what the software has to do and we will tell you what it takes.

Bring the messy version of the problem. That is the conversation we enjoy most.

Email us