AutoRpa/rpa_contract/job.py

108 lines
4.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 合同班次:领取表 3 → 按供应商、货主和三个日期合单 → runner 录合同 → 写表 4。
#
# 崩在 process_status=3:超过租约可再领,未过期需改回 0。
# 未捕获异常原因列只写 PAGE_FAILED。原文进审计表,会话结束后另写。
from __future__ import annotations
import logging
from contextlib import nullcontext
from dataclasses import dataclass
from typing import Callable
from contract_draft import ContractDraft
from errors import DiligenceError, NodeError
from group import draft_from_group, group_by_supplier, missing_mdm_reason
from persist import KIND_DILIGENCE, KIND_FAIL, KIND_OK, ContractOutcome, build_save
from rpa_oracle import ShiftError
PAGE_FAILED = "PAGE_FAILED"
Runner = Callable[[ContractDraft], ContractOutcome]
logger = logging.getLogger("rpa_contract")
# 一个合同班次的领取、合单和成败计数。
@dataclass
class JobSummary:
claimed: int = 0
groups: int = 0
ok: int = 0
failed: int = 0
def _session(repo):
"""有 session 就借一条,没有就空上下文。页面调用必须在这个块外面。"""
session = getattr(repo, "session", None)
if session is None:
return nullcontext()
return session()
def run_batch(repo, *, runner: Runner, limit: int | None = None) -> JobSummary:
"""领取待录合同行,按供应商合单、调 runner、落表。
session 只包领取和每一组的落表。页面在连接归还之后跑。一组落完再处理下一组。
limit 交给领取层。
"""
audits: list[ShiftError] = []
with _session(repo):
claimed = repo.claim_pending(limit=limit)
summary = JobSummary(claimed=len(claimed), groups=0)
for rows in group_by_supplier(claimed):
summary.groups += 1
outcome = _run_group(rows, runner)
with _session(repo):
repo.save_group(build_save(rows, outcome))
if outcome.error_text:
supplier = rows[0].supplier_id if rows else ""
audits.append(ShiftError("合同", str(supplier), PAGE_FAILED, outcome.error_text))
if outcome.kind == KIND_OK and (outcome.contract_no or "").strip():
summary.ok += 1
else:
summary.failed += 1
repo.save_shift_errors(audits)
return summary
# 录一组同一供应商的行。缺 MDM 不调页面;未捕获异常写 PAGE_FAILED。
def _run_group(rows, runner: Runner) -> ContractOutcome:
reason = missing_mdm_reason(rows)
if reason:
return ContractOutcome(KIND_FAIL, None, reason)
try:
outcome = runner(draft_from_group(rows))
except DiligenceError:
return ContractOutcome(KIND_DILIGENCE, None, "客户未尽调")
except NodeError as exc:
return ContractOutcome(KIND_FAIL, None, str(exc))
except Exception as exc:
logger.exception("合同录入未捕获异常 supplier=%s", rows[0].supplier_id if rows else "")
return ContractOutcome(KIND_FAIL, None, PAGE_FAILED, _text(exc))
if outcome.kind == KIND_OK and not (outcome.contract_no or "").strip():
return ContractOutcome(KIND_FAIL, None, "submit 模式未读取到真实合同编号")
return outcome
# 把页面 run() 的合同号或异常收成 ContractOutcome。
#
# 尽调失败写客户未尽调;NodeError 写业务文案;其它异常写 PAGE_FAILED,原文放 error_text。
def outcome_from_run(run: Callable[[], str]) -> ContractOutcome:
try:
number = (run() or "").strip()
except DiligenceError:
return ContractOutcome(KIND_DILIGENCE, None, "客户未尽调")
except NodeError as exc:
return ContractOutcome(KIND_FAIL, None, str(exc))
except Exception as exc:
logger.exception("合同页面未捕获异常")
return ContractOutcome(KIND_FAIL, None, PAGE_FAILED, _text(exc))
if not number:
return ContractOutcome(KIND_FAIL, None, "submit 模式未读取到真实合同编号")
return ContractOutcome(KIND_OK, number, None)
# 取出异常原文。空串不当审计正文。
def _text(exc: BaseException) -> str | None:
text = str(exc).strip()
return text or None