from datetime import date from decimal import Decimal from errors import DiligenceError, NodeError from job import outcome_from_run, run_batch from persist import KIND_DILIGENCE, KIND_FAIL, KIND_OK, ContractOutcome, build_save from repo import MemoryContractRepo, PurchaseContractRow def _row(**overrides) -> PurchaseContractRow: data = dict( fid=1, oms_id=2026100501, supplier_id="SUP001", supplier_name="示例供应商", contract_sign_date=date(2026, 10, 1), contract_start_date=date(2026, 10, 2), contract_end_date=date(2027, 9, 30), payment_term=90, goods_id="G0001", goods_name="阿莫西林胶囊", reg_no="国药准字H20201234", spec="0.25g*24粒", unit="盒", qty=Decimal("10.0000"), unit_price=Decimal("100.0000"), purchaser_id="P001", purchaser_name="张三", planner_id="PL001", planner_name="李四", contract_entry_status=None, contract_no=None, process_status=0, org_code="901", goods_mdm_code="QX0100167583", supplier_mdm_code="DW0100046361", ) data.update(overrides) return PurchaseContractRow(**data) def test_claim_pending_sets_processing_and_skips_protocol_rerun(): repo = MemoryContractRepo( [ _row(), _row(fid=2, oms_id=2026100502, process_status=1), _row(fid=3, oms_id=2026100503, contract_entry_status=0, contract_no="HT-OLD"), ] ) claimed = repo.claim_pending() assert [row.fid for row in claimed] == [1] assert repo.table3[1].process_status == 3 assert repo.table3[3].process_status == 0 def test_save_group_inserts_one_table4_row_per_oms_id(): repo = MemoryContractRepo( [ _row(), _row(fid=2, oms_id=2026100501, goods_id="G0002"), _row(fid=3, oms_id=2026100502, goods_id="G0003"), ] ) repo.claim_pending() outcome = ContractOutcome(KIND_OK, "HT20261005001", None) payload = build_save(list(repo.table3.values()), outcome) repo.save_group(payload) assert [row["oms_id"] for row in repo.table4] == [2026100501, 2026100502] assert {row["contract_no"] for row in repo.table4} == {"HT20261005001"} assert {row["action_code"] for row in repo.table4} == {1} assert {row["contract_status"] for row in repo.table4} == {1} assert {row["push_status"] for row in repo.table4} == {0} assert repo.table3[1].process_status == 1 assert repo.table3[3].process_status == 1 def test_job_merges_same_supplier_and_writes_table4(): repo = MemoryContractRepo( [ _row(), _row(fid=2, oms_id=2026100502, goods_id="G0002"), _row(fid=3, oms_id=2026100503, contract_entry_status=0, contract_no="HT-OLD"), ] ) calls: list = [] def runner(draft): calls.append(draft) return ContractOutcome(KIND_OK, "HT20261005001", None) run_batch(repo, runner=runner) assert len(calls) == 1 assert [line.goods_code for line in calls[0].goods_lines] == [ "QX0100167583", "QX0100167583", ] assert [line.mdm_code for line in calls[0].goods_lines] == [ "QX0100167583", "QX0100167583", ] assert calls[0].customer_code == "DW0100046361" assert [row["oms_id"] for row in repo.table4] == [2026100501, 2026100502] assert {row["contract_no"] for row in repo.table4} == {"HT20261005001"} assert {row["action_code"] for row in repo.table4} == {1} assert {row["contract_status"] for row in repo.table4} == {1} assert {row["push_status"] for row in repo.table4} == {0} assert repo.table3[1].process_status == 1 assert repo.table3[2].process_status == 1 assert repo.table3[3].process_status == 0 def test_job_marks_diligence_fail(): repo = MemoryContractRepo([_row()]) def runner(draft): return ContractOutcome(KIND_DILIGENCE, None, "客户未尽调") run_batch(repo, runner=runner) assert repo.table3[1].process_status == 2 assert "客户未尽调" in repo.table3[1].process_result assert repo.table4[0]["contract_status"] == 3 assert repo.table4[0]["contract_no"] is None assert repo.table4[0]["contract_fail_reason"] == "客户未尽调" def test_job_skips_runner_when_mdm_missing(): repo = MemoryContractRepo([_row(supplier_mdm_code="")]) called = {"n": 0} def runner(draft): called["n"] += 1 return ContractOutcome(KIND_OK, "HT-X", None) run_batch(repo, runner=runner) assert called["n"] == 0 assert repo.table3[1].process_status == 2 assert "缺少供应商MDM码" in repo.table3[1].process_result def test_outcome_from_run_maps_diligence(): def run(): raise DiligenceError("客户未尽调") out = outcome_from_run(run) assert out.kind == KIND_DILIGENCE assert out.reason == "客户未尽调" assert out.contract_no is None def test_outcome_from_run_maps_empty_number_to_fail(): out = outcome_from_run(lambda: "") assert out.kind == KIND_FAIL assert "未读取到真实合同编号" in (out.reason or "") def test_outcome_from_run_ok(): out = outcome_from_run(lambda: "HT9") assert out == ContractOutcome(KIND_OK, "HT9", None) def test_outcome_from_run_maps_node_error(): def run(): raise NodeError("客商加载失败") out = outcome_from_run(run) assert out.kind == KIND_FAIL assert "客商加载失败" in (out.reason or "")