Product · Inventory · Billing
Sari-sari store
Stock as a ledger, so a wrong count can be explained
Inventory, costing and billing for a neighbourhood shop. Quantities are derived from stock movements rather than stored, costs are weighted-average, and receipts extract VAT from prices that already include it.
- TypeScript
- React
- Vitest
- 33 tests
Stock
1 item at or below reorder point: Eggs, tray of 6
stock at cost ₱3,945.00
Receipt
Tap an item to ring it up.
Stock book
reconciles ✓| Movement | Qty | Unit cost | Balance |
|---|---|---|---|
| receiptOpening | +20 | ₱48.00 | 20 |
| receiptRestock | +10 | ₱54.00 | 30 |
The quantity above is derived from these movements, not stored alongside them. That is what makes the difference between a count that is wrong and a count you can explain — and the reconciliation flag is the invariant being checked as you use it.
Rice arrived twice at different prices, so its average cost is neither of them. Ring some up and watch the stock book below: the quantity is derived from those movements, not stored next to them.
Try to buy more eggs than there are. The sale is refused rather than letting the count go negative — which would quietly corrupt the stock value, the margin and the reorder list all at once.
What this is, and what it is not
This is the part of the product that is worth building carefully: the rules that decide what can be sold, for how much, and what happens when the answer is no. It is a real engine with tests beside it, running in your browser with no server involved.
It is not a whole product. There is no login, no database and no payment provider — those are known quantities, and building them here would demonstrate nothing that the interesting half does not already demonstrate better. State lives in the page and resets when you reload it.
Read the full case study