research_donation_sample_collection.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. """科研捐赠样本采集信息列表 CRUD(样本信息管理 · 样本采集信息)"""
  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. BizCollectionRegion,
  14. )
  15. from app.models.research_donation import (
  16. BizResearchDonation,
  17. BizResearchDonationCollection,
  18. BizResearchDonationParty,
  19. BizResearchDonationSample,
  20. )
  21. def total_pages(total: int, page_size: int) -> int:
  22. if total <= 0:
  23. return 0
  24. return int(math.ceil(total / float(page_size)))
  25. def _fmt_dt(t: Optional[datetime]) -> str:
  26. return t.strftime("%Y-%m-%d %H:%M:%S") if t else ""
  27. def _sample_status_label(status: str) -> str:
  28. return {
  29. "ycj": "已采集",
  30. "jcz": "检测中",
  31. "yrk": "已入库",
  32. "yzf": "已作废",
  33. }.get(status, "未采集" if not status else status)
  34. def _row_to_dict(
  35. *,
  36. donation_sample_id: str,
  37. research_donation_id: str,
  38. donation_no: str,
  39. donation_status: str,
  40. expected_delivery_date: str,
  41. mother_name: str,
  42. newborn_name: str,
  43. sample_code: str,
  44. sample_type_name: str,
  45. sample_status: str,
  46. sample_status_label: str,
  47. region_name: str,
  48. hospital_name: str,
  49. medical_staff: str,
  50. sample_collect_time: str,
  51. maternal_blood_collect_time: str,
  52. ) -> Dict[str, Any]:
  53. return {
  54. "donationSampleId": donation_sample_id,
  55. "researchDonationId": research_donation_id,
  56. "donationNo": donation_no,
  57. "donationStatus": donation_status,
  58. "expectedDeliveryDate": expected_delivery_date,
  59. "motherName": mother_name,
  60. "newbornName": newborn_name,
  61. "sampleCode": sample_code,
  62. "sampleTypeName": sample_type_name,
  63. "sampleStatus": sample_status,
  64. "sampleStatusLabel": sample_status_label,
  65. "regionName": region_name,
  66. "hospitalName": hospital_name,
  67. "medicalStaff": medical_staff,
  68. "sampleCollectTime": sample_collect_time,
  69. "maternalBloodCollectTime": maternal_blood_collect_time,
  70. }
  71. def _base_query(db: Session, tenant_no: str):
  72. return (
  73. db.query(
  74. BizResearchDonationSample,
  75. BizResearchDonation,
  76. BizResearchDonationParty,
  77. BizResearchDonationCollection,
  78. )
  79. .join(
  80. BizResearchDonation,
  81. and_(
  82. BizResearchDonation.id == BizResearchDonationSample.research_donation_id,
  83. BizResearchDonation.del_flag == 0,
  84. ),
  85. )
  86. .outerjoin(
  87. BizResearchDonationParty,
  88. and_(
  89. BizResearchDonationParty.research_donation_id == BizResearchDonation.id,
  90. BizResearchDonationParty.del_flag == 0,
  91. ),
  92. )
  93. .outerjoin(
  94. BizResearchDonationCollection,
  95. and_(
  96. BizResearchDonationCollection.research_donation_id == BizResearchDonation.id,
  97. BizResearchDonationCollection.del_flag == 0,
  98. ),
  99. )
  100. .filter(
  101. BizResearchDonationSample.tenant_no == tenant_no,
  102. BizResearchDonationSample.del_flag == 0,
  103. )
  104. )
  105. def _apply_sample_status_filter(q, sample_status: Optional[str]):
  106. st = (sample_status or "").strip()
  107. if not st or st == "all":
  108. return q
  109. if st == "uncollected":
  110. return q.filter(
  111. or_(
  112. BizResearchDonationSample.sample_status.is_(None),
  113. BizResearchDonationSample.sample_status == "",
  114. )
  115. )
  116. if st == "collected":
  117. return q.filter(BizResearchDonationSample.sample_status.in_(["ycj", "jcz", "yrk"]))
  118. if st == "voided":
  119. return q.filter(BizResearchDonationSample.sample_status == "yzf")
  120. return q.filter(BizResearchDonationSample.sample_status == st)
  121. def list_donation_sample_collections(
  122. db: Session,
  123. *,
  124. page: int = 1,
  125. page_size: int = 10,
  126. tenant_no: str,
  127. keyword: Optional[str] = None,
  128. sample_status: Optional[str] = None,
  129. collection_flag: Optional[str] = None,
  130. reception_flag: Optional[str] = None,
  131. qc_flag: Optional[str] = None,
  132. storage_flag: Optional[str] = None,
  133. user_info=None,
  134. ) -> Tuple[List[Dict[str, Any]], int]:
  135. if not tenant_no:
  136. return [], 0
  137. from app.crud.protocol_sample_collection import (
  138. _apply_qc_flag_filter,
  139. _apply_reception_flag_filter,
  140. _apply_storage_flag_filter,
  141. )
  142. q = _base_query(db, tenant_no)
  143. q = _apply_sample_status_filter(q, collection_flag or sample_status)
  144. q = _apply_reception_flag_filter(
  145. q,
  146. reception_flag,
  147. sample_code_col=BizResearchDonationSample.sample_code,
  148. tenant_no=tenant_no,
  149. source="research_donation",
  150. )
  151. q = _apply_qc_flag_filter(
  152. q,
  153. qc_flag,
  154. sample_code_col=BizResearchDonationSample.sample_code,
  155. tenant_no=tenant_no,
  156. source="research_donation",
  157. )
  158. q = _apply_storage_flag_filter(
  159. q,
  160. storage_flag,
  161. sample_code_col=BizResearchDonationSample.sample_code,
  162. tenant_no=tenant_no,
  163. source="research_donation",
  164. )
  165. if keyword and keyword.strip():
  166. kw = f"%{keyword.strip()}%"
  167. kw_conds = [
  168. BizResearchDonationParty.mother_name.like(kw),
  169. BizResearchDonationSample.sample_code.like(kw),
  170. ]
  171. from app.crud import sample_reception as reception_svc
  172. lab_codes = reception_svc.sample_codes_matching_lab_storage_keyword(
  173. db, tenant_no, keyword, reception_source="research_donation"
  174. )
  175. if lab_codes:
  176. kw_conds.append(BizResearchDonationSample.sample_code.in_(lab_codes))
  177. q = q.filter(or_(*kw_conds))
  178. if user_info:
  179. from app.core.data_scope import apply_data_scope
  180. q = apply_data_scope(q, BizResearchDonation, user_info, db, tenant_no)
  181. total = q.count()
  182. page = max(1, page)
  183. page_size = min(max(1, page_size), 100)
  184. rows = (
  185. q.order_by(
  186. BizResearchDonationParty.expected_delivery_date.is_(None),
  187. BizResearchDonationParty.expected_delivery_date.desc(),
  188. BizResearchDonation.create_time.desc(),
  189. BizResearchDonationSample.id.desc(),
  190. )
  191. .offset((page - 1) * page_size)
  192. .limit(page_size)
  193. .all()
  194. )
  195. sample_type_ids = list({
  196. ds.sample_type_id for ds, _, _, _ in rows if ds and ds.sample_type_id
  197. })
  198. type_map: Dict[str, str] = {}
  199. if sample_type_ids:
  200. for t in (
  201. db.query(BizCellSampleType)
  202. .filter(BizCellSampleType.id.in_(sample_type_ids), BizCellSampleType.del_flag == 0)
  203. .all()
  204. ):
  205. type_map[t.id] = t.type_name or ""
  206. region_ids = list({
  207. (ci.region_id or "").strip()
  208. for _, _, _, ci in rows
  209. if ci and (ci.region_id or "").strip()
  210. })
  211. hospital_ids = list({
  212. (ci.hospital_id or "").strip()
  213. for _, _, _, ci in rows
  214. if ci and (ci.hospital_id or "").strip()
  215. })
  216. region_name_map: Dict[str, str] = {}
  217. hospital_name_map: Dict[str, str] = {}
  218. if region_ids:
  219. for r in (
  220. db.query(BizCollectionRegion.id, BizCollectionRegion.region_name)
  221. .filter(BizCollectionRegion.id.in_(region_ids), BizCollectionRegion.del_flag == 0)
  222. .all()
  223. ):
  224. region_name_map[r.id] = r.region_name or ""
  225. if hospital_ids:
  226. for h in (
  227. db.query(BizCollectionHospital.id, BizCollectionHospital.hospital_name)
  228. .filter(BizCollectionHospital.id.in_(hospital_ids), BizCollectionHospital.del_flag == 0)
  229. .all()
  230. ):
  231. hospital_name_map[h.id] = h.hospital_name or ""
  232. items: List[Dict[str, Any]] = []
  233. for dsample, donation, party, ci in rows:
  234. status = (dsample.sample_status or "").strip() if dsample else ""
  235. region_id = (ci.region_id or "").strip() if ci else ""
  236. hospital_id = (ci.hospital_id or "").strip() if ci else ""
  237. item = _row_to_dict(
  238. donation_sample_id=dsample.id,
  239. research_donation_id=donation.id if donation else "",
  240. donation_no=(donation.donation_no or "") if donation else "",
  241. donation_status=(donation.donation_status or "") if donation else "",
  242. expected_delivery_date=format_date(party.expected_delivery_date) if party else "",
  243. mother_name=(party.mother_name or "") if party else "",
  244. newborn_name=(party.newborn_name or "") if party else "",
  245. sample_code=dsample.sample_code or "",
  246. sample_type_name=type_map.get(dsample.sample_type_id or "", ""),
  247. sample_status=status,
  248. sample_status_label=_sample_status_label(status),
  249. region_name=region_name_map.get(region_id, ""),
  250. hospital_name=hospital_name_map.get(hospital_id, ""),
  251. medical_staff=(ci.medical_staff or "") if ci else "",
  252. sample_collect_time=_fmt_dt(ci.sample_collect_time) if ci else "",
  253. maternal_blood_collect_time=_fmt_dt(ci.maternal_blood_collect_time) if ci else "",
  254. )
  255. item.update(sample_special_to_dict(dsample))
  256. items.append(item)
  257. if items and tenant_no:
  258. from app.crud import sample_reception as reception_svc
  259. codes = [i["sampleCode"] for i in items if i.get("sampleCode")]
  260. code_map = reception_svc.get_lab_storage_code_map_by_sample_codes(
  261. db, tenant_no, codes, reception_source="research_donation"
  262. )
  263. for item in items:
  264. sc = (item.get("sampleCode") or "").strip()
  265. item["labStorageCode"] = code_map.get(sc, "") if sc else ""
  266. from app.services.sample_info_list_enrich import enrich_sample_info_items
  267. enrich_sample_info_items(
  268. db,
  269. tenant_no,
  270. items,
  271. reception_source="research_donation",
  272. include_handle_flags=False,
  273. )
  274. return items, total