pl_plan_arrange_copy_flags.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. """运输计划列表:批量计算「复制给司机 / 现场 / 磅单二维码」按钮显示标志。
  2. 语义与查看弹窗 / usePlPlanArrangeActions / build_site_records_for_arranged_plan 对齐:
  3. - 已安排且未取消,且存在司机链接或现场记录或地磅出入库关联时,显示「复制给司机」
  4. - 有现场链接记录(含运输单按段 site_access_token、出入库单现场令牌回退)时显示「复制给现场」
  5. - 有勾选生成地磅单的出入库关联(db_purchase_plan_id 非空)时显示「复制磅单二维码」
  6. """
  7. from __future__ import annotations
  8. from dataclasses import dataclass
  9. from typing import Iterable
  10. from sqlalchemy.orm import Session
  11. from app.models.pl_models import (
  12. PlInboundOrder,
  13. PlOutboundOrder,
  14. PlPlan,
  15. PlTransport,
  16. PlTransportDbLink,
  17. PlTransportSiteLink,
  18. )
  19. from app.services.pl_dibang_bridge_service import plan_has_load_stop
  20. from app.services.pl_internal_customer import plan_requires_site_link
  21. @dataclass(frozen=True)
  22. class PlanArrangeCopyFlags:
  23. can_copy_to_driver: bool = False
  24. can_copy_to_site: bool = False
  25. can_copy_pound_qrcode: bool = False
  26. def load_plan_arrange_copy_flags(
  27. db: Session,
  28. tenant_no: str,
  29. plans: Iterable[PlPlan],
  30. ) -> dict[str, PlanArrangeCopyFlags]:
  31. plan_list = list(plans)
  32. empty = PlanArrangeCopyFlags()
  33. result: dict[str, PlanArrangeCopyFlags] = {plan.id: empty for plan in plan_list}
  34. candidates = [
  35. plan
  36. for plan in plan_list
  37. if plan.transport_status == "arranged"
  38. and plan.follow_status != "cancelled"
  39. and (plan.transport_id or "").strip()
  40. ]
  41. if not candidates:
  42. return result
  43. plan_ids = [plan.id for plan in candidates]
  44. transport_ids = list({plan.transport_id for plan in candidates if plan.transport_id})
  45. transports = (
  46. db.query(PlTransport)
  47. .filter(
  48. PlTransport.id.in_(transport_ids),
  49. PlTransport.tenant_no == tenant_no,
  50. PlTransport.del_flag == 0,
  51. )
  52. .all()
  53. )
  54. transport_by_id = {
  55. row.id: row for row in transports if row.transport_status != "cancelled"
  56. }
  57. site_plan_ids: set[str] = {
  58. plan_id
  59. for (plan_id,) in db.query(PlTransportSiteLink.plan_id)
  60. .filter(
  61. PlTransportSiteLink.tenant_no == tenant_no,
  62. PlTransportSiteLink.plan_id.in_(plan_ids),
  63. PlTransportSiteLink.del_flag == 0,
  64. )
  65. .distinct()
  66. .all()
  67. }
  68. inventory_plan_ids: set[str] = set()
  69. dibang_plan_ids: set[str] = set()
  70. for plan_id, db_purchase_plan_id in (
  71. db.query(PlTransportDbLink.plan_id, PlTransportDbLink.db_purchase_plan_id)
  72. .filter(
  73. PlTransportDbLink.tenant_no == tenant_no,
  74. PlTransportDbLink.plan_id.in_(plan_ids),
  75. PlTransportDbLink.del_flag == 0,
  76. )
  77. .all()
  78. ):
  79. inventory_plan_ids.add(plan_id)
  80. if (db_purchase_plan_id or "").strip():
  81. dibang_plan_ids.add(plan_id)
  82. # 与 get_plan_arrange_info 一致:无 site_link 时,用未作废出入库单的现场令牌回退
  83. inbound_ids = [
  84. plan.id
  85. for plan in candidates
  86. if plan.plan_type == "inbound" and plan.id not in site_plan_ids
  87. ]
  88. if inbound_ids:
  89. for plan_id, site_access_token, status in (
  90. db.query(PlInboundOrder.plan_id, PlInboundOrder.site_access_token, PlInboundOrder.status)
  91. .filter(
  92. PlInboundOrder.tenant_no == tenant_no,
  93. PlInboundOrder.plan_id.in_(inbound_ids),
  94. PlInboundOrder.del_flag == 0,
  95. )
  96. .all()
  97. ):
  98. if status != "voided" and (site_access_token or "").strip():
  99. site_plan_ids.add(plan_id)
  100. outbound_ids = [
  101. plan.id
  102. for plan in candidates
  103. if plan.plan_type == "outbound" and plan.id not in site_plan_ids
  104. ]
  105. if outbound_ids:
  106. for plan_id, site_access_token, status in (
  107. db.query(
  108. PlOutboundOrder.plan_id, PlOutboundOrder.site_access_token, PlOutboundOrder.status
  109. )
  110. .filter(
  111. PlOutboundOrder.tenant_no == tenant_no,
  112. PlOutboundOrder.plan_id.in_(outbound_ids),
  113. PlOutboundOrder.del_flag == 0,
  114. )
  115. .all()
  116. ):
  117. if status != "voided" and (site_access_token or "").strip():
  118. site_plan_ids.add(plan_id)
  119. # 与 build_site_records_for_arranged_plan 一致:无 site_link 行时,装货段 + 运输单 site_access_token
  120. for plan in candidates:
  121. if plan.id in site_plan_ids:
  122. continue
  123. if not plan_requires_site_link(db, tenant_no=tenant_no, plan=plan):
  124. continue
  125. transport = transport_by_id.get(plan.transport_id or "")
  126. if not transport:
  127. continue
  128. if not plan_has_load_stop(db, tenant_no, plan.id):
  129. continue
  130. if (transport.site_access_token or "").strip():
  131. site_plan_ids.add(plan.id)
  132. for plan in candidates:
  133. transport = transport_by_id.get(plan.transport_id or "")
  134. if not transport:
  135. continue
  136. has_driver = bool((transport.driver_access_token or "").strip())
  137. has_site = plan.id in site_plan_ids
  138. has_inventory = plan.id in inventory_plan_ids
  139. has_dibang = plan.id in dibang_plan_ids
  140. if not (has_driver or has_site or has_inventory):
  141. continue
  142. result[plan.id] = PlanArrangeCopyFlags(
  143. can_copy_to_driver=True,
  144. can_copy_to_site=has_site,
  145. can_copy_pound_qrcode=has_dibang,
  146. )
  147. return result