dodil/erp-brick-consolidation
DODIL ERP brick: consolidation
tests/test_slice_c1.py
"""Slice C1: the fair-value step-up of an acquisition (CON-02), CON-16 divestiture, CON-17 ownership changes and step acquisitions."""
from decimal import Decimal
from conftest import api
from test_slice_b1 import BASE, add_rules, eliminate, entries, lines, tb
from world import ACC, CTRL, P, acquire, db, group, ok, sign_period, state, world
D = Decimal
CHANGES = "/structure-changes"
def book(key):
return tb().get(key, D(0))
def nets_to_zero():
assert sum(tb().values(), D(0)) == 0 and all(sum(e_l["amount"] for e_l in e["lines"]) == 0 for e in entries() if e["document_type"] != "translation")
# ═══ CON-02 (slice B gap): the fair-value step-up enters the consolidated books ══════════════════════════════════════
LAND = {"description": "land", "amount": "100000.00", "tax_rate_pct": "30", "group_account_id": "G-1500"}
def fv_world(gl):
"""exact_world's group, but UK01 bought DE01 with a 100,000.00 EUR land uplift carrying 30% deferred tax."""
group(gl)
add_rules(("G-1500", "closing"), ("G-2500", "closing"), roles=[("deferred_tax_liability", "G-2500")])
acquire("DE01", fair_value_adjustments=[LAND])
ok(api("POST", "/groups/GRP/group-periods", ACC, {"period_id": P}))
gl.set_rows("DE01", P, [("G-1100", "1060000.00", "0.00"), ("G-3000", "-500000.00", "0.00"), ("G-3100", "-500000.00", "0.00"),
("G-4000", "-60000.00", "-10000.00")])
gl.set_rows("UK01", P, [("G-1600", "774000.00", "0.00", "DE01"), ("G-3000", "-774000.00", "0.00")])
for step in ("collect-group-data", "translate-currencies"):
ok(api("POST", f"{BASE}/{step}", ACC, {}))
def test_con_02_the_fair_value_step_up_and_its_deferred_tax_enter_the_books_and_goodwill_is_computed_after_them(gl):
fv_world(gl)
change = ok(api("GET", f"{CHANGES}/GRP:DE01:2026-04:acquisition", ACC))
assert (D(change["net_assets"]), D(change["goodwill"]), D(change["nci"])) == (D("1070000.00"), D("44000.00"), D("214000.00"))
assert (D(change["goodwill_group"]), D(change["nci_group"])) == (D("37840.00"), D("184040.00"))
eliminate()
# IAS 21.47: land, its deferred tax and the goodwill are held in EUR and shown at the closing 0.87 (acquired at 0.86):
# land 100,000 EUR x 0.87 = 87,000.00 up; deferred tax 30% = 26,100.00 down; goodwill 44,000 EUR x 0.87 = 38,280.00
assert book(("DE01", "G-1500", "", "", "")) == D("87000.00") and book(("DE01", "G-2500", "", "", "")) == D("-26100.00")
assert book(("DE01", "G-1700", "", "", "")) == D("38280.00") and book(("UK01", "G-1600", "DE01", "", "")) == 0 # exactly zero: the gate
# the minority: 20% of the adjusted equity at acquisition (184,040.00), of the result to date (60,000 EUR at 0.86 = 51,600.00: 10,320.00) and
# of the translation difference (10,600.00: 2,120.00)
assert book(("DE01", "G-3950", "", "", "")) == D("-196620.00") and book(("DE01", "G-3960", "", "", "")) == D("10320.00") # 140.00 more: 20% of the step-up's 700.00 difference
[inv] = entries("investment")
# the translation reserve takes the group's part of what the goodwill (440.00) and the step-up (700.00 less the minority's 140.00) gained
assert [ln["amount"] for ln in inv["lines"] if ln["group_account_id"] == "G-3900"] == [D("-1000.00")]
nets_to_zero()
def test_con_02_without_an_account_for_the_uplift_or_its_tax_the_run_names_what_to_set(gl):
group(gl)
acquire("DE01", fair_value_adjustments=[{"description": "land", "amount": "100000.00", "tax_rate_pct": "30"}])
ok(api("POST", "/groups/GRP/group-periods", ACC, {"period_id": P}))
gl.set_rows("UK01", P, [("G-1600", "774000.00", "0.00", "DE01"), ("G-3000", "-774000.00", "0.00")])
gl.set_rows("DE01", P, [("G-1100", "1000000.00", "0.00"), ("G-3000", "-500000.00", "0.00"), ("G-3100", "-500000.00", "0.00")])
for step in ("collect-group-data", "translate-currencies"):
ok(api("POST", f"{BASE}/{step}", ACC, {}))
status, body = api("POST", f"{BASE}/run-eliminations", ACC, {})
assert status == 409 and "fair_value_step_up" in str(body) and [e for e in db(lambda r: r.data.entries("GRP", P, 1)) if e["document_type"] != "translation"] == []
# ═══ CON-16 divestiture ════════════════════════════════════════════════════════════════════════════════════════════
def sale_world(gl, *, books_sale=True):
"""exact_world with UK01's October books after selling DE01 on 2026-10-31 for 1,200,000.00 EUR (1,044,000.00 GBP at the closing rate 0.87): cash in, the
investment out, its own gain (270,000.00 over the 774,000.00 cost) on gain_on_disposal against DE01."""
group(gl)
add_rules(("G-4900", "average"), roles=[("gain_on_disposal", "G-4900"), ("group_retained_earnings", "G-3200")])
acquire("DE01")
ok(api("POST", "/groups/GRP/group-periods", ACC, {"period_id": P}))
gl.rates[("EUR", "closing", "2026-10-31")] = D("0.870000")
gl.set_rows("DE01", P, [("G-1100", "1060000.00", "0.00"), ("G-3000", "-500000.00", "0.00"), ("G-3100", "-500000.00", "0.00"),
("G-4000", "-60000.00", "-10000.00")])
uk = [("G-1100", "1044000.00", "0.00"), ("G-3000", "-774000.00", "0.00"), ("G-4900", "-270000.00", "-270000.00", "DE01")] if books_sale else \
[("G-1600", "774000.00", "0.00", "DE01"), ("G-3000", "-774000.00", "0.00")]
gl.set_rows("UK01", P, uk)
for step in ("collect-group-data", "translate-currencies"):
ok(api("POST", f"{BASE}/{step}", ACC, {}))
def sell(**extra):
body = {"group_id": "GRP", "kind": "divestiture", "company_code": "DE01", "investor_company_code": "UK01", "ownership_pct": "80",
"effective_date": "2026-10-31", "proceeds": "1200000.00", **extra}
return api("POST", CHANGES, ACC, body)
def test_con_16_the_gain_the_recycled_reserve_and_the_minority_are_shown_to_the_penny_then_the_controller_approves(gl):
sale_world(gl)
status, shown = sell()
assert status == 200, shown
figs = __import__("json").loads(shown["figures_json"])
# v3: every term at gl's CLOSING rate on the disposal date (0.87): net assets 1,060,000 EUR = 922,200.00; the group's 80% = 737,760.00; the goodwill's
# closing carrying amount 100,000 EUR x 0.87 = 87,000.00 (never the acquisition rate 0.86); proceeds 1,200,000 EUR = 1,044,000.00
assert (figs["net_assets_group"], figs["share_of_net_assets"], figs["goodwill_group"], figs["proceeds_group"]) == \
("922200.00", "737760.00", "87000.00", "1044000.00")
assert figs["gain"] == "219240.00" # proceeds - (share of net assets + goodwill)
# the reserve recycled: the company's 8,480.00 and the 1,000.00 goodwill's retranslation (87,000.00 - 86,000.00) put there since acquisition
assert (figs["cta_recycled"], figs["total_gain"], figs["nci_derecognised"]) == ("9480.00", "228720.00", "184440.00")
assert D(shown["gain_loss"]) == D("228720.00") and shown["status"] == "proposed"
approved = ok(api("POST", f"{CHANGES}/{shown['change_id']}/approve-structure-change", CTRL, {}))
assert approved["status"] == "effective"
g, _ = state()
row = next(r for r in g.group_structures if r.company_code == "DE01" and r.method == "none")
assert row.valid_from_period == "2026-11" # the structure row is method none from the next period
eliminate()
[entry] = entries("disposal")
got = lines(entry)
assert got[("G-4900", "UK01", "DE01")] == D("-219240.00") + D("-8480.00") + D("-1000.00") + D("270000.00") - D("270000.00") + D("270000.00")
assert book(("UK01", "G-4900", "DE01", "", "")) == D("-228720.00") # the group's gain replaces UK01's own: gain + recycled reserve
assert book(("DE01", "G-1100", "", "", "")) == 0 and book(("DE01", "G-3000", "", "", "")) == 0 # its balance sheet no longer from the period's end
assert book(("DE01", "G-4000", "", "", "")) == D("-51600.00") # its profit and loss is consolidated up to the sale
assert book(("DE01", "G-3900", "", "", "")) == 0 and book(("DE01", "G-3950", "", "", "")) == 0 # the reserve is recycled, the minority derecognised
assert book(("DE01", "G-3960", "", "", "")) == D("10320.00") # the minority's share of the profit to the sale
assert not [ln for ln in entry["lines"] if ln["group_account_id"] == "G-3200"] # the entry balanced by itself: no plug on retained earnings
nets_to_zero()
assert not entries("investment") and not entries("nci") # the sold company is not eliminated as a subsidiary
def test_con_16_a_sale_dated_inside_a_signed_period_or_not_on_a_period_end_is_refused(gl):
sale_world(gl)
status, body = sell(effective_date="2026-10-15")
assert status == 409 and "last day" in str(body)
sign_period("2026-10")
status, body = sell()
assert status == 409 and "signed" in str(body)
def test_con_16_only_the_whole_holding_of_a_subsidiary_and_with_proceeds(gl):
sale_world(gl)
assert sell(ownership_pct="50")[0] == 409
assert sell(proceeds="")[0] == 422 and sell(proceeds="12345678901234567.00")[0] == 422
def test_con_16_the_later_months_of_the_year_keep_what_the_company_earned_until_the_sale(gl):
sale_world(gl)
ok(api("POST", CHANGES, ACC, {"group_id": "GRP", "kind": "divestiture", "company_code": "DE01", "investor_company_code": "UK01",
"ownership_pct": "80", "effective_date": "2026-10-31", "proceeds": "1200000.00"}))
ok(api("POST", f"{CHANGES}/GRP:DE01:2026-10:divestiture/approve-structure-change", CTRL, {}))
eliminate()
ok(api("POST", f"{BASE}/sign-off-group-close", CTRL, {}))
ok(api("POST", "/groups/GRP/group-periods", ACC, {"period_id": "2026-11"}))
gl.set_rows("UK01", "2026-11", [("G-1100", "1044000.00", "0.00"), ("G-3000", "-774000.00", "0.00"), ("G-4900", "-270000.00", "0.00", "DE01")])
nov = "/groups/GRP/group-periods/2026-11"
for step in ("collect-group-data", "translate-currencies", "run-eliminations"):
ok(api("POST", f"{nov}/{step}", ACC, {}))
g, _ = state()
gp = next(x for x in g.group_periods if x.period_id == "2026-11")
from brick_consolidation.application import reports
books = db(lambda r: reports.consolidated(r, g, gp)[0])
assert books[("DE01", "G-4000", "", "", "")] == D("-51600.00") and books[("UK01", "G-4900", "DE01", "", "")] == D("-228720.00")
assert not [k for k in books if k[0] == "DE01" and k[1] in ("G-1100", "G-3000", "G-3900")] and sum(books.values(), D(0)) == 0
# ═══ CON-17 ownership changes ═════════════════════════════════════════════════════════════════════════════════════
def buy_more(**extra):
body = {"group_id": "GRP", "kind": "ownership_change", "company_code": "DE01", "investor_company_code": "UK01", "ownership_pct": "90",
"effective_date": "2026-10-01", "consideration": "120000.00", **extra}
return api("POST", CHANGES, ACC, body)
def stake_world(gl):
"""UK01 holds 80% of DE01 and buys another 10% on 2026-10-01 for 120,000.00 EUR (104,400.00 GBP at 0.87). DE01's net assets at the end of
September: capital and reserves 1,000,000.00 + the result to date 50,000.00 = 1,050,000.00 EUR."""
world(gl, opened=False)
gl.rates[("EUR", "spot", "2026-10-01")] = D("0.870000")
gl.set_rows("DE01", "2026-09", [("G-1100", "1050000.00", "0.00"), ("G-3000", "-500000.00", "0.00"), ("G-3100", "-500000.00", "0.00"),
("G-4000", "-50000.00", "-10000.00")])
add_rules(roles=[("group_retained_earnings", "G-3200")])
def test_con_17_without_a_change_of_control_the_difference_goes_to_equity_and_the_minority_is_10_percent(gl):
stake_world(gl)
status, shown = buy_more()
assert status == 200, shown
figs = __import__("json").loads(shown["figures_json"])
# net assets 1,050,000 EUR x 0.87 = 913,500.00; the 10% bought is 91,350.00 of minority given up for 104,400.00: 13,050.00 to equity, no goodwill
assert (figs["net_assets_group"], figs["nci_given_group"], figs["price_group"], figs["equity_group"]) == ("913500.00", "91350.00", "104400.00", "13050.00")
assert shown["goodwill"] is None and shown["gain_loss"] is None
ok(api("POST", f"{CHANGES}/{shown['change_id']}/approve-structure-change", CTRL, {}))
g, _ = state()
assert next(r for r in g.group_structures if r.company_code == "DE01" and r.valid_from_period == "2026-10").ownership_pct == D("90")
ok(api("POST", "/groups/GRP/group-periods", ACC, {"period_id": P}))
gl.set_rows("DE01", P, [("G-1100", "1060000.00", "0.00"), ("G-3000", "-500000.00", "0.00"), ("G-3100", "-500000.00", "0.00"),
("G-4000", "-60000.00", "-10000.00")])
gl.set_rows("UK01", P, [("G-1600", "878400.00", "0.00", "DE01"), ("G-3000", "-878400.00", "0.00")])
for step in ("collect-group-data", "translate-currencies"):
ok(api("POST", f"{BASE}/{step}", ACC, {}))
eliminate()
[entry] = entries("ownership_change")
assert lines(entry) == {("G-1600", "UK01", "DE01"): D("-104400.00"), ("G-3950", "DE01", ""): D("91350.00"), ("G-3200", "DE01", ""): D("13050.00")}
assert book(("UK01", "G-1600", "DE01", "", "")) == 0 and book(("DE01", "G-1700", "", "", "")) == D("87000.00") # the goodwill is untouched, shown at the closing rate
assert book(("DE01", "G-3960", "", "", "")) == D("5160.00") # 10% of 51,600.00 from October, not 20%
assert not [ln for e in entries() for ln in e["lines"] if ln["group_account_id"] == "G-4900"] # never profit
nets_to_zero()
def test_con_17_a_smaller_holding_a_missing_price_and_a_signed_period_are_refused(gl):
stake_world(gl)
assert buy_more(ownership_pct="70")[0] == 409 and buy_more(consideration="")[0] == 422
assert api("POST", CHANGES, ACC, {"group_id": "GRP", "kind": "ownership_change", "company_code": "DE01", "investor_company_code": "UK01",
"ownership_pct": "101", "effective_date": "2026-10-01", "consideration": "1.00"})[0] == 422
sign_period("2026-10")
status, body = buy_more()
assert status == 409 and "signed" in str(body)
def test_con_17_taking_control_of_an_associate_remeasures_the_interest_held_in_profit_and_computes_goodwill_at_the_date(gl):
world(gl)
add_rules(("G-4900", "average"), roles=[("gain_on_disposal", "G-4900")])
gl.set_rows("FR01", "2026-03", [("G-1100", "300000.00", "0.00"), ("G-3000", "-300000.00", "0.00")])
acquire("FR01", "30", "150000.00", "equity")
sign_period("2026-03")
db(lambda r: r.data.add_translated([{"group_id": "GRP", "period_id": "2026-03", "run_no": 1, "company_code": "UK01", "group_account_id": "G-1600",
"partner_company": "FR01", "movement_type": "", "segment": "", "ytd_group": D("129000.00"), "rate_type": "closing",
"rate": D("1.000000")}])) # the 30% carried at 150,000 EUR x 0.86
status, shown = api("POST", CHANGES, ACC, {"group_id": "GRP", "kind": "ownership_change", "company_code": "FR01", "investor_company_code": "UK01",
"ownership_pct": "60", "method": "full", "effective_date": "2026-04-01", "consideration": "200000.00",
"previously_held_fair_value": "250000.00"})
assert status == 200, shown
# price 200,000 + the 30% held at fair value 250,000 = 450,000; 60% of net assets 300,000 = 180,000: goodwill 270,000 EUR, minority 120,000 EUR
assert (D(shown["goodwill"]), D(shown["nci"]), D(shown["goodwill_group"]), D(shown["nci_group"])) == \
(D("270000.00"), D("120000.00"), D("232200.00"), D("103200.00"))
assert D(shown["gain_loss"]) == D("86000.00") # 250,000 x 0.86 = 215,000.00 less the 129,000.00 carried
ok(api("POST", f"{CHANGES}/{shown['change_id']}/approve-structure-change", CTRL, {}))
g, _ = state()
row = next(r for r in g.group_structures if r.company_code == "FR01" and r.valid_from_period == "2026-04")
assert (row.method, row.ownership_pct) == ("full", D("60"))
gl.set_rows("FR01", P, [("G-1100", "300000.00", "0.00"), ("G-3000", "-300000.00", "0.00")])
gl.set_rows("DE01", P, [("G-1100", "1060000.00", "0.00"), ("G-3000", "-500000.00", "0.00"), ("G-3100", "-500000.00", "0.00"),
("G-4000", "-60000.00", "-10000.00")])
gl.set_rows("UK01", P, [("G-1600", "774000.00", "0.00", "DE01"), ("G-1600", "301000.00", "0.00", "FR01"), ("G-3000", "-1075000.00", "0.00")])
for step in ("collect-group-data", "translate-currencies"):
ok(api("POST", f"{BASE}/{step}", ACC, {}))
eliminate()
assert book(("UK01", "G-1600", "FR01", "", "")) == 0 and book(("FR01", "G-1700", "", "", "")) == D("234900.00")
assert book(("FR01", "G-4900", "", "", "")) == D("-86000.00") # the gain on the interest held before, in profit (IFRS 3.42)
[inv] = [e for e in entries("investment") if any(ln["company_code"] == "FR01" for ln in e["lines"])]
# price + fair value = parent's share + goodwill at acquisition; the goodwill's gain since (270,000 EUR: 232,200.00 to 234,900.00) is the only reserve line
assert [ln["amount"] for ln in inv["lines"] if ln["group_account_id"] == "G-3900"] == [D("-2700.00")]
assert not entries("equity_method")
nets_to_zero()
# ═══ CON-15 the year-end carry-forward ═══════════════════════════════════════════════════════════════════════════════
def year_world(gl, extra=0):
"""A calendar-year group of two sterling companies: UK01 bought 80% of DE01 on 2026-01-01 for 900,000.00 (goodwill 100,000.00, minority
200,000.00). December 2026: DE01 earned 50,000.00 (the minority's share 10,000.00) and holds 12,000.00 of UK01 goods at 25% margin, tax 30%
(3,000.00 eliminated, 900.00 deferred tax). FY2026 is signed through 2026-12."""
from world import RULES
gl.company("UK01", "GBP", fy_end=12)
gl.company("DE01", "GBP", fy_end=12)
ok(api("POST", "/groups", ACC, {"group_id": "GRP", "name": "Group", "group_currency": "GBP", "ledger_id": "IFRS", "group_chart_id": "GROUP",
"top_company_code": "UK01", "fiscal_year_end_month": "12"}))
ok(api("POST", "/groups/GRP/rules", ACC, {**RULES, "account_roles": RULES["account_roles"] + [{"role": "group_retained_earnings", "valid_from_period": "2020-01",
"group_account_id": "G-3200"}]}))
gl.set_rows("DE01", "2025-12", [("G-1100", "1000000.00", "0.00"), ("G-3000", "-500000.00", "0.00"), ("G-3100", "-500000.00", "0.00")])
acquire("DE01", date_="2026-01-01")
held = []
for i in range(extra): # more sterling subsidiaries, bought whole for 100.00
code = f"X{i:02d}"
gl.company(code, "GBP", fy_end=12)
gl.set_rows(code, "2025-12", [("G-1100", "100.00", "0.00"), ("G-3000", "-100.00", "0.00")])
gl.set_rows(code, "2026-12", [("G-1100", "100.00", "0.00"), ("G-3000", "-100.00", "0.00")])
acquire(code, "100", "100.00", date_="2026-01-01")
held.append(("G-1600", "100.00", "0.00", code))
gl.set_rows("UK01", "2026-12", [("G-1600", "900000.00", "0.00", "DE01"), ("G-3000", str(D("-900000.00") - 100 * extra), "0.00")] + held)
gl.set_rows("DE01", "2026-12", [("G-1100", "1038000.00", "0.00"), ("G-1400", "12000.00", "0.00"), ("G-3000", "-500000.00", "0.00"),
("G-3100", "-500000.00", "0.00"), ("G-4000", "-50000.00", "-50000.00")])
ok(api("POST", "/groups/GRP/group-periods", ACC, {"period_id": "2026-12"}))
close_period("2026-12", inventory=True)
def at(period):
return f"/groups/GRP/group-periods/{period}"
def close_period(period, *, inventory=False, signer=CTRL):
for step in ("collect-group-data", "translate-currencies"):
ok(api("POST", f"{at(period)}/{step}", ACC, {}))
if inventory:
ok(api("POST", f"{at(period)}/ic-inventory", ACC, {"holder_company": "DE01", "seller_company": "UK01", "inventory_amount": "12000.00",
"margin_pct": "25", "tax_rate_pct": "30"}))
ok(api("POST", f"{at(period)}/run-eliminations", ACC, {}))
ok(api("POST", f"{at(period)}/sign-off-group-close", signer, {}))
def carried(fiscal_year="FY2027"):
return {(r["group_account_id"], r["company_code"], r["partner_company"]): D(r["opening"])
for r in db(lambda rp: rp.data.carry_forward("GRP", fiscal_year)) if D(r["opening"])}
def january(gl):
gl.set_rows("UK01", "2027-01", [("G-1600", "900000.00", "0.00", "DE01"), ("G-3000", "-900000.00", "0.00")])
gl.set_rows("DE01", "2027-01", [("G-1100", "1050000.00", "0.00"), ("G-3000", "-500000.00", "0.00"), ("G-3100", "-550000.00", "0.00")])
def test_con_15_the_closing_balance_sheet_effects_open_the_next_year_and_their_profit_and_loss_opens_on_retained_earnings(gl):
year_world(gl)
january(gl)
opened = ok(api("POST", "/groups/GRP/group-periods", ACC, {"period_id": "2027-01"}))
assert opened["result"]["carry_forward"]["from_period"] == "2026-12"
# the stock profit (3,000.00 and its 900.00 deferred tax), the minority's 10,000.00 share of the year's result, and what hit profit and loss
assert carried() == {("G-1400", "DE01", "UK01"): D("-3000.00"), ("G-1450", "DE01", "UK01"): D("900.00"), ("G-3950", "DE01", ""): D("-10000.00"),
("G-3200", "DE01", ""): D("9100.00"), ("G-3200", "UK01", ""): D("3000.00")}
assert sum(carried().values(), D(0)) == 0
before = db(lambda r: r.data.carry_forward("GRP", "FY2027"))
again = ok(api("POST", "/groups/GRP/group-periods", ACC, {"period_id": "2027-01"})) # opens again: the same rows, nothing changed
assert again["result"]["carry_forward"]["changed"] is False
after = db(lambda r: r.data.carry_forward("GRP", "FY2027"))
assert [(r["group_account_id"], r["opening"]) for r in after] == [(r["group_account_id"], r["opening"]) for r in before] and len(after) == len(before)
def test_con_15_last_years_stock_profit_is_realised_this_year_the_group_result_rises_by_the_net_2100(gl):
year_world(gl)
january(gl)
ok(api("POST", "/groups/GRP/group-periods", ACC, {"period_id": "2027-01"}))
for step in ("collect-group-data", "translate-currencies"):
ok(api("POST", f"{at('2027-01')}/{step}", ACC, {}))
ok(api("POST", f"{at('2027-01')}/ic-inventory", ACC, {"holder_company": "DE01", "seller_company": "UK01", "none_stated": True}))
ok(api("POST", f"{at('2027-01')}/run-eliminations", ACC, {}))
g, _ = state()
gp = next(x for x in g.group_periods if x.period_id == "2027-01")
from brick_consolidation.application import reports
books = db(lambda r: reports.consolidated(r, g, gp)[0])
assert sum(books.values(), D(0)) == 0
assert books[("UK01", "G-5000", "DE01", "", "")] == D("-3000.00") and books[("DE01", "G-6500", "UK01", "", "")] == D("900.00") # into cost of sales and tax
assert books.get(("DE01", "G-1400", "UK01", "", ""), D(0)) == 0 and books.get(("DE01", "G-1450", "UK01", "", ""), D(0)) == 0 # the opening is cleared
assert -sum((v for (c, a, p, m, s), v in books.items() if a in ("G-5000", "G-6500", "G-3960")), D(0)) == D("2100.00") # the group result rises by 2,100.00 net
assert ok(api("GET", f"{at('2027-01')}/readiness", CTRL))["met"] is True
def test_con_15_a_reopened_and_re_signed_year_end_makes_the_carry_forward_stale_and_blocks_the_first_sign_off(gl):
from test_slice_b2 import CTRL2
year_world(gl)
january(gl)
ok(api("POST", "/groups/GRP/group-periods", ACC, {"period_id": "2027-01"}))
for step in ("collect-group-data", "translate-currencies"):
ok(api("POST", f"{at('2027-01')}/{step}", ACC, {}))
ok(api("POST", f"{at('2027-01')}/ic-inventory", ACC, {"holder_company": "DE01", "seller_company": "UK01", "none_stated": True}))
ok(api("POST", f"{at('2027-01')}/run-eliminations", ACC, {}))
ok(api("POST", f"{at('2026-12')}/reopen-group-period", CTRL2, {"reason": "a 500.00 accrual was missed"}))
gate = next(g for g in ok(api("GET", f"{at('2027-01')}/readiness", CTRL))["gates"] if g["gate"] == "carry_forward_current")
assert gate["met"] is False and "2026-12" in gate["reason"]
status, body = api("POST", f"{at('2027-01')}/sign-off-group-close", CTRL2, {})
assert status == 409 and "carry_forward_current" in str(body)
gl.set_rows("DE01", "2026-12", [("G-1100", "1038000.00", "0.00"), ("G-1400", "12000.00", "0.00"), ("G-2000", "-500.00", "0.00"),
("G-3000", "-500000.00", "0.00"), ("G-3100", "-500000.00", "0.00"), ("G-4000", "-49500.00", "-49500.00")])
ok(api("POST", f"{at('2026-12')}/collect-group-data", ACC, {}))
for step in ("translate-currencies", "run-eliminations"):
ok(api("POST", f"{at('2026-12')}/{step}", ACC, {}))
signed = ok(api("POST", f"{at('2026-12')}/sign-off-group-close", CTRL2, {}))
assert signed["result"]["carry_forward"]["changed"] is True # derived again from the new signing
assert carried()[("G-3950", "DE01", "")] == D("-9900.00") # the minority's 20% of 49,500.00
assert next(x for x in state()[0].group_periods if x.period_id == "2027-01").status == "collected" # 2027-01 stood on the old opening: stale