OmniPharmacy OS
A till that refuses the sale is doing its job.
Retail software for a regulated counter: the same screen that takes the money also decides whether the medicine is allowed to leave the shelf, and records who decided.
Demo PINs 740126 Owner · 583641 Pharmacist · 618352 Manager · 294718 Cashier
Sample data only. Pick a branch when prompted — the owner works across both.
- Regulated retail & dispensing
- Domain modelling · Engineering · Deployment
- Django 5 · SQLite · PWA · M-Pesa Daraja
- Live demo
A pharmacy is a shop with a licence attached.
Most pharmacy software is a supermarket till with drug names typed into it. It will happily sell a prescription-only medicine to anyone, in any quantity, dispensed by whoever happens to be standing at the counter.
The controls that matter are not commercial, they are legal: who may hand over a POM, what goes in the controlled drugs register, which batch was sold and when it expires, and whether the two medicines in the same basket should be taken together.
If those rules live only in the pharmacist’s head, they stop existing the moment the pharmacist steps out.
The rules are in the transaction, not in the training manual.
Every sale passes through the same gate. The product carries its own classification and the person at the till carries their own role and registration number, so the system can answer “is this allowed?” before it answers “how much?”
A PIN, not a password — a pharmacist signs in dozens of times a shift, and a shared account destroys the audit trail.
The terminal states which premises it is in. Stock, tills and the controlled register are all branch-scoped.
The product resolves to a batch with an expiry date, not just a name and a price.
Classification, prescription requirement and ingredient interactions are evaluated against the basket.
A POM needs a licensed dispenser. A controlled drug needs a pharmacist. The system will not be talked round.
Who dispensed, to whom, from which batch — written at the moment it happens.
Cash or M-Pesa, then a till session that has to reconcile at the end of the shift.
Decisions worth defending.
PIN authentication with an escalating lockout. A short PIN is weak against a script, so attempts are throttled per terminal and each further block of failures costs more time. The threat model is a busy counter, not a login page.
Permissions are a matrix, not a flag. Six roles map to explicit capabilities — dispensing a controlled drug, approving a stock adjustment, editing a price, viewing financial reports. Nobody is simply an “admin”.
Policy is typed, per business. Whether a POM requires a recorded prescription is a field an auditor can query, not a setting buried in code. The defaults are the safe posture, and loosening one is itself an audited change.
Stock is held as lots. A batch number and an expiry date, because a recall is a question about a batch and not about a product.
Built for someone standing up.
Four surfaces on one data model: the till, the dispensary, the stockroom, and a command view for the owner. Each shows only what its operator can act on.
The command view after signing in as owner — branch, staff member and the day’s operational state.
What the system changes.
The register stops being a book that someone remembers to fill in. Expiry and interaction checks happen on every basket rather than on the ones where somebody is paying attention, and the question “who dispensed this?” has an answer that does not depend on anyone’s recall.
This instance is a live demo seeded with sample data — a real pharmacy model, real regulations, invented patients.