pl_plan_print_service.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. """运输计划打印数据聚合(一车多卸)"""
  2. from __future__ import annotations
  3. from decimal import Decimal
  4. from typing import Optional
  5. from sqlalchemy.orm import Session
  6. from app.schemas.pl.plan import (
  7. DeliveryPrintCustomerOption,
  8. DeliverySlipPrintRead,
  9. PlanDeliveryRead,
  10. PlanStopProductRead,
  11. )
  12. from app.services.pl_plan_stop_service import load_plan_stops_grouped
  13. def _customer_key(customer_id: Optional[str], customer_name: Optional[str]) -> str:
  14. cid = (customer_id or "").strip()
  15. if cid:
  16. return f"id:{cid}"
  17. name = (customer_name or "").strip()
  18. if name:
  19. return f"name:{name}"
  20. return ""
  21. def _resolve_customer_id(customer_id: Optional[str], customer_name: Optional[str]) -> str:
  22. cid = (customer_id or "").strip()
  23. if cid:
  24. return cid
  25. return (customer_name or "").strip()
  26. def list_delivery_print_customers(
  27. deliveries: list[PlanDeliveryRead],
  28. ) -> list[DeliveryPrintCustomerOption]:
  29. grouped: dict[str, dict] = {}
  30. for delivery in deliveries:
  31. key = _customer_key(delivery.customer_id, delivery.customer_name)
  32. if not key:
  33. continue
  34. if key not in grouped:
  35. grouped[key] = {
  36. "customer_id": _resolve_customer_id(delivery.customer_id, delivery.customer_name),
  37. "customer_name": (delivery.customer_name or "").strip() or "—",
  38. "delivery_count": 0,
  39. "min_seq": delivery.seq or 9999,
  40. }
  41. grouped[key]["delivery_count"] += 1
  42. grouped[key]["min_seq"] = min(grouped[key]["min_seq"], delivery.seq or 9999)
  43. rows = sorted(grouped.values(), key=lambda item: (item["min_seq"], item["customer_name"]))
  44. return [
  45. DeliveryPrintCustomerOption(
  46. customer_id=item["customer_id"],
  47. customer_name=item["customer_name"],
  48. delivery_count=item["delivery_count"],
  49. )
  50. for item in rows
  51. ]
  52. def _delivery_matches_customer(
  53. delivery: PlanDeliveryRead,
  54. customer_id: str,
  55. ) -> bool:
  56. target = (customer_id or "").strip()
  57. if not target:
  58. return False
  59. delivery_id = (delivery.customer_id or "").strip()
  60. if delivery_id and delivery_id == target:
  61. return True
  62. delivery_name = (delivery.customer_name or "").strip()
  63. return not delivery_id and delivery_name == target
  64. def _merge_stop_products(products: list[PlanStopProductRead]) -> list[PlanStopProductRead]:
  65. merged: dict[str, PlanStopProductRead] = {}
  66. order: list[str] = []
  67. for product in products:
  68. key = (product.product_id or "").strip() or product.id
  69. if key not in merged:
  70. merged[key] = product.model_copy(deep=True)
  71. order.append(key)
  72. continue
  73. existing = merged[key]
  74. for field in ("plan_qty", "outbound_qty", "inbound_qty"):
  75. left = getattr(existing, field, None)
  76. right = getattr(product, field, None)
  77. if left is None and right is None:
  78. continue
  79. total = (left or Decimal("0")) + (right or Decimal("0"))
  80. setattr(existing, field, total)
  81. if not (existing.remark or "").strip() and (product.remark or "").strip():
  82. existing.remark = product.remark
  83. return [merged[key] for key in order]
  84. def build_delivery_slip_print(
  85. db: Session,
  86. *,
  87. tenant_no: str,
  88. plan_id: str,
  89. plan_no: str,
  90. plan_date: Optional[str],
  91. customer_id: str,
  92. ) -> DeliverySlipPrintRead:
  93. stops = load_plan_stops_grouped(db, tenant_no, [plan_id]).get(plan_id)
  94. if not stops:
  95. raise ValueError("计划未配置送货段")
  96. _load, deliveries = stops
  97. if not deliveries:
  98. raise ValueError("计划未配置送货段")
  99. matched = [item for item in deliveries if _delivery_matches_customer(item, customer_id)]
  100. if not matched:
  101. raise ValueError("未找到匹配的送货客户")
  102. matched.sort(key=lambda item: (item.seq or 9999, item.create_time))
  103. primary = matched[0]
  104. products: list[PlanStopProductRead] = []
  105. for delivery in matched:
  106. products.extend(delivery.products or [])
  107. merged_products = _merge_stop_products(products)
  108. return DeliverySlipPrintRead(
  109. plan_id=plan_id,
  110. plan_no=plan_no or "",
  111. plan_date=plan_date,
  112. customer_id=_resolve_customer_id(primary.customer_id, primary.customer_name),
  113. receiving_unit=(primary.customer_name or "").strip(),
  114. receive_contact=(primary.contact or "").strip(),
  115. delivery_address=(primary.address or "").strip() or None,
  116. products=merged_products,
  117. )
  118. def get_delivery_slip_print_context(
  119. db: Session,
  120. *,
  121. tenant_no: str,
  122. plan_id: str,
  123. plan_no: str,
  124. plan_date: Optional[str],
  125. customer_id: Optional[str] = None,
  126. ) -> DeliverySlipPrintRead:
  127. stops = load_plan_stops_grouped(db, tenant_no, [plan_id]).get(plan_id)
  128. deliveries = stops[1] if stops else []
  129. customers = list_delivery_print_customers(deliveries)
  130. if not customer_id:
  131. return DeliverySlipPrintRead(
  132. plan_id=plan_id,
  133. plan_no=plan_no or "",
  134. plan_date=plan_date,
  135. customers=customers,
  136. )
  137. detail = build_delivery_slip_print(
  138. db,
  139. tenant_no=tenant_no,
  140. plan_id=plan_id,
  141. plan_no=plan_no,
  142. plan_date=plan_date,
  143. customer_id=customer_id,
  144. )
  145. detail.customers = customers
  146. return detail