129 lines
4.0 KiB
Python
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 "细单导入失败"
|