"""fq → fgd:计算并期变更指令(纯函数,不写库)""" from __future__ import annotations from decimal import Decimal from typing import Any, Dict, Iterable, List, Mapping from app.services.protocol_transfer_status import schedule_paid_and_arrears 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 plan_fq_to_fgd_merge( *, schedules: Iterable[Mapping[str, Any]], transfers: Iterable[Mapping[str, Any]], ) -> Dict[str, Any]: sch_list = [ s for s in schedules if _status(s, "scheduleStatus", "schedule_status") != "void" and not s.get("_deleted") ] sch_list.sort(key=lambda s: int(s.get("scheduleNo") or s.get("schedule_no") or 0)) tr_list = list(transfers or []) by_sch: Dict[str, List[Mapping[str, Any]]] = {} for t in tr_list: sid = str(t.get("scheduleId") or t.get("schedule_id") or "") by_sch.setdefault(sid, []).append(t) def _arrears(s: Mapping[str, Any]) -> Decimal: sid = str(s.get("id") or "") confirmed = [ _dec(t.get("transferAmount", t.get("transfer_amount"))) for t in by_sch.get(sid, []) if _status(t, "payStatus", "pay_status") == "yjf" ] _paid, arr = schedule_paid_and_arrears( due=s.get("dueAmount", s.get("due_amount")), confirmed_transfers=confirmed, ) arr_d = _dec(arr) return arr_d if arr_d > 0 else Decimal("0") target = None for s in sch_list: if _arrears(s) > 0: target = s break if target is None: for s in sch_list: if _status(s, "scheduleStatus", "schedule_status") != "cleared": target = s break if target is None: raise ValueError("没有可合并的未清期次") target_no = int(target.get("scheduleNo") or target.get("schedule_no") or 0) later = [ s for s in sch_list if int(s.get("scheduleNo") or s.get("schedule_no") or 0) > target_no ] for s in later: sid = str(s.get("id") or "") if any( _status(t, "payStatus", "pay_status") == "yjf" for t in by_sch.get(sid, []) ): raise ValueError("后续期已有已确认转账,无法合并为分改趸") remain = sum((_arrears(s) for s in sch_list), Decimal("0")) tid = str(target.get("id") or "") target_confirmed = sum( ( _dec(t.get("transferAmount", t.get("transfer_amount"))) for t in by_sch.get(tid, []) if _status(t, "payStatus", "pay_status") == "yjf" ), Decimal("0"), ) new_due = target_confirmed + remain soft_ids = [str(s.get("id") or "") for s in later if str(s.get("id") or "")] soft_set = set(soft_ids) kept = [s for s in sch_list if str(s.get("id") or "") not in soft_set] return { "targetScheduleId": tid, "newDueAmount": new_due, "softDeleteScheduleIds": soft_ids, "newInstallmentCount": len(kept), }