Feb 2026 – present · Posted
OneFinOps — purchases, sales, banking, accounting
I work across the finance side of the product: the procure-to-pay service that takes a purchase order through goods receipt, supplier bill, matching, withholding tax and payment; the sales, banking and accounting modules around it; and the React screens that drive them. The hard part is that every write moves money. A double-click, a retried request or two workers settling the same bill must never pay or post twice, and every undo has to reverse exactly what its original created in the ledger.
| Item | PO | Rcvd | Billed |
|---|---|---|---|
| Steel brackets, 40 mm | 24 | 24 | 24 |
| Cable trays, 2 m | 12 | 10 | 12 |
| Installation, site B | 1 | 1 | 1 |
Line 02 billed 12 against 10 received. Payment is blocked until someone authorised accepts the variance.
As posted
Detect · decide · actA supplier bill whose lines disagree with the purchase order or the goods receipt — a unit price outside tolerance, a quantity beyond what was received or already billed, a line that isn't on the order at all.
Per line — matched, under-billed partial, or mismatch — then rolls the lines up to one verdict for the bill. The gate fails closed.
Blocks submit and payment until an authorised person accepts the variance, and records who accepted it.
The hard part
Money that survives retries and racesA bill can be settled from four paths at once: a manual clearance, a payout-cleared event from the bank worker, a direct payment completion, and an import. Each loaded the bill, applied the payment, and wrote the whole row back — so two concurrent settlements both read the same paid amount, both passed the over-payment check, and the second write silently erased the first, under-stating what was paid and risking a duplicate ledger posting.
The fix was a compare-and-set: a targeted update of paid amount, balance and status that only succeeds if the paid amount is still what was read, with every settle path routed through one helper that reads, applies, compare-and-sets, and reloads-and-retries on a lost race. All four paths had to move together, or a straggler would clobber the guarded value.
The same principle runs through the rest of the module: document numbers are minted inside the transaction so a failed create never burns one, and each idempotency key is scoped to its purpose and recorded in the same transaction as the work — so a rollback takes both, and a retried “hold” can never answer for a “release”.
Found while testing against a clean organisation — not in a customer's books.