dodil/erp-brick-fixed-assets

DODIL ERP brick: fixed assets

tests/test_audit_musts.py

"""Audit wave 2 (fixed-assets): each test proves one MUST on HEAD 580c9de and fails for the stated reason."""
from decimal import Decimal as D

from conftest import ACCOUNTANT, OWNER, SYSTEM, acquire, api, create_asset, must, setup_policy
from test_migration import (  # noqa: F401  (odoo_world fixture below)
    LOAD,
    RUN,
    assets_of,
    odoo_world,  # noqa: F401,F811
    reconcile,
    values,
)


# M1 (class 7): a NEW run (a later cutover) after a signed run rewrites the signed run's takeover transaction in place —
# the takeover id has no run in it (odoo:<db>:account.asset:<id>:takeover), so the signed history changes.
def test_m1_a_later_run_never_rewrites_what_a_signed_run_took_over(odoo_world):  # noqa: F811
    must(api("POST", "/migration/odoo/load", SYSTEM, LOAD))
    reconcile()
    must(api("POST", f"/migration/odoo/{RUN}/sign", OWNER))
    signed_acc = values("odoo:erp:account.asset:11")["accumulated_depreciation"]
    odoo_world.moves_by[11].append(("2026-10-28", 200))                # Odoo kept posting after the signed cutover
    api("POST", "/migration/odoo/load", SYSTEM, {**LOAD, "cutover_date": "2026-10-31"})
    assert values("odoo:erp:account.asset:11")["accumulated_depreciation"] == signed_acc   # signed history unchanged


# M2 (class 6): a migrated asset that keeps its Odoo name is claimed with seq 0, so claim_number never steps over it: when
# the series reaches that rendered number the plain INSERT collides on every retry and the class can create no asset.
def test_m2_a_kept_odoo_number_does_not_jam_the_class_series(odoo_world):  # noqa: F811
    odoo_world.rows[1]["name"] = "2026-VEHICLES-0001"                  # an Odoo name in the series' own format
    must(api("POST", "/migration/odoo/load", SYSTEM, LOAD))            # asset_numbers: keep
    s, b = api("POST", "/assets", ACCOUNTANT,
               {"request_id": "after", "company_code": "UK01", "class_code": "VEHICLES", "description": "New van"})
    assert s == 200, (s, b)
    assert b["asset_no"] != "2026-VEHICLES-0001"


# M3 (class 8): gl's ledger_id is VARCHAR ("IFRS", "LOCAL"; 2-8 upper-case letters or digits — gl spec entity ledger,
# erp-brick-gl inputs.LedgerCode = text(8)); fixed-assets types it BigId (uc_settings set_area, fa_area.ledger_id:BI) and
# int()s gl's answer, so no area can be mapped to a real gl ledger. The FakeGL answers integers, hiding it.
def test_m3_an_area_maps_to_a_real_gl_ledger_code(gl):
    gl.ledgers["UK01"] = [{"ledger_id": "LOCAL", "kind": "leading", "accounting_principle": "UK GAAP"},
                          {"ledger_id": "IFRS", "kind": "non_leading", "accounting_principle": "IFRS"}]
    s, b = api("POST", "/settings/areas", OWNER, {"area_code": "01", "name": "Book", "purpose": "book", "ledger_id": "LOCAL",
                                                  "company_code": "UK01", "currency": "GBP"})
    assert s == 200, (s, b)


# M4 (class 5): the amount is bounded, the TRANSLATED amount is not: amount x gl's rate is stored as the area cost and sent
# to gl at 2.7e16 — above NUMERIC(18,2) — instead of a 422 {gate: bounds}.
def test_m4_a_translated_acquisition_is_bounded_before_it_is_stored(gl):
    setup_policy(classes=("VEH",), start="2026-01-01")
    a = create_asset("big")
    gl.fx["USD"] = "3"
    s, b = acquire(a["asset_id"], "9000000000000000.00", currency="USD", ref="pur:FX-1:1")
    assert s == 422, (s, b)
    assert all(abs(D(str(ln.get("debit") or ln.get("amount") or 0))) < D("1e16")
               for j in gl.journals.values() for ln in j.get("lines", []))