"""样本缴费方案进度汇总(列表「按样本」维度)""" from __future__ import annotations from decimal import Decimal from typing import Any, Dict, Iterable, List, Mapping def _dec(v: Any) -> Decimal: if v is None or v == "": return Decimal("0") return Decimal(str(v)) def _status(row: Mapping[str, Any], *keys: str) -> str: for k in keys: if k in row and row[k] is not None: return str(row[k]).strip() return "" def compute_sample_payment_progress( *, schedules: Iterable[Mapping[str, Any]], transfers: Iterable[Mapping[str, Any]], ) -> Dict[str, Any]: """ progressStatus 优先级:yjq > bfyj > dfj(不含付款确认中) 已缴仅计 pay_status=yjf;有效期排除 schedule_status=void canConfirm:无 jfqrz,且存在有效未清期 """ sch_list: List[Mapping[str, Any]] = list(schedules or []) tr_list: List[Mapping[str, Any]] = list(transfers or []) active = [ s for s in sch_list if _status(s, "scheduleStatus", "schedule_status") != "void" ] active_schedule_count = len(active) cleared_schedule_count = sum( 1 for s in active if _status(s, "scheduleStatus", "schedule_status") == "cleared" ) paid = sum( ( _dec(t.get("transferAmount", t.get("transfer_amount"))) for t in tr_list if _status(t, "payStatus", "pay_status") == "yjf" ), Decimal("0"), ) due_total = sum((_dec(s.get("dueAmount", s.get("due_amount"))) for s in active), Decimal("0")) due_remain = due_total - paid if due_remain < 0: due_remain = Decimal("0") has_jfqrz = any(_status(t, "payStatus", "pay_status") == "jfqrz" for t in tr_list) if active_schedule_count > 0 and cleared_schedule_count == active_schedule_count: progress = "yjq" elif paid > 0: progress = "bfyj" else: progress = "dfj" can_confirm = (not has_jfqrz) and any( _status(s, "scheduleStatus", "schedule_status") != "cleared" for s in active ) return { "progressStatus": progress, "paidAmount": paid, "dueRemainAmount": due_remain, "clearedScheduleCount": cleared_schedule_count, "activeScheduleCount": active_schedule_count, "canConfirm": can_confirm, }