AutoRpa/rpa_rebate/job.py

129 lines
4.0 KiB
Python

"""返利班次:领表 4 起草行 / 表 3 只录协议行 → runner 查号+总单+细单+送审 → 写表 4。"""
from __future__ import annotations
from collections import OrderedDict
from dataclasses import dataclass
from typing import Callable
from persist import (
KIND_FAIL,
KIND_NOT_FOUND,
KIND_OK,
AgreementOutcome,
RebateGroup,
agreement_ok,
build_found,
build_not_found,
)
from repo import Claimed
Runner = Callable[[RebateGroup], AgreementOutcome]
Header = Callable[[str], str]
Importer = Callable[[str], object]
Submitter = Callable[[str], object]
@dataclass
class JobSummary:
claimed: int = 0
groups: int = 0
ok: int = 0
not_found: int = 0
failed: int = 0
def recorder(
contract_no: str,
header: Header,
importer: Importer,
submitter: Submitter,
) -> AgreementOutcome:
try:
order_no = (header(contract_no) or "").strip()
except Exception as exc:
return AgreementOutcome(KIND_FAIL, None, None, str(exc) or "总单失败")
if not order_no:
return AgreementOutcome(KIND_FAIL, None, None, "总单未拿到号")
try:
imported = importer(contract_no)
except Exception as exc:
return AgreementOutcome(KIND_FAIL, None, None, str(exc) or "细单导入失败")
if not _import_ok(imported):
return AgreementOutcome(KIND_FAIL, None, None, _import_reason(imported))
try:
submitted = submitter(contract_no)
except Exception as exc:
return AgreementOutcome(KIND_FAIL, None, None, str(exc) or "送审失败")
if submitted is None or not bool(getattr(submitted, "submitted", False)):
return AgreementOutcome(KIND_FAIL, None, None, "送审失败")
agreement_id = (getattr(submitted, "agreement_id", None) or "").strip()
if not agreement_id:
return AgreementOutcome(KIND_FAIL, None, None, "送审未读到总单号")
agreement_dtl_id = (getattr(submitted, "agreement_dtl_id", None) or "").strip() or None
return AgreementOutcome(KIND_OK, agreement_id, agreement_dtl_id, None)
def run_batch(
repo,
*,
runner: Runner,
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:
outcome = runner(group)
except Exception as exc:
outcome = AgreementOutcome(KIND_FAIL, None, None, str(exc))
if outcome is None:
outcome = AgreementOutcome(KIND_FAIL, None, None, "协议录入失败")
if outcome.kind == KIND_NOT_FOUND:
repo.save_group(build_not_found(group))
summary.not_found += 1
continue
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())
def _import_ok(result: object) -> bool:
if result is None:
return False
success = int(getattr(result, "success_count", 0) or 0)
fail = int(getattr(result, "fail_count", 0) or 0)
parsed = getattr(result, "parsed", True)
return bool(parsed) and success > 0 and fail == 0
def _import_reason(result: object) -> str:
if result is None:
return "细单导入失败"
reasons = getattr(result, "fail_reasons", ()) or ()
if reasons:
return str(reasons[0]).strip() or "细单导入失败"
return "细单导入失败"