



The problem
A Kenyan retail chemist sells the same medicine by the pack, the strip and the single tablet, and most systems model one unit — so staff divide in their heads and the stock count drifts. Expiry quietly eats margin when nobody can see which batch goes off next. M-Pesa is checked on a cashier's own phone, cash variances have no owner, the internet and power both drop, controlled-drug records are what a PPB inspection looks at first, and insurers pay only on itemised, correctly referenced claims.
My approach
I built it as a counter first: scan or type, Enter adds, function keys for tender, hold and recall, and every product priced per unit it is sold in. Behind the counter sits a batch-level stock ledger fed by goods-received notes, so the till can sell first-expiry-first-out; shifts that open with a float and close with a blind count; M-Pesa prompts sent from the till; a prescription and controlled-drugs register; insurance claims raised against receipts; supplier balances and reports. It shipped with a written acceptance script, staff training sheets and an owner's handover pack, not just the software.
How it's built
- A pnpm/Turborepo monorepo: a Next.js 16 back office and point-of-sale terminal (installable PWA), a Hono API, and shared packages for domain logic, the database, auth, Zod contracts and design tokens.
- Domain rules — money, units of measure, access control, first-expiry-first-out allocation, pricing and reordering — live in a package with no I/O at all, tested on their own.
- PostgreSQL row-level security: the API connects as a role that owns nothing, and every tenant table filters on a tenant id set per transaction, so one pharmacy's query cannot read another's rows even by mistake.
- The audit trail is an append-only table whose rows are SHA-256 chained; an endpoint recomputes the chain to prove nothing was edited after the fact.
- The counter keeps selling offline: catalogue and prices cached in IndexedDB, sales held in a durable outbox and synced when the connection returns.
- M-Pesa through Daraja STK Push from the till, confirmed by callback with a status query as the fallback, and a simulator for training without real money.
Highlights
- Sells the way a chemist does — by pack, strip or single tablet, each with its own price — while stock is held in base units, so the count still adds up.
- Every supplier delivery creates batches with expiry dates, and the counter hands out the earliest-expiring first.
- Prescription and controlled medicines are flagged on the product; a controlled sale goes into a register that needs a witness.
- Insurance claims raised against the receipt, tracked from draft to paid, and exported for the insurer's portal.
- An audit log that can prove it has not been edited.