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
In short
- Movements are the record; the quantity is a fold over them, so every count can be traced
- Weighted-average costing, re-averaged on each delivery and captured at the moment of sale
- Stock cannot go negative — the bug that silently destroys stock value, margin and the reorder report
- VAT extracted from tax-inclusive prices, with net plus VAT exactly equal to the total
A quantity column cannot answer the question
The tempting design is a number you increment on delivery and decrement on sale. It is also the design that leaves a shop owner looking at a figure that says 14 when the shelf holds 11, with nothing to check it against.
Storing the movements and deriving the quantity costs a fold and buys the only question worth asking when the count is wrong: which movement was it? The demo shows the running balance beside the movements that produced it, and asserts live that the two agree.
Weighted average, and why not FIFO
A sari-sari store buys the same sachets from whichever supplier was cheapest that week and tips them into the same box. There is no first-in to identify, so FIFO would be a fiction maintained in software about a physical situation that does not support it.
Weighted average matches what actually happens, and it means a sale does not have to be matched against a particular delivery. Two deliveries of rice at ₱48 and ₱54 produce an average that is neither — weighted by quantity, not by delivery, which is a distinction that quietly matters when one delivery is nine times the size of the other.
The cost is captured onto the sale movement at the moment it happens, so a later delivery cannot rewrite the margin on a sale that already went through.
Negative stock is the expensive bug
Letting a quantity go below zero looks harmless — the sale went through, the customer left happy. It silently corrupts everything downstream: the stock valuation, the margin, and the reorder report that existed to prevent exactly this.
Every sale goes through a function that refuses to take stock that is not there, and returns how much there actually is. There is a test that hammers it ten times against five units and asserts the balance lands on zero rather than minus five.
VAT the other way round
Prices on a shelf in the Philippines include VAT. That inverts the usual arithmetic: instead of adding tax to a net price, the receipt has to extract the tax already inside a gross one.
Adding 12% to a tax-inclusive price overstates the VAT by about 1.4% of every sale, and the error surfaces only when somebody reconciles against a filing. The engine divides rather than multiplies, rounds once, and the tests assert that net plus VAT equals the total exactly — across hundreds of amounts, because that is the property a till has to satisfy or the drawer does not balance.
Source
- src/lib/inventory/ledger.ts
- src/lib/inventory/billing.ts
- src/lib/inventory/inventory.test.ts