pilot_checkup.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956
  1. """中试申请单-体检 CRUD(体检信息、预约项目、实际项目)"""
  2. from __future__ import annotations
  3. import uuid
  4. from datetime import datetime
  5. from typing import Any, Dict, List, Optional
  6. from sqlalchemy import func, or_
  7. from sqlalchemy.orm import Session
  8. from app.models.models import (
  9. BizCheckupInstitution,
  10. BizPilotApplication,
  11. BizPilotCheckup,
  12. BizPilotCheckupActualItem,
  13. BizPilotCheckupReserveItem,
  14. BizPilotPatient,
  15. )
  16. # ────────────────────────────────────────────
  17. # Helpers
  18. # ────────────────────────────────────────────
  19. def _parse_optional_datetime(val: Any) -> Optional[datetime]:
  20. if val is None or val == "":
  21. return None
  22. if isinstance(val, datetime):
  23. return val
  24. if isinstance(val, str):
  25. text = val.strip()
  26. for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M"):
  27. try:
  28. return datetime.strptime(text, fmt)
  29. except ValueError:
  30. continue
  31. try:
  32. return datetime.fromisoformat(text.replace("Z", "+00:00"))
  33. except ValueError:
  34. return None
  35. return None
  36. # ────────────────────────────────────────────
  37. # Helpers: _row_to_dict
  38. # ────────────────────────────────────────────
  39. def _generate_checkup_no(db: Session, tenant_no: str) -> str:
  40. today_str = datetime.now().strftime("%Y%m%d")
  41. prefix = f"ZSTJ{today_str}"
  42. existing = (
  43. db.query(BizPilotCheckup.checkup_status)
  44. .filter(
  45. BizPilotCheckup.tenant_no == tenant_no,
  46. BizPilotCheckup.id.like(f"{prefix}%"),
  47. BizPilotCheckup.del_flag == 0,
  48. )
  49. .order_by(BizPilotCheckup.create_time.desc())
  50. .first()
  51. )
  52. seq = 1
  53. if existing:
  54. seq = 1
  55. return f"{prefix}{seq:04d}"
  56. def _row_to_dict_checkup(row, db=None, tenant_no=None) -> Dict[str, Any]:
  57. d: Dict[str, Any] = {
  58. "id": row.id,
  59. "pilotApplicationId": row.pilot_application_id or "",
  60. "pilotPatientId": row.pilot_patient_id or "",
  61. "checkupInstitutionId": row.checkup_institution_id or "",
  62. "reserveTime": row.reserve_time.strftime("%Y-%m-%d %H:%M:%S") if row.reserve_time else "",
  63. "reserveFee": float(row.reserve_fee) if row.reserve_fee is not None else None,
  64. "actualCheckupTime": row.actual_checkup_time.strftime("%Y-%m-%d %H:%M:%S") if row.actual_checkup_time else "",
  65. "actualCheckupFee": float(row.actual_checkup_fee) if row.actual_checkup_fee is not None else None,
  66. "feeSettled": row.fee_settled if row.fee_settled is not None else 0,
  67. "checkupStatus": row.checkup_status or "",
  68. "feeStatus": row.fee_status or "",
  69. "checkupRemark": row.checkup_remark or "",
  70. "reportAttachments": row.report_attachments or "",
  71. "tenantNo": row.tenant_no or "",
  72. "remark": row.remark or "",
  73. "createBy": row.create_by or "",
  74. "createTime": row.create_time.strftime("%Y-%m-%d %H:%M:%S") if row.create_time else "",
  75. "updateTime": row.update_time.strftime("%Y-%m-%d %H:%M:%S") if row.update_time else "",
  76. }
  77. if db and tenant_no:
  78. # institutionName — lookup from BizCheckupInstitution
  79. if row.checkup_institution_id:
  80. inst = (
  81. db.query(BizCheckupInstitution.institution_name)
  82. .filter(
  83. BizCheckupInstitution.id == row.checkup_institution_id,
  84. BizCheckupInstitution.del_flag == 0,
  85. )
  86. .first()
  87. )
  88. d["institutionName"] = inst.institution_name if inst else ""
  89. else:
  90. d["institutionName"] = ""
  91. # patientName / sex / age / phoneNo — from BizPilotPatient
  92. if row.pilot_patient_id:
  93. patient = (
  94. db.query(BizPilotPatient)
  95. .filter(
  96. BizPilotPatient.id == row.pilot_patient_id,
  97. BizPilotPatient.del_flag == 0,
  98. )
  99. .first()
  100. )
  101. if patient:
  102. d["patientName"] = patient.patient_name or ""
  103. d["sex"] = patient.sex or ""
  104. d["age"] = patient.age
  105. d["phoneNo"] = patient.phone_no or ""
  106. else:
  107. d["patientName"] = ""
  108. d["sex"] = ""
  109. d["age"] = None
  110. d["phoneNo"] = ""
  111. else:
  112. d["patientName"] = ""
  113. d["sex"] = ""
  114. d["age"] = None
  115. d["phoneNo"] = ""
  116. # applicationNo / channelName — from BizPilotApplication
  117. if row.pilot_application_id:
  118. app = (
  119. db.query(BizPilotApplication)
  120. .filter(
  121. BizPilotApplication.id == row.pilot_application_id,
  122. BizPilotApplication.del_flag == 0,
  123. )
  124. .first()
  125. )
  126. if app:
  127. d["applicationNo"] = app.application_no or ""
  128. d["channelName"] = app.channel_name or ""
  129. else:
  130. d["applicationNo"] = ""
  131. d["channelName"] = ""
  132. else:
  133. d["applicationNo"] = ""
  134. d["channelName"] = ""
  135. # reserveItemCount
  136. reserve_count = (
  137. db.query(func.count(BizPilotCheckupReserveItem.id))
  138. .filter(
  139. BizPilotCheckupReserveItem.pilot_checkup_id == row.id,
  140. BizPilotCheckupReserveItem.tenant_no == tenant_no,
  141. BizPilotCheckupReserveItem.del_flag == 0,
  142. )
  143. .scalar()
  144. ) or 0
  145. d["reserveItemCount"] = reserve_count
  146. # actualItemCount
  147. actual_count = (
  148. db.query(func.count(BizPilotCheckupActualItem.id))
  149. .filter(
  150. BizPilotCheckupActualItem.pilot_checkup_id == row.id,
  151. BizPilotCheckupActualItem.tenant_no == tenant_no,
  152. BizPilotCheckupActualItem.del_flag == 0,
  153. )
  154. .scalar()
  155. ) or 0
  156. d["actualItemCount"] = actual_count
  157. return d
  158. def _row_to_dict_reserve_item(row) -> Dict[str, Any]:
  159. return {
  160. "id": row.id,
  161. "pilotCheckupId": row.pilot_checkup_id or "",
  162. "projectName": row.project_name or "",
  163. "projectFee": float(row.project_fee) if row.project_fee is not None else None,
  164. "orderNum": row.order_num if row.order_num is not None else 0,
  165. "remark": row.remark or "",
  166. "createTime": row.create_time.strftime("%Y-%m-%d %H:%M:%S") if row.create_time else "",
  167. "updateTime": row.update_time.strftime("%Y-%m-%d %H:%M:%S") if row.update_time else "",
  168. }
  169. def _row_to_dict_actual_item(row) -> Dict[str, Any]:
  170. return {
  171. "id": row.id,
  172. "pilotCheckupId": row.pilot_checkup_id or "",
  173. "projectName": row.project_name or "",
  174. "projectFee": float(row.project_fee) if row.project_fee is not None else None,
  175. "orderNum": row.order_num if row.order_num is not None else 0,
  176. "remark": row.remark or "",
  177. "createTime": row.create_time.strftime("%Y-%m-%d %H:%M:%S") if row.create_time else "",
  178. "updateTime": row.update_time.strftime("%Y-%m-%d %H:%M:%S") if row.update_time else "",
  179. }
  180. # ────────────────────────────────────────────
  181. # Checkup CRUD
  182. # ────────────────────────────────────────────
  183. def get_checkup_by_application(
  184. db: Session, application_id: str, tenant_no: str
  185. ) -> Optional[Dict[str, Any]]:
  186. row = (
  187. db.query(BizPilotCheckup)
  188. .filter(
  189. BizPilotCheckup.pilot_application_id == application_id,
  190. BizPilotCheckup.tenant_no == tenant_no,
  191. BizPilotCheckup.del_flag == 0,
  192. )
  193. .first()
  194. )
  195. if not row:
  196. return None
  197. return _row_to_dict_checkup(row, db, tenant_no)
  198. def get_checkup_by_id(
  199. db: Session, checkup_id: str, tenant_no: str
  200. ) -> Optional[Dict[str, Any]]:
  201. row = (
  202. db.query(BizPilotCheckup)
  203. .filter(
  204. BizPilotCheckup.id == checkup_id,
  205. BizPilotCheckup.tenant_no == tenant_no,
  206. BizPilotCheckup.del_flag == 0,
  207. )
  208. .first()
  209. )
  210. if not row:
  211. return None
  212. return _row_to_dict_checkup(row, db, tenant_no)
  213. def create_checkup(
  214. db: Session,
  215. application_id: str,
  216. data: dict,
  217. tenant_no: str,
  218. user_id: str,
  219. ) -> Dict[str, Any]:
  220. now = datetime.now()
  221. row = BizPilotCheckup(
  222. id=uuid.uuid4().hex,
  223. pilot_application_id=application_id,
  224. pilot_patient_id=data.get("pilotPatientId") or None,
  225. checkup_institution_id=data.get("checkupInstitutionId") or "",
  226. reserve_time=_parse_optional_datetime(data.get("reserveTime")),
  227. reserve_fee=data.get("reserveFee") if data.get("reserveFee") is not None else None,
  228. actual_checkup_time=data.get("actualCheckupTime") or None,
  229. actual_checkup_fee=data.get("actualCheckupFee") if data.get("actualCheckupFee") is not None else None,
  230. fee_settled=0,
  231. checkup_status="dtj",
  232. fee_status="wjs",
  233. checkup_remark=data.get("checkupRemark") or "",
  234. report_attachments=data.get("reportAttachments") or "",
  235. tenant_no=tenant_no,
  236. order_num=0,
  237. remark=data.get("remark") or "",
  238. create_by=user_id,
  239. create_dept_id="",
  240. create_time=now,
  241. update_by=user_id,
  242. update_time=now,
  243. del_flag=0,
  244. )
  245. db.add(row)
  246. db.commit()
  247. db.refresh(row)
  248. return _row_to_dict_checkup(row, db, tenant_no)
  249. def update_checkup(
  250. db: Session, checkup_id: str, data: dict, tenant_no: str
  251. ) -> Dict[str, Any]:
  252. row = (
  253. db.query(BizPilotCheckup)
  254. .filter(
  255. BizPilotCheckup.id == checkup_id,
  256. BizPilotCheckup.tenant_no == tenant_no,
  257. BizPilotCheckup.del_flag == 0,
  258. )
  259. .first()
  260. )
  261. if not row:
  262. raise ValueError("体检信息不存在")
  263. fields = {
  264. "checkupInstitutionId": "checkup_institution_id",
  265. "reserveTime": "reserve_time",
  266. "reserveFee": "reserve_fee",
  267. "actualCheckupTime": "actual_checkup_time",
  268. "actualCheckupFee": "actual_checkup_fee",
  269. "checkupRemark": "checkup_remark",
  270. "reportAttachments": "report_attachments",
  271. "remark": "remark",
  272. }
  273. for camel, snake in fields.items():
  274. if camel in data and data[camel] is not None:
  275. val = data[camel]
  276. if snake in ("reserve_time", "actual_checkup_time"):
  277. val = _parse_optional_datetime(val)
  278. setattr(row, snake, val)
  279. row.update_time = datetime.now()
  280. db.commit()
  281. db.refresh(row)
  282. return _row_to_dict_checkup(row, db, tenant_no)
  283. def soft_delete_checkup(db: Session, checkup_id: str, tenant_no: str) -> None:
  284. now = datetime.now()
  285. row = (
  286. db.query(BizPilotCheckup)
  287. .filter(
  288. BizPilotCheckup.id == checkup_id,
  289. BizPilotCheckup.tenant_no == tenant_no,
  290. BizPilotCheckup.del_flag == 0,
  291. )
  292. .first()
  293. )
  294. if not row:
  295. raise ValueError("体检信息不存在")
  296. row.del_flag = 1
  297. row.update_time = now
  298. # Cascade soft-delete to reserve items
  299. db.query(BizPilotCheckupReserveItem).filter(
  300. BizPilotCheckupReserveItem.pilot_checkup_id == checkup_id,
  301. BizPilotCheckupReserveItem.tenant_no == tenant_no,
  302. BizPilotCheckupReserveItem.del_flag == 0,
  303. ).update({"del_flag": 1, "update_time": now}, synchronize_session=False)
  304. # Cascade soft-delete to actual items
  305. db.query(BizPilotCheckupActualItem).filter(
  306. BizPilotCheckupActualItem.pilot_checkup_id == checkup_id,
  307. BizPilotCheckupActualItem.tenant_no == tenant_no,
  308. BizPilotCheckupActualItem.del_flag == 0,
  309. ).update({"del_flag": 1, "update_time": now}, synchronize_session=False)
  310. db.commit()
  311. def mark_checkup_checked(
  312. db: Session, checkup_id: str, tenant_no: str, user_id: str
  313. ) -> Dict[str, Any]:
  314. row = (
  315. db.query(BizPilotCheckup)
  316. .filter(
  317. BizPilotCheckup.id == checkup_id,
  318. BizPilotCheckup.tenant_no == tenant_no,
  319. BizPilotCheckup.del_flag == 0,
  320. )
  321. .first()
  322. )
  323. if not row:
  324. raise ValueError("体检信息不存在")
  325. if row.checkup_status != "dtj":
  326. raise ValueError("当前状态不允许标记已体检")
  327. row.checkup_status = "ytj"
  328. if not row.actual_checkup_time:
  329. row.actual_checkup_time = datetime.now()
  330. row.update_by = user_id
  331. row.update_time = datetime.now()
  332. db.commit()
  333. db.refresh(row)
  334. return _row_to_dict_checkup(row, db, tenant_no)
  335. def settle_checkup_fee(
  336. db: Session, checkup_id: str, tenant_no: str, user_id: str
  337. ) -> Dict[str, Any]:
  338. row = (
  339. db.query(BizPilotCheckup)
  340. .filter(
  341. BizPilotCheckup.id == checkup_id,
  342. BizPilotCheckup.tenant_no == tenant_no,
  343. BizPilotCheckup.del_flag == 0,
  344. )
  345. .first()
  346. )
  347. if not row:
  348. raise ValueError("体检信息不存在")
  349. if row.fee_status != "wjs":
  350. raise ValueError("当前状态不允许结算")
  351. row.fee_status = "yjs"
  352. row.fee_settled = 1
  353. row.update_by = user_id
  354. row.update_time = datetime.now()
  355. db.commit()
  356. db.refresh(row)
  357. return _row_to_dict_checkup(row, db, tenant_no)
  358. # ────────────────────────────────────────────
  359. # Reserve Items CRUD
  360. # ────────────────────────────────────────────
  361. def list_reserve_items(
  362. db: Session, checkup_id: str, tenant_no: str
  363. ) -> List[Dict[str, Any]]:
  364. rows = (
  365. db.query(BizPilotCheckupReserveItem)
  366. .filter(
  367. BizPilotCheckupReserveItem.pilot_checkup_id == checkup_id,
  368. BizPilotCheckupReserveItem.tenant_no == tenant_no,
  369. BizPilotCheckupReserveItem.del_flag == 0,
  370. )
  371. .order_by(
  372. BizPilotCheckupReserveItem.order_num.asc(),
  373. BizPilotCheckupReserveItem.create_time.asc(),
  374. )
  375. .all()
  376. )
  377. return [_row_to_dict_reserve_item(r) for r in rows]
  378. def save_reserve_items(
  379. db: Session,
  380. checkup_id: str,
  381. items_json: list,
  382. tenant_no: str,
  383. user_id: str,
  384. ) -> List[Dict[str, Any]]:
  385. now = datetime.now()
  386. # Soft-delete existing items
  387. db.query(BizPilotCheckupReserveItem).filter(
  388. BizPilotCheckupReserveItem.pilot_checkup_id == checkup_id,
  389. BizPilotCheckupReserveItem.tenant_no == tenant_no,
  390. BizPilotCheckupReserveItem.del_flag == 0,
  391. ).update({"del_flag": 1, "update_time": now}, synchronize_session=False)
  392. # Insert new items
  393. for idx, item in enumerate(items_json):
  394. row = BizPilotCheckupReserveItem(
  395. id=uuid.uuid4().hex,
  396. pilot_checkup_id=checkup_id,
  397. project_name=item.get("projectName") or item.get("project_name") or "",
  398. project_fee=item.get("projectFee") if item.get("projectFee") is not None else (
  399. item.get("project_fee") if item.get("project_fee") is not None else None
  400. ),
  401. tenant_no=tenant_no,
  402. order_num=idx + 1,
  403. remark=item.get("remark") or "",
  404. create_by=user_id,
  405. create_dept_id="",
  406. create_time=now,
  407. update_by=user_id,
  408. update_time=now,
  409. del_flag=0,
  410. )
  411. db.add(row)
  412. db.commit()
  413. return list_reserve_items(db, checkup_id, tenant_no)
  414. def delete_reserve_item(db: Session, item_id: str, tenant_no: str) -> None:
  415. row = (
  416. db.query(BizPilotCheckupReserveItem)
  417. .filter(
  418. BizPilotCheckupReserveItem.id == item_id,
  419. BizPilotCheckupReserveItem.tenant_no == tenant_no,
  420. BizPilotCheckupReserveItem.del_flag == 0,
  421. )
  422. .first()
  423. )
  424. if not row:
  425. raise ValueError("预约项目不存在")
  426. row.del_flag = 1
  427. row.update_time = datetime.now()
  428. db.commit()
  429. # ────────────────────────────────────────────
  430. # Actual Items CRUD
  431. # ────────────────────────────────────────────
  432. def list_actual_items(
  433. db: Session, checkup_id: str, tenant_no: str
  434. ) -> List[Dict[str, Any]]:
  435. rows = (
  436. db.query(BizPilotCheckupActualItem)
  437. .filter(
  438. BizPilotCheckupActualItem.pilot_checkup_id == checkup_id,
  439. BizPilotCheckupActualItem.tenant_no == tenant_no,
  440. BizPilotCheckupActualItem.del_flag == 0,
  441. )
  442. .order_by(
  443. BizPilotCheckupActualItem.order_num.asc(),
  444. BizPilotCheckupActualItem.create_time.asc(),
  445. )
  446. .all()
  447. )
  448. return [_row_to_dict_actual_item(r) for r in rows]
  449. def save_actual_items(
  450. db: Session,
  451. checkup_id: str,
  452. items_json: list,
  453. tenant_no: str,
  454. user_id: str,
  455. ) -> List[Dict[str, Any]]:
  456. now = datetime.now()
  457. # Soft-delete existing items
  458. db.query(BizPilotCheckupActualItem).filter(
  459. BizPilotCheckupActualItem.pilot_checkup_id == checkup_id,
  460. BizPilotCheckupActualItem.tenant_no == tenant_no,
  461. BizPilotCheckupActualItem.del_flag == 0,
  462. ).update({"del_flag": 1, "update_time": now}, synchronize_session=False)
  463. # Insert new items
  464. for idx, item in enumerate(items_json):
  465. row = BizPilotCheckupActualItem(
  466. id=uuid.uuid4().hex,
  467. pilot_checkup_id=checkup_id,
  468. project_name=item.get("projectName") or item.get("project_name") or "",
  469. project_fee=item.get("projectFee") if item.get("projectFee") is not None else (
  470. item.get("project_fee") if item.get("project_fee") is not None else None
  471. ),
  472. tenant_no=tenant_no,
  473. order_num=idx + 1,
  474. remark=item.get("remark") or "",
  475. create_by=user_id,
  476. create_dept_id="",
  477. create_time=now,
  478. update_by=user_id,
  479. update_time=now,
  480. del_flag=0,
  481. )
  482. db.add(row)
  483. db.commit()
  484. return list_actual_items(db, checkup_id, tenant_no)
  485. def delete_actual_item(db: Session, item_id: str, tenant_no: str) -> None:
  486. row = (
  487. db.query(BizPilotCheckupActualItem)
  488. .filter(
  489. BizPilotCheckupActualItem.id == item_id,
  490. BizPilotCheckupActualItem.tenant_no == tenant_no,
  491. BizPilotCheckupActualItem.del_flag == 0,
  492. )
  493. .first()
  494. )
  495. if not row:
  496. raise ValueError("实际项目不存在")
  497. row.del_flag = 1
  498. row.update_time = datetime.now()
  499. db.commit()
  500. # ────────────────────────────────────────────
  501. # Checkup List (Paginated)
  502. # ────────────────────────────────────────────
  503. def list_checkups_paginated(
  504. db: Session,
  505. tenant_no: str,
  506. page: int = 1,
  507. page_size: int = 10,
  508. checkup_status: Optional[str] = None,
  509. fee_status: Optional[str] = None,
  510. checkup_institution_id: Optional[str] = None,
  511. keyword: Optional[str] = None,
  512. application_no: Optional[str] = None,
  513. patient_name: Optional[str] = None,
  514. channel_id: Optional[str] = None,
  515. biz_type: Optional[str] = None,
  516. ) -> Dict[str, Any]:
  517. q = db.query(BizPilotCheckup).filter(
  518. BizPilotCheckup.tenant_no == tenant_no,
  519. BizPilotCheckup.del_flag == 0,
  520. )
  521. if biz_type:
  522. app_subq = (
  523. db.query(BizPilotApplication.id)
  524. .filter(
  525. BizPilotApplication.biz_type == biz_type,
  526. BizPilotApplication.del_flag == 0,
  527. )
  528. .subquery()
  529. )
  530. q = q.filter(BizPilotCheckup.pilot_application_id.in_(app_subq))
  531. if checkup_status:
  532. statuses = [s.strip() for s in checkup_status.split(",") if s.strip()]
  533. if statuses:
  534. q = q.filter(BizPilotCheckup.checkup_status.in_(statuses))
  535. if fee_status:
  536. statuses = [s.strip() for s in fee_status.split(",") if s.strip()]
  537. if statuses:
  538. q = q.filter(BizPilotCheckup.fee_status.in_(statuses))
  539. if checkup_institution_id:
  540. q = q.filter(BizPilotCheckup.checkup_institution_id == checkup_institution_id)
  541. # keyword: fuzzy search across application_no, patient_name, channel_name
  542. if keyword:
  543. kw = f"%{keyword}%"
  544. app_ids_by_no = (
  545. db.query(BizPilotApplication.id)
  546. .filter(
  547. BizPilotApplication.application_no.like(kw),
  548. BizPilotApplication.del_flag == 0,
  549. )
  550. .subquery()
  551. )
  552. app_ids_by_channel = (
  553. db.query(BizPilotApplication.id)
  554. .filter(
  555. BizPilotApplication.channel_name.like(kw),
  556. BizPilotApplication.del_flag == 0,
  557. )
  558. .subquery()
  559. )
  560. patient_ids_by_name = (
  561. db.query(BizPilotPatient.id)
  562. .filter(
  563. BizPilotPatient.patient_name.like(kw),
  564. BizPilotPatient.del_flag == 0,
  565. )
  566. .subquery()
  567. )
  568. q = q.filter(
  569. or_(
  570. BizPilotCheckup.pilot_application_id.in_(app_ids_by_no),
  571. BizPilotCheckup.pilot_application_id.in_(app_ids_by_channel),
  572. BizPilotCheckup.pilot_patient_id.in_(patient_ids_by_name),
  573. )
  574. )
  575. if application_no:
  576. app_subq = (
  577. db.query(BizPilotApplication.id)
  578. .filter(
  579. BizPilotApplication.application_no.like(f"%{application_no}%"),
  580. BizPilotApplication.del_flag == 0,
  581. )
  582. .subquery()
  583. )
  584. q = q.filter(BizPilotCheckup.pilot_application_id.in_(app_subq))
  585. if patient_name:
  586. patient_subq = (
  587. db.query(BizPilotPatient.id)
  588. .filter(
  589. BizPilotPatient.patient_name.like(f"%{patient_name}%"),
  590. BizPilotPatient.del_flag == 0,
  591. )
  592. .subquery()
  593. )
  594. q = q.filter(BizPilotCheckup.pilot_patient_id.in_(patient_subq))
  595. if channel_id:
  596. app_subq = (
  597. db.query(BizPilotApplication.id)
  598. .filter(
  599. BizPilotApplication.channel_id == channel_id,
  600. BizPilotApplication.del_flag == 0,
  601. )
  602. .subquery()
  603. )
  604. q = q.filter(BizPilotCheckup.pilot_application_id.in_(app_subq))
  605. total = q.order_by(None).with_entities(func.count(BizPilotCheckup.id)).scalar() or 0
  606. page = max(1, page)
  607. page_size = min(max(1, page_size), 100)
  608. rows = (
  609. q.order_by(BizPilotCheckup.create_time.desc())
  610. .offset((page - 1) * page_size)
  611. .limit(page_size)
  612. .all()
  613. )
  614. items = [_row_to_dict_checkup(r, db, tenant_no) for r in rows]
  615. return {
  616. "list": items,
  617. "pagination": {"page": page, "pageSize": page_size, "total": total},
  618. }
  619. def list_pending_patients(
  620. db: Session,
  621. tenant_no: str,
  622. page: int = 1,
  623. page_size: int = 10,
  624. application_no: Optional[str] = None,
  625. patient_name: Optional[str] = None,
  626. channel_id: Optional[str] = None,
  627. ) -> Dict[str, Any]:
  628. """查询 need_checkup=1 但尚未有 biz_pilot_checkup 记录的患者"""
  629. checked_patient_ids = (
  630. db.query(BizPilotCheckup.pilot_patient_id)
  631. .filter(
  632. BizPilotCheckup.tenant_no == tenant_no,
  633. BizPilotCheckup.del_flag == 0,
  634. BizPilotCheckup.pilot_patient_id.isnot(None),
  635. BizPilotCheckup.pilot_patient_id != "",
  636. )
  637. .subquery()
  638. )
  639. q = db.query(BizPilotPatient).filter(
  640. BizPilotPatient.need_checkup == 1,
  641. BizPilotPatient.tenant_no == tenant_no,
  642. BizPilotPatient.del_flag == 0,
  643. ~BizPilotPatient.id.in_(checked_patient_ids),
  644. )
  645. if patient_name:
  646. q = q.filter(BizPilotPatient.patient_name.like(f"%{patient_name}%"))
  647. if application_no or channel_id:
  648. app_q = db.query(BizPilotApplication.id).filter(
  649. BizPilotApplication.tenant_no == tenant_no,
  650. BizPilotApplication.del_flag == 0,
  651. )
  652. if application_no:
  653. app_q = app_q.filter(
  654. BizPilotApplication.application_no.like(f"%{application_no}%")
  655. )
  656. if channel_id:
  657. app_q = app_q.filter(BizPilotApplication.channel_id == channel_id)
  658. app_subq = app_q.subquery()
  659. q = q.filter(BizPilotPatient.pilot_application_id.in_(app_subq))
  660. total = q.order_by(None).with_entities(func.count(BizPilotPatient.id)).scalar() or 0
  661. page = max(1, page)
  662. page_size = min(max(1, page_size), 100)
  663. rows = (
  664. q.order_by(BizPilotPatient.create_time.desc())
  665. .offset((page - 1) * page_size)
  666. .limit(page_size)
  667. .all()
  668. )
  669. items = []
  670. for r in rows:
  671. d = {
  672. "id": r.id,
  673. "pilotApplicationId": r.pilot_application_id or "",
  674. "patientName": r.patient_name or "",
  675. "sex": r.sex or "",
  676. "age": r.age,
  677. "phoneNo": r.phone_no or "",
  678. }
  679. app = (
  680. db.query(BizPilotApplication)
  681. .filter(
  682. BizPilotApplication.id == r.pilot_application_id,
  683. BizPilotApplication.del_flag == 0,
  684. )
  685. .first()
  686. )
  687. if app:
  688. d["applicationNo"] = app.application_no or ""
  689. d["channelName"] = app.channel_name or ""
  690. d["channelId"] = app.channel_id or ""
  691. else:
  692. d["applicationNo"] = ""
  693. d["channelName"] = ""
  694. d["channelId"] = ""
  695. items.append(d)
  696. return {
  697. "list": items,
  698. "pagination": {"page": page, "pageSize": page_size, "total": total},
  699. }
  700. def batch_create_checkups(
  701. db: Session,
  702. patient_ids: list,
  703. checkup_institution_id: str,
  704. reserve_time: Optional[str],
  705. reserve_items: list,
  706. tenant_no: str,
  707. user_id: str,
  708. ) -> list:
  709. """为多个患者批量创建体检记录 + 预约项目"""
  710. results = []
  711. now = datetime.now()
  712. for patient_id in patient_ids:
  713. patient = (
  714. db.query(BizPilotPatient)
  715. .filter(
  716. BizPilotPatient.id == patient_id,
  717. BizPilotPatient.tenant_no == tenant_no,
  718. BizPilotPatient.del_flag == 0,
  719. )
  720. .first()
  721. )
  722. if not patient:
  723. continue
  724. checkup_row = BizPilotCheckup(
  725. id=uuid.uuid4().hex,
  726. pilot_application_id=patient.pilot_application_id,
  727. pilot_patient_id=patient_id,
  728. checkup_institution_id=checkup_institution_id or "",
  729. reserve_time=_parse_optional_datetime(reserve_time),
  730. reserve_fee=sum(
  731. float(item.get("projectFee") or 0) for item in reserve_items
  732. ) if reserve_items else None,
  733. actual_checkup_time=None,
  734. actual_checkup_fee=None,
  735. fee_settled=0,
  736. checkup_status="dtj",
  737. fee_status="wjs",
  738. checkup_remark="",
  739. report_attachments="",
  740. tenant_no=tenant_no,
  741. order_num=0,
  742. remark="",
  743. create_by=user_id,
  744. create_dept_id="",
  745. create_time=now,
  746. update_by=user_id,
  747. update_time=now,
  748. del_flag=0,
  749. )
  750. db.add(checkup_row)
  751. db.flush()
  752. for idx, item in enumerate(reserve_items):
  753. ri = BizPilotCheckupReserveItem(
  754. id=uuid.uuid4().hex,
  755. pilot_checkup_id=checkup_row.id,
  756. project_name=item.get("projectName") or item.get("project_name") or "",
  757. project_fee=item.get("projectFee") if item.get("projectFee") is not None else (
  758. item.get("project_fee") if item.get("project_fee") is not None else None
  759. ),
  760. tenant_no=tenant_no,
  761. order_num=idx + 1,
  762. remark="",
  763. create_by=user_id,
  764. create_dept_id="",
  765. create_time=now,
  766. update_by=user_id,
  767. update_time=now,
  768. del_flag=0,
  769. )
  770. db.add(ri)
  771. results.append(_row_to_dict_checkup(checkup_row, db, tenant_no))
  772. db.commit()
  773. return results
  774. def ensure_checkup_for_patient(
  775. db: Session,
  776. patient_id: str,
  777. application_id: str,
  778. tenant_no: str,
  779. user_id: str,
  780. ) -> Optional[str]:
  781. """为患者创建体检记录(如不存在)。返回 checkup_id 或 None。"""
  782. existing = (
  783. db.query(BizPilotCheckup)
  784. .filter(
  785. BizPilotCheckup.pilot_patient_id == patient_id,
  786. BizPilotCheckup.tenant_no == tenant_no,
  787. BizPilotCheckup.del_flag == 0,
  788. )
  789. .first()
  790. )
  791. if existing:
  792. return existing.id
  793. now = datetime.now()
  794. row = BizPilotCheckup(
  795. id=uuid.uuid4().hex,
  796. pilot_application_id=application_id,
  797. pilot_patient_id=patient_id,
  798. checkup_institution_id="",
  799. checkup_status="dtj",
  800. fee_status="wjs",
  801. fee_settled=0,
  802. checkup_remark="",
  803. report_attachments="",
  804. tenant_no=tenant_no,
  805. order_num=0,
  806. remark="",
  807. create_by=user_id,
  808. create_dept_id="",
  809. create_time=now,
  810. update_by=user_id,
  811. update_time=now,
  812. del_flag=0,
  813. )
  814. db.add(row)
  815. db.flush()
  816. return row.id
  817. def remove_checkup_for_patient(
  818. db: Session,
  819. patient_id: str,
  820. tenant_no: str,
  821. ) -> None:
  822. """软删除患者的体检记录(仅在初始状态,无机构数据时才删除)。"""
  823. row = (
  824. db.query(BizPilotCheckup)
  825. .filter(
  826. BizPilotCheckup.pilot_patient_id == patient_id,
  827. BizPilotCheckup.tenant_no == tenant_no,
  828. BizPilotCheckup.del_flag == 0,
  829. )
  830. .first()
  831. )
  832. if not row:
  833. return
  834. if row.checkup_institution_id:
  835. return
  836. now = datetime.now()
  837. row.del_flag = 1
  838. row.update_time = now
  839. db.query(BizPilotCheckupReserveItem).filter(
  840. BizPilotCheckupReserveItem.pilot_checkup_id == row.id,
  841. BizPilotCheckupReserveItem.tenant_no == tenant_no,
  842. BizPilotCheckupReserveItem.del_flag == 0,
  843. ).update({"del_flag": 1, "update_time": now}, synchronize_session=False)
  844. db.query(BizPilotCheckupActualItem).filter(
  845. BizPilotCheckupActualItem.pilot_checkup_id == row.id,
  846. BizPilotCheckupActualItem.tenant_no == tenant_no,
  847. BizPilotCheckupActualItem.del_flag == 0,
  848. ).update({"del_flag": 1, "update_time": now}, synchronize_session=False)