DODIL BLOG
DocsLog inSign up

Modules / Accounting

Accounting

The books: every transaction as a balanced entry, account balances you can trust, month-end close, and the reports your accountant asks for — fed by the rest of the business rather than re-keyed from it.

People ask for it as: “our books”, “the accounts”, “month-end”, “profit and loss”, “what does the accountant need”, “the auditor asked for”

Who uses it

RoleDoesSees
Bookkeeperposts journals, records what the other modules send, reconciles subledgersevery account and journal
Accountant / finance managersets up the chart of accounts, closes periods, reverses mistakes, runs the reportseverything
Auditor / ownerreads the books and the trail behind any numbereverything, changes nothing
The systemposts what other modules send, re-derives balances, materialises reportseverything, through its own service account

What it has to do

Reference user stories. Each has a test written into it — the given / when / then — which becomes an automated check once it is built. Expect to keep some, change some and drop some.

Things people do

  • GL-01As the accountant, I want a chart of accounts that matches how we report, so that every entry lands somewhere meaningful.

    Given a new set of books · when I add accounts with their type and their parent · then each account has a stable id, a type (asset, liability, equity, income, expense) and a place in the tree; an account already used by a journal cannot change type

    Accountant / finance manager · screen: Accounts · reference: implemented

    Usually different per customer: the chart itself — every country, accountant and trade has its own; the shape is ours, the contents are theirs

  • GL-02As the bookkeeper, I want to post a balanced journal, so that the books record what happened.

    Given lines totalling 1,000 debit and 1,000 credit, in an open period · when I post it · then it is accepted, each line lands against its account, the affected balances are re-derived, and an unbalanced journal is refused with the difference named

    Bookkeeper · screen: Journal · reference: implemented

  • GL-03As the accountant, I want to correct a mistake with a reversing entry rather than an edit, so that the trail survives an audit.

    Given a posted journal · when I reverse it · then a mirror journal is posted referencing the original, both stay readable, the balances return to where they were, and reversing twice does not double the correction

    Accountant / finance manager · screen: Journal · reference: implemented

  • GL-04As the auditor, I want to see every entry behind an account balance, so that I can trace a number to its source.

    Given an account with a balance · when I open it · then I see every line, its journal, its date, its source module and its reference, oldest to newest, with a running balance

    Auditor / owner · screen: Account · reference: not built — the package returns an account's BALANCE but has no route listing the lines behind it, and journal lines carry no reference column. This is the first thing to add.

  • GL-05As the accountant, I want a trial balance at a date, so that I can prove the books balance before I report anything.

    Given posted journals · when I run the trial balance · then total debits equal total credits exactly — no float tail — and each account shows its balance in the reporting currency

    Accountant / finance manager · screen: Reports · reference: partial — the package snapshots a trial balance per PERIOD; there is no as-of-date version

  • GL-06As the accountant, I want profit and loss and a balance sheet for a period, so that I can tell the owner how the business did.

    Given a closed or open period · when I run the reports · then income less expenses gives the result for the period, the balance sheet balances, and each line can be opened to the accounts behind it

    Accountant / finance manager · screen: Reports · reference: partial — the package sums the all-time ledger and LABELS the result with a period; it does not filter by period, and its statements carry no drill-down. What it reports is therefore "since the last close", which is only the period because the close rolls the P&L away every month (see known_gaps).

  • GL-07As the accountant, I want to close a month, so that nobody can change history after we have reported it.

    Given a period with everything posted · when I close it · then further operational postings into that period are refused with the period named, the closing entry itself is allowed, and reopening is a deliberate act that is recorded

    Accountant / finance manager · screen: Periods · reference: implemented

    Usually different per customer: who may close and reopen, and whether a soft close (warn) comes before the hard one

  • GL-08As the bookkeeper, I want to reconcile what the ledger says against the open invoices behind it, so that the receivables figure is not a guess.

    Given a receivables control account and the open items behind it · when I reconcile · then I see the ledger balance, the sum of open items, and any difference broken down as breaks I can work through

    Bookkeeper · screen: Reconcile · reference: implemented

  • GL-09As the accountant, I want balances in a second currency restated at the rate of the day, so that foreign balances are not carried at last year's rate.

    Given a foreign-currency balance and a rate for the date · when I revalue · then the difference posts as a gain or loss journal, the run is recorded, and running it twice for the same date changes nothing

    Accountant / finance manager · screen: Currencies · reference: implemented

  • GL-10As the accountant, I want to see a group of accounts rolled up, so that a report line means the same thing as the accounts under it.

    Given a parent account with children · when I open the rollup · then the parent shows the total of everything beneath it, however deep, and the tree can be walked from any node

    Accountant / finance manager · screen: Accounts · reference: partial — the rollup reads a graph that nothing in the package builds (adding an account writes parent_account_id but never an edge, and no CREATE GRAPH runs), and the walk is capped at five levels.

Things that happen on their own

  • GL-20after every postingThe system re-derives the balances of the accounts a journal touched, so that a balance is always the sum of its lines and never drifts.

    Given three postings landing on one account at the same time · when they commit · then the balance equals the signed sum of every posted line — computed with SUM, never incremented — so concurrent postings land once each

    · reference: implemented

  • GL-21on request, and after a closeThe system materialises the trial balance and the statements, so that a report of a closed period is fast and stable rather than recomputed differently each time.

    Given a closed period · when the statements are materialised · then the stored figures match a fresh computation, and re-materialising changes nothing

    · reference: partial — materialising works on request; closing a period does NOT trigger it

