"""指导成本解析只认已登记事实:多条候选不猜、单位与封存状态必须显式匹配。""" from decimal import Decimal import pytest from app.core.exceptions import BadRequestError from app.services.tplus.guidance import check_unit_guidance_conflicts, resolve_guidance class Fact: """最小事实替身:resolve_guidance 只读取 kind 与 data_json。""" def __init__(self, data_json): self.kind = "GUIDANCE" self.data_json = data_json def facts_of(*rows): """按 (kind, fact_key) 组装成 latest_guidance_facts 的返回结构。""" return {("GUIDANCE", str(index)): Fact(row) for index, row in enumerate(rows)} def final_row(**overrides): row = { "product_id": 9, "month": "2026-03", "basis": "UNIT", "unit": "根", "is_finalized": True, } row.update(overrides) return row def test_没有匹配事实时返回缺失(): state, guidance = resolve_guidance(facts_of(), 9, "line-1", "2026-03", "根") assert state == "MISSING" assert guidance is None def test_多条候选时不选择其中一条(): rows = facts_of(final_row(), final_row(shipment_line_id="line-1")) state, guidance = resolve_guidance(rows, 9, "line-1", "2026-03", "根") assert state == "AMBIGUOUS" assert guidance is None def test_唯一候选且单位一致时返回已封存事实(): row = final_row() state, guidance = resolve_guidance(facts_of(row), 9, "line-1", "2026-03", "根") assert state == "FINALIZED" assert guidance is row def test_单位不一致时不采用该事实(): state, guidance = resolve_guidance(facts_of(final_row(unit="吨")), 9, "line-1", "2026-03", "根") assert state == "UNIT_MISMATCH" assert guidance is None def test_未封存事实不能作为指导价依据(): state, guidance = resolve_guidance(facts_of(final_row(is_finalized=False)), 9, "line-1", "2026-03", "根") assert state == "UNFINALIZED" assert guidance is None def test_按明细行登记的指导价只对该行生效(): rows = facts_of(final_row(basis="LINE", shipment_line_id="line-1")) assert resolve_guidance(rows, 9, "line-1", "2026-03", "根")[0] == "FINALIZED" assert resolve_guidance(rows, 9, "line-2", "2026-03", "根")[0] == "MISSING" def test_同一产品同月已有事实时拒绝重复登记(): with pytest.raises(BadRequestError, match="重叠"): check_unit_guidance_conflicts(facts_of(final_row()), 9, "2026-03", "根", "10", []) def test_历史封存明细的成本或单位不一致时拒绝(): with pytest.raises(BadRequestError, match="不一致"): check_unit_guidance_conflicts( facts_of(), 9, "2026-03", "根", "10", [{"unit_cost": "11", "unit": "根"}], ) with pytest.raises(BadRequestError, match="不一致"): check_unit_guidance_conflicts( facts_of(), 9, "2026-03", "根", "10", [{"unit_cost": "10", "unit": "吨"}], ) def test_事实为空且历史一致时放行(): check_unit_guidance_conflicts( facts_of(), 9, "2026-03", "根", Decimal("10"), [{"unit_cost": "10", "unit": "根"}, {"unit_cost": "10.00", "unit": "根"}], ) def test_金额非法时拒绝(): for bad in ("abc", "-1", "nan"): with pytest.raises(BadRequestError, match="不一致"): check_unit_guidance_conflicts(facts_of(), 9, "2026-03", "根", bad, [])