dodil/erp-brick-consolidation

DODIL ERP brick: consolidation

AGENTS.md

Instructions for agents working on this brick

Scaffolded from the consolidation module spec by erp-compose scaffold: the shape is done, the rules are not. Read erp-platform's BRICKS.md before changing anything — the rules (ownership, use cases, data, identity, tests) and why each one exists.

Your work list

grep -rn "TODO(" src tests      # every gap, by kind
pytest -q -rx                    # one xfail per story still to implement
KindOpen
TODO(ai)1
TODO(permission)21
TODO(rule)103
TODO(scenario)6
TODO(test)26
TODO(visibility)9
  • TODO(rule): a gate, an effect or a story's Then. Write it in domain/, raising RuleBroken in the words a person reads; call the check_<story> stub from the transition it guards (or fold it in).
  • TODO(test): rewrite the story's test from its Given/When/Then and remove the xfail mark.
  • TODO(visibility): who sees which rows — the spec's row_visibility rule, in _visible_<root>().
  • TODO(permission): a permission the spec's catalog does not name — add it to the spec, or pick one.
  • TODO(scenario): the scenario's steps over the doors (tests/scenarios/catalogue.py).

The stories

  • CON-01 (Group, check_con_01): the structure in force on a period's last day decides scope and method (full | equity | none); ownership and voting are NUMERIC(9,6) above 0 and [...]
  • CON-02 (Group, check_con_02): goodwill = consideration - 80% of net assets = 100,000.00 EUR (86,000.00 GBP at the acquisition rate), non-controlling interest = 20% of net [...]
  • CON-03 (Group, check_con_03): consolidation calls gl's GET /group-reporting/balances per company and stores the rows (year-to-date and period, by group account, partner [...]
  • CON-04 (Group, check_con_04): balance-sheet accounts at the closing rate, income and expense period by period at each month's average rate (summed year to date), equity at [...]
  • CON-05 (Group, check_con_05): per pair and elimination rule (receivable vs payable, loans), an automatic entry removes both sides; the difference within tolerance posts to [...]
  • CON-06 (Group, check_con_06): each rule pair (revenue vs cost of sales or expense, interest income vs expense) is removed per pair, the difference to ic_difference_pl; the [...]
  • CON-07 (Group, check_con_07): unrealised profit = 12,000.00 x 25% = 3,000.00 EUR, translated at closing: Dr cost of sales (UK01) / Cr inventory (DE01), and deferred tax at [...]
  • CON-08 (Group, check_con_08): the investment (G-1600 partner DE01) is eliminated against 80% of the equity at acquisition, goodwill and the NCI at acquisition are posted; the [...]
  • CON-09 (Group, check_con_09): FR01's lines are not added line by line; one entry raises the investment by 15,000.00 EUR (30% of the result) to the share of profit of [...]
  • CON-10 (Group, check_con_10): the entry is balanced in group currency, names company and partner per line and a movement type where the account tracks movements; it posts [...]
  • CON-11 (Group, check_con_11): each line is the sum of translated data and consolidation entries through the version's assignment; each opens to the companies, to the entries [...]
  • CON-12 (Group, check_con_12): the rows are validated (accounts exist in the group chart, amounts NUMERIC(18,2), the data nets to zero, partners are group companies) and [...]
  • CON-13 (Group, check_con_13): readiness lists each gate as {gate, met, reason, fix}: every company in scope collected and current, each company's and the translated data nets [...]
  • CON-14 (Group, check_con_14): the sign-off records who, when and the digest of the consolidated trial balance; the preparer cannot sign (email AND sub); signed figures are [...]
  • CON-16 (Group, check_con_16): DE01's profit and loss is consolidated up to 2027-06 and its balance sheet no longer from that period's end; the gain = proceeds - (the group's [...]
  • CON-17 (Group, check_con_17): without a change of control the difference between the price and the NCI given up goes to group equity (IFRS 10.23), not to profit, and the NCI [...]
  • CON-18 (Group, check_con_18): every check is equal or explained where explaining is allowed; carried consolidated balances per group account and period must be equal; signing [...]
  • CON-19 (Group, check_con_19): the accountant is refused (a control); the owner's row takes effect from tomorrow (business date in business_timezone), never today or earlier; [...]
  • CON-15 (Group, check_con_15): cumulative entries' balance-sheet lines open FY2027 (per group account, company, partner, segment); their P&L effect opens on group retained [...]
  • CON-20 (Group, check_con_20): DE01's new digest is stored, translation and eliminations become stale (the period moves back to collected), the next translate and eliminate [...]
  • CON-21 (Group, check_con_21): the stored figures equal a fresh computation at the sign-off digest, and the second run changes nothing (keyed report, group, period, node)
  • CON-30 (Group, check_con_30): the rows, the period status, the functional currency, the unmapped accounts and the digest come back; consolidation stores them as reported data [...]
  • CON-31 (Group, check_con_31): the matched items and breaks come back; an accepted break is eliminated to ic_difference; an unaccepted one above tolerance blocks the pair; no [...]
  • CON-32 (Group, check_con_32): the rates in force come back; a 404 names the currency, type and date and blocks translation with gl's fix
  • CON-33 (Group, check_con_33): the lists come back; a structure row naming a company gl does not know is refused; a report on a version that is not published is refused
  • CON-34 (Group, check_con_34): the structure per period, the account rules and the consolidation journals of closed periods written read-only, keyed on the Odoo record; a [...]

Where a change goes

You are changing…It goes in
a business ruledomain/ — and its test in tests/test_rules.py
who may do something, or which rows they seeapplication/ — the permission, or _visible_<root>()
a new action for people and agentsa new use case in application/use_cases.py — a route AND a tool
a column or tableinfrastructure/tables.py AND schema.sql (append; never drop or rename) and a new migrations/NNNNN_<name>/

Never add an HTTP route or an MCP tool by hand. Never import SQLAlchemy, FastAPI or MCP in domain/. Don't write to another brick's tables — call its seam. Don't write an upsert by hand — use the repository.

Before you commit

pytest -q --tb=short     # rules + both doors + schema + composition, no bucket
ruff check src tests

State: slices A and B done (CON-01..14)

Built: structure + acquisition (application/structure.py), collect/translate/eliminate incl. investments, NCI and the equity method (application/runs.py), uploads, readiness, sign-off and reopen (application/close.py), statements (application/reports.py), manual entries (use cases + domain/cons_journal.py), pure arithmetic (domain/consolidation.py), gl port (infrastructure/seams.py, fake in tests/fakes.py shaped only from gl's signed spec), bulk rows and number claims (infrastructure/data.py). Every group-level operation saves the Group aggregate (0.9.6 snapshot save), so a run, a manual entry, a sign-off, a reopen and an upload acceptance of one group collide on cons_group_versions; entry numbers are claimed on cons_number_series (plain INSERT, claim_tables). tests/test_statement_budget.py pins every request path's statements (flat from 2 to 10 companies, count_statements). Later slices: C (CON-15..21), D (CON-30..34).