pilot_checkup.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. """中试服务体检管理 API(体检信息、预约项目、实际项目)"""
  2. from typing import List, Optional
  3. from fastapi import APIRouter, Depends, Query
  4. from pydantic import AliasChoices, BaseModel, ConfigDict, Field
  5. from starlette.requests import Request
  6. from sqlalchemy.orm import Session
  7. from app.core.database import get_db
  8. from app.core.menu_guard import require_admin_or_super_admin
  9. from app.core.response import error_response, success_response
  10. from app.crud import pilot_checkup
  11. from app.models.models import SysUser
  12. router = APIRouter(prefix="/web/admin", tags=["中试服务体检管理"])
  13. def _get_tenant_no(db: Session, user: dict) -> str:
  14. user_id = user.get("user_id")
  15. if not user_id:
  16. raise PermissionError("用户身份信息缺失,无法确定租户")
  17. sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
  18. if not sys_user or not sys_user.tenant_no:
  19. raise PermissionError("用户租户信息缺失")
  20. return sys_user.tenant_no
  21. # ---------------------------------------------------------------------------
  22. # Pydantic Schemas
  23. # ---------------------------------------------------------------------------
  24. class IdBody(BaseModel):
  25. id: str
  26. class CheckupSaveBody(BaseModel):
  27. model_config = ConfigDict(populate_by_name=True)
  28. application_id: Optional[str] = Field(
  29. None, validation_alias=AliasChoices("applicationId", "application_id")
  30. )
  31. checkup_id: Optional[str] = Field(
  32. None, validation_alias=AliasChoices("checkupId", "checkup_id")
  33. )
  34. patient_id: Optional[str] = Field(
  35. None, validation_alias=AliasChoices("patientId", "patient_id")
  36. )
  37. checkup_institution_id: Optional[str] = Field(
  38. None, validation_alias=AliasChoices("checkupInstitutionId", "checkup_institution_id")
  39. )
  40. reserve_time: Optional[str] = Field(
  41. None, validation_alias=AliasChoices("reserveTime", "reserve_time")
  42. )
  43. reserve_fee: Optional[float] = Field(
  44. None, validation_alias=AliasChoices("reserveFee", "reserve_fee")
  45. )
  46. actual_checkup_time: Optional[str] = Field(
  47. None, validation_alias=AliasChoices("actualCheckupTime", "actual_checkup_time")
  48. )
  49. actual_checkup_fee: Optional[float] = Field(
  50. None, validation_alias=AliasChoices("actualCheckupFee", "actual_checkup_fee")
  51. )
  52. checkup_remark: Optional[str] = Field(
  53. None, validation_alias=AliasChoices("checkupRemark", "checkup_remark")
  54. )
  55. report_attachments: Optional[str] = Field(
  56. None, validation_alias=AliasChoices("reportAttachments", "report_attachments")
  57. )
  58. class CheckupItemBody(BaseModel):
  59. model_config = ConfigDict(populate_by_name=True)
  60. project_name: str = Field(..., validation_alias=AliasChoices("projectName", "project_name"))
  61. project_fee: Optional[float] = Field(
  62. None, validation_alias=AliasChoices("projectFee", "project_fee")
  63. )
  64. class CheckupItemsSaveBody(BaseModel):
  65. model_config = ConfigDict(populate_by_name=True)
  66. pilot_checkup_id: str = Field(
  67. ..., validation_alias=AliasChoices("pilotCheckupId", "pilot_checkup_id")
  68. )
  69. items: List[CheckupItemBody]
  70. class BatchCheckupSaveBody(BaseModel):
  71. model_config = ConfigDict(populate_by_name=True)
  72. patient_ids: List[str] = Field(..., validation_alias=AliasChoices("patientIds", "patient_ids"))
  73. checkup_institution_id: Optional[str] = Field(
  74. None, validation_alias=AliasChoices("checkupInstitutionId", "checkup_institution_id")
  75. )
  76. reserve_time: Optional[str] = Field(
  77. None, validation_alias=AliasChoices("reserveTime", "reserve_time")
  78. )
  79. reserve_items: Optional[List[CheckupItemBody]] = Field(
  80. None, validation_alias=AliasChoices("reserveItems", "reserve_items")
  81. )
  82. # ---------------------------------------------------------------------------
  83. # 1. 体检信息 CRUD + 状态变更
  84. # ---------------------------------------------------------------------------
  85. @router.get("/pilot-checkup/detail")
  86. async def api_get_checkup_detail(
  87. application_id: Optional[str] = Query(None, alias="applicationId"),
  88. checkup_id: Optional[str] = Query(None, alias="checkupId"),
  89. user: dict = Depends(require_admin_or_super_admin),
  90. db: Session = Depends(get_db),
  91. ):
  92. tenant_no = _get_tenant_no(db, user)
  93. if checkup_id:
  94. result = pilot_checkup.get_checkup_by_id(
  95. db, checkup_id=checkup_id, tenant_no=tenant_no
  96. )
  97. elif application_id:
  98. result = pilot_checkup.get_checkup_by_application(
  99. db, application_id=application_id, tenant_no=tenant_no
  100. )
  101. else:
  102. return error_response(400, "请提供 application_id 或 checkupId")
  103. if result is None:
  104. return success_response(None)
  105. return success_response(result)
  106. @router.post("/pilot-checkup/save")
  107. async def api_save_checkup(
  108. request: Request,
  109. user: dict = Depends(require_admin_or_super_admin),
  110. db: Session = Depends(get_db),
  111. ):
  112. try:
  113. payload = await request.json()
  114. if not isinstance(payload, dict):
  115. return error_response(400, "请求体格式错误")
  116. body = CheckupSaveBody.model_validate(payload)
  117. tenant_no = _get_tenant_no(db, user)
  118. data = {
  119. "checkupInstitutionId": body.checkup_institution_id,
  120. "pilotPatientId": body.patient_id,
  121. "reserveTime": body.reserve_time,
  122. "reserveFee": body.reserve_fee,
  123. "actualCheckupTime": body.actual_checkup_time,
  124. "actualCheckupFee": body.actual_checkup_fee,
  125. "checkupRemark": body.checkup_remark,
  126. "reportAttachments": body.report_attachments,
  127. }
  128. if body.checkup_id:
  129. result = pilot_checkup.update_checkup(
  130. db, checkup_id=body.checkup_id, data=data, tenant_no=tenant_no
  131. )
  132. return success_response(result, message="更新成功")
  133. if not body.application_id:
  134. return error_response(400, "请提供 application_id 或 checkup_id")
  135. existing = pilot_checkup.get_checkup_by_application(
  136. db, application_id=body.application_id, tenant_no=tenant_no
  137. )
  138. if existing:
  139. result = pilot_checkup.update_checkup(
  140. db, checkup_id=existing["id"], data=data, tenant_no=tenant_no
  141. )
  142. return success_response(result, message="更新成功")
  143. else:
  144. result = pilot_checkup.create_checkup(
  145. db,
  146. application_id=body.application_id,
  147. data=data,
  148. tenant_no=tenant_no,
  149. user_id=user.get("user_id"),
  150. )
  151. return success_response(result, message="创建成功")
  152. except ValueError as e:
  153. return error_response(400, str(e))
  154. @router.delete("/pilot-checkup/delete")
  155. async def api_delete_checkup(
  156. body: IdBody,
  157. user: dict = Depends(require_admin_or_super_admin),
  158. db: Session = Depends(get_db),
  159. ):
  160. try:
  161. tenant_no = _get_tenant_no(db, user)
  162. pilot_checkup.soft_delete_checkup(db, checkup_id=body.id, tenant_no=tenant_no)
  163. return success_response(None, message="删除成功")
  164. except ValueError as e:
  165. return error_response(400, str(e))
  166. @router.post("/pilot-checkup/mark-checked")
  167. async def api_mark_checked(
  168. body: IdBody,
  169. user: dict = Depends(require_admin_or_super_admin),
  170. db: Session = Depends(get_db),
  171. ):
  172. try:
  173. tenant_no = _get_tenant_no(db, user)
  174. result = pilot_checkup.mark_checkup_checked(
  175. db, checkup_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id")
  176. )
  177. return success_response(result, message="操作成功")
  178. except ValueError as e:
  179. return error_response(400, str(e))
  180. @router.post("/pilot-checkup/settle-fee")
  181. async def api_settle_fee(
  182. body: IdBody,
  183. user: dict = Depends(require_admin_or_super_admin),
  184. db: Session = Depends(get_db),
  185. ):
  186. try:
  187. tenant_no = _get_tenant_no(db, user)
  188. result = pilot_checkup.settle_checkup_fee(
  189. db, checkup_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id")
  190. )
  191. return success_response(result, message="结算成功")
  192. except ValueError as e:
  193. return error_response(400, str(e))
  194. # ---------------------------------------------------------------------------
  195. # 2. 预约项目
  196. # ---------------------------------------------------------------------------
  197. @router.get("/pilot-checkup/reserve-items")
  198. async def api_list_reserve_items(
  199. pilot_checkup_id: str = Query(..., alias="pilotCheckupId"),
  200. user: dict = Depends(require_admin_or_super_admin),
  201. db: Session = Depends(get_db),
  202. ):
  203. tenant_no = _get_tenant_no(db, user)
  204. result = pilot_checkup.list_reserve_items(
  205. db, checkup_id=pilot_checkup_id, tenant_no=tenant_no
  206. )
  207. return success_response(result)
  208. @router.post("/pilot-checkup/reserve-items/save")
  209. async def api_save_reserve_items(
  210. body: CheckupItemsSaveBody,
  211. user: dict = Depends(require_admin_or_super_admin),
  212. db: Session = Depends(get_db),
  213. ):
  214. try:
  215. tenant_no = _get_tenant_no(db, user)
  216. items_json = [item.model_dump(by_alias=False) for item in body.items]
  217. result = pilot_checkup.save_reserve_items(
  218. db,
  219. checkup_id=body.pilot_checkup_id,
  220. items_json=items_json,
  221. tenant_no=tenant_no,
  222. user_id=user.get("user_id"),
  223. )
  224. return success_response(result, message="保存成功")
  225. except ValueError as e:
  226. return error_response(400, str(e))
  227. @router.delete("/pilot-checkup/reserve-item/delete")
  228. async def api_delete_reserve_item(
  229. body: IdBody,
  230. user: dict = Depends(require_admin_or_super_admin),
  231. db: Session = Depends(get_db),
  232. ):
  233. try:
  234. tenant_no = _get_tenant_no(db, user)
  235. pilot_checkup.delete_reserve_item(db, item_id=body.id, tenant_no=tenant_no)
  236. return success_response(None, message="删除成功")
  237. except ValueError as e:
  238. return error_response(400, str(e))
  239. # ---------------------------------------------------------------------------
  240. # 3. 实际项目
  241. # ---------------------------------------------------------------------------
  242. @router.get("/pilot-checkup/actual-items")
  243. async def api_list_actual_items(
  244. pilot_checkup_id: str = Query(..., alias="pilotCheckupId"),
  245. user: dict = Depends(require_admin_or_super_admin),
  246. db: Session = Depends(get_db),
  247. ):
  248. tenant_no = _get_tenant_no(db, user)
  249. result = pilot_checkup.list_actual_items(
  250. db, checkup_id=pilot_checkup_id, tenant_no=tenant_no
  251. )
  252. return success_response(result)
  253. @router.post("/pilot-checkup/actual-items/save")
  254. async def api_save_actual_items(
  255. body: CheckupItemsSaveBody,
  256. user: dict = Depends(require_admin_or_super_admin),
  257. db: Session = Depends(get_db),
  258. ):
  259. try:
  260. tenant_no = _get_tenant_no(db, user)
  261. items_json = [item.model_dump(by_alias=False) for item in body.items]
  262. result = pilot_checkup.save_actual_items(
  263. db,
  264. checkup_id=body.pilot_checkup_id,
  265. items_json=items_json,
  266. tenant_no=tenant_no,
  267. user_id=user.get("user_id"),
  268. )
  269. return success_response(result, message="保存成功")
  270. except ValueError as e:
  271. return error_response(400, str(e))
  272. @router.delete("/pilot-checkup/actual-item/delete")
  273. async def api_delete_actual_item(
  274. body: IdBody,
  275. user: dict = Depends(require_admin_or_super_admin),
  276. db: Session = Depends(get_db),
  277. ):
  278. try:
  279. tenant_no = _get_tenant_no(db, user)
  280. pilot_checkup.delete_actual_item(db, item_id=body.id, tenant_no=tenant_no)
  281. return success_response(None, message="删除成功")
  282. except ValueError as e:
  283. return error_response(400, str(e))
  284. # ---------------------------------------------------------------------------
  285. # 4. 体检管理列表、待体检患者、批量创建
  286. # ---------------------------------------------------------------------------
  287. @router.get("/pilot-checkup/list")
  288. async def api_list_checkups(
  289. page: int = Query(1, ge=1),
  290. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  291. checkupStatus: Optional[str] = Query(None, alias="checkupStatus"),
  292. feeStatus: Optional[str] = Query(None, alias="feeStatus"),
  293. checkupInstitutionId: Optional[str] = Query(None, alias="checkupInstitutionId"),
  294. keyword: Optional[str] = Query(None, alias="keyword"),
  295. applicationNo: Optional[str] = Query(None, alias="applicationNo"),
  296. patientName: Optional[str] = Query(None, alias="patientName"),
  297. channelId: Optional[str] = Query(None, alias="channelId"),
  298. bizType: Optional[str] = Query(None, alias="bizType"),
  299. user: dict = Depends(require_admin_or_super_admin),
  300. db: Session = Depends(get_db),
  301. ):
  302. tenant_no = _get_tenant_no(db, user)
  303. result = pilot_checkup.list_checkups_paginated(
  304. db,
  305. tenant_no=tenant_no,
  306. page=page,
  307. page_size=pageSize,
  308. checkup_status=checkupStatus,
  309. fee_status=feeStatus,
  310. checkup_institution_id=checkupInstitutionId,
  311. keyword=keyword,
  312. application_no=applicationNo,
  313. patient_name=patientName,
  314. channel_id=channelId,
  315. biz_type=bizType,
  316. )
  317. return success_response(result)
  318. @router.get("/pilot-checkup/pending-patients")
  319. async def api_list_pending_patients(
  320. page: int = Query(1, ge=1),
  321. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  322. applicationNo: Optional[str] = Query(None, alias="applicationNo"),
  323. patientName: Optional[str] = Query(None, alias="patientName"),
  324. channelId: Optional[str] = Query(None, alias="channelId"),
  325. user: dict = Depends(require_admin_or_super_admin),
  326. db: Session = Depends(get_db),
  327. ):
  328. tenant_no = _get_tenant_no(db, user)
  329. result = pilot_checkup.list_pending_patients(
  330. db,
  331. tenant_no=tenant_no,
  332. page=page,
  333. page_size=pageSize,
  334. application_no=applicationNo,
  335. patient_name=patientName,
  336. channel_id=channelId,
  337. )
  338. return success_response(result)
  339. @router.post("/pilot-checkup/batch-save")
  340. async def api_batch_save_checkup(
  341. body: BatchCheckupSaveBody,
  342. user: dict = Depends(require_admin_or_super_admin),
  343. db: Session = Depends(get_db),
  344. ):
  345. try:
  346. tenant_no = _get_tenant_no(db, user)
  347. items_json = [item.model_dump(by_alias=False) for item in body.reserve_items] if body.reserve_items else []
  348. result = pilot_checkup.batch_create_checkups(
  349. db,
  350. patient_ids=body.patient_ids,
  351. checkup_institution_id=body.checkup_institution_id or "",
  352. reserve_time=body.reserve_time or None,
  353. reserve_items=items_json,
  354. tenant_no=tenant_no,
  355. user_id=user.get("user_id"),
  356. )
  357. return success_response(result, message=f"成功为 {len(result)} 名患者创建体检记录")
  358. except ValueError as e:
  359. return error_response(400, str(e))