sample_payment_fgd_convert.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. """fq → fgd:计算并期变更指令(纯函数,不写库)"""
  2. from __future__ import annotations
  3. from decimal import Decimal
  4. from typing import Any, Dict, Iterable, List, Mapping
  5. from app.services.protocol_transfer_status import schedule_paid_and_arrears
  6. def _dec(v: Any) -> Decimal:
  7. if v is None or v == "":
  8. return Decimal("0")
  9. return Decimal(str(v))
  10. def _status(row: Mapping[str, Any], *keys: str) -> str:
  11. for k in keys:
  12. if k in row and row[k] is not None:
  13. return str(row[k]).strip()
  14. return ""
  15. def plan_fq_to_fgd_merge(
  16. *,
  17. schedules: Iterable[Mapping[str, Any]],
  18. transfers: Iterable[Mapping[str, Any]],
  19. ) -> Dict[str, Any]:
  20. sch_list = [
  21. s
  22. for s in schedules
  23. if _status(s, "scheduleStatus", "schedule_status") != "void"
  24. and not s.get("_deleted")
  25. ]
  26. sch_list.sort(key=lambda s: int(s.get("scheduleNo") or s.get("schedule_no") or 0))
  27. tr_list = list(transfers or [])
  28. by_sch: Dict[str, List[Mapping[str, Any]]] = {}
  29. for t in tr_list:
  30. sid = str(t.get("scheduleId") or t.get("schedule_id") or "")
  31. by_sch.setdefault(sid, []).append(t)
  32. def _arrears(s: Mapping[str, Any]) -> Decimal:
  33. sid = str(s.get("id") or "")
  34. confirmed = [
  35. _dec(t.get("transferAmount", t.get("transfer_amount")))
  36. for t in by_sch.get(sid, [])
  37. if _status(t, "payStatus", "pay_status") == "yjf"
  38. ]
  39. _paid, arr = schedule_paid_and_arrears(
  40. due=s.get("dueAmount", s.get("due_amount")),
  41. confirmed_transfers=confirmed,
  42. )
  43. arr_d = _dec(arr)
  44. return arr_d if arr_d > 0 else Decimal("0")
  45. target = None
  46. for s in sch_list:
  47. if _arrears(s) > 0:
  48. target = s
  49. break
  50. if target is None:
  51. for s in sch_list:
  52. if _status(s, "scheduleStatus", "schedule_status") != "cleared":
  53. target = s
  54. break
  55. if target is None:
  56. raise ValueError("没有可合并的未清期次")
  57. target_no = int(target.get("scheduleNo") or target.get("schedule_no") or 0)
  58. later = [
  59. s
  60. for s in sch_list
  61. if int(s.get("scheduleNo") or s.get("schedule_no") or 0) > target_no
  62. ]
  63. for s in later:
  64. sid = str(s.get("id") or "")
  65. if any(
  66. _status(t, "payStatus", "pay_status") == "yjf" for t in by_sch.get(sid, [])
  67. ):
  68. raise ValueError("后续期已有已确认转账,无法合并为分改趸")
  69. remain = sum((_arrears(s) for s in sch_list), Decimal("0"))
  70. tid = str(target.get("id") or "")
  71. target_confirmed = sum(
  72. (
  73. _dec(t.get("transferAmount", t.get("transfer_amount")))
  74. for t in by_sch.get(tid, [])
  75. if _status(t, "payStatus", "pay_status") == "yjf"
  76. ),
  77. Decimal("0"),
  78. )
  79. new_due = target_confirmed + remain
  80. soft_ids = [str(s.get("id") or "") for s in later if str(s.get("id") or "")]
  81. soft_set = set(soft_ids)
  82. kept = [s for s in sch_list if str(s.get("id") or "") not in soft_set]
  83. return {
  84. "targetScheduleId": tid,
  85. "newDueAmount": new_due,
  86. "softDeleteScheduleIds": soft_ids,
  87. "newInstallmentCount": len(kept),
  88. }