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", []))