dodil/erp-brick-tax
DODIL ERP brick: tax
tests/test_audit1.py
"""Audit wave 1: seven proven defects, each pinned by the proof that found it."""
import base64
import json
from conftest import (
_ROLES,
ADMIN,
ADMIN2,
BILLER,
FULL,
OWNER,
SYSTEM,
TODAY,
api,
compute,
customer,
line,
load_uk,
must,
on_day,
registered,
setup,
uk_rules,
)
from test_migration import ( # noqa: F401
DB,
DOOR,
FakeOdoo,
migrate,
odoo_rows, # noqa: F401 (fixture)
)
def who(role, sub, email=None):
"""A token with a sub and, optionally, an email (person() always gives both, equal)."""
payload = base64.urlsafe_b64encode(json.dumps({"permissions": list(_ROLES[role])}).encode()).decode()
user = {"sub": sub, "app_roles": [role]}
if email:
user["email"] = email
return {"X-Dodil-User": json.dumps(user), "X-Dodil-User-JWT": f"h.{payload.rstrip('=')}.s"}
def test_1_a_broad_rule_over_covered_pairs_is_not_a_first_row():
setup()
must(api("POST", "/tax-controls/tax_changes_need_second_person", OWNER, {"value": True}))
got = must(api("POST", "/rules", ADMIN, {"category_code": "any", "treatment_code": "any", "tax_code": "Z", "priority": 1000}))
assert got["change_state"] == "pending" and got["valid_from"] == "2026-11-21"
assert api("POST", "/rules/approve", ADMIN, {"category_code": "any", "treatment_code": "any", "valid_from": "2026-11-21"})[0] == 409
assert must(compute([line(1, "10.00")]))["lines"][0]["tax_code"] == "S" # today's invoices keep their tax
status, _ = api("POST", "/rules", ADMIN, {"category_code": "any", "treatment_code": "any", "tax_code": "E", "valid_from": TODAY,
"supply": "goods"})
assert status == 409 # a covered pair never starts today
def test_2_the_migration_does_not_back_date_a_rule_already_in_use(odoo_rows): # noqa: F811
from erp_platform import work # noqa: F401
with on_day("2026-11-10T09:00:00+00:00"):
load_uk()
registered()
uk_rules()
customer()
out = must(migrate(confirm=True, cutover_date="2026-11-15"))
rows = [r for r in must(api("GET", "/rules", ADMIN))["rules"]
if (r["category_code"], r["treatment_code"], r["supply"]) == ("standard", "overseas_business", "services")]
assert [(r["valid_from"], r["tax_code"]) for r in rows] == [("2026-11-10", "OS")]
assert any("standard" in d and "overseas_business" in d for d in out["written"]["differs"]), out["written"]["differs"]
def test_3_the_approver_is_not_the_author_under_another_identity():
setup()
must(api("POST", "/tax-controls/tax_changes_need_second_person", OWNER, {"value": True}))
a = who("tax_admin", "u-1", "a@shop.example")
same_person = who("tax_admin", "u-1") # same sub, no email
must(api("POST", "/rules", a, {"category_code": "standard", "treatment_code": "uk_business", "tax_code": "R", "valid_from": "2026-11-25"}))
body = {"category_code": "standard", "treatment_code": "uk_business", "valid_from": "2026-11-25"}
assert api("POST", "/rules/approve", same_person, body)[0] == 409
must(api("POST", "/codes/S/schedule-rate", a, {"rate_pct": "21", "valid_from": "2027-04-01"}))
assert api("POST", "/codes/S/approve-rate", same_person, {"valid_from": "2027-04-01"})[0] == 409
must(api("POST", "/rules/approve", ADMIN2, body))
# an override that lowers tax
must(compute([line(1, "100.00")], document_ref="D3"))
biller = who("biller", "u-2", "b@shop.example")
must(api("POST", "/documents/D3/lines/1/override-line", biller, {"tax_code": "Z", "reason": "export"}))
assert api("POST", "/documents/D3/lines/1/approve-override", who("tax_admin", "u-2"), {})[0] == 409
assert api("POST", "/documents/D3/lines/1/approve-override", ADMIN2, {})[0] == 200
def test_4_an_approved_lowering_override_does_not_survive_a_line_change():
setup()
must(compute([line(1, "10.00")], document_ref="D4"))
must(api("POST", "/documents/D4/lines/1/override-line", BILLER, {"tax_code": "Z", "reason": "export"}))
must(api("POST", "/documents/D4/lines/1/approve-override", ADMIN, {}))
again = must(compute([line(1, "250000.00", description="something else entirely")], document_ref="D4"))
assert again["lines"][0]["override"]["state"] == "waiting"
assert api("POST", "/documents/D4/freeze", SYSTEM, {"contents": FULL})[0] == 409
same = must(compute([line(1, "250000.00", description="something else entirely")], document_ref="D4"))
assert same["lines"][0]["override"]["state"] == "waiting"
must(api("POST", "/documents/D4/lines/1/approve-override", ADMIN2, {}))
kept = must(compute([line(1, "250000.00", description="something else entirely", quantity="2")], document_ref="D4"))
assert kept["lines"][0]["override"]["state"] == "approved" # amount, category, supply, description unchanged
def _today():
return must(api("GET", "/rules", ADMIN))["today"]
def test_5a_a_zone_row_is_in_force_by_the_business_date_of_the_zone_before_it():
with on_day("2026-11-20T05:00:00+00:00"): # 18:00 on 11-19 in Pago_Pago
must(api("POST", "/tax-controls/business_timezone", OWNER, {"value": "Pacific/Pago_Pago"}))
assert _today() == "2026-11-19"
must(api("POST", "/tax-controls/business_timezone", OWNER, {"value": "Pacific/Kiritimati", "valid_from": "2026-11-20"}))
assert _today() == "2026-11-19" # the Kiritimati row is still in the future
with on_day("2026-11-20T12:00:00+00:00"): # 11-20 in Pago_Pago: Kiritimati applies (11-21 there)
assert _today() == "2026-11-21"
def test_5b_today_never_goes_backwards_across_a_zone_switch():
with on_day("2026-11-19T12:00:00+00:00"):
must(api("POST", "/tax-controls/business_timezone", OWNER, {"value": "Pacific/Kiritimati"}))
must(api("POST", "/tax-controls/business_timezone", OWNER, {"value": "Pacific/Pago_Pago", "valid_from": "2026-11-21"}))
with on_day("2026-11-20T12:00:00+00:00"): # 02:00 on 11-21 in Kiritimati
assert _today() == "2026-11-21"
with on_day("2026-11-21T01:30:00+00:00"): # the clock flips back to 11-20 in Pago_Pago
assert _today() == "2026-11-21"
def test_6_an_issued_documents_tax_is_stored_and_never_recomputed(monkeypatch):
setup()
must(compute([line(1, "100.00"), line(2, "33.33", category="zero")], document_ref="D6"))
frozen = must(api("POST", "/documents/D6/freeze", SYSTEM, {"contents": FULL}))
from brick_tax.domain import rounding
def boom(*a, **k):
raise AssertionError("an issued document was recomputed")
monkeypatch.setattr(rounding, "compute", boom)
assert must(api("GET", "/documents/D6", ADMIN)) == frozen
assert frozen["totals"]["vat"] == "20.00"
def test_7_an_amount_that_overflows_the_ledger_is_refused_not_a_500():
setup()
status, body = compute([line(1, "999999999999999.00", unit_price="1")], currency="EUR", fx_rate="999999", document_ref="BIG")
assert status == 422, (status, body)
assert api("GET", "/documents/BIG", ADMIN)[0] == 404