protocol_sample_collection.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. """采集协议样本采集信息列表 CRUD(样本信息管理 · 样本采集信息 Tab)"""
  2. from __future__ import annotations
  3. import math
  4. from datetime import datetime
  5. from typing import Any, Dict, List, Optional, Tuple
  6. from sqlalchemy import and_, or_
  7. from sqlalchemy.orm import Session
  8. from app.core.utils import format_date
  9. from app.utils.sample_special import sample_special_to_dict
  10. from app.models.models import (
  11. BizCellSampleType,
  12. BizCollectionHospital,
  13. BizCollectionProtocol,
  14. BizCollectionRegion,
  15. BizProtocolCollectionInfo,
  16. BizProtocolPartyCustomer,
  17. BizProtocolSample,
  18. )
  19. def total_pages(total: int, page_size: int) -> int:
  20. if total <= 0:
  21. return 0
  22. return int(math.ceil(total / float(page_size)))
  23. def _fmt_dt(t: Optional[datetime]) -> str:
  24. return t.strftime("%Y-%m-%d %H:%M:%S") if t else ""
  25. def _sample_status_label(status: str) -> str:
  26. return {
  27. "ycj": "已采集",
  28. "jcz": "检测中",
  29. "yrk": "已入库",
  30. "yzf": "已作废",
  31. }.get(status, "未采集" if not status else status)
  32. def _row_to_dict(
  33. *,
  34. protocol_sample_id: str,
  35. collection_protocol_id: str,
  36. protocol_no: str,
  37. protocol_status: str,
  38. expected_delivery_date: str,
  39. mother_name: str,
  40. newborn_name: str,
  41. sample_code: str,
  42. sample_type_name: str,
  43. sample_status: str,
  44. sample_status_label: str,
  45. region_name: str,
  46. hospital_name: str,
  47. medical_staff: str,
  48. sample_collect_time: str,
  49. maternal_blood_collect_time: str,
  50. ) -> Dict[str, Any]:
  51. return {
  52. "protocolSampleId": protocol_sample_id,
  53. "collectionProtocolId": collection_protocol_id,
  54. "protocolNo": protocol_no,
  55. "protocolStatus": protocol_status,
  56. "expectedDeliveryDate": expected_delivery_date,
  57. "motherName": mother_name,
  58. "newbornName": newborn_name,
  59. "sampleCode": sample_code,
  60. "sampleTypeName": sample_type_name,
  61. "sampleStatus": sample_status,
  62. "sampleStatusLabel": sample_status_label,
  63. "regionName": region_name,
  64. "hospitalName": hospital_name,
  65. "medicalStaff": medical_staff,
  66. "sampleCollectTime": sample_collect_time,
  67. "maternalBloodCollectTime": maternal_blood_collect_time,
  68. }
  69. def _base_query(db: Session, tenant_no: str):
  70. return (
  71. db.query(
  72. BizProtocolSample,
  73. BizCollectionProtocol,
  74. BizProtocolPartyCustomer,
  75. BizProtocolCollectionInfo,
  76. )
  77. .join(
  78. BizCollectionProtocol,
  79. and_(
  80. BizCollectionProtocol.id == BizProtocolSample.collection_protocol_id,
  81. BizCollectionProtocol.del_flag == 0,
  82. ),
  83. )
  84. .outerjoin(
  85. BizProtocolPartyCustomer,
  86. and_(
  87. BizProtocolPartyCustomer.collection_protocol_id == BizCollectionProtocol.id,
  88. BizProtocolPartyCustomer.del_flag == 0,
  89. ),
  90. )
  91. .outerjoin(
  92. BizProtocolCollectionInfo,
  93. and_(
  94. BizProtocolCollectionInfo.collection_protocol_id == BizCollectionProtocol.id,
  95. BizProtocolCollectionInfo.del_flag == 0,
  96. ),
  97. )
  98. .filter(
  99. BizProtocolSample.tenant_no == tenant_no,
  100. BizProtocolSample.del_flag == 0,
  101. )
  102. )
  103. def _apply_sample_status_filter(q, sample_status: Optional[str]):
  104. st = (sample_status or "").strip()
  105. if not st or st == "all":
  106. return q
  107. if st == "uncollected":
  108. return q.filter(
  109. or_(
  110. BizProtocolSample.sample_status.is_(None),
  111. BizProtocolSample.sample_status == "",
  112. )
  113. )
  114. if st == "collected":
  115. return q.filter(BizProtocolSample.sample_status.in_(["ycj", "jcz", "yrk"]))
  116. if st == "voided":
  117. return q.filter(BizProtocolSample.sample_status == "yzf")
  118. return q.filter(BizProtocolSample.sample_status == st)
  119. def _apply_reception_flag_filter(q, flag: Optional[str], *, sample_code_col, tenant_no: str, source: str):
  120. from sqlalchemy import exists
  121. from app.models.models import BizSampleReception
  122. f = (flag or "").strip()
  123. if not f:
  124. return q
  125. base = and_(
  126. BizSampleReception.tenant_no == tenant_no,
  127. BizSampleReception.del_flag == 0,
  128. BizSampleReception.reception_source == source,
  129. BizSampleReception.sample_code == sample_code_col,
  130. )
  131. if f == "js":
  132. return q.filter(exists().where(and_(base, BizSampleReception.accept_conclusion == "js")))
  133. if f == "bj":
  134. return q.filter(exists().where(and_(base, BizSampleReception.accept_conclusion == "bj")))
  135. if f == "none":
  136. return q.filter(
  137. ~exists().where(and_(base, BizSampleReception.accept_conclusion.in_(["js", "bj"])))
  138. )
  139. return q
  140. def _apply_qc_flag_filter(q, flag: Optional[str], *, sample_code_col, tenant_no: str, source: str):
  141. from sqlalchemy import exists
  142. from app.models.models import BizQcWorkOrder
  143. f = (flag or "").strip()
  144. if not f:
  145. return q
  146. base = and_(
  147. BizQcWorkOrder.tenant_no == tenant_no,
  148. BizQcWorkOrder.del_flag == 0,
  149. BizQcWorkOrder.reception_source == source,
  150. BizQcWorkOrder.trigger_source == "reception",
  151. BizQcWorkOrder.sample_code == sample_code_col,
  152. )
  153. if f == "complete":
  154. return q.filter(exists().where(and_(base, BizQcWorkOrder.conclusion.in_(["tg", "btg"]))))
  155. if f == "voided":
  156. return q.filter(exists().where(and_(base, BizQcWorkOrder.work_order_status == "yzf")))
  157. if f == "incomplete":
  158. return q.filter(
  159. ~exists().where(
  160. and_(
  161. base,
  162. or_(
  163. BizQcWorkOrder.conclusion.in_(["tg", "btg"]),
  164. BizQcWorkOrder.work_order_status == "yzf",
  165. ),
  166. )
  167. )
  168. )
  169. return q
  170. def _apply_storage_flag_filter(q, flag: Optional[str], *, sample_code_col, tenant_no: str, source: str):
  171. from sqlalchemy import exists
  172. from app.models.models import BizStorageReleaseReview
  173. f = (flag or "").strip()
  174. if not f:
  175. return q
  176. base = and_(
  177. BizStorageReleaseReview.tenant_no == tenant_no,
  178. BizStorageReleaseReview.del_flag == 0,
  179. BizStorageReleaseReview.reception_source == source,
  180. BizStorageReleaseReview.sample_code == sample_code_col,
  181. )
  182. if f == "yrk":
  183. return q.filter(exists().where(and_(base, BizStorageReleaseReview.storage_status == "yrk")))
  184. if f == "rejected":
  185. return q.filter(
  186. exists().where(
  187. and_(
  188. base,
  189. BizStorageReleaseReview.storage_status != "yrk",
  190. BizStorageReleaseReview.review_conclusion == "btg",
  191. )
  192. )
  193. )
  194. if f == "none":
  195. return q.filter(
  196. ~exists().where(
  197. and_(
  198. base,
  199. or_(
  200. BizStorageReleaseReview.storage_status == "yrk",
  201. BizStorageReleaseReview.review_conclusion == "btg",
  202. ),
  203. )
  204. )
  205. )
  206. return q
  207. def list_protocol_sample_collections(
  208. db: Session,
  209. *,
  210. page: int = 1,
  211. page_size: int = 10,
  212. tenant_no: str,
  213. keyword: Optional[str] = None,
  214. sample_status: Optional[str] = None,
  215. collection_flag: Optional[str] = None,
  216. reception_flag: Optional[str] = None,
  217. qc_flag: Optional[str] = None,
  218. storage_flag: Optional[str] = None,
  219. storage_cert_handled: Optional[str] = None,
  220. insurance_handled: Optional[str] = None,
  221. user_info=None,
  222. ) -> Tuple[List[Dict[str, Any]], int]:
  223. if not tenant_no:
  224. return [], 0
  225. q = _base_query(db, tenant_no)
  226. q = _apply_sample_status_filter(q, collection_flag or sample_status)
  227. q = _apply_reception_flag_filter(
  228. q, reception_flag, sample_code_col=BizProtocolSample.sample_code, tenant_no=tenant_no, source="cell_storage"
  229. )
  230. q = _apply_qc_flag_filter(
  231. q, qc_flag, sample_code_col=BizProtocolSample.sample_code, tenant_no=tenant_no, source="cell_storage"
  232. )
  233. q = _apply_storage_flag_filter(
  234. q, storage_flag, sample_code_col=BizProtocolSample.sample_code, tenant_no=tenant_no, source="cell_storage"
  235. )
  236. from app.models.models import BizProtocolFullmoonTodo, BizProtocolStorageCert
  237. sch = (storage_cert_handled or "").strip()
  238. if sch in ("1", "0", "true", "false"):
  239. want = sch in ("1", "true")
  240. handled_pred = or_(
  241. BizProtocolStorageCert.handle_time.isnot(None),
  242. BizProtocolStorageCert.make_status == "1",
  243. and_(BizProtocolStorageCert.make_date.isnot(None), BizProtocolStorageCert.make_date != ""),
  244. )
  245. q = q.outerjoin(
  246. BizProtocolStorageCert,
  247. and_(
  248. BizProtocolStorageCert.collection_protocol_id == BizCollectionProtocol.id,
  249. BizProtocolStorageCert.tenant_no == tenant_no,
  250. BizProtocolStorageCert.del_flag == 0,
  251. ),
  252. )
  253. q = q.filter(handled_pred) if want else q.filter(~handled_pred)
  254. ih = (insurance_handled or "").strip()
  255. if ih in ("1", "0", "true", "false"):
  256. want = ih in ("1", "true")
  257. handled_pred = or_(
  258. BizProtocolFullmoonTodo.handle_time.isnot(None),
  259. BizProtocolFullmoonTodo.todo_status == "1",
  260. and_(BizProtocolFullmoonTodo.handle_date.isnot(None), BizProtocolFullmoonTodo.handle_date != ""),
  261. )
  262. q = q.outerjoin(
  263. BizProtocolFullmoonTodo,
  264. and_(
  265. BizProtocolFullmoonTodo.collection_protocol_id == BizCollectionProtocol.id,
  266. BizProtocolFullmoonTodo.tenant_no == tenant_no,
  267. BizProtocolFullmoonTodo.del_flag == 0,
  268. ),
  269. )
  270. q = q.filter(handled_pred) if want else q.filter(~handled_pred)
  271. if keyword and keyword.strip():
  272. kw = f"%{keyword.strip()}%"
  273. kw_conds = [
  274. BizProtocolPartyCustomer.mother_name.like(kw),
  275. BizProtocolSample.sample_code.like(kw),
  276. ]
  277. from app.crud import sample_reception as reception_svc
  278. lab_codes = reception_svc.sample_codes_matching_lab_storage_keyword(
  279. db, tenant_no, keyword, reception_source="cell_storage"
  280. )
  281. if lab_codes:
  282. kw_conds.append(BizProtocolSample.sample_code.in_(lab_codes))
  283. q = q.filter(or_(*kw_conds))
  284. if user_info:
  285. from app.core.data_scope import apply_data_scope
  286. q = apply_data_scope(q, BizProtocolSample, user_info, db, tenant_no)
  287. total = q.count()
  288. page = max(1, page)
  289. page_size = min(max(1, page_size), 100)
  290. rows = (
  291. q.order_by(
  292. BizProtocolPartyCustomer.expected_delivery_date.is_(None),
  293. BizProtocolPartyCustomer.expected_delivery_date.desc(),
  294. BizCollectionProtocol.create_time.desc(),
  295. BizProtocolSample.id.desc(),
  296. )
  297. .offset((page - 1) * page_size)
  298. .limit(page_size)
  299. .all()
  300. )
  301. sample_type_ids = list({
  302. ps.sample_type_id for ps, _, _, _ in rows if ps and ps.sample_type_id
  303. })
  304. type_map: Dict[str, str] = {}
  305. if sample_type_ids:
  306. for t in (
  307. db.query(BizCellSampleType)
  308. .filter(BizCellSampleType.id.in_(sample_type_ids), BizCellSampleType.del_flag == 0)
  309. .all()
  310. ):
  311. type_map[t.id] = t.type_name or ""
  312. region_ids = list({
  313. (ci.region_id or "").strip()
  314. for _, _, _, ci in rows
  315. if ci and (ci.region_id or "").strip()
  316. })
  317. hospital_ids = list({
  318. (ci.hospital_id or "").strip()
  319. for _, _, _, ci in rows
  320. if ci and (ci.hospital_id or "").strip()
  321. })
  322. region_name_map: Dict[str, str] = {}
  323. hospital_name_map: Dict[str, str] = {}
  324. if region_ids:
  325. for r in (
  326. db.query(BizCollectionRegion.id, BizCollectionRegion.region_name)
  327. .filter(BizCollectionRegion.id.in_(region_ids), BizCollectionRegion.del_flag == 0)
  328. .all()
  329. ):
  330. region_name_map[r.id] = r.region_name or ""
  331. if hospital_ids:
  332. for h in (
  333. db.query(BizCollectionHospital.id, BizCollectionHospital.hospital_name)
  334. .filter(BizCollectionHospital.id.in_(hospital_ids), BizCollectionHospital.del_flag == 0)
  335. .all()
  336. ):
  337. hospital_name_map[h.id] = h.hospital_name or ""
  338. items: List[Dict[str, Any]] = []
  339. for psample, proto, party, ci in rows:
  340. status = (psample.sample_status or "").strip() if psample else ""
  341. region_id = (ci.region_id or "").strip() if ci else ""
  342. hospital_id = (ci.hospital_id or "").strip() if ci else ""
  343. item = _row_to_dict(
  344. protocol_sample_id=psample.id,
  345. collection_protocol_id=proto.id if proto else "",
  346. protocol_no=(proto.protocol_no or "") if proto else "",
  347. protocol_status=(proto.protocol_status or "") if proto else "",
  348. expected_delivery_date=format_date(party.expected_delivery_date) if party else "",
  349. mother_name=(party.mother_name or "") if party else "",
  350. newborn_name=(party.newborn_name or "") if party else "",
  351. sample_code=psample.sample_code or "",
  352. sample_type_name=type_map.get(psample.sample_type_id or "", ""),
  353. sample_status=status,
  354. sample_status_label=_sample_status_label(status),
  355. region_name=region_name_map.get(region_id, ""),
  356. hospital_name=hospital_name_map.get(hospital_id, ""),
  357. medical_staff=(ci.medical_staff or "") if ci else "",
  358. sample_collect_time=_fmt_dt(ci.sample_collect_time) if ci else "",
  359. maternal_blood_collect_time=_fmt_dt(ci.maternal_blood_collect_time) if ci else "",
  360. )
  361. item.update(sample_special_to_dict(psample))
  362. # 客户服务宽表:当事人 / 协议附件 / 备注 / 市场专员 id
  363. item["motherPhone"] = (party.mother_phone or "") if party else ""
  364. item["motherIdCard"] = (party.mother_id_card or "") if party else ""
  365. item["fatherName"] = (party.father_name or "") if party else ""
  366. item["fatherPhone"] = (party.father_phone or "") if party else ""
  367. item["fatherIdCard"] = (party.father_id_card or "") if party else ""
  368. item["newbornIdCard"] = (party.newborn_id_card or "") if party else ""
  369. item["newbornSex"] = (party.newborn_sex or "") if party else ""
  370. item["newbornDeliveryTime"] = _fmt_dt(party.newborn_delivery_time) if party else ""
  371. item["protocolRemark"] = (proto.remark or "") if proto else ""
  372. item["protocolAttachments"] = (proto.protocol_attachments or "") if proto else ""
  373. item["consentFormUrls"] = (proto.consent_form_urls or "") if proto else ""
  374. item["healthSurveyFormUrls"] = (proto.health_survey_form_urls or "") if proto else ""
  375. item["salesUserId"] = (ci.sales_user_id or "") if ci else ""
  376. items.append(item)
  377. if items and tenant_no:
  378. from app.crud import sample_reception as reception_svc
  379. from app.crud.storage_release_review import _get_user_name
  380. from app.models.models import BizProtocolSamplePayment
  381. codes = [i["sampleCode"] for i in items if i.get("sampleCode")]
  382. code_map = reception_svc.get_lab_storage_code_map_by_sample_codes(
  383. db, tenant_no, codes, reception_source="cell_storage"
  384. )
  385. pay_mode_map: Dict[str, str] = {}
  386. if codes:
  387. for sp in (
  388. db.query(BizProtocolSamplePayment)
  389. .filter(
  390. BizProtocolSamplePayment.tenant_no == tenant_no,
  391. BizProtocolSamplePayment.del_flag == 0,
  392. BizProtocolSamplePayment.sample_code.in_(codes),
  393. )
  394. .all()
  395. ):
  396. sc = (sp.sample_code or "").strip()
  397. if sc and sc not in pay_mode_map:
  398. pay_mode_map[sc] = (sp.pay_mode or "").strip()
  399. sales_ids = list({(i.get("salesUserId") or "").strip() for i in items if i.get("salesUserId")})
  400. sales_name_map = {uid: _get_user_name(db, uid) for uid in sales_ids if uid}
  401. pay_mode_label = {"dj": "趸交", "fq": "分期"}
  402. for item in items:
  403. sc = (item.get("sampleCode") or "").strip()
  404. item["labStorageCode"] = code_map.get(sc, "") if sc else ""
  405. mode = pay_mode_map.get(sc, "")
  406. item["payMode"] = mode
  407. item["payModeLabel"] = pay_mode_label.get(mode, mode or "")
  408. sid = (item.get("salesUserId") or "").strip()
  409. item["marketSpecialistName"] = sales_name_map.get(sid, "") if sid else ""
  410. from app.services.sample_info_list_enrich import enrich_sample_info_items
  411. enrich_sample_info_items(
  412. db, tenant_no, items, reception_source="cell_storage", include_handle_flags=True
  413. )
  414. return items, total