Case study · Product I own
Cafe POS
A recipe aware point of sale: every sale deducts its ingredients, and offline terminals reconcile on reconnect.
- Role
- Owner, sole architect and developer
- Status
- Live
- Stack
- PHP 8, CodeIgniter 4, MariaDB, IndexedDB sync, Laravel (rewrite), React PWA (rewrite)
Summary
- Problem
- Cafes lose money on stock they cannot see, and the till has to keep selling when the wifi drops.
- System
- Recipes behind every product deduct real ingredients on each sale; offline first terminals sell with no internet and sync idempotently, on a plain cPanel host with no daemons.
- Outcome
- Live in a cafe on CodeIgniter 4, with a Laravel and React PWA rewrite underway.
What needed to exist
A till that knows the recipe and never stops selling.
Cafes bleed money on stock they cannot see. A latte quietly spends beans, milk, a cup and a lid, but most point of sale systems only record that a latte was sold, so the shelf and the sales report never agree. And the till has to keep working when the wifi drops, because a cafe cannot stop taking orders while it waits for a signal.
Cafe POS answers both. It knows the recipe behind every product and deducts the real ingredients on every sale, and it is offline first so the counter never stops. One codebase runs any cafe, white label, so a new site is a setup, not a rebuild.
Constraints
Cheap shared hosting, no daemons.
The target is a plain cPanel host with no daemons, no Redis and no websockets, so nothing depends on infrastructure a small cafe will not pay for. Background work runs on the database queue driven by a single cron, and the front end ships as a static build.
Architecture
The till is the source of truth while it is offline.
-
POS terminal
A progressive web app that sells offline from an on device store.
-
Server
A PHP API that runs idempotent sync and the ingredient deduction.
-
Data
MariaDB holding stock, recipes and orders.
The rewrite keeps the same constraints: a Laravel API with cookie based auth and a React progressive web app the terminals install like a native till.
Decisions
Sell offline, reconcile exactly once.
The till must keep selling with the internet down, and sort itself out later with nobody pressing sync. Sales made offline are stored on the device and sync automatically when connectivity returns. Every write carries an idempotency key, so orders, payments, stock deductions and receipts reconcile on the server exactly once, however many times a flaky connection retries.
Hard problems
Turning a sale into stock movements.
-
Recipe deduction
Each product maps to a recipe of ingredients, so selling it draws down the real stock behind it, down to the cup and the syrup pump.
-
FEFO stock
Inventory is consumed first expired, first out, so the shelf and the system waste less and agree more.
-
Modifiers and pricing
Add a shot, swap the milk, size up: modifiers adjust both the price and the ingredients deducted.
-
Refunds and Z read
Refunds reverse the stock movements too, and an end of day Z read closes the till with numbers that tie out.
Implementation
Money is never a float.
The shipped build carries a test suite that defines the correct behaviour of the deduction engine, modifier pricing, FEFO consumption, refunds and the Z read, so a change that breaks the maths fails loudly. The Laravel and React rewrite ports those scenarios one for one and has to match the same results before it can replace anything.
- DECIMAL money, never floats
- Idempotent writes
- Explicit DB transactions
- Offline first PWA
- Ported test scenarios
- Small, typed domain core
- cPanel friendly, no Redis
- White label per cafe
Result
Live in a cafe.
Cafe POS is shipped and running on CodeIgniter 4 in a cafe, with the Laravel and React PWA rewrite underway.