dodil/erp-brick-consolidation

DODIL ERP brick: consolidation

tests/scenarios/catalogue.py

"""cons's scenario catalogue (module.yaml tests.scenarios), each mapped to its stories and written ONCE over
the composition's doors — routes, MCP tools, public links — never a table. A skeleton: each scenario says its
Given/When/Then and is skipped until its steps are written.

    erp-scenarios list tests/scenarios/catalogue.py
    erp-scenarios run  tests/scenarios/catalogue.py
"""
import pathlib

from erp_platform.scenarios import Catalogue, Skip

HERE = pathlib.Path(__file__).resolve().parent
CAT = Catalogue("consolidation", compose=HERE / "compose.yaml", roles=HERE / "roles.yaml")

# Generated over the composition's doors, both HTTP and MCP (erp_platform.scenarios.probes): every role ×
# every use case — 401 with no identity, 403 without the permission, served with it. The finer probes (a role
# the workflow does not name, state guards, a terminal parent, separation of duties, seams, idempotency,
# mass assignment) are tests/test_probes.py. TODO(scenario): `rows=Rows(make, owner, other)` for row visibility.
CAT.permission_matrix()


@CAT.scenario("SC-CON-01", "first consolidation of an 80% subsidiary", stories=["CON-01", "CON-02", "CON-03", "CON-04", "CON-08"])
def sc_sc_con_01(s):
    s.given("UK01 (GBP) acquires 80% of DE01 (EUR) on 2026-04-01 for 900,000.00 EUR, net assets 1,000,000.00 EUR")
    s.when("the structure and acquisition are approved, 2026-04 to 2026-10 are collected, translated and eliminated")
    s.then("goodwill 100,000.00 EUR at 0.860000; NCI 20% of net assets plus 20% of post-acquisition result and translation reserve; the investment nets to zero; the consolidated balance sheet balances")
    raise Skip("TODO(scenario): write the steps over the doors (routes, tools, links)")


@CAT.scenario("SC-CON-02", "intercompany out of balance, then fixed", stories=["CON-05", "CON-31", "CON-20"])
def sc_sc_con_02(s):
    s.given("a UK01/DE01 pair 50.00 apart (tolerance 5.00)")
    s.when("eliminations run, are refused, the difference is posted in gl, the data re-collected and eliminations run again twice")
    s.then("the first run refused ic_within_tolerance; after the fix one set of elimination entries (run 2) and run 1's reversed")
    raise Skip("TODO(scenario): write the steps over the doors (routes, tools, links)")


@CAT.scenario("SC-CON-03", "stock profit, deferred tax, next year's realisation", stories=["CON-07", "CON-15"])
def sc_sc_con_03(s):
    s.given("DE01 holds 12,000.00 EUR of UK01 goods at 25% margin, tax 30%, at 2026-12-31")
    s.when("2026-12 is eliminated and signed, FY2027 opens, the goods are sold and 2027-01 eliminated with no stock left")
    s.then("3,000.00 EUR eliminated with 900.00 deferred tax in 2026; in 2027 the opening is realised into cost of sales and the group result rises by 2,100.00 net")
    raise Skip("TODO(scenario): write the steps over the doors (routes, tools, links)")


@CAT.scenario("SC-CON-04", "sign-off under a second login and a late change", stories=["CON-13", "CON-14", "CON-20"])
def sc_sc_con_04(s):
    s.given("GRP 2026-10 eliminated by the group accountant")
    s.when("the same person (same sub, other email) tries to sign, another controller signs, then DE01 reopens in gl and posts")
    s.then("the first sign refused second_person; after signing the figures stay; the new digest shows a difference until the period is reopened by a controller other than the signer")
    raise Skip("TODO(scenario): write the steps over the doors (routes, tools, links)")


@CAT.scenario("SC-CON-05", "divestiture recycles the translation reserve", stories=["CON-16"])
def sc_sc_con_05(s):
    s.given("DE01 consolidated with a translation reserve of 12,000.00 GBP")
    s.when("it is sold on 2027-06-30")
    s.then("the reserve is recycled to profit within the disposal gain; DE01's balance sheet leaves the group from 2027-06; its P&L to June stays")
    raise Skip("TODO(scenario): write the steps over the doors (routes, tools, links)")