From 5f35ff38d94ba02b8e4f118c771d8ed6d6624f1d Mon Sep 17 00:00:00 2001 From: delchev Date: Mon, 24 Aug 2026 11:06:17 +0300 Subject: [PATCH] docs(intent): kind: statement - balance sheets and income statements A statutory statement is a fixed line structure over the chart of accounts, which a balance report cannot express: its output is one row per dimension value. Document `kind: statement` - the `account:` field the lines select on, the leaf/computed split, the selector grammar (prefix, exact, range over equally long code prefixes) and the twelve measures. The section that earns its place is why the Net measures net per account before the line sums: a settlement account is an asset in debit and a liability in credit, and pairing closingNetDebit with closingNetCredit files each account on the side its actual balance puts it on. Refs eclipse-dirigible/dirigible#6909. --- docs/help/intent/dsl-reference.md | 91 +++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/docs/help/intent/dsl-reference.md b/docs/help/intent/dsl-reference.md index 1bc9184d8..d73c41051 100644 --- a/docs/help/intent/dsl-reference.md +++ b/docs/help/intent/dsl-reference.md @@ -1618,6 +1618,97 @@ reports: In `filter:`, reference relations via `relation.field` (translated to a JOIN); a bare relation name passes into the SQL untranslated. +### kind: statement - balance sheets and income statements + +A balance report answers "what is the balance of each account". A statutory statement asks something +one altitude up: **what are the lines of the form, and which accounts make up each one**. The line +structure is prescribed by the jurisdiction, the mapping from accounts to lines by the chart, and +neither can be derived from the ledger - so `kind: statement` declares them: + +```yaml +reports: + - name: BalanceSheet + kind: statement + source: JournalEntryItem # the ledger line items - as for kind: balance + date: journalEntry.entryDate # the date the runtime From/To pickers apply to + debit: debit + credit: credit + account: account.code # the account CODE the lines select on (a string field) + filter: "journalEntry.status == 2" # only posted entries count + lines: + - { code: A.I, label: Fixed assets, accounts: "20*,21*", measure: closingNetDebit } + - { code: A.II, label: Receivables, accounts: "41*", measure: closingNetDebit } + - { code: A, label: Total assets, sum: [A.I, A.II] } + - { code: B.I, label: Payables, accounts: "40-49", measure: closingNetCredit } + - { code: B, label: Net assets, sum: [A], less: [B.I] } +``` + +The report's rows are the declared `lines`, in the authored order, as three columns - **Code**, +**Label**, **Amount** - and it carries the same runtime From/To date parameters a balance report does, +with the same window meaning. + +A line is either a **leaf** or **computed**, never both. A leaf reads the ledger: `accounts` selects +the accounts, `measure` says which of their balances to take. A computed line is arithmetic over other +lines of the same statement, named by their `code` - `sum:` adds them, `less:` subtracts them, and a +line may carry both. Subtotals compose, so a total may reference other totals. + +#### accounts - the selector + +Comma-separated terms over the account code; an account matching any term contributes: + +| term | selects | +| ------- | ------------------------------------------------------------------------------ | +| `20*` | every account whose code starts with `20` | +| `4110` | exactly that account | +| `60-69` | every account starting inside the range - `601` and `6999` included | + +A range compares equally long code prefixes, which is why its bounds must be the same length: a plain +comparison of whole codes would drop `601`, since `601` sorts after `69`. An account code may hold +letters, digits, dot and underscore; the hyphen is the range separator and a trailing asterisk makes a +prefix. + +#### measure - which balance the line takes + +Twelve names, `opening` / `period` / `closing` crossed with `Debit`, `Credit`, `NetDebit` and +`NetCredit`: + +``` +openingDebit openingCredit openingNetDebit openingNetCredit +periodDebit periodCredit periodNetDebit periodNetCredit +closingDebit closingCredit closingNetDebit closingNetCredit +``` + +The plain four sum the raw side, which is a **turnover** - what an income statement's gross movement +lines want. The `Net` four **net an account's two sides before the line sums it** and keep only what is +left on the named side, which is what a balance-sheet line almost always wants: + +::: tip Why netting happens per account +A settlement account is an asset when it is in debit and a liability when it is in credit, and which +one it is this period is a fact about the data, not about the model. Put `closingNetDebit` on the +asset line and `closingNetCredit` on the liability line and each account files itself on the right +side - a debit balance contributes to the first and exactly nothing to the second. Netting after the +sum instead of per account would report the section's gross turnover and silently place every +both-type account on whichever side the section happened to be. +::: + +#### Rules + +- `dimensions` and `measures` must be empty - the lines *are* the rows. +- `date` must be a `date` field (a `timestamp` is rejected, as for a balance report); `debit` and + `credit` numeric fields of the source; `account` a `string` field, its own or one hop away. +- Line codes are unique; every `sum`/`less` code names a declared line; the references must not form + a cycle. +- A line whose accounts hold nothing renders as **zero**, not as a missing row - the structure is the + statement. +- `filter:` and `scope:` restrict which ledger rows count, exactly as for a balance report. + +::: info The print layout stays yours +A statement report computes the statement's **numbers**. The legally mandated form - its typography, +its column blocks, its comparative prior-year column on paper - stays a hand-authored +[print template](/help/intent/printing) over that result. That boundary is +deliberate: the model expresses the mapping, a document designer expresses the form. +::: + ### scope - which lifecycle rows an aggregate counts An aggregation over an entity that carries a lifecycle (a `function: EntityStatus` relation) is