Petty Cash & Task System with Two-Stage Approval
Role: Product Manager & Lead Developer
Petty cash gets run on memory: the balance is a number somebody states, and whoever spends is whoever signs off. This system moves the prevention out of a policy document and into the path itself — nobody approves closing their own custody, permission or not.
Six sections — the follow-up board, custodies, tasks and requests, reports, the audit log, and settings. The approval path is two stages on separated permissions: whoever opens a custody is not who reviews it, and whoever reviews is not who approves. Tasks carry five states — new, in progress, transferred, done, archived — and move between departments with a transfer trail that persists. The system is in daily production use — not a prototype.
And no balance field exists: the balance is derived from the opening amount plus top-ups minus approved spending, and every line in the statement is computed under an order pinned by three keys — date, then creation time, then id — so a line never shifts when two dates coincide. When the custody holder is the reviewer, the closure request skips its own first stage, so nobody reviews themselves. A rejection always demands a reason. A transfer is refused if it exceeds the source balance, and the message names both numbers — requested and available — so the requester learns the gap rather than only the refusal; when accepted it creates two transactions under one reference: spending at the source and a top-up at the destination. Lateness is derived from the due date, and attachments use time-limited links whose lifetime tracks their sensitivity. When an invoice arrives, one button — automatic analyse-and-record — has an n8n pipeline read it into a posted transaction instead of typing its fields by hand. And the audit log itself is role-bound: a manager reads the system's complete actions, everyone else reads only their own.
Key Features
Nobody Signs Off on Their Own Spending
Review then approval by two different people; a holder who reviews skips their own stage — and every rejection demands a reason.
A Balance Computed From Transactions, Never Typed
No stored balance: opening plus top-ups minus approved spending — and the running balance keeps a fixed order that cannot drift.
A Transfer That Cannot Exceed the Balance, and Loses Neither End
Refused beyond the source balance, stating requested and available; acceptance creates both legs under one reference.
Five Task States, With Lateness the System Computes
Five states, with transfers that leave a trail — lateness is derived from the due date, so no manual flag goes stale.
Attachment Links That Expire by Sensitivity
Link lifetime follows content sensitivity: a short-lived invoice link, a longer task one — with explicit size limits.
Duties Separated by Action, Not by Screen
Opening custody, first review and final approval belong to three roles — and a department manager is refused explicitly.
The Business Problem
Petty cash gets run on memory and messages: the balance is a number somebody states, invoices are scattered photos in chats, and the statement is rebuilt by hand every time it is asked for. Worse, whoever spends is whoever signs off on it — not because anyone intended that, but because nothing in the tooling prevents it.
Why We Built It
The balance should not be an opinion. Record the transaction and let the balance follow from it, so the figure becomes a trace of movements rather than a summary somebody asserts. Then move conflict-of-interest prevention out of policy and into the path: the state itself refuses to let a holder approve their own closure, so nobody has to remember the rule or confront a colleague with it. Permissions are separated at the action, not the screen — hiding a screen is user experience; refusing the action is the protection.
How We Built It
Deliberately light architecture: a single-page interface over an explicit database schema, with navigation as internal state changes (custody list → custody detail, task list → task detail) rather than successive pages. Derived values are computed at read time rather than stored — balance, custody total, approved spending, lateness — so no number can fall out of step with its transactions. Files live in two separate containers behind time-limited links whose lifetime is set by the sensitivity of the content, not its file type. Every data change is written to an audit log filterable by date, type and user, with a "go to the item" link that returns the reader from the log to the thing itself — making the log an investigative tool rather than an archive read once.