Things that cross into other modules

  • GL-30when another module records something with an accounting consequenceOther modules post their entries here rather than keeping their own books, so that the accounts are complete without anyone re-keying.

    Given invoicing issuing an invoice, or purchasing receiving a bill · when it posts · then a balanced journal exists with the source module and document named, its id derived from that document so re-posting changes nothing, and posting into a closed period is refused

    · owned by gl · reference: implemented (the posting route; the callers are the other modules' work)

  • GL-31when an invoice or bill is issued, and when it settlesModules that owe or are owed money keep their open items here, so that the control account can be reconciled against the detail.

    Given an issued invoice · when invoicing records the open item and later clears it · then the subledger shows it open then cleared, tied to the party, and GL-08 can reconcile the control account against it

    · owned by gl · reference: implemented

How things move

journal

posted → reversed

  • none → posted by bookkeeper or accountant or system · when balanced, and the period is open (or this is the closing entry) · lines written, balances re-derived, audit row appended
  • posted → reversed by accountant · a mirror journal referencing the original; neither is deleted

period

open → closed

  • open → closed by accountant · when the close journal has posted · operational postings into it are refused
  • closed → open by accountant · needs reason · recorded — reopening a reported period is a decision somebody owns

What it keeps

ThingMeaningIdentified byShared?
accounta bucket the business reports on — a bank account, a revenue line, a tax liabilityaccount_id (a number you choose; there are no sequences here)SHARED — this module is the sole writer
account edgethe account tree, as a graph you can walk to any depthparent_idchild_idmodule-owned
journalone balanced accounting entryjournal_id — a BIGINT the CALLER supplies. Derive it: int.from_bytes(blake2b(<document id>, digest_size=7)), so a module re-posting the same invoice writes the same journal. The package does not derive it for you.module-owned
journal lineone side of an entry against one accountjournal_idline_nomodule-owned
ledgerthe balance of each account — DERIVED, never typed and never incrementedaccount_idmodule-owned (derived)
perioda month, and whether it is still open to postingsperiod_id ("2026-09")SHARED — sole writer; every module's postings are gated by it
journal auditwhat happened to a journal and whenaudit_idmodule-owned (append only)
subledger itemone open item behind a control account — an unpaid invoice, an unpaid billitem_idmodule-owned; written by the module that owns the document, through this module's route
reconciliationa comparison of a control account against the open items behind it, and what did not matchrecon_idmodule-owned
fx ratethe rate used to restate a currency on a datefrom_ccyto_ccyrate_dateSHARED — sole writer; invoicing and purchasing read it
fx revala revaluation run and the gain or loss it postedreval_idmodule-owned
statementa materialised trial balance, profit and loss or balance sheet for a period[period, account_id] for the trial balance; [period, section] for the two statementsmodule-owned (derived)

The chart of accounts, the period lock and the FX rates are shared: every module that posts money reads them and none of them writes them. A module keeping its own copy of any of the three is how two reports of the same month stop agreeing.

What runs on its own

StoryWhenWhat
GL-20after every postingre-derive the touched accounts' balances
GL-21on request, and after a closematerialise the trial balance and statements

Questions that shape your version

Read these out and let the customer correct one — people rarely recall a policy on demand and can always react to one. Standard is a named authority you can look up and hold us to. Commonly seen is exactly that: arrangements we have met, with no survey behind them. A customer doing none of them is not doing it wrong.

  1. Do you keep your books here, or in Xero/QuickBooks with this feeding them?
  2. Who is allowed to close a month, and who can reopen one?
  3. What does your chart of accounts look like today?
    most smes
    their accountant's standard template, lightly edited — bring it across as-is
    warning
    a chart redesigned during implementation makes every prior-year comparison wrong
  4. Do you hold balances in more than one currency?
  5. Does your accountant want the trial balance and statements in a particular layout?
  6. What else should post automatically — invoices, bills, payroll, stock movements?
  7. Do you report by department, site or project as well as by account?
    single site services
    account only
    multi site or project work
    one dimension — site, or project — and rarely more than one
    warning
    adding a dimension after the first posting means restating history
  8. Is your month-end a real close, or just a cut-off you report from?

    Standard IAS 1 governs what a set of financial statements must contain; it does not require a monthly close. Monthly is an operating choice — the obligation is the reporting period itself.

    audited or investor reporting
    a real monthly close with a lock
    owner managed
    a cut-off they report from, closed properly only at year end
    note
    the package's default rolls the P&L away monthly, which is usually more than they want
  9. Which accounts do retained earnings, FX gains and losses post to?

    Standard IAS 21 — the effects of changes in foreign exchange rates: which differences go to profit or loss and which to other comprehensive income.

What you can change yourself

These are settings, not code: someone in your team changes them on a screen, without a developer and without a release.

Where the platform limits what we can promise

There is no enforced cross-table constraint, so the period lock is an application control. it holds only because one function is the sole writer of journals — route every posting through it, including the other modules' postings
No read-your-writes inside an open transaction. post the lines, commit, then re-derive balances in a second transaction with SUM()
A zero DECIMAL renders as JSON 0, not "0.00". assert that the trial balance nets to zero exactly, not that it prints a particular string

For builders

Known gaps in the reference

© 2026 DODILHomeDocsConsole