pilot_outbound_status_sync.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. """中试服务出库生产/质检工单与输注计划生产状态、质检状态双向联动。"""
  2. from __future__ import annotations
  3. from datetime import datetime
  4. from typing import Optional
  5. from sqlalchemy.orm import Session
  6. from app.models.models import (
  7. BizPilotInfusionSchedule,
  8. BizProductionWorkOrder,
  9. BizQcWorkOrder,
  10. )
  11. _OUTBOUND_BIZ_TYPES = ("pilot_service", "clinical_application", "basic_experiment")
  12. def _is_pilot_outbound_production(wo: BizProductionWorkOrder) -> bool:
  13. return (
  14. (getattr(wo, "work_order_type", None) or "") == "outbound"
  15. and (getattr(wo, "biz_type", None) or "") in _OUTBOUND_BIZ_TYPES
  16. and bool(getattr(wo, "infusion_schedule_id", None))
  17. )
  18. def _is_pilot_outbound_qc(wo: BizQcWorkOrder) -> bool:
  19. return (
  20. (getattr(wo, "work_order_type", None) or "") == "outbound"
  21. and (getattr(wo, "biz_type", None) or "") in _OUTBOUND_BIZ_TYPES
  22. and bool(getattr(wo, "infusion_schedule_id", None))
  23. )
  24. def _get_schedule(
  25. db: Session, schedule_id: str, tenant_no: str
  26. ) -> Optional[BizPilotInfusionSchedule]:
  27. if not (schedule_id or "").strip():
  28. return None
  29. return (
  30. db.query(BizPilotInfusionSchedule)
  31. .filter(
  32. BizPilotInfusionSchedule.id == schedule_id.strip(),
  33. BizPilotInfusionSchedule.tenant_no == tenant_no,
  34. BizPilotInfusionSchedule.del_flag == 0,
  35. )
  36. .first()
  37. )
  38. def _touch(row, user_id: Optional[str]) -> None:
  39. now = datetime.now()
  40. if user_id:
  41. row.update_by = user_id
  42. row.update_time = now
  43. def sync_schedule_from_production_work_order(
  44. db: Session,
  45. wo: BizProductionWorkOrder,
  46. *,
  47. user_id: Optional[str] = None,
  48. ) -> None:
  49. """出库生产工单状态 → 输注计划 schedule_status。"""
  50. if not _is_pilot_outbound_production(wo):
  51. return
  52. sched = _get_schedule(db, wo.infusion_schedule_id or "", wo.tenant_no or "")
  53. if not sched:
  54. return
  55. wo_status = (wo.work_order_status or "").strip()
  56. current = (sched.schedule_status or "").strip()
  57. new_status: Optional[str] = None
  58. if wo_status in ("dpz", "jxz"):
  59. if current not in ("ysc", "yjf", "yqx"):
  60. new_status = "dsc"
  61. elif wo_status == "ywc":
  62. if current not in ("yjf", "yqx"):
  63. new_status = "ysc"
  64. elif wo_status == "yzf":
  65. new_status = "yqx"
  66. if new_status and new_status != current:
  67. sched.schedule_status = new_status
  68. _touch(sched, user_id)
  69. db.add(sched)
  70. def sync_schedule_from_qc_work_order(
  71. db: Session,
  72. wo: BizQcWorkOrder,
  73. *,
  74. user_id: Optional[str] = None,
  75. ) -> None:
  76. """出库质检工单状态 → 输注计划 qc_status(仅计划自动质检参与联动)。"""
  77. if not _is_pilot_outbound_qc(wo):
  78. return
  79. trigger = (getattr(wo, "trigger_source", None) or "reception").strip()
  80. if trigger != "schedule":
  81. return
  82. sched = _get_schedule(db, wo.infusion_schedule_id or "", wo.tenant_no or "")
  83. if not sched:
  84. return
  85. wo_status = (wo.work_order_status or "").strip()
  86. current = (sched.qc_status or "").strip()
  87. new_status: Optional[str] = None
  88. if wo_status in ("dpz", "ypz"):
  89. if current not in ("yzj", "yqx"):
  90. new_status = "dzc"
  91. elif wo_status == "ywc":
  92. if current != "yqx":
  93. new_status = "yzj"
  94. elif wo_status == "yzf":
  95. new_status = "yqx"
  96. if new_status and new_status != current:
  97. sched.qc_status = new_status
  98. _touch(sched, user_id)
  99. db.add(sched)
  100. def sync_production_work_order_from_schedule(
  101. db: Session,
  102. schedule: BizPilotInfusionSchedule,
  103. *,
  104. user_id: Optional[str] = None,
  105. ) -> None:
  106. """输注计划 schedule_status → 出库生产工单状态。"""
  107. schedule_id = schedule.id or ""
  108. tenant_no = schedule.tenant_no or ""
  109. if not schedule_id:
  110. return
  111. wo = (
  112. db.query(BizProductionWorkOrder)
  113. .filter(
  114. BizProductionWorkOrder.infusion_schedule_id == schedule_id,
  115. BizProductionWorkOrder.tenant_no == tenant_no,
  116. BizProductionWorkOrder.del_flag == 0,
  117. BizProductionWorkOrder.work_order_type == "outbound",
  118. BizProductionWorkOrder.biz_type.in_(_OUTBOUND_BIZ_TYPES),
  119. )
  120. .first()
  121. )
  122. if not wo:
  123. return
  124. sched_status = (schedule.schedule_status or "").strip()
  125. current = (wo.work_order_status or "").strip()
  126. new_status: Optional[str] = None
  127. if sched_status in ("ysc", "yjf"):
  128. if current not in ("ywc", "yzf"):
  129. new_status = "ywc"
  130. elif sched_status == "yqx":
  131. if current != "ywc":
  132. new_status = "yzf"
  133. if new_status and new_status != current:
  134. wo.work_order_status = new_status
  135. _touch(wo, user_id)
  136. db.add(wo)
  137. def sync_qc_work_order_from_schedule(
  138. db: Session,
  139. schedule: BizPilotInfusionSchedule,
  140. *,
  141. user_id: Optional[str] = None,
  142. ) -> None:
  143. """输注计划 qc_status → 出库质检工单状态。"""
  144. schedule_id = schedule.id or ""
  145. tenant_no = schedule.tenant_no or ""
  146. if not schedule_id:
  147. return
  148. wo = (
  149. db.query(BizQcWorkOrder)
  150. .filter(
  151. BizQcWorkOrder.infusion_schedule_id == schedule_id,
  152. BizQcWorkOrder.tenant_no == tenant_no,
  153. BizQcWorkOrder.del_flag == 0,
  154. BizQcWorkOrder.work_order_type == "outbound",
  155. BizQcWorkOrder.biz_type.in_(_OUTBOUND_BIZ_TYPES),
  156. BizQcWorkOrder.trigger_source == "schedule",
  157. )
  158. .first()
  159. )
  160. if not wo:
  161. return
  162. qc_status = (schedule.qc_status or "").strip()
  163. current = (wo.work_order_status or "").strip()
  164. new_status: Optional[str] = None
  165. if qc_status == "yzj":
  166. if current not in ("ywc", "yzf"):
  167. new_status = "ywc"
  168. elif qc_status == "yqx":
  169. if current != "ywc":
  170. new_status = "yzf"
  171. if new_status and new_status != current:
  172. wo.work_order_status = new_status
  173. _touch(wo, user_id)
  174. db.add(wo)
  175. def sync_pilot_outbound_from_schedule(
  176. db: Session,
  177. schedule: BizPilotInfusionSchedule,
  178. *,
  179. user_id: Optional[str] = None,
  180. ) -> None:
  181. """输注计划状态变更后,回写关联出库工单。"""
  182. sync_production_work_order_from_schedule(db, schedule, user_id=user_id)
  183. sync_qc_work_order_from_schedule(db, schedule, user_id=user_id)