Retail & distribution
StockSense
A multi-location inventory system built around batches, with expiry tracking, an append-only ledger, and audited transfers between branches.
- Services
- Tools for your team
- Stack
- React, Vite, Express, Drizzle ORM, PostgreSQL
- Delivery inBatch 2291Branch 2+24
- MovedBatch 2240Branch 1−6
- SoldBatch 2187Branch 2−2
At a glance
- Problem
- Each branch kept its own spreadsheet, so nobody saw total stock or what was about to expire.
- Solution
- Batch-level inventory with expiry horizons, an append-only movement ledger, and two-click transfers between branches.
- Result
- Stock is derived from the ledger, so every number is explainable and nothing can silently drift.
The problem
Multi-location businesses that hold perishable or batch-tracked stock, such as pharmacies, clinics, food distributors, and specialty retail, almost always run on a spreadsheet per location.
Nobody can see total stock across sites, nobody knows what’s about to expire until it has, and a transfer between two branches is a phone call and a hope. The cost isn’t dramatic: it’s a slow bleed of written-off stock and emergency reorders.
What we built
Batch-level tracking
Every unit carries its lot number, expiry date, cost, and location. Stock is a set of dated batches, not a single number.
Stock changesFull history- Delivery inBatch 2291Branch 2+24
- MovedBatch 2240Branch 1−6
- SoldBatch 2187Branch 2−2
Expiry intelligence
A rolling 90-day expiry horizon per location with alert thresholds, and first-expired-first-out picking suggested by default.
Orders today128Stock alerts32 expiringDemo dataA ledger behind every number
Receipts, sales, transfers, adjustments, and write-offs are immutable, attributed rows. You can always answer how a figure was reached.
Stock changesFull history- Delivery inBatch 2291Branch 2+24
- MovedBatch 2240Branch 1−6
- SoldBatch 2187Branch 2−2
Transfers with an audit trail
Moving stock between branches is a two-click flow, recorded on both sides with who did it and when.
OrderSourceStatus4471EmailDone4470WebsiteIn progress4468EmailNeeds a look
Architecture
Engineering decisions
- Why an append-only ledger instead of a quantity column?
- Inventory disputes are the reason these systems get replaced. A mutable column can’t tell you who changed what, but a ledger can, forever, at almost no cost.
- Why Postgres instead of a document store?
- Batches, locations, and movements are deeply relational, and expiry reporting is a set of range queries. That is exactly the shape SQL handles well.
- Why calculate stock on read?
- A stored running total is one more thing that can disagree with the ledger. Summing signed quantities on read means the number can never contradict the movements behind it.
Tech stack
- React
- Vite
- Express
- Drizzle ORM
- PostgreSQL