← All case files

Case file 009Hybrid ERP

The problem

“We ring sales on a till. We track stock by hand. When the month ends, nobody can say what anything cost.”

Solved

For
Businesses with both retail POS and production or inventory
Built
Desktop app + server: POS, production docs, stock, costing
Stack
Tauri · NestJS · Postgres · Next.js admin · Better Auth
Result
A sale on 300K rows of history: 3.6 s → instant
Before: the till prints faster than the ledger can keep up3D pixel film · made by me

01 · The pain

Two worlds that never agreed.

  • A sale on the till doesn’t move the stock.
  • Material sent to production is written on paper.
  • What did this batch cost? Nobody knows until month end.

02 · The fix

Stock in, stock out, cost on post.

A sample warehouse opening a shift, ringing a sale, receiving stock, and issuing to production. Watch the cost, stock on hand, and ledger all move together.

Shift

Closed

Stock on hand
ItemQtyAvg costValue
Item A (fabric)10010.001000
Item B (trim)5016.00800
Record a transaction

Cost at post

10.00

Total value

50

Ledger

TypeItemQtyCostTime
receiveitem-a+10010.00Today, 08:00
receiveitem-b+5016.00Today, 09:00

Sample data: Two items with different cost bases. Pick an action, select an item and qty, and post. Watch the weighted-average cost recalculate based on everything in the ledger.

03 · How it works

Three flows, and a hard problem about timing.

  • A

    Every document touches stock

    When staff ring a sale, issue material to production, or receive goods from a supplier, the system records it. Each document is a transaction: sale, requisition, issuance, receive note, discard. Nothing goes unseen.

  • B

    Stock finds its cost in real time

    A weighted-average cost is calculated at post time from the full ledger history: if you received 100 units at cost 10, then 50 at cost 16, the next issuance costs at 12. Two posts racing on the same item lock in order so cost never reads stale.

  • C

    Documents are offline-first, synced later

    The desktop app works on LAN or standalone. Shifts, sales, requisitions—all stored locally. When the server is reachable, they sync up. Licenses and modules can be disabled from the admin panel, and the desktop app finds out even while offline.

The hard part

Weighted-average at post time, with concurrent writes.

Every ledger entry has a cost: received 100 units @10 then 50 @16. A month later, a real issuance happens—pick 30 units—what is their cost? The system calculates it: 10+10+…+16+16+… = the average of everything issued so far. Two issuances racing on the same item and warehouse can both try to read and then write that average at once. The code uses a Postgres advisory lock, locked within the transaction scope, taken before any read and before the items are locked in order—so two documents with overlapping items never deadlock each other.

Even at scale—a stock ledger with 300,000 entries—replaying the entire history for every cost lookup took 3.6 seconds per sale and crashed the server past that point. The fix was aStockBalance table: a cache of the last resolved cost and the item count at that point. On the next write, only new ledger entries since the last cache hit are replayed. That brought it to O(1) after the first touch.

Stock LedgerReceive + 100 @10Receive + 50 @16Issuance -30 @?Issuance -20 @?StockBalance CacheAvg cost: 12 | Count: 150LockedOn postTwo posts locking in order; no race, no deadlock.History replay O(n) first time, O(1) on next hit.

04 · What changed

End-to-end.

Test coverage

97 tests — auth, admin kill switches (user disable, tenant suspend, device disable), license & seat limiting, POS sale & void, production documents (requisition, issuance, receive, discard), weighted-average costing, concurrent write race fix, BOM-based cost rollup, sync push, sync pull, embedded Postgres lifecycle, offline grace period enforcement.

Verification

Every flow verified live against real Postgres, not mocks. Desktop app compiled and run: tenant setup, POS shift, sale, void, production requisition → issuance → receive. Offline mode tested: LAN-only network namespace, embedded Postgres, license cache, grace period enforcement.

Scale & performance

Stock ledger with 300,000 entries, load test found and fixed latency bug. Per-sale costing calculation now O(1) after first touch via StockBalance cache. Concurrent writes protected with Postgres advisory locks; no race, no deadlock.

Client & user count · to confirm

Time per month-end close · to confirm

Active branches · to confirm