A cash-control platform that tracks every dinar from the drawer to the treasury.

Overview.
A retail group with four branches and one treasury
Cashiers, couriers and field collectors handle cash all day. A CFO, treasurer and branch heads need to know, at any moment, how much is where and who touched it.
Paper counts, typed totals, no trail
Closings took hours, totals were typed by hand, change and credit sales blurred the numbers, and disputes could not be settled because nothing was immutable.
A denomination-first ledger the phone cannot lie to
An Arabic Android app for the three mobile roles, backed by a Postgres money engine that only accepts counts, computes everything server-side, and never edits history.
Screens.

One phone, several sensitive roles, two passwords
The client asked for a second password check before anyone touches money. Sign-in has two gates: the central account, then a financial re-verification that routes the user straight to their post.
- 1Central gateUsername is mapped to an email alias behind the scenes so cashiers never type an email. Standard Supabase Auth underneath.
- 2Financial gateA real password re-verification, not a PIN. A single RPC then returns role, branch, cashbox and the open financial day atomically.
- 3Automatic routingCashier, courier and field collector land on their own home. Admin, CFO, treasurer and branch-head accounts are web-only and get blocked on mobile by a device guard.

The financial day, at a glance
A cashier's day has a lifecycle: open, operate, hand over, close. The home screen shows where the day stands and what is allowed right now.
- 1Day statusOpen or closed, session A or B, and whether a CFO-approved partial close is pending.
- 2Drawer compositionLive totals derived from denomination movements, never from a typed amount.
- 3Quick actionsCash in, direct sale, change return, exchange, expenses and the journal, each gated by the day state.

Count pieces, not totals
The client forbade hand-typed totals. Every count in the app, from opening the day to swapping notes for coins, is entered per denomination, and the server computes the amounts.
- 1Drawer composition, computedThe current drawer is derived from recorded movements and labelled as computed automatically. Nobody types a balance.
- 2The full Algerian setEvery note and coin in circulation, with the 200-dinar paper note and the 200-dinar coin treated as different items.
- 3Exchange without changing the totalBreak a large note into smaller ones, or the reverse, and the server checks that the pieces leaving the drawer actually exist before it accepts the swap.
- 4Non-circulable protocolA damaged or suspect note triggers mandatory camera evidence, the holder's identity, a server-numbered protection receipt and a triple notification to client, branch head and CFO.

Every dinar in gets a reason
Money entering the drawer must be classified: sales, debt settlement, other, or a collection on behalf of another branch.
- 1Mandatory classificationThree-way split plus cross-branch collection. No uncategorised cash.
- 2Cross-branch collectionWrites the cashbox entry and a pending inter-branch balance in one atomic call. Only the CFO can settle it.
- 3Idempotent by designEach write carries a client-generated UUID with a unique constraint server-side, so a retried request on a bad connection can never post twice.

Two cash flows, one net amount, one receipt
A customer pays 2,000 for a 1,750 item. The drawer receives 2,000 and gives back 250, but the sale is 1,750. The app records both movements atomically.
- 1Received and change counted separatelyBoth sides are denomination counts, so the drawer composition stays exact.
- 2Net equals item valueThe server records IN and OUT movements together and nets them, with an auto-numbered receipt.
- 3Change return hard-linkedA later change return must reference a parent operation and is capped by that operation's amount and by what the drawer actually holds.

An immutable record the cashier can read
Nothing is ever edited or deleted. Corrections are reverse entries linked to the original, and the journal shows the full story of the day.
- 1Per-type identitiesEach operation type has its own visual identity so a shift can be scanned at a glance.
- 2Day archiveSession A and session B grouped, with transfer status per handover.
- 3Audit log behind itWho, what, when and from which device, written server-side for every mutation.

Declare first, then compare
At close, the cashier declares the commercial figures without seeing the counted cash. The server matches the declaration against the drawer and surfaces the difference.
- 1Three steps, in orderCommercial reconciliation first, then the automatic count, then the declaration and close. The commercial figures are entered before the counted cash is revealed.
- 2Deferred debtsCredit sales are declared separately and tracked as debts, not hidden in the cash figure.
- 3Partial closeWith CFO permission, a mid-day close opens a second session so a handover does not stop trading.

Money in motion, expenses on order
Cash leaves the branch only through a courier chain, and only after the day is closed. Expenses leave the drawer only against orders the CFO has already approved.
- 1Locked until closeThe transfer tab stays locked while the day is open. A drawer emptying request becomes available the moment the financial day is closed, for exactly the counted amount.
- 2Transfer chainAssigned, picked up, delivered, received by treasury. Each step is a state transition the server validates, and the courier has its own screens for it.
- 3Execution ordersA cashier can execute an approved expense only, photographing the invoice as evidence into a private storage bucket.
- 4Field collectorsA third mobile role collects from clients in the field with the same denomination-first rules.
Money rules.
Architecture.
Engineering deep dives.
1. An immutable, double-entry-style money model
- Problem
- The records must be legally defensible. A balance that was edited in place proves nothing.
- Approach
- Append-only transactions and denomination movements. Corrections exist only as linked reverse entries. Balances are always derived from the branch ledger, never stored and updated. Amounts are integer centimes.
- Trade-off
- Every balance is a query over history. Heavier reads in exchange for tamper evidence and replayability.
2. Server-authoritative denomination accounting
- Problem
- Cashiers count pieces, not totals, and the client forbade hand-typed amounts.
- Approach
- The app only ever sends denomination counts. The server computes amounts, validates drawer availability per denomination before any outflow such as change or exchange, and treats the paper 200 and the coin 200 as distinct.
- Trade-off
- Chattier RPC payloads, but a client device can never fabricate an amount.
3. Two-gate auth with automatic post routing
- Problem
- One physical phone, multiple sensitive roles, and a client demand for a second password before the financial workspace.
- Approach
- Central Supabase session, then a real signInWithPassword re-verification, then a platform_login RPC that returns role, branch, cashbox and open day atomically. The navigator routes by role and bounces web-only roles to a device guard.
- Trade-off
- An extra network round-trip at login, softened with a branded routing overlay.
4. Idempotent financial writes, offline-ready
- Problem
- Branch networks drop mid-request. A retried 'record cash' must never post twice.
- Approach
- Every mutating RPC carries a client-generated UUID with a unique constraint on the server. The planned SQLite sync queue reuses the same key, so offline replay is safe by construction.
- Trade-off
- Idempotency plumbing on every call before the offline store even ships.
5. Compound atomic operations
- Problem
- A direct sale with change moves two cash flows but nets one amount. Half-completed operations would corrupt the drawer.
- Approach
- A single RPC records the IN and OUT movements together with net equal to the item value and an auto-numbered receipt. Cross-branch collection uses the same pattern, double-writing the cashbox entry and an inter-branch pending ledger that only the CFO can settle.
- Trade-off
- Fatter RPCs, in exchange for operations that cannot half-complete.
6. Zero-write RLS posture
- Problem
- A leaked client key must not allow any data write.
- Approach
- Row-level security on all 25 tables with no insert, update or delete policies for clients. Every write path is a SECURITY DEFINER function, and a hardening pass revoked the implicit public execute grant from every function after a security advisor warning.
- Trade-off
- All authorization logic lives in SQL, so function review becomes the security boundary.
Cash flow.
- Open and close the day
- Intake, sales, change, exchange
- Non-circulable protocol
- Transfer task chain
- Pickup and delivery proof
- Client collections in the field
- Same denomination rules
- Permissions and settlements
- Web dashboards (planned)
- Blocked on mobile
What's next.
Engineering debt I would pay first
Mirror the live migrations back into the repository, replace the remaining mock client list with real client records, add tests around the RPC contracts, and set up CI for signed builds.
Product roadmap
Offline queue on SQLite reusing the idempotency keys, push notifications for the triple-notification protocol, and the web dashboards for the CFO, treasurer and branch heads.