Corrections never rewrite history
Posted entries are immutable. Changing a record reverses the old posting and posts the new one, both kept and labelled with the revision that produced them. Voiding reverses; it never deletes.
47 Ledger · Double-entry books
47 Ledger is a double-entry accounting system for small businesses, with several businesses under one login. Every invoice, bill, payment, return and transfer posts a balanced journal entry, and every report is read from those postings.
The app sits behind a sign-in and is invite only for now.
Why I built it
Coming from audit, the question I always ask of a set of books is: where did this number come from? Most small-business tools answer with a total. I wanted one that answers with an entry: every figure traceable to a balanced posting, nothing edited after the fact, and the accounting rules enforced by the database rather than by good intentions.
The application
Redrawn live from the application’s own layout with sample figures, not customer records.
Net profit against the previous period, cash and bank, what is owed in and out, with overdue balances on the first screen.
Your financial picture
Net profit
USD 17,841.40
▲ 111% vs previous period
Income 26,250.00 − expenses 8,408.60
Cash / Bank
USD 30,031.40
Ledger balance · all time
Receivables
USD 6,650.00
1 overdue · USD 3,200.00
Payables
USD 2,370.00
1 overdue · USD 900.00
Aurora StudioUSD 24,350.00
Cedar Print CoUSD 1,900.00
One register for every movement: income, expenses, bills, owner money and applied payments, in the original currency beside the base amount.
47 Ledger
Every row is a balanced journal entry. Owner money and payments are kept out of income and expense.
Issue an invoice and it becomes a receivable. A part payment moves the balance, the due date and the overdue flag together.
47 Ledger
✓ Reconciled to control account 1100
ClientNorthwind Retail
ProjectNorthwind website
Issued28 Aug 2026
Due25 Sep 2026
AmountUSD 4,800.00
Notes50% on delivery, 50% on launch
Payments
05 Sep 2026USD 2,000.00
Outstanding USD 2,800.00
One of twelve reports, all read from the same posted journal, for any period.
Insights
Checks
✓ Trial balance — debits equal credits
✓ Assets = liabilities + equity
✓ Receivables agree to control account
Margin
68%
Read from posted lines only.
How it works
Invoices, bills, payments, returns, transfers and opening balances all post double-entry journals. Reports are derived from posted lines, never from stored totals.
Buying stock is an asset, not an expense; selling it books cost of sales at the cost that left the shelf. Tax runs through Output and Input VAT control accounts. Money borrowed or lent never touches profit.
Each business has its own figures, and reports filter by business, client or project. Invoices and payments work in the currency of the deal, at the rate actually received.
Profit and loss, balance sheet, cash flow, trial balance, tax return, general ledger and six breakdowns. The balance sheet checks the accounting equation as it draws.
Audit trail
Change an invoice and the ledger reverses the previous posting and posts the correction. Both stay in the books, so an accountant can follow any figure back to the entry that produced it. This is the part of the design I cared about most.
Report
✓ Balance agrees to INV-1042 outstanding. Nothing was edited in place — the correction is two new lines.
What I decided
Posted entries are immutable. Changing a record reverses the old posting and posts the new one, both kept and labelled with the revision that produced them. Voiding reverses; it never deletes.
Once a period is filed it can be locked, and nothing can be posted, changed or reversed inside it. The rule sits on every register in Postgres, so no screen or API call can post around it.
Bank reconciliation compares the ledger, the bank-confirmed figure and the statement. Marking a line reconciled posts nothing, so a filed figure can never move because someone ticked a box.
I folded leads into clients (a prospect is just a client at an earlier stage) and renamed engagements to jobs. There are deliberately no bank feeds: payments are recorded when they happen, and the books stay correct because every one is a balanced entry.
Built withNext.jsTypeScriptSupabase Postgres (RLS, plpgsql rules)decimal.js20 SQL migrationsPGlite tests
Questions about it
I am happy to walk through the design, the data model or the decisions above. Send me a message.
The other two tools