A double-entry posting engine that cannot silently go wrong
Seventy-four places in the system move money. All seventy-four go through one piece of code, and nothing reaches the accounts any other way.
| Account | Debit | Credit |
|---|---|---|
| 1200Accounts Receivable | 1,240.500 | — |
| 4000Sales Revenue | — | 1,181.429 |
| 2300VAT Output Tax | — | 59.071 |
| 5000Cost of Goods Sold | 742.000 | — |
| 1300Inventory | — | 742.000 |
| Totals | 1,982.500 | 1,982.500 |
Interface rendered in code from the platform’s own structure — illustrative, not a screenshot. Figures are illustrative and balance as the engine requires.
The problem
A sale, a purchase, a return, a stock revaluation, a payroll run — every one of them moves money, and every one has to land on the right accounts. Get it wrong and the books are quietly incorrect: no error, no alert, and nobody finds out until a year-end that does not reconcile. Get it defensive and you block the sale, which stops a shop trading.
A multi-tenant ERP and POS platform running the daily operations of twenty-plus retail businesses across Kuwait and the Gulf, including a mid-size superstore. Real cash, real inventory, real tax filings — and no second engineer to catch a mistake in review.
What I built
All of it. I designed the engine, chose its four-stage shape, wrote the rules format, built it, deployed it and support it in production. There was no architect to hand me a spec and no reviewer to disagree with me.
I made the engine four stages, each replaceable. A classifier turns a business event into a transaction type. A rules provider supplies declarative posting rules for that type, with reversal symmetry built into the format rather than written twice. An account resolver maps those rules onto the chart of accounts through 67 named posting roles, so a rule says what it is posting and never which account number. A journal builder writes the balanced debit and credit lines, and each document keeps the tax rate it was posted at. The rules are data rather than code because an accountant can read data, compare two versions of it and argue with it. Seventy-four hand-written posting routines would give them nothing to read.
The decision with no clean answer
When the resolver cannot find an account, there are three options and all three are bad. Guess an account and the books are corrupted silently — the worst possible outcome, because it is invisible. Block the sale and a shop with a customer at the till stops trading over a configuration gap. Post to Suspense and the entry is balanced, visible, wrong in a way anyone can see, and queued for a human. I chose Suspense. It is the only one of the three that is honest and still keeps the till open.
What it produced
- Nothing persists unbalanced — if debits and credits disagree the posting throws inside a database transaction, so a half-written journal cannot exist.
- Money is
decimal(18,3)end to end, full precision in, rounded once at the edge. - Document numbering is gap-free, which is a legal requirement in most of the jurisdictions this runs in.
- Every ledger line links back to the document that created it — you can go from a figure on a customer's screen to the journal lines behind it, and back to the invoice.
What I would do next
Automated tests on the financial code. There are none on the web platform, and for the parts I refactor slowly that means nothing tells me when I have broken something. It is the single thing I would change about this engine.
Appendix — all 48 transaction types
48 declarative types. Each one states what it debits and credits.
- Sale
- SalesReturn
- SaleAdjustment
- SaleDiscountChannelAdjustment
- Purchase
- PurchaseReturn
- PurchaseInvoicePost
- PurchaseInvoiceVoid
- COGS
- Payment
- Receipt
- SupplierPayment
- SupplierAdvance
- SupplierRefundPayment
- SupplierCreditRefund
- SupplierWriteOff
- CustomerAdvance
- WriteOff
- InventoryAdjustment
- InventoryAdjustmentVoid
- InventoryRevaluation
- StockInTransit
- StockInTransitReceive
- StockInTransitWriteOff
- StockReceive
- StockReceiveVoid
- OperatingExpense
- AccruedExpense
- Payroll
- Depreciation
- FixedAsset
- Dividend
- TaxDeduction
- ForexGainLoss
- RoundingDifference
- CashShortageSurplus
- CashDeposit
- CashTransaction
- BankTransaction
- InterAccountTransfer
- SafeDrop
- RegisterOpen
- RegisterClose
- CommissionAccrual
- CommissionPayment
- CommissionReversal
- PromotionalGiveaway
- Suspense
- Suspense — the honest fallback
- Reversal counterpart
Ten minutes on a screen-share tells you more than this page can. Get in touch.