protocol_sample_list_summary.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. """协议/捐赠列表卡片:按样本编码批量取接收结论与门禁质检结论(轻量字段)。"""
  2. from __future__ import annotations
  3. from collections import defaultdict
  4. from typing import Any, Dict, List, Optional
  5. from sqlalchemy.orm import Session
  6. from app.models.models import BizQcTask, BizQcWorkOrder, BizSampleReception
  7. from app.services.qc_work_order_conclusion import (
  8. build_qc_conclusion_fields,
  9. work_order_qc_conclusion_label,
  10. )
  11. _ACCEPT_CONCLUSION_LABELS = {"js": "接收", "bj": "拒收"}
  12. _VALID_QC_CONCLUSIONS = frozenset({"tg", "btg"})
  13. _QC_WORK_ORDER_STATUS_LABELS = {
  14. "dpz": "待配置",
  15. "ypz": "已配置",
  16. "ywc": "已完成",
  17. "yzf": "已作废",
  18. }
  19. def empty_gate_qc_summary_fields() -> Dict[str, str]:
  20. return {
  21. "qcWorkOrderNo": "",
  22. "qcWorkOrderStatus": "",
  23. "qcWorkOrderStatusLabel": "",
  24. "qcConclusion": "",
  25. "qcConclusionLabel": "",
  26. }
  27. def _empty_gate_qc_summary_fields() -> Dict[str, str]:
  28. return empty_gate_qc_summary_fields()
  29. def _load_derived_qc_conclusions_by_work_order_ids(
  30. db: Session,
  31. work_order_ids: List[str],
  32. ) -> Dict[str, List[Optional[str]]]:
  33. conclusions_by_wo: Dict[str, List[Optional[str]]] = defaultdict(list)
  34. ids = list({(i or "").strip() for i in work_order_ids if (i or "").strip()})
  35. if not ids:
  36. return conclusions_by_wo
  37. task_rows = (
  38. db.query(BizQcTask.work_order_id, BizQcTask.conclusion)
  39. .filter(
  40. BizQcTask.del_flag == 0,
  41. BizQcTask.work_order_id.in_(ids),
  42. )
  43. .all()
  44. )
  45. for wo_id, conc in task_rows:
  46. wid = (wo_id or "").strip()
  47. if wid:
  48. conclusions_by_wo[wid].append(conc)
  49. return conclusions_by_wo
  50. def _build_gate_qc_summary_fields(
  51. wo: BizQcWorkOrder,
  52. conclusions_by_wo: Dict[str, List[Optional[str]]],
  53. ) -> Dict[str, str]:
  54. stored = (getattr(wo, "conclusion", None) or "").strip()
  55. if stored in _VALID_QC_CONCLUSIONS:
  56. qc_conclusion = stored
  57. qc_label = work_order_qc_conclusion_label(stored)
  58. else:
  59. wid = (wo.id or "").strip()
  60. fields = build_qc_conclusion_fields(conclusions_by_wo.get(wid, []))
  61. qc_conclusion = fields["qcConclusion"]
  62. qc_label = fields["qcConclusionLabel"]
  63. status = (wo.work_order_status or "").strip()
  64. return {
  65. "qcWorkOrderNo": (wo.work_order_no or "").strip(),
  66. "qcWorkOrderStatus": status,
  67. "qcWorkOrderStatusLabel": _QC_WORK_ORDER_STATUS_LABELS.get(status, status),
  68. "qcConclusion": qc_conclusion,
  69. "qcConclusionLabel": qc_label,
  70. }
  71. def _build_gate_qc_summaries_for_work_orders(
  72. db: Session,
  73. work_orders: List[BizQcWorkOrder],
  74. ) -> Dict[str, Dict[str, str]]:
  75. if not work_orders:
  76. return {}
  77. need_derive_ids: List[str] = []
  78. for wo in work_orders:
  79. stored = (getattr(wo, "conclusion", None) or "").strip()
  80. if stored not in _VALID_QC_CONCLUSIONS:
  81. wid = (wo.id or "").strip()
  82. if wid:
  83. need_derive_ids.append(wid)
  84. conclusions_by_wo = _load_derived_qc_conclusions_by_work_order_ids(db, need_derive_ids)
  85. out: Dict[str, Dict[str, str]] = {}
  86. for wo in work_orders:
  87. wid = (wo.id or "").strip()
  88. if wid:
  89. out[wid] = _build_gate_qc_summary_fields(wo, conclusions_by_wo)
  90. return out
  91. def build_sample_item_reception_qc_fields(
  92. reception: Optional[Dict[str, Any]],
  93. qc: Optional[Dict[str, Any]],
  94. ) -> Dict[str, Any]:
  95. """合并接收/质检结论摘要为列表 sampleItems 字段。"""
  96. conclusion = ""
  97. remark = ""
  98. if reception:
  99. conclusion = (reception.get("acceptConclusion") or "").strip()
  100. remark = (reception.get("remark") or "").strip()
  101. qc_code = ""
  102. qc_label = ""
  103. if qc:
  104. qc_code = (qc.get("qcConclusion") or "").strip()
  105. qc_label = (qc.get("qcConclusionLabel") or "").strip()
  106. if qc_code and not qc_label:
  107. qc_label = work_order_qc_conclusion_label(qc_code)
  108. return {
  109. "receptionAcceptConclusion": conclusion,
  110. "receptionAcceptConclusionLabel": _ACCEPT_CONCLUSION_LABELS.get(conclusion, ""),
  111. "receptionRemark": remark,
  112. "qcConclusion": qc_code,
  113. "qcConclusionLabel": qc_label,
  114. }
  115. def get_list_reception_summary_map_by_sample_codes(
  116. db: Session,
  117. tenant_no: str,
  118. sample_codes: List[str],
  119. *,
  120. reception_source: str,
  121. ) -> Dict[str, Dict[str, str]]:
  122. """按租户 + 样本编码 + 来源返回接收摘要(acceptConclusion / remark),同码取首条。"""
  123. codes = list({(c or "").strip() for c in sample_codes if (c or "").strip()})
  124. if not tenant_no or not codes or not (reception_source or "").strip():
  125. return {}
  126. rows = (
  127. db.query(BizSampleReception)
  128. .filter(
  129. BizSampleReception.tenant_no == tenant_no,
  130. BizSampleReception.del_flag == 0,
  131. BizSampleReception.reception_source == reception_source,
  132. BizSampleReception.sample_code.in_(codes),
  133. )
  134. .all()
  135. )
  136. out: Dict[str, Dict[str, str]] = {}
  137. for rec in rows:
  138. code = (rec.sample_code or "").strip()
  139. if not code:
  140. continue
  141. prev = out.get(code)
  142. prev_t = prev.get("_receptionTime") if prev else None
  143. rec_t = rec.reception_time
  144. if prev and rec_t and prev_t and rec_t <= prev_t:
  145. continue
  146. if prev and rec_t is None and prev_t is not None:
  147. continue
  148. out[code] = {
  149. "acceptConclusion": (rec.accept_conclusion or "").strip(),
  150. "remark": (rec.remark or "").strip(),
  151. "receptionTime": rec_t.strftime("%Y-%m-%d %H:%M:%S") if rec_t else "",
  152. "_receptionTime": rec_t,
  153. }
  154. for v in out.values():
  155. v.pop("_receptionTime", None)
  156. return out
  157. def get_list_gate_qc_summary_map_by_reception_ids(
  158. db: Session,
  159. tenant_no: str,
  160. reception_ids: List[str],
  161. ) -> Dict[str, Dict[str, str]]:
  162. """按 reception_id 批量返回样本接收触发的门禁质检摘要(工单号、状态、结论)。"""
  163. ids = list({(i or "").strip() for i in reception_ids if (i or "").strip()})
  164. if not tenant_no or not ids:
  165. return {}
  166. rows = (
  167. db.query(BizQcWorkOrder)
  168. .filter(
  169. BizQcWorkOrder.tenant_no == tenant_no,
  170. BizQcWorkOrder.del_flag == 0,
  171. BizQcWorkOrder.trigger_source == "reception",
  172. BizQcWorkOrder.reception_id.in_(ids),
  173. )
  174. .all()
  175. )
  176. summaries_by_wo_id = _build_gate_qc_summaries_for_work_orders(db, rows)
  177. out: Dict[str, Dict[str, str]] = {}
  178. for wo in rows:
  179. rid = (wo.reception_id or "").strip()
  180. wid = (wo.id or "").strip()
  181. if not rid or rid in out or not wid:
  182. continue
  183. out[rid] = summaries_by_wo_id.get(wid) or _empty_gate_qc_summary_fields()
  184. return out
  185. def get_list_qc_conclusion_map_by_sample_codes(
  186. db: Session,
  187. tenant_no: str,
  188. sample_codes: List[str],
  189. *,
  190. reception_source: str,
  191. ) -> Dict[str, Dict[str, str]]:
  192. """按租户 + 样本编码 + 来源返回门禁质检摘要(工单号、状态、结论)。"""
  193. codes = list({(c or "").strip() for c in sample_codes if (c or "").strip()})
  194. if not tenant_no or not codes or not (reception_source or "").strip():
  195. return {}
  196. rows = (
  197. db.query(BizQcWorkOrder)
  198. .filter(
  199. BizQcWorkOrder.tenant_no == tenant_no,
  200. BizQcWorkOrder.del_flag == 0,
  201. BizQcWorkOrder.reception_source == reception_source,
  202. BizQcWorkOrder.trigger_source == "reception",
  203. BizQcWorkOrder.sample_code.in_(codes),
  204. )
  205. .all()
  206. )
  207. code_to_wo: Dict[str, BizQcWorkOrder] = {}
  208. for wo in rows:
  209. code = (wo.sample_code or "").strip()
  210. if code and code not in code_to_wo:
  211. code_to_wo[code] = wo
  212. summaries_by_wo_id = _build_gate_qc_summaries_for_work_orders(db, list(code_to_wo.values()))
  213. out: Dict[str, Dict[str, str]] = {}
  214. for code, wo in code_to_wo.items():
  215. wid = (wo.id or "").strip()
  216. out[code] = summaries_by_wo_id.get(wid) or _empty_gate_qc_summary_fields()
  217. return out
  218. # 兼容旧调用名
  219. def get_list_qc_status_map_by_sample_codes(
  220. db: Session,
  221. tenant_no: str,
  222. sample_codes: List[str],
  223. *,
  224. reception_source: str,
  225. ) -> Dict[str, Dict[str, str]]:
  226. return get_list_qc_conclusion_map_by_sample_codes(
  227. db, tenant_no, sample_codes, reception_source=reception_source
  228. )