diff --git a/rpa_contract/persist.py b/rpa_contract/persist.py new file mode 100644 index 0000000..63567fa --- /dev/null +++ b/rpa_contract/persist.py @@ -0,0 +1,91 @@ +"""合同班结果:ContractOutcome → 表 4 行 + 表 3 状态。""" +from __future__ import annotations + +from dataclasses import dataclass + +from repo import PurchaseContractRow + +KIND_OK = "ok" +KIND_DILIGENCE = "diligence" +KIND_FAIL = "fail" +STAMP = "合同班" +PROCESS_RESULT_MAX = 1000 +FAIL_REASON_MAX = 500 + + +@dataclass(frozen=True) +class ContractOutcome: + kind: str + contract_no: str | None + reason: str | None + + +@dataclass(frozen=True) +class GroupSave: + fids: list[int] + process_status: int + process_result: str + table4_rows: list[dict] + + +def clip(text: str, max_len: int) -> str: + text = (text or "").strip() + if len(text) <= max_len: + return text + return text[: max_len - 1] + "…" + + +def build_save(rows: list[PurchaseContractRow], outcome: ContractOutcome) -> GroupSave: + kind = outcome.kind + number = (outcome.contract_no or "").strip() or None + if kind == KIND_OK and number: + process_status = 1 + process_result = "合同已提交" + contract_status = 1 + fail_reason = None + contract_no = number + elif kind == KIND_DILIGENCE: + process_status = 2 + process_result = clip("客户未尽调", PROCESS_RESULT_MAX) + contract_status = 3 + fail_reason = "客户未尽调" + contract_no = None + else: + reason = outcome.reason or "合同录入失败" + if kind == KIND_OK and not number: + reason = "submit 模式未读取到真实合同编号" + process_status = 2 + process_result = clip(reason, PROCESS_RESULT_MAX) + contract_status = 3 + fail_reason = clip(reason, FAIL_REASON_MAX) + contract_no = None + + seen: dict[int, PurchaseContractRow] = {} + for row in rows: + seen.setdefault(row.oms_id, row) + table4 = [ + { + "oms_id": row.oms_id, + "action_code": 1, + "contract_status": contract_status, + "contract_fail_reason": fail_reason, + "contract_no": contract_no, + "agreement_status": None, + "agreement_fail_reason": None, + "agreement_id": None, + "agreement_dtl_id": None, + "push_status": 0, + "org_code": row.org_code, + "create_user_id": 0, + "create_user_name": STAMP, + "update_user_id": 0, + "update_user_name": STAMP, + } + for row in seen.values() + ] + return GroupSave( + fids=[row.fid for row in rows], + process_status=process_status, + process_result=process_result, + table4_rows=table4, + ) diff --git a/rpa_contract/repo.py b/rpa_contract/repo.py index c6c7531..296f026 100644 --- a/rpa_contract/repo.py +++ b/rpa_contract/repo.py @@ -1,9 +1,10 @@ -"""表 3 合同行与仓库。Memory / Oracle 在后续任务补全。""" +"""表 3 领取、表 4 写入。Oracle 仓库后续任务补全。""" from __future__ import annotations from dataclasses import dataclass from datetime import date from decimal import Decimal +from typing import Any @dataclass @@ -34,3 +35,35 @@ class PurchaseContractRow: goods_mdm_code: str | None supplier_mdm_code: str | None process_result: str = "" + + +class MemoryContractRepo: + def __init__(self, rows: list[PurchaseContractRow]): + self.table3 = {row.fid: row for row in rows} + self.table4: list[dict] = [] + self._next_fid = 1 + + def claim_pending(self, limit: int | None = None) -> list[PurchaseContractRow]: + from group import is_claimable + + pending = sorted( + (row for row in self.table3.values() if is_claimable(row)), + key=lambda row: row.fid, + ) + if limit is not None: + pending = pending[:limit] + for row in pending: + row.process_status = 3 + return pending + + def save_group(self, payload: Any) -> None: + for row in payload.table4_rows: + item = dict(row) + item["fid"] = self._next_fid + self._next_fid += 1 + self.table4.append(item) + for fid in payload.fids: + target = self.table3[fid] + target.process_status = payload.process_status + target.process_result = payload.process_result + diff --git a/rpa_contract/tests/test_job.py b/rpa_contract/tests/test_job.py new file mode 100644 index 0000000..2e1b5f6 --- /dev/null +++ b/rpa_contract/tests/test_job.py @@ -0,0 +1,72 @@ +from datetime import date +from decimal import Decimal + +from persist import 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