"""返利班次:领表 4 起草行 / 表 3 只录协议行 → 查号 → 录协议替身 → 写表 4。""" from __future__ import annotations from collections import OrderedDict from dataclasses import dataclass from typing import Callable from persist import ( KIND_FAIL, AgreementOutcome, RebateGroup, agreement_ok, build_found, build_not_found, ) from repo import Claimed Finder = Callable[[str], bool] Recorder = Callable[[str], AgreementOutcome] @dataclass class JobSummary: claimed: int = 0 groups: int = 0 ok: int = 0 not_found: int = 0 failed: int = 0 def run_batch( repo, *, finder: Finder, recorder: Recorder, limit: int | None = None, ) -> JobSummary: claimed = repo.claim_pending(limit=limit) groups = group_claimed(claimed) summary = JobSummary( claimed=len(claimed.drafts) + len(claimed.protocols), groups=len(groups), ) for group in groups: try: found = bool(finder(group.contract_no)) except Exception: repo.save_group(build_not_found(group)) summary.not_found += 1 continue if not found: repo.save_group(build_not_found(group)) summary.not_found += 1 continue try: outcome = recorder(group.contract_no) except Exception as exc: outcome = AgreementOutcome(KIND_FAIL, None, None, str(exc)) if outcome is None: outcome = AgreementOutcome(KIND_FAIL, None, None, "协议录入失败") repo.save_group(build_found(group, outcome)) if agreement_ok(outcome): summary.ok += 1 else: summary.failed += 1 return summary def group_claimed(claimed: Claimed) -> list[RebateGroup]: groups: OrderedDict[str, RebateGroup] = OrderedDict() def bucket(contract_no: str) -> RebateGroup: number = (contract_no or "").strip() if number not in groups: groups[number] = RebateGroup(contract_no=number) return groups[number] for row in claimed.drafts: bucket(row["contract_no"]).drafts.append(row) for row in claimed.protocols: bucket(row["contract_no"]).protocols.append(row) return list(groups.values())