# Designing a chart of accounts you can reconcile

> What finance people mean by a ledger, and the modelling mistakes that make month-end hurt.

![A close you can trust is a rule that says no. Once a month is reported, nothing posts into it without approval.](https://arifhossen.dev/media/01a15df1-9acc-7213-b058-8327b55120bd)

Published 2026-10-15 by Arif Hossen  
Tags: Schema  
Source: https://arifhossen.dev/blog/designing-a-chart-of-accounts-you-can-reconcile

Where it started

The finance team at an East African Islamic insurer started using the statements I had built in earnest, and they did not balance. The reports summed only the lowest-level accounts. They ignored balances carried forward from prior years. There was no retained-earnings line. And in this kind of insurer every one of those figures has to appear twice, once for the participants' pool and once for the shareholders' fund.

A statement that does not balance is not a cosmetic bug for a finance head. It is a stop. Every one of those mistakes was in the model, not in the arithmetic.

## One ledger, two sets of books

Two funds

An Islamic insurer keeps the participants' money and the shareholders' money apart. Not as a report filter, but as two sets of books that each have to balance on their own.

So the fund is a property of the account, not of the posting. Every account belongs to exactly one fund. The same name can exist in both, each with its own code. Retained earnings exist once per fund. Business modules never name an account directly. They ask a mapping for salary expense or retained earnings in a given fund, so payroll and claims post to the right book without hard-coding a code.

## Let the code carry the hierarchy

Codes

Account codes are generated, never typed. Seven digits, where the trailing zeros give the level.

```text
1000000   assets (category root)
1100000   group
1110000   sub-group
1110001   postable leaf
```

*Trailing zeros give the level*

Two rules follow from that, and both are enforced. A parent account cannot take postings, only leaves can. And an account that already has journal lines cannot be turned back into a parent: the save is refused with a reason.

Those rules are what make the roll-up possible. If a parent can hold its own postings, its balance is no longer the sum of its children, and nobody can tell which number is right.

## Make the wrong entry impossible to post

The ledger refuses

The ledger accepts a journal only when it is approved, has a fund, and balances. An unbalanced entry is refused with a message naming the debit total, the credit total and the difference.

Once posted, an entry is read-only. A mistake is corrected by a reversal: a mirror entry with a written reason, linked to the original, which stays visible. Nothing is edited after the fact.

Time is controlled the same way. Each month is a period that closes itself after a grace window, locks after two months, and can be reopened only one step back, with approval. Journals, payments, purchase orders and payroll all check the period before they post.

## Recoding a chart that already has history

Changing it live

Seven months in, finance needed seven-digit codes instead of six. By then the chart held real accounts with real postings.

I shipped it as one reviewed change with three reversible conversions: one to convert the codes, one to fix parent codes the old format had left wrong, and one to fix leaves that pointed at the wrong sub-parent. The seeders moved to the new format in the same change, along with a seeder that loads the client's real chart, so test environments rehearsed against the real thing.

## The statements were rebuilt, not patched

What it cost

The fix for the imbalance went into the calculation layer. Parent accounts roll up their children, prior years carry forward, retained earnings sit per fund, and a year-end close moves the result into them. Posted transactions were not touched. Over 1,100 lines of tests prove each parent equals the sum beneath it.

The lesson I took: roll-ups, carry-forwards and retained earnings are not reporting features to add later. They are the model. I built journals and a ledger first and statements on top, and finance found the gap before I did.
