pl_customer_stop_pref_service.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. """客户装/送货物流默认记忆"""
  2. from __future__ import annotations
  3. import logging
  4. from decimal import Decimal
  5. from typing import Any, Mapping, Optional, Sequence
  6. from sqlalchemy.orm import Session
  7. from app.core.utils import generate_uuid
  8. from app.models.pl_models import PlCustomerStopPref
  9. logger = logging.getLogger(__name__)
  10. STOP_LOAD = "load"
  11. STOP_DELIVERY = "delivery"
  12. def _as_str(value: Any) -> str:
  13. return str(value or "").strip()
  14. def _row_to_item(stop_type: str, row: Mapping[str, Any]) -> Optional[dict]:
  15. customer_id = _as_str(row.get("customer_id") or row.get("customerId"))
  16. if not customer_id:
  17. return None
  18. lng = row.get("lng")
  19. lat = row.get("lat")
  20. return {
  21. "customer_id": customer_id,
  22. "customer_name": _as_str(row.get("customer_name") or row.get("customerName")) or None,
  23. "stop_type": stop_type,
  24. "address": (row.get("address") if row.get("address") is not None else "") or "",
  25. "lng": lng,
  26. "lat": lat,
  27. "contact": (row.get("contact") if row.get("contact") is not None else "") or "",
  28. "notes": (row.get("notes") if row.get("notes") is not None else "") or "",
  29. }
  30. def build_stop_pref_upsert_items(
  31. *,
  32. load: Optional[Mapping[str, Any]],
  33. deliveries: Optional[Sequence[Mapping[str, Any]]],
  34. ) -> list[dict]:
  35. """抽出待 upsert 条目;同客户同 stop_type 后者覆盖(保持列表中每种键一条)。"""
  36. merged: dict[tuple[str, str], dict] = {}
  37. if load:
  38. item = _row_to_item(STOP_LOAD, load)
  39. if item:
  40. merged[(item["customer_id"], item["stop_type"])] = item
  41. for row in deliveries or []:
  42. item = _row_to_item(STOP_DELIVERY, row)
  43. if item:
  44. merged[(item["customer_id"], item["stop_type"])] = item
  45. return list(merged.values())
  46. def get_customer_stop_pref(
  47. db: Session,
  48. tenant_no: str,
  49. customer_id: str,
  50. stop_type: str,
  51. ) -> Optional[PlCustomerStopPref]:
  52. cid = _as_str(customer_id)
  53. st = _as_str(stop_type)
  54. if not cid or st not in (STOP_LOAD, STOP_DELIVERY):
  55. return None
  56. return (
  57. db.query(PlCustomerStopPref)
  58. .filter(
  59. PlCustomerStopPref.tenant_no == tenant_no,
  60. PlCustomerStopPref.customer_id == cid,
  61. PlCustomerStopPref.stop_type == st,
  62. PlCustomerStopPref.del_flag == 0,
  63. )
  64. .first()
  65. )
  66. def upsert_customer_stop_pref(
  67. db: Session,
  68. *,
  69. tenant_no: str,
  70. item: Mapping[str, Any],
  71. user_id: Optional[str],
  72. dept_id: Optional[str],
  73. ) -> None:
  74. customer_id = _as_str(item.get("customer_id"))
  75. stop_type = _as_str(item.get("stop_type"))
  76. if not customer_id or stop_type not in (STOP_LOAD, STOP_DELIVERY):
  77. return
  78. row = get_customer_stop_pref(db, tenant_no, customer_id, stop_type)
  79. lng = item.get("lng")
  80. lat = item.get("lat")
  81. if row is None:
  82. row = PlCustomerStopPref(
  83. id=generate_uuid(),
  84. tenant_no=tenant_no,
  85. customer_id=customer_id,
  86. create_by=user_id,
  87. create_dept_id=dept_id or "",
  88. )
  89. db.add(row)
  90. row.customer_name = item.get("customer_name")
  91. row.stop_type = stop_type
  92. row.address = item.get("address") if item.get("address") is not None else ""
  93. row.lng = Decimal(str(lng)) if lng is not None and str(lng) != "" else None
  94. row.lat = Decimal(str(lat)) if lat is not None and str(lat) != "" else None
  95. row.contact = item.get("contact") if item.get("contact") is not None else ""
  96. row.notes = item.get("notes") if item.get("notes") is not None else ""
  97. row.update_by = user_id
  98. row.del_flag = 0
  99. def upsert_prefs_from_plan_stops(
  100. db: Session,
  101. *,
  102. tenant_no: str,
  103. load: Optional[Mapping[str, Any]],
  104. deliveries: Optional[Sequence[Mapping[str, Any]]],
  105. user_id: Optional[str],
  106. dept_id: Optional[str],
  107. ) -> None:
  108. """供 plan 保存调用;异常由调用方用 savepoint 吞掉。"""
  109. for item in build_stop_pref_upsert_items(load=load, deliveries=deliveries):
  110. upsert_customer_stop_pref(
  111. db,
  112. tenant_no=tenant_no,
  113. item=item,
  114. user_id=user_id,
  115. dept_id=dept_id,
  116. )
  117. def safe_upsert_prefs_from_plan_stops(
  118. db: Session,
  119. *,
  120. tenant_no: str,
  121. load: Optional[Any],
  122. deliveries: Optional[Sequence[Any]],
  123. user_id: Optional[str],
  124. dept_id: Optional[str],
  125. ) -> None:
  126. """savepoint 包裹;失败只记日志,不抛给计划主流程。"""
  127. load_map = None
  128. if load is not None:
  129. load_map = load.model_dump(by_alias=False) if hasattr(load, "model_dump") else dict(load)
  130. delivery_maps = []
  131. for d in deliveries or []:
  132. delivery_maps.append(
  133. d.model_dump(by_alias=False) if hasattr(d, "model_dump") else dict(d)
  134. )
  135. try:
  136. with db.begin_nested():
  137. upsert_prefs_from_plan_stops(
  138. db,
  139. tenant_no=tenant_no,
  140. load=load_map,
  141. deliveries=delivery_maps,
  142. user_id=user_id,
  143. dept_id=dept_id,
  144. )
  145. except Exception:
  146. logger.exception(
  147. "upsert customer stop pref failed tenant=%s (plan save continues)",
  148. tenant_no,
  149. )