pl_menu_badge_service.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. """运输管理 - 侧栏菜单角标统计"""
  2. from __future__ import annotations
  3. from collections import defaultdict
  4. from typing import Dict, List, Optional
  5. from sqlalchemy.orm import Session
  6. from app.models.pl_models import (
  7. PlDocumentReminder,
  8. PlFreightSettlement,
  9. PlPlan,
  10. PlPlanProduct,
  11. PlVehicleMaintenanceApply,
  12. PlVehicleRechargeApply,
  13. PlVehicleUreaApply,
  14. )
  15. from app.services.pl_cert_service import scan_cert_expiry
  16. from app.services.pl_data_scope import apply_pl_scope
  17. from app.services.pl_freight_settlement_service import can_user_approve_settlement
  18. from app.services.pl_plan_service import plan_has_hazard_products
  19. MENU_CODE_DOCUMENT = "pl:document:list"
  20. MENU_CODE_FREIGHT_APPROVAL = "pl:freight-approval:list"
  21. MENU_CODE_FREIGHT_DISBURSE = "pl:freight-disburse:list"
  22. MENU_CODE_RECHARGE_RECORD = "pl:vehicle-recharge-record:list"
  23. MENU_CODE_MAINT_PROCESS = "pl:vehicle-maintenance-process:list"
  24. MENU_CODE_UREA_DISBURSE = "pl:vehicle-urea-disburse:list"
  25. MENU_CODE_PLAN_CERT = "pl:plan-cert:list"
  26. MENU_CODE_PLAN_ARCHIVE = "pl:plan-archive:list"
  27. MENU_CODE_PLAN_CONTRACT = "pl:plan-contract:list"
  28. SUPPORTED_MENU_CODES = {
  29. MENU_CODE_DOCUMENT,
  30. MENU_CODE_FREIGHT_APPROVAL,
  31. MENU_CODE_FREIGHT_DISBURSE,
  32. MENU_CODE_RECHARGE_RECORD,
  33. MENU_CODE_MAINT_PROCESS,
  34. MENU_CODE_UREA_DISBURSE,
  35. MENU_CODE_PLAN_CERT,
  36. MENU_CODE_PLAN_ARCHIVE,
  37. MENU_CODE_PLAN_CONTRACT,
  38. }
  39. def _count_pending_applies(db: Session, tenant_no: str, user, model) -> int:
  40. query = db.query(model).filter(
  41. model.tenant_no == tenant_no,
  42. model.del_flag == 0,
  43. model.status == "pending",
  44. )
  45. query = apply_pl_scope(query, model, user, db, tenant_no)
  46. return query.count()
  47. def count_document_reminder_expired_pending(db: Session, tenant_no: str, user) -> int:
  48. scan_cert_expiry(db, tenant_no)
  49. db.flush()
  50. query = db.query(PlDocumentReminder).filter(
  51. PlDocumentReminder.tenant_no == tenant_no,
  52. PlDocumentReminder.del_flag == 0,
  53. PlDocumentReminder.status == "pending",
  54. PlDocumentReminder.days_remaining <= 0,
  55. )
  56. query = apply_pl_scope(query, PlDocumentReminder, user, db, tenant_no)
  57. return query.count()
  58. def count_freight_approval_pending(db: Session, tenant_no: str, user_id: str) -> int:
  59. if not user_id:
  60. return 0
  61. rows = (
  62. db.query(PlFreightSettlement.id)
  63. .filter(
  64. PlFreightSettlement.tenant_no == tenant_no,
  65. PlFreightSettlement.del_flag == 0,
  66. PlFreightSettlement.settlement_status == "approving",
  67. )
  68. .all()
  69. )
  70. total = 0
  71. for (settlement_id,) in rows:
  72. if can_user_approve_settlement(db, settlement_id, user_id):
  73. total += 1
  74. return total
  75. def count_plan_cert_pending(db: Session, tenant_no: str, user) -> int:
  76. query = db.query(PlPlan).filter(
  77. PlPlan.tenant_no == tenant_no,
  78. PlPlan.del_flag == 0,
  79. PlPlan.hazard_cert_ready == 0,
  80. PlPlan.archive_status != "archived",
  81. PlPlan.follow_status != "cancelled",
  82. )
  83. query = apply_pl_scope(query, PlPlan, user, db, tenant_no)
  84. plans = query.all()
  85. if not plans:
  86. return 0
  87. plan_ids = [plan.id for plan in plans]
  88. products_by_plan: dict[str, list[PlPlanProduct]] = defaultdict(list)
  89. product_rows = (
  90. db.query(PlPlanProduct)
  91. .filter(
  92. PlPlanProduct.plan_id.in_(plan_ids),
  93. PlPlanProduct.del_flag == 0,
  94. )
  95. .all()
  96. )
  97. for product in product_rows:
  98. products_by_plan[str(product.plan_id)].append(product)
  99. total = 0
  100. for plan in plans:
  101. products = products_by_plan.get(str(plan.id), [])
  102. if plan_has_hazard_products(db, tenant_no, products):
  103. total += 1
  104. return total
  105. def count_plan_archive_pending(db: Session, tenant_no: str, user) -> int:
  106. """待归档角标:与归档列表一致,仅统计已安排运输且未归档的计划。"""
  107. query = db.query(PlPlan).filter(
  108. PlPlan.tenant_no == tenant_no,
  109. PlPlan.del_flag == 0,
  110. PlPlan.archive_status == "unarchived",
  111. PlPlan.follow_status != "cancelled",
  112. PlPlan.transport_status == "arranged",
  113. )
  114. query = apply_pl_scope(query, PlPlan, user, db, tenant_no)
  115. return query.count()
  116. def count_plan_contract_gaps(db: Session, tenant_no: str, user) -> int:
  117. from app.services.pl_plan_contract_gap import plan_has_contract_gap_exists
  118. query = db.query(PlPlan).filter(
  119. PlPlan.tenant_no == tenant_no,
  120. PlPlan.del_flag == 0,
  121. PlPlan.follow_status != "cancelled",
  122. plan_has_contract_gap_exists(tenant_no),
  123. )
  124. query = apply_pl_scope(query, PlPlan, user, db, tenant_no)
  125. return query.count()
  126. def count_freight_disburse_pending(db: Session, tenant_no: str, user) -> int:
  127. query = db.query(PlFreightSettlement).filter(
  128. PlFreightSettlement.tenant_no == tenant_no,
  129. PlFreightSettlement.del_flag == 0,
  130. PlFreightSettlement.settlement_status == "approved",
  131. )
  132. query = apply_pl_scope(query, PlFreightSettlement, user, db, tenant_no)
  133. return query.count()
  134. def build_menu_badge_counts(
  135. db: Session,
  136. *,
  137. tenant_no: str,
  138. user,
  139. user_id: str,
  140. menu_codes: Optional[List[str]] = None,
  141. ) -> Dict[str, int]:
  142. requested = {c.strip() for c in (menu_codes or []) if c and c.strip()}
  143. if not requested:
  144. requested = set(SUPPORTED_MENU_CODES)
  145. requested &= SUPPORTED_MENU_CODES
  146. counts: Dict[str, int] = {}
  147. if MENU_CODE_DOCUMENT in requested:
  148. counts[MENU_CODE_DOCUMENT] = count_document_reminder_expired_pending(db, tenant_no, user)
  149. if MENU_CODE_FREIGHT_APPROVAL in requested:
  150. counts[MENU_CODE_FREIGHT_APPROVAL] = count_freight_approval_pending(db, tenant_no, user_id)
  151. if MENU_CODE_FREIGHT_DISBURSE in requested:
  152. counts[MENU_CODE_FREIGHT_DISBURSE] = count_freight_disburse_pending(db, tenant_no, user)
  153. if MENU_CODE_RECHARGE_RECORD in requested:
  154. counts[MENU_CODE_RECHARGE_RECORD] = _count_pending_applies(
  155. db, tenant_no, user, PlVehicleRechargeApply
  156. )
  157. if MENU_CODE_MAINT_PROCESS in requested:
  158. counts[MENU_CODE_MAINT_PROCESS] = _count_pending_applies(
  159. db, tenant_no, user, PlVehicleMaintenanceApply
  160. )
  161. if MENU_CODE_UREA_DISBURSE in requested:
  162. counts[MENU_CODE_UREA_DISBURSE] = _count_pending_applies(
  163. db, tenant_no, user, PlVehicleUreaApply
  164. )
  165. if MENU_CODE_PLAN_CERT in requested:
  166. counts[MENU_CODE_PLAN_CERT] = count_plan_cert_pending(db, tenant_no, user)
  167. if MENU_CODE_PLAN_ARCHIVE in requested:
  168. counts[MENU_CODE_PLAN_ARCHIVE] = count_plan_archive_pending(db, tenant_no, user)
  169. if MENU_CODE_PLAN_CONTRACT in requested:
  170. counts[MENU_CODE_PLAN_CONTRACT] = count_plan_contract_gaps(db, tenant_no, user)
  171. return counts