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