dodil/erp-brick-consolidation
DODIL ERP brick: consolidation
src/brick_consolidation/application/process.yaml
# The consolidation process as the customer signed it (module.yaml workflows, copied; tests/test_process.py compares the two).
id: consolidation
name: Group consolidation
version: 3
actors:
- id: group_accountant
name: Group accountant
does: keeps the group structure and acquisitions, collects and uploads the companies' data, translates, enters intercompany inventory, runs the eliminations, proposes manual consolidation entries, prepares the group close
sees: the whole group
permissions:
- cons:group:read
- cons:structure:write
- cons:data:collect
- cons:data:upload
- cons:translate:run
- cons:eliminate:run
- cons:inventory:write
- cons:journal:write
- cons:journal:reverse
- cons:period:open
- cons:report:read
- cons:settings:write
- cons:migration:read
- id: group_controller
name: Group finance controller
does: approves structure changes, uploads and manual consolidation entries, signs off the group close and reopens it
sees: the whole group
permissions:
- cons:group:read
- cons:structure:approve
- cons:data:accept
- cons:journal:approve
- cons:period:signoff
- cons:period:reopen
- cons:report:read
- cons:migration:read
note: 'the controller never approves what they proposed or prepared: matched on email AND sub'
- id: auditor
name: Group auditor
does: reads the structure, the collected data, every consolidation entry and the trail to gl
sees: everything, changes nothing
permissions:
- cons:group:read
- cons:report:read
- cons:migration:read
- id: owner
name: The owner (or whoever the owner names)
does: holds the controls on the people who consolidate (second person for entries and sign-off, whether data may be collected from an open company period, the AI step mode) and signs the migration reconciliation
sees: everything
permissions:
- cons:group:read
- cons:report:read
- cons:settings:controls
- cons:migration:read
- cons:migration:sign
note: a group accountant or controller holding cons:settings:controls is a configuration error the access test refuses
- id: system
name: The system
does: collects the companies' data from gl, posts the automatic consolidation entries, re-runs what a changed digest made stale, derives the group carry-forward, materialises signed statements, numbers consolidation journals, loads and reconciles a migration
sees: the whole group, through its own service account
permissions:
- cons:group:read
- cons:data:load
- cons:journal:post_auto
- cons:carry_forward:derive
- cons:statement:materialise
- cons:number_series:claim
- cons:migration:load
- cons:migration:reconcile
note: each is an exact grant to consolidation's service client, never reached by a wildcard; the same client holds gl:group_data:extract, gl:company:read, gl:fx:read and gl:period:read on gl
workflows:
- entity: group_period
states:
- open
- collected
- translated
- eliminated
- signed
transitions:
- key: open_group_period
name: Open the group period
from: none
to: open
by: group_accountant
gate: the structure is approved for the period; the previous period is signed (or this is the first)
effect: scope and methods fixed from the structure in force on the period's last day; for a year's first period the group carry-forward derived (CON-15)
- key: upload_reported_data
name: Upload a company's figures
from: open
to: open
by: group_accountant
gate: the company is in scope with a structure row of source upload; the file validates
effect: rows stored with source upload, waiting for acceptance (CON-12)
- key: accept_reported_upload
name: Accept the upload
from: open
to: open
by: group_controller
approval: true
gate: the accepter is not the uploader (email AND sub)
effect: the rows become the company's reported data
- key: collect_group_data
name: Collect the companies' figures
from: open
to: collected
by:
- group_accountant
- system
gate: every company in scope collected from gl or by an accepted upload; each nets to zero; no unmapped accounts; the gl period closed (unless allow_open_period_collect)
effect: reported data with gl's digest per company (CON-03)
- key: recollect_group_data
name: Collect again
from:
- collected
- translated
- eliminated
to: collected
by:
- group_accountant
- system
gate: a company's digest changed, an upload was replaced, or the structure changed
effect: later steps stale; the next runs take run_no + 1 (CON-20)
- key: translate_currencies
name: Translate into the group currency
from: collected
to: translated
by: group_accountant
gate: a closing rate on the period end and an average rate for every month of the year so far, for every foreign company's currency
effect: translated data and one translation-reserve entry per company, with the NCI share (CON-04)
- key: run_eliminations
name: Run the eliminations
from: translated
to: eliminated
by: group_accountant
gate: intercompany within tolerance or accepted per pair; intercompany inventory entered or stated none per selling pair; an approved acquisition for every full and equity company
effect: automatic entries for intercompany balances, revenue and expense, dividends, stock profit, investments, NCI and the equity method, keyed per task, pair and run (CON-05 to CON-09)
- key: sign_off_group_close
name: Sign off the group close
from: eliminated
to: signed
by: group_controller
approval: true
gate: readiness met (CON-13); the signer is not the preparer (email AND sub)
effect: who, when and the digest recorded; statements materialised (CON-21)
- key: reopen_group_period
name: Reopen the group period
from: signed
to: collected
by: group_controller
requires: reason
approval: true
gate: the reopener is not who signed it; the next period is not signed
effect: recorded; the figures provisional; the next runs take run_no + 1
readiness:
- transition: open_group_period
gate: structure_approved
reason: a structure change effective in the period waits for approval
fix: 'Structure: ask the controller to approve it'
- transition: open_group_period
gate: prior_signed
reason: the previous group period is not signed
fix: sign it first
- transition: collect_group_data
gate: company_periods_closed
reason: a company's period is open in gl, named
fix: close it in gl, or the owner allows collecting from open periods
- transition: collect_group_data
gate: group_mapped
reason: accounts with balances have no group account in gl, named
fix: 'gl Accounts: map them (GL-41)'
- transition: collect_group_data
gate: data_nets_to_zero
reason: a company's data does not net to zero, named with the difference
fix: 'a gl fault or a wrong upload: fix and collect again'
- transition: collect_group_data
gate: all_companies_present
reason: companies in scope have no data, named
fix: collect from gl or upload and accept
- transition: translate_currencies
gate: rates_present
reason: no closing or average rate for a currency and month, named
fix: 'gl Currencies: enter the rate (GL-33)'
- transition: run_eliminations
gate: ic_within_tolerance
reason: a pair's difference is above tolerance and not accepted in gl, named
fix: fix it in gl or accept it there (GL-52)
- transition: run_eliminations
gate: ic_inventory_entered
reason: a selling pair has intercompany sales and no inventory entry
fix: 'Eliminations: enter it, or state none'
- transition: run_eliminations
gate: acquisitions_recorded
reason: a full or equity company has no approved acquisition
fix: 'Structure: record it (CON-02)'
- transition: run_eliminations
gate: tax_rate_known
reason: an intercompany inventory entry of {company} has no tax rate, and gl has no statutory_tax_rate for {company} on {period_end}
fix: enter the rate on the entry, or set statutory_tax_rate in gl (GL settings)
- transition: sign_off_group_close
gate: no_stale_step
reason: a step is stale after a new collection
fix: translate and eliminate again
- transition: sign_off_group_close
gate: no_proposed_entry
reason: manual consolidation entries wait for approval
fix: approve or reject them (CON-10)
- transition: sign_off_group_close
gate: investment_eliminated
reason: an investment in a subsidiary is not fully eliminated, named
fix: run the eliminations; check the acquisition
- transition: sign_off_group_close
gate: trial_balance_zero
reason: the consolidated trial balance does not net to zero
fix: 'a fault: stop and report it'
- transition: sign_off_group_close
gate: carry_forward_current
reason: the group carry-forward is older than a change to the previous year
fix: the system re-derives it (CON-15)
- transition: sign_off_group_close
gate: second_person
reason: you prepared this period (ran a step or posted an entry)
fix: ask another controller
- transition: reopen_group_period
gate: second_person
reason: you signed it
fix: ask another controller
- entity: cons_journal
states:
- proposed
- posted
- rejected
- reversed
terminal:
- rejected
- reversed
transitions:
- key: post_cons_auto_entry
name: Post an automatic consolidation entry
from: none
to: posted
by: system
gate: balanced in group currency; the period not signed; keyed derived_id('cons', group, period, task, company, partner, run_no)
effect: numbered in the group's book and year; a replay answers the entry posted; the previous run's entry for the key reversed
- key: propose_cons_entry
name: Propose a manual consolidation entry
from: none
to: proposed
by: group_accountant
gate: balanced in group currency; every line names a company in scope; the period not signed
effect: proposer recorded (email and sub); document type and period-specific flag recorded
- key: approve_cons_entry
name: Approve the entry
from: proposed
to: posted
by: group_controller
approval: true
gate: the approver is not the proposer (email AND sub) where signoff_second_person is on; the period not signed
effect: numbered; a period-specific entry's reversal due at the next period's start
- key: reject_cons_entry
name: Reject the entry
from: proposed
to: rejected
by: group_controller
requires: reason
- key: reverse_cons_entry
name: Reverse the entry
from: posted
to: reversed
by:
- group_accountant
- system
requires: reason
gate: the period not signed
effect: a mirror entry keyed derived_id(cons_journal_id, 'reversal'); neither deleted
readiness:
- transition: approve_cons_entry
gate: second_person
reason: you proposed it
fix: ask another controller
- transition: propose_cons_entry
gate: period_not_signed
reason: the group period is signed
fix: post it in the next period, or reopen
- entity: structure_change
states:
- proposed
- effective
- rejected
terminal:
- effective
- rejected
transitions:
- key: propose_structure_change
name: Propose a structure change
from: none
to: proposed
by: group_accountant
gate: 'kind acquisition | ownership_change | divestiture | method_change; v3, by the row''s source: source gl (the default) = gl knows the company (GET /companies/{company_code} answers 200) and its functional currency is gl''s; source upload (CON-12) = gl does NOT know it (404), company_code matches ^[A-Z0-9]{1,4}$ and is unique in the group, and the row names local_currency (ISO 4217) and a name; ownership within (0, 100]; the effective period not signed; every fair-value adjustment has a tax rate (tax_rate_known)'
effect: goodwill, NCI, gain or equity effect computed from the collected data and shown (CON-02, CON-16, CON-17)
- key: approve_structure_change
name: Approve the structure change
from: proposed
to: effective
by: group_controller
approval: true
gate: the approver is not the proposer (email AND sub); the effective period still not signed
effect: the dated structure row written; historical amounts fixed; later periods' steps stale
- key: reject_structure_change
name: Reject the structure change
from: proposed
to: rejected
by: group_controller
requires: reason
readiness:
- transition: approve_structure_change
gate: second_person
reason: you proposed it
fix: ask another controller
- transition: propose_structure_change
gate: period_not_signed
reason: the effective period is signed
fix: make it effective from the next unsigned period
- transition: propose_structure_change
gate: equity_collected
reason: the company's equity at the effective date is not collected
fix: collect the period before the effective one
- transition: propose_structure_change
gate: source_matches_gl
reason: source gl names a company gl does not know, or source upload names one gl knows ({company_code})
fix: source upload for a company kept outside gl (CON-12), source gl for one of gl's companies
- transition: propose_structure_change
gate: tax_rate_known
reason: a fair-value adjustment has no tax rate, and gl has no statutory_tax_rate for {company} on {effective_date}
fix: enter the rate on the adjustment, or set it in gl
- entity: cons_migration_run
states:
- loaded
- reconciled
- signed
- discarded
terminal:
- signed
- discarded
transitions:
- key: load_cons_from_odoo
name: Load Odoo's consolidation
from: none
to: loaded
by: system
gate: 'the doors decided; gl''s migration signed (v3: GET /migration/status?db=<the run''s Odoo database> answers status signed; none, loaded or reconciled refuse with gate gl_signed naming the status; nothing after gl''s cutover_date is loaded)'
effect: group, structure per period, account rules and closed periods' consolidation journals written read-only, keyed on the Odoo record
- key: reconcile_cons_migration
name: Reconcile against Odoo
from: loaded
to: reconciled
by: system
effect: every migration.odoo.reconcile check computed on both sides
- key: explain_cons_difference
name: Explain each difference
from: reconciled
to: reconciled
by: group_accountant
requires: reason
gate: the check differs and may be explained
- key: reload_cons_migration
name: Load again after a fix
from:
- loaded
- reconciled
to: loaded
by: system
gate: not signed
- key: sign_cons_reconciliation
name: Sign the reconciliation
from: reconciled
to: signed
by: owner
approval: true
gate: every check equal, or explained where allowed
effect: who, when and checksums recorded
- key: discard_cons_migration
name: Discard the run
from:
- loaded
- reconciled
to: discarded
by: owner
requires: reason
readiness:
- transition: load_cons_from_odoo
gate: gl_signed
reason: gl's migration of {db} is {status} (none | loaded | reconciled), not signed
fix: sign gl's reconciliation of {db} first (gl GL-34)
- transition: sign_cons_reconciliation
gate: zero_unexplained
reason: 'checks neither equal nor explained: named'
fix: explain each, or fix and reload
- transition: sign_cons_reconciliation
gate: must_equal
reason: the carried consolidated balance per group account and period differs
fix: fix the mapping and reload