pl_plan_list_attachments.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. """运输计划列表:聚合司机/现场/出入库现场等单据附件。"""
  2. from __future__ import annotations
  3. import json
  4. from typing import Optional
  5. from sqlalchemy.orm import Session
  6. from app.models.pl_models import (
  7. PlInboundOrder,
  8. PlOutboundOrder,
  9. PlPlan,
  10. PlPlanDelivery,
  11. PlTransport,
  12. PlTransportDriverStopFeedback,
  13. PlTransportSiteLink,
  14. PlTransportSiteStopFeedback,
  15. )
  16. def _parse_image_urls(raw: Optional[str]) -> list[str]:
  17. if not raw:
  18. return []
  19. text = str(raw).strip()
  20. if not text:
  21. return []
  22. try:
  23. parsed = json.loads(text)
  24. if isinstance(parsed, list):
  25. return [str(item).strip() for item in parsed if str(item).strip()]
  26. except json.JSONDecodeError:
  27. pass
  28. return [item.strip() for item in text.split(",") if item.strip()]
  29. def _short_label(text: str, max_len: int = 8) -> str:
  30. value = (text or "").strip()
  31. if not value:
  32. return "现场"
  33. if len(value) <= max_len:
  34. return value
  35. return f"{value[: max_len - 1]}…"
  36. def _stop_source_label(
  37. stop_type: str,
  38. stop_id: str,
  39. source: str,
  40. delivery_seq_by_id: dict[str, int],
  41. ) -> str:
  42. normalized = (stop_type or "").strip()
  43. if normalized == "load":
  44. return f"装货·{source}"
  45. seq = delivery_seq_by_id.get((stop_id or "").strip())
  46. if seq:
  47. return f"送货#{seq}·{source}"
  48. return f"送货·{source}"
  49. def _stop_sort_key(
  50. stop_type: str,
  51. stop_id: str,
  52. source: str,
  53. delivery_seq_by_id: dict[str, int],
  54. ) -> tuple[int, int, int, str]:
  55. normalized = (stop_type or "").strip()
  56. stop_rank = 0 if normalized == "load" else 1
  57. seq = delivery_seq_by_id.get((stop_id or "").strip(), 999) if normalized != "load" else 0
  58. source_rank = 0 if source == "司机" else 1
  59. return (stop_rank, seq, source_rank, stop_id or "")
  60. def load_plan_document_attachments_by_plan_ids(
  61. db: Session,
  62. *,
  63. tenant_no: str,
  64. plan_ids: list[str],
  65. ) -> dict[str, list[dict[str, str]]]:
  66. if not plan_ids:
  67. return {}
  68. result: dict[str, list[dict[str, str]]] = {plan_id: [] for plan_id in plan_ids}
  69. seen: dict[str, set[str]] = {plan_id: set() for plan_id in plan_ids}
  70. pending: dict[str, list[tuple[tuple[int, int, int, str], str, list[str]]]] = {
  71. plan_id: [] for plan_id in plan_ids
  72. }
  73. def add(plan_id: str, label: str, urls: list[str], sort_key: tuple[int, int, int, str]) -> None:
  74. if plan_id not in pending:
  75. return
  76. cleaned = [url for url in urls if url]
  77. if not cleaned:
  78. return
  79. pending[plan_id].append((sort_key, label, cleaned))
  80. def flush(plan_id: str) -> None:
  81. bucket = result.get(plan_id)
  82. if bucket is None:
  83. return
  84. for _, label, urls in sorted(pending.get(plan_id, []), key=lambda item: item[0]):
  85. for url in urls:
  86. if url in seen[plan_id]:
  87. continue
  88. seen[plan_id].add(url)
  89. bucket.append({"url": url, "label": label})
  90. delivery_seq_by_id: dict[str, int] = {}
  91. delivery_rows = (
  92. db.query(PlPlanDelivery.id, PlPlanDelivery.plan_id, PlPlanDelivery.seq)
  93. .filter(
  94. PlPlanDelivery.plan_id.in_(plan_ids),
  95. PlPlanDelivery.tenant_no == tenant_no,
  96. PlPlanDelivery.del_flag == 0,
  97. )
  98. .all()
  99. )
  100. for delivery_id, _plan_id, seq in delivery_rows:
  101. delivery_seq_by_id[str(delivery_id)] = int(seq or 0)
  102. plan_rows = (
  103. db.query(PlPlan.id, PlPlan.transport_id)
  104. .filter(
  105. PlPlan.id.in_(plan_ids),
  106. PlPlan.tenant_no == tenant_no,
  107. PlPlan.del_flag == 0,
  108. )
  109. .all()
  110. )
  111. transport_ids = [transport_id for _, transport_id in plan_rows if transport_id]
  112. plan_by_transport = {
  113. transport_id: plan_id for plan_id, transport_id in plan_rows if transport_id
  114. }
  115. if transport_ids:
  116. transports = (
  117. db.query(PlTransport)
  118. .filter(
  119. PlTransport.id.in_(transport_ids),
  120. PlTransport.tenant_no == tenant_no,
  121. PlTransport.del_flag == 0,
  122. )
  123. .all()
  124. )
  125. for transport in transports:
  126. plan_id = plan_by_transport.get(transport.id)
  127. if not plan_id:
  128. continue
  129. add(
  130. plan_id,
  131. "司机",
  132. _parse_image_urls(transport.feedback_images),
  133. (0, 0, 0, "transport"),
  134. )
  135. driver_rows = (
  136. db.query(PlTransportDriverStopFeedback)
  137. .filter(
  138. PlTransportDriverStopFeedback.plan_id.in_(plan_ids),
  139. PlTransportDriverStopFeedback.tenant_no == tenant_no,
  140. PlTransportDriverStopFeedback.del_flag == 0,
  141. )
  142. .all()
  143. )
  144. for row in driver_rows:
  145. label = _stop_source_label(row.stop_type, row.stop_id, "司机", delivery_seq_by_id)
  146. add(
  147. row.plan_id,
  148. label,
  149. _parse_image_urls(row.attachment_urls),
  150. _stop_sort_key(row.stop_type, row.stop_id, "司机", delivery_seq_by_id),
  151. )
  152. site_stop_rows = (
  153. db.query(PlTransportSiteStopFeedback)
  154. .filter(
  155. PlTransportSiteStopFeedback.plan_id.in_(plan_ids),
  156. PlTransportSiteStopFeedback.tenant_no == tenant_no,
  157. PlTransportSiteStopFeedback.del_flag == 0,
  158. )
  159. .all()
  160. )
  161. for row in site_stop_rows:
  162. label = _stop_source_label(row.stop_type, row.stop_id, "现场", delivery_seq_by_id)
  163. add(
  164. row.plan_id,
  165. label,
  166. _parse_image_urls(row.attachment_urls),
  167. _stop_sort_key(row.stop_type, row.stop_id, "现场", delivery_seq_by_id),
  168. )
  169. site_rows = (
  170. db.query(PlTransportSiteLink)
  171. .filter(
  172. PlTransportSiteLink.plan_id.in_(plan_ids),
  173. PlTransportSiteLink.tenant_no == tenant_no,
  174. PlTransportSiteLink.del_flag == 0,
  175. )
  176. .order_by(PlTransportSiteLink.order_num, PlTransportSiteLink.create_time)
  177. .all()
  178. )
  179. for row in site_rows:
  180. product = (row.product or "").strip()
  181. label = f"现场·{_short_label(product)}" if product else "现场"
  182. add(row.plan_id, label, _parse_image_urls(row.site_feedback_images), (2, 0, 1, row.id or ""))
  183. inbound_rows = (
  184. db.query(PlInboundOrder)
  185. .filter(
  186. PlInboundOrder.plan_id.in_(plan_ids),
  187. PlInboundOrder.tenant_no == tenant_no,
  188. PlInboundOrder.del_flag == 0,
  189. )
  190. .all()
  191. )
  192. for row in inbound_rows:
  193. add(row.plan_id, "入库现场", _parse_image_urls(row.site_feedback_images), (3, 0, 1, row.id or ""))
  194. outbound_rows = (
  195. db.query(PlOutboundOrder)
  196. .filter(
  197. PlOutboundOrder.plan_id.in_(plan_ids),
  198. PlOutboundOrder.tenant_no == tenant_no,
  199. PlOutboundOrder.del_flag == 0,
  200. )
  201. .all()
  202. )
  203. for row in outbound_rows:
  204. add(row.plan_id, "出库现场", _parse_image_urls(row.site_feedback_images), (3, 0, 2, row.id or ""))
  205. for plan_id in plan_ids:
  206. flush(plan_id)
  207. return result