dodil/erp-brick-consolidation

DODIL ERP brick: consolidation

tests/test_rules.py

"""The business rules, with no database — one test per story (module.yaml stories), each an xfail until
its rule is written: `pytest -q` shows exactly which stories remain. Implement the rule, rewrite the test
from its Given/When/Then, and remove the mark (strict: a rule that starts passing must lose its mark)."""
import pytest

from brick_consolidation.domain.group import Group


def a_group() -> Group:
    return Group(group_id="GROUP-ID-1")


# CON-15..21: the acceptance tests run through both doors in tests/test_slice_c1.py and tests/test_slice_c2.py; the pure arithmetic they rest on
# is held here without a database.
def test_con_16_the_gain_is_proceeds_less_the_groups_share_of_net_assets_and_goodwill_and_the_reserve_is_recycled_within_it():
    from decimal import Decimal as D

    from brick_consolidation.domain import consolidation as C
    rows = [{"group_account_id": "A", "ytd_group": D("1000.00")}, {"group_account_id": "E", "ytd_group": D("-900.00")},
            {"group_account_id": "R", "ytd_group": D("-120.00")}]
    figs = C.disposal_figures(rows=rows, pl={"R"}, equity={"E"}, proceeds_group=D("1200.00"), share=D("80"), goodwill_group=D("50.00"))
    assert figs["net_assets_group"] == D("1000.00") and figs["gain"] == D("350.00")                  # 1,200 - (800 + 50)
    # the rows leave 20.00 on the credit side: the parent's 80% of that difference (16.00) is a debit reserve, recycled as a loss within the gain
    assert figs["cta_line"] == D("16.00") and figs["cta_recycled"] == D("-16.00") and figs["total_gain"] == D("334.00")
    assert figs["nci_derecognised"] == D("200.00")


def test_con_17_the_difference_between_the_price_and_the_minority_given_up_is_equity_not_profit():
    from decimal import Decimal as D

    from brick_consolidation.domain import consolidation as C
    figs = C.ownership_change_figures(price_group=D("104400.00"), net_assets_group=D("913500.00"), delta=D("10"))
    assert (figs["nci_given_group"], figs["equity_group"]) == (D("91350.00"), D("13050.00"))
    lines = C.ownership_change_lines(figs, company="DE01", investor="UK01", accounts={"investment": "I", "nci_equity": "N", "retained_earnings": "R"})
    assert sum(ln["amount"] for ln in lines) == 0


def test_con_15_a_financial_year_opens_on_the_month_after_its_end():
    from brick_consolidation.application import carry
    assert carry.first_period_of(Group(group_id="G", fiscal_year_end_month="12"), "2027-03") == "2027-01"
    assert carry.first_period_of(Group(group_id="G", fiscal_year_end_month="3"), "2026-10") == "2026-04"
    assert carry.closing_period_before(Group(group_id="G", fiscal_year_end_month="3"), "2026-10") == "2026-03"


# CON-30..34 (what consolidation reads from gl, and the Odoo loader): acceptance tests through both doors in tests/test_slice_d.py.


def test_con_18_a_balance_that_must_equal_odoos_is_never_signed_even_with_an_explanation_written_in():
    import json

    from brick_consolidation.domain.cons_migration_run import ConsMigrationRun
    from brick_consolidation.domain.errors import RuleBroken
    run = ConsMigrationRun(cons_migration_run_id="odoo:X:1", status="reconciled")
    run.checks_json = json.dumps([{"check": "consolidated_balance", "odoo": "1", "ours": "2", "status": "differs", "must": "equal",
                                   "explanation": {"reason": "r", "by": "x", "at": ""}}])
    with pytest.raises(RuleBroken, match="consolidated_balance"):
        run.sign_cons_reconciliation(by="owner@x", sub="s")