sample_payment_progress.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. """样本缴费方案进度汇总(列表「按样本」维度)"""
  2. from __future__ import annotations
  3. from decimal import Decimal
  4. from typing import Any, Dict, Iterable, List, Mapping
  5. def _dec(v: Any) -> Decimal:
  6. if v is None or v == "":
  7. return Decimal("0")
  8. return Decimal(str(v))
  9. def _status(row: Mapping[str, Any], *keys: str) -> str:
  10. for k in keys:
  11. if k in row and row[k] is not None:
  12. return str(row[k]).strip()
  13. return ""
  14. def _schedule_id(row: Mapping[str, Any]) -> str:
  15. return str(row.get("id") or row.get("scheduleId") or row.get("schedule_id") or "").strip()
  16. def _transfer_schedule_id(row: Mapping[str, Any]) -> str:
  17. return str(row.get("scheduleId") or row.get("schedule_id") or "").strip()
  18. def is_schedule_effectively_cleared(
  19. schedule: Mapping[str, Any],
  20. *,
  21. confirmed_paid: Decimal,
  22. ) -> bool:
  23. """
  24. 批次已缴清:显式 cleared,或财务确认实缴(yjf)已覆盖应缴。
  25. 兼容历史数据未回写 schedule_status 的情况。
  26. """
  27. if _status(schedule, "scheduleStatus", "schedule_status") == "cleared":
  28. return True
  29. due = _dec(schedule.get("dueAmount", schedule.get("due_amount")))
  30. return confirmed_paid >= due
  31. def compute_sample_payment_progress(
  32. *,
  33. schedules: Iterable[Mapping[str, Any]],
  34. transfers: Iterable[Mapping[str, Any]],
  35. ) -> Dict[str, Any]:
  36. """
  37. progressStatus 优先级:yjq > bfyj > dfj(不含付款确认中)
  38. 已缴仅计 pay_status=yjf;有效期排除 schedule_status=void
  39. 已缴清期:schedule_status=cleared,或该期 yjf 合计 >= 应缴
  40. canConfirm:无 jfqrz,且存在有效未清期
  41. """
  42. sch_list: List[Mapping[str, Any]] = list(schedules or [])
  43. tr_list: List[Mapping[str, Any]] = list(transfers or [])
  44. paid_by_schedule: Dict[str, Decimal] = {}
  45. paid = Decimal("0")
  46. for t in tr_list:
  47. if _status(t, "payStatus", "pay_status") != "yjf":
  48. continue
  49. amount = _dec(t.get("transferAmount", t.get("transfer_amount")))
  50. paid += amount
  51. sid = _transfer_schedule_id(t)
  52. if sid:
  53. paid_by_schedule[sid] = paid_by_schedule.get(sid, Decimal("0")) + amount
  54. active = [
  55. s
  56. for s in sch_list
  57. if _status(s, "scheduleStatus", "schedule_status") != "void"
  58. ]
  59. active_schedule_count = len(active)
  60. cleared_flags = [
  61. is_schedule_effectively_cleared(
  62. s,
  63. confirmed_paid=paid_by_schedule.get(_schedule_id(s), Decimal("0")),
  64. )
  65. for s in active
  66. ]
  67. cleared_schedule_count = sum(1 for cleared in cleared_flags if cleared)
  68. due_total = sum((_dec(s.get("dueAmount", s.get("due_amount"))) for s in active), Decimal("0"))
  69. due_remain = due_total - paid
  70. if due_remain < 0:
  71. due_remain = Decimal("0")
  72. has_jfqrz = any(_status(t, "payStatus", "pay_status") == "jfqrz" for t in tr_list)
  73. if active_schedule_count > 0 and cleared_schedule_count == active_schedule_count:
  74. progress = "yjq"
  75. elif paid > 0:
  76. progress = "bfyj"
  77. else:
  78. progress = "dfj"
  79. can_confirm = (not has_jfqrz) and any(not cleared for cleared in cleared_flags)
  80. return {
  81. "progressStatus": progress,
  82. "paidAmount": paid,
  83. "dueRemainAmount": due_remain,
  84. "clearedScheduleCount": cleared_schedule_count,
  85. "activeScheduleCount": active_schedule_count,
  86. "canConfirm": can_confirm,
  87. }