"""PR-C — cost-centre authority + expense governance.""" from __future__ import annotations from decimal import Decimal import pytest import pytest_asyncio from app.domains.finance import read_service from app.domains.finance.models import ( FinanceCostCenter, FinanceExternalStatement, FinanceSpendFact, ) PERIOD = "2026-05" @pytest_asyncio.fixture async def seeded(db_session): db = db_session # Authoritative master. CC-MKT + CC-FIN have no spend → TWO unused # codes, which would crash the old sorted(generator-of-dicts) path. db.add(FinanceCostCenter(source="huilianyi", code="CC-RD", name="研发", active=True)) db.add(FinanceCostCenter(source="huilianyi", code="CC-MKT", name="市场", active=True)) db.add(FinanceCostCenter(source="huilianyi", code="CC-FIN", name="财务", active=True)) # Internal spend: $500 on CC-RD (in master) + $100 on CC-GHOST (orphan). for i, (cc, usd) in enumerate([("CC-RD", "500"), ("CC-GHOST", "100")]): db.add(FinanceSpendFact( fact_key=f"f-{i}", period=PERIOD, cost_type="cloud", basis="consumption", sub_category="EC2", vendor="aws", cost_center=cc, allocation_source="scope_owner", amount_native=Decimal(usd), currency_native="USD", amount_usd=Decimal(usd), fx_basis="native_usd", is_provisional=True, source_domain="cmdb", rollup_fingerprint="x", )) def stmt(doc, line, **kw): base = dict( statement_key=f"k-{doc}-{line}", source="huilianyi", external_doc_no=doc, line_no=line, period=PERIOD, amount_native=Decimal("10"), currency="USD", amount_usd=Decimal("10"), fx_basis="native_usd", cost_center="CC-RD", ) base.update(kw) return FinanceExternalStatement(**base) # Duplicate invoice INV-1 across two docs; one PAID-but-unverified. db.add_all([ stmt("D1", 0, invoice_no="INV-1", invoice_verified=True, payment_status="PAID"), stmt("D2", 0, invoice_no="INV-1", invoice_verified=True, payment_status="PAID"), stmt("D3", 0, invoice_no="INV-9", invoice_verified=False, payment_status="PAID", vendor="X"), ]) await db.flush() return db @pytest.mark.asyncio async def test_cost_center_coverage_counts_consumption_basis_only(db_session): """Regression (2026-07-08): cost_center_coverage must count the consumption basis ONLY. consumption / procurement / cash are three lenses on the SAME money; summing them (the old bug) double-counts a hardware asset (cash purchase + monthly depreciation) and a cloud PO (procurement commitment + its consumption bill) on the same cost centre, inflating total_spend_usd and skewing coverage_pct. """ db = db_session db.add(FinanceCostCenter(source="huilianyi", code="CC-RD", name="研发", active=True)) def _fact(key, basis, usd, cost_type): return FinanceSpendFact( fact_key=key, period=PERIOD, cost_type=cost_type, basis=basis, sub_category="x", vendor="v", cost_center="CC-RD", allocation_source="scope_owner", amount_native=Decimal(usd), currency_native="USD", amount_usd=Decimal(usd), fx_basis="native_usd", is_provisional=True, source_domain="cmdb", rollup_fingerprint="x", ) # Same cost centre, three bases: only the $100 consumption row is spend. db.add_all([ _fact("c-cons", "consumption", "100", "cloud"), _fact("c-proc", "procurement", "5000", "procurement"), _fact("c-cash", "cash", "12000", "hardware"), ]) await db.flush() r = await read_service.cost_center_coverage(db, PERIOD) # Total spend reflects ONLY the consumption lens — not 100+5000+12000. assert r["total_spend_usd"] == "100.00" assert r["coverage_pct"] == 100.0 # CC-RD is in master → fully covered @pytest.mark.asyncio async def test_cost_center_coverage_flags_orphan(seeded): r = await read_service.cost_center_coverage(seeded, PERIOD) assert r["master_count"] == 3 orphan_codes = {o["code"] for o in r["orphans"]} assert "CC-GHOST" in orphan_codes # spend on a non-master code assert "CC-RD" not in orphan_codes assert r["coverage_pct"] < 100.0 # Two master codes with no spend — exercises the unused sort (no crash). unused_codes = {u["code"] for u in r["unused"]} assert {"CC-MKT", "CC-FIN"} <= unused_codes @pytest.mark.asyncio async def test_expense_governance_duplicate_and_unverified(seeded): r = await read_service.expense_governance(seeded, PERIOD) dups = {d["invoice_no"]: d for d in r["duplicate_invoices"]} assert "INV-1" in dups assert dups["INV-1"]["doc_count"] == 2 paid_unverified = {p["external_doc_no"] for p in r["paid_unverified"]} assert "D3" in paid_unverified # PAID but invoice_verified=False assert "D1" not in paid_unverified # verified → not flagged