# 合同班次入口:领表 3、调页面流、写表 4。不推接口 4。 # # 崩在 process_status=3。账密读 CONTRACT_USERNAME / CONTRACT_PASSWORD,缺则失败。 from __future__ import annotations import argparse from apa_browser_adapter import ApaBrowserAdapter from contract_create_node import CONTRACT_URL, ContractCreateInput, build_flow_context from contract_draft import ContractDraft from contract_flow_steps import run_contract_create_flow from pathlib import Path from rpa_oracle import load_job_env from job import outcome_from_run, run_batch from persist import KIND_DILIGENCE, KIND_HOLD, KIND_OK, LOOKUP_ONLY_RESULT, ContractOutcome from repo import OracleContractRepo DEFAULT_CLAIM_LIMIT = 10 def parse_args(argv: list[str] | None = None) -> argparse.Namespace: """解析合同班次参数。不传 --limit 时按行领 10 条,不再全部领走。""" parser = argparse.ArgumentParser(description="合同班次:领表 3、按供应商合单、写表 4") parser.add_argument( "--limit", type=int, default=DEFAULT_CLAIM_LIMIT, help="本班最多领取 N 行,默认 10", ) return parser.parse_args(argv) def contract_lookup_title(draft: ContractDraft) -> str: """拼查重标题:客商名 + 采购协议 + 签订日。不含货主和起止日,也不是合单键。""" who = (draft.customer_name or draft.supplier_name or "").strip() sign = (draft.contract_sign_date or draft.order_date or "").replace("-", "") return f"{who}采购协议{sign}" def decide_existing_contracts( numbers: list[str], *, lookup_only: bool ) -> ContractOutcome | None: """按「我的合同」命中行决定这一组。 0 条且还可以起草时返回 None,调用方才走 S06–S12。 恰好 1 条复用该合同号,只落表 4。多条停在处理中,不取最新一条。 只查号时 0 条也停在处理中。 """ hits = list(numbers) if len(hits) > 1: return ContractOutcome(KIND_HOLD, None, "我的合同命中多条") if len(hits) == 1: number = (hits[0] or "").strip() if not number: return ContractOutcome(KIND_HOLD, None, LOOKUP_ONLY_RESULT) return ContractOutcome(KIND_OK, number, None) if lookup_only: return ContractOutcome(KIND_HOLD, None, LOOKUP_ONLY_RESULT) return None def _pull_back_to_draft(browser) -> None: """一组开始前离开上一张合同的页面,回到起草页。""" from contract_common import FORM_PAGE_URL browser.open_url(FORM_PAGE_URL) def _flow_context(draft: ContractDraft): """按这组草稿建页面上下文。账密仍只认环境变量。""" spec = ContractCreateInput( job_id="contract-batch", supplier_name=draft.supplier_name, order_date=draft.order_date, source_type=draft.source_type or "", submit_mode="submit", ) return build_flow_context(spec, draft) def _lookup_my_contracts(browser, draft: ContractDraft) -> list[str]: """进起草前查「我的合同」。命中行的合同号原样返回,不取最新一条。""" from contract_number import ( _open_my_contracts, _scrape_my_contract_rows, _search_my_contracts, ) ctx = _flow_context(draft) _open_my_contracts(browser, ctx) _search_my_contracts( browser, contract_lookup_title(draft), (draft.customer_code or "").strip(), ) rows = _scrape_my_contract_rows(browser) return [str(row.get("no") or "") for row in rows] def _create_and_read_number(browser, draft: ContractDraft) -> ContractOutcome: """0 条时走 S06–S12。提交后读不到号则停在处理中,下一班只查号。""" _pull_back_to_draft(browser) ctx = _flow_context(draft) def run() -> str: """在已打开的浏览器里跑起草并返回合同号。""" return run_contract_create_flow(browser, ctx) outcome = outcome_from_run(run) if outcome.kind == KIND_OK and (outcome.contract_no or "").strip(): return outcome if outcome.kind == KIND_DILIGENCE: return outcome if outcome.kind == KIND_FAIL and "未读取到真实合同编号" in (outcome.reason or ""): return ContractOutcome(KIND_HOLD, None, LOOKUP_ONLY_RESULT, outcome.error_text) return outcome def production_runner( browser: ApaBrowserAdapter, draft: ContractDraft, *, find_contracts=None, create=None, ) -> ContractOutcome: """每个合单组先拉回起草页,再查「我的合同」。 查重三态:0 条才走 S06–S12;恰好 1 条只落表 4,不起草第二份; 多条停在 process_status=3,不新建,也不取最新一条。 标题是客商名 + 「采购协议」 + 签订日,不含货主和起止日。标题和客商不是合单键。 提交后读不到号也停在 3,下一班只查号。 """ if find_contracts is None: _pull_back_to_draft(browser) numbers = _lookup_my_contracts(browser, draft) else: numbers = list(find_contracts(browser, draft)) decided = decide_existing_contracts( numbers, lookup_only=bool(getattr(draft, "lookup_only", False)), ) if decided is not None: return decided if create is None: return _create_and_read_number(browser, draft) number = (create(browser, draft) or "").strip() if number: return ContractOutcome(KIND_OK, number, None) return ContractOutcome(KIND_HOLD, None, LOOKUP_ONLY_RESULT) def main(argv: list[str] | None = None, repo=None, runner=None) -> int: """领表 3、按供应商合单录合同、写表 4。有失败则进程退出码 1。 不传 --limit 时最多领 10 行。传入 repo 和 runner 时不打开浏览器。 """ args = parse_args(argv) load_job_env(Path(__file__).resolve().parent) if repo is None: repo = OracleContractRepo.from_env() if runner is None: browser = ApaBrowserAdapter(url=CONTRACT_URL, flow="CONTRACT_CREATE") runner = lambda draft: production_runner(browser, draft) summary = run_batch(repo, runner=runner, limit=args.limit) print( f"claimed={summary.claimed} groups={summary.groups} " f"ok={summary.ok} failed={summary.failed}" ) return 0 if summary.failed == 0 else 1 if __name__ == "__main__": raise SystemExit(main())