pilot_application.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. """中试服务申请单管理 API(申请单、患者信息、输注方案、输注计划、出库联动)"""
  2. from typing import Optional
  3. from fastapi import APIRouter, Depends, Query
  4. from pydantic import BaseModel
  5. from sqlalchemy.orm import Session
  6. from app.core.database import get_db
  7. from app.core.menu_guard import require_admin_or_super_admin
  8. from app.core.response import error_response, success_response
  9. from app.crud import pilot_application
  10. from app.models.models import SysUser
  11. router = APIRouter(prefix="/web/admin", tags=["中试服务申请单管理"])
  12. def _get_tenant_no(db: Session, user: dict) -> str:
  13. user_id = user.get("user_id")
  14. if not user_id:
  15. raise PermissionError("用户身份信息缺失,无法确定租户")
  16. sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
  17. if not sys_user or not sys_user.tenant_no:
  18. raise PermissionError("用户租户信息缺失")
  19. return sys_user.tenant_no
  20. # ---------------------------------------------------------------------------
  21. # Pydantic Schemas
  22. # ---------------------------------------------------------------------------
  23. class IdBody(BaseModel):
  24. id: str
  25. class PlanIdBody(BaseModel):
  26. plan_id: str
  27. class ApplicationCreateBody(BaseModel):
  28. channel_service_order_id: str
  29. remark: Optional[str] = None
  30. class ApplicationUpdateBody(BaseModel):
  31. id: str
  32. remark: Optional[str] = None
  33. class PatientSaveBody(BaseModel):
  34. application_id: str
  35. customer_id: Optional[str] = None
  36. patient_name: str
  37. sex: Optional[str] = None
  38. age: Optional[int] = None
  39. id_card_no: Optional[str] = None
  40. phone_no: Optional[str] = None
  41. emergency_contact: Optional[str] = None
  42. emergency_phone: Optional[str] = None
  43. clinical_diagnosis: Optional[str] = None
  44. diagnosis_attachments: Optional[str] = None
  45. need_checkup: int = 1
  46. class InfusionPlanCreateBody(BaseModel):
  47. pilot_application_id: str
  48. cell_type: str
  49. spec_text: Optional[str] = None
  50. total_course_count: int
  51. course_interval: Optional[str] = None
  52. infusion_route: Optional[str] = None
  53. infusion_start_date: Optional[str] = None
  54. class InfusionPlanUpdateBody(BaseModel):
  55. id: str
  56. cell_type: Optional[str] = None
  57. spec_text: Optional[str] = None
  58. total_course_count: Optional[int] = None
  59. course_interval: Optional[str] = None
  60. infusion_route: Optional[str] = None
  61. infusion_start_date: Optional[str] = None
  62. class InfusionScheduleUpdateBody(BaseModel):
  63. id: str
  64. infusion_time: Optional[str] = None
  65. remark: Optional[str] = None
  66. attachments: Optional[str] = None
  67. class ScheduleCreateBody(BaseModel):
  68. plan_id: str
  69. infusion_time: Optional[str] = None
  70. remark: Optional[str] = None
  71. # ---------------------------------------------------------------------------
  72. # 1. 申请单 CRUD + 状态变更
  73. # ---------------------------------------------------------------------------
  74. @router.get("/pilot-application/list")
  75. async def api_list_applications(
  76. page: int = Query(1, ge=1),
  77. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  78. channelId: Optional[str] = Query(None, alias="channelId"),
  79. applicationStatus: Optional[str] = Query(None, alias="applicationStatus"),
  80. channelServiceOrderId: Optional[str] = Query(None, alias="channelServiceOrderId"),
  81. user: dict = Depends(require_admin_or_super_admin),
  82. db: Session = Depends(get_db),
  83. ):
  84. tenant_no = _get_tenant_no(db, user)
  85. result = pilot_application.list_applications(
  86. db,
  87. tenant_no=tenant_no,
  88. page=page,
  89. page_size=pageSize,
  90. channel_id=channelId,
  91. application_status=applicationStatus,
  92. channel_service_order_id=channelServiceOrderId,
  93. )
  94. return success_response(result)
  95. @router.get("/pilot-application/detail")
  96. async def api_get_application(
  97. id: str = Query(...),
  98. user: dict = Depends(require_admin_or_super_admin),
  99. db: Session = Depends(get_db),
  100. ):
  101. tenant_no = _get_tenant_no(db, user)
  102. result = pilot_application.get_application(db, app_id=id, tenant_no=tenant_no)
  103. if result is None:
  104. return error_response(404, "申请单不存在")
  105. return success_response(result)
  106. @router.post("/pilot-application/create")
  107. async def api_create_application(
  108. body: ApplicationCreateBody,
  109. user: dict = Depends(require_admin_or_super_admin),
  110. db: Session = Depends(get_db),
  111. ):
  112. try:
  113. tenant_no = _get_tenant_no(db, user)
  114. data = {
  115. "channelServiceOrderId": body.channel_service_order_id,
  116. "remark": body.remark,
  117. }
  118. result = pilot_application.create_application(
  119. db,
  120. data=data,
  121. tenant_no=tenant_no,
  122. user_id=user.get("user_id"),
  123. dept_id=user.get("region_id", ""),
  124. )
  125. return success_response(result, message="创建成功")
  126. except ValueError as e:
  127. return error_response(400, str(e))
  128. @router.put("/pilot-application/update")
  129. async def api_update_application(
  130. body: ApplicationUpdateBody,
  131. user: dict = Depends(require_admin_or_super_admin),
  132. db: Session = Depends(get_db),
  133. ):
  134. try:
  135. tenant_no = _get_tenant_no(db, user)
  136. data = {}
  137. if body.remark is not None:
  138. data["remark"] = body.remark
  139. result = pilot_application.update_application(
  140. db, app_id=body.id, data=data, tenant_no=tenant_no
  141. )
  142. return success_response(result, message="更新成功")
  143. except ValueError as e:
  144. return error_response(400, str(e))
  145. @router.delete("/pilot-application/delete")
  146. async def api_delete_application(
  147. body: IdBody,
  148. user: dict = Depends(require_admin_or_super_admin),
  149. db: Session = Depends(get_db),
  150. ):
  151. try:
  152. tenant_no = _get_tenant_no(db, user)
  153. pilot_application.soft_delete_application(db, app_id=body.id, tenant_no=tenant_no)
  154. return success_response(None, message="删除成功")
  155. except ValueError as e:
  156. return error_response(400, str(e))
  157. @router.post("/pilot-application/start")
  158. async def api_start_application(
  159. body: IdBody,
  160. user: dict = Depends(require_admin_or_super_admin),
  161. db: Session = Depends(get_db),
  162. ):
  163. try:
  164. tenant_no = _get_tenant_no(db, user)
  165. result = pilot_application.start_application(
  166. db, app_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id")
  167. )
  168. return success_response(result, message="操作成功")
  169. except ValueError as e:
  170. return error_response(400, str(e))
  171. @router.post("/pilot-application/complete")
  172. async def api_complete_application(
  173. body: IdBody,
  174. user: dict = Depends(require_admin_or_super_admin),
  175. db: Session = Depends(get_db),
  176. ):
  177. try:
  178. tenant_no = _get_tenant_no(db, user)
  179. result = pilot_application.complete_application(
  180. db, app_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id")
  181. )
  182. return success_response(result, message="操作成功")
  183. except ValueError as e:
  184. return error_response(400, str(e))
  185. @router.post("/pilot-application/void")
  186. async def api_void_application(
  187. body: IdBody,
  188. user: dict = Depends(require_admin_or_super_admin),
  189. db: Session = Depends(get_db),
  190. ):
  191. try:
  192. tenant_no = _get_tenant_no(db, user)
  193. result = pilot_application.void_application(
  194. db, app_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id")
  195. )
  196. return success_response(result, message="操作成功")
  197. except ValueError as e:
  198. return error_response(400, str(e))
  199. # ---------------------------------------------------------------------------
  200. # 2. 患者信息
  201. # ---------------------------------------------------------------------------
  202. @router.get("/pilot-patient/detail")
  203. async def api_get_patient(
  204. application_id: str = Query(...),
  205. user: dict = Depends(require_admin_or_super_admin),
  206. db: Session = Depends(get_db),
  207. ):
  208. tenant_no = _get_tenant_no(db, user)
  209. result = pilot_application.get_patient_by_application(
  210. db, app_id=application_id, tenant_no=tenant_no
  211. )
  212. if result is None:
  213. return success_response(None)
  214. return success_response(result)
  215. @router.post("/pilot-patient/save")
  216. async def api_save_patient(
  217. body: PatientSaveBody,
  218. user: dict = Depends(require_admin_or_super_admin),
  219. db: Session = Depends(get_db),
  220. ):
  221. try:
  222. tenant_no = _get_tenant_no(db, user)
  223. data = {
  224. "customerId": body.customer_id,
  225. "patientName": body.patient_name,
  226. "sex": body.sex,
  227. "age": body.age,
  228. "idCardNo": body.id_card_no,
  229. "phoneNo": body.phone_no,
  230. "emergencyContact": body.emergency_contact,
  231. "emergencyPhone": body.emergency_phone,
  232. "clinicalDiagnosis": body.clinical_diagnosis,
  233. "diagnosisAttachments": body.diagnosis_attachments,
  234. "needCheckup": body.need_checkup,
  235. }
  236. result = pilot_application.save_patient(
  237. db,
  238. app_id=body.application_id,
  239. data=data,
  240. tenant_no=tenant_no,
  241. user_id=user.get("user_id"),
  242. dept_id=user.get("region_id", ""),
  243. )
  244. return success_response(result, message="保存成功")
  245. except ValueError as e:
  246. return error_response(400, str(e))
  247. # ---------------------------------------------------------------------------
  248. # 3. 输注方案
  249. # ---------------------------------------------------------------------------
  250. @router.get("/pilot-infusion-plan/list")
  251. async def api_list_plans(
  252. application_id: str = Query(...),
  253. user: dict = Depends(require_admin_or_super_admin),
  254. db: Session = Depends(get_db),
  255. ):
  256. tenant_no = _get_tenant_no(db, user)
  257. result = pilot_application.list_infusion_plans(
  258. db, app_id=application_id, tenant_no=tenant_no
  259. )
  260. return success_response(result)
  261. @router.post("/pilot-infusion-plan/create")
  262. async def api_create_plan(
  263. body: InfusionPlanCreateBody,
  264. user: dict = Depends(require_admin_or_super_admin),
  265. db: Session = Depends(get_db),
  266. ):
  267. try:
  268. tenant_no = _get_tenant_no(db, user)
  269. data = {
  270. "cellType": body.cell_type,
  271. "specText": body.spec_text,
  272. "totalCourseCount": body.total_course_count,
  273. "courseInterval": body.course_interval,
  274. "infusionRoute": body.infusion_route,
  275. "infusionStartDate": body.infusion_start_date,
  276. }
  277. result = pilot_application.create_infusion_plan(
  278. db,
  279. app_id=body.pilot_application_id,
  280. data=data,
  281. tenant_no=tenant_no,
  282. user_id=user.get("user_id"),
  283. dept_id=user.get("region_id", ""),
  284. )
  285. return success_response(result, message="创建成功")
  286. except ValueError as e:
  287. return error_response(400, str(e))
  288. @router.put("/pilot-infusion-plan/update")
  289. async def api_update_plan(
  290. body: InfusionPlanUpdateBody,
  291. user: dict = Depends(require_admin_or_super_admin),
  292. db: Session = Depends(get_db),
  293. ):
  294. try:
  295. tenant_no = _get_tenant_no(db, user)
  296. data = {}
  297. if body.cell_type is not None:
  298. data["cellType"] = body.cell_type
  299. if body.spec_text is not None:
  300. data["specText"] = body.spec_text
  301. if body.total_course_count is not None:
  302. data["totalCourseCount"] = body.total_course_count
  303. if body.course_interval is not None:
  304. data["courseInterval"] = body.course_interval
  305. if body.infusion_route is not None:
  306. data["infusionRoute"] = body.infusion_route
  307. if body.infusion_start_date is not None:
  308. data["infusionStartDate"] = body.infusion_start_date
  309. result = pilot_application.update_infusion_plan(
  310. db, plan_id=body.id, data=data, tenant_no=tenant_no
  311. )
  312. return success_response(result, message="更新成功")
  313. except ValueError as e:
  314. return error_response(400, str(e))
  315. @router.delete("/pilot-infusion-plan/delete")
  316. async def api_delete_plan(
  317. body: IdBody,
  318. user: dict = Depends(require_admin_or_super_admin),
  319. db: Session = Depends(get_db),
  320. ):
  321. try:
  322. tenant_no = _get_tenant_no(db, user)
  323. pilot_application.soft_delete_infusion_plan(
  324. db, plan_id=body.id, tenant_no=tenant_no
  325. )
  326. return success_response(None, message="删除成功")
  327. except ValueError as e:
  328. return error_response(400, str(e))
  329. # ---------------------------------------------------------------------------
  330. # 4. 输注计划
  331. # ---------------------------------------------------------------------------
  332. @router.post("/pilot-infusion-schedule/auto-generate")
  333. async def api_auto_generate(
  334. body: PlanIdBody,
  335. user: dict = Depends(require_admin_or_super_admin),
  336. db: Session = Depends(get_db),
  337. ):
  338. try:
  339. tenant_no = _get_tenant_no(db, user)
  340. result = pilot_application.auto_generate_schedules(
  341. db,
  342. plan_id=body.plan_id,
  343. tenant_no=tenant_no,
  344. user_id=user.get("user_id"),
  345. dept_id=user.get("region_id", ""),
  346. )
  347. return success_response(result, message="生成成功")
  348. except ValueError as e:
  349. return error_response(400, str(e))
  350. @router.post("/pilot-infusion-schedule/create")
  351. async def api_create_schedule(
  352. body: ScheduleCreateBody,
  353. user: dict = Depends(require_admin_or_super_admin),
  354. db: Session = Depends(get_db),
  355. ):
  356. try:
  357. tenant_no = _get_tenant_no(db, user)
  358. data = {
  359. "infusionTime": body.infusion_time,
  360. "remark": body.remark,
  361. }
  362. result = pilot_application.create_schedule(
  363. db,
  364. plan_id=body.plan_id,
  365. data=data,
  366. tenant_no=tenant_no,
  367. user_id=user.get("user_id"),
  368. dept_id=user.get("region_id", ""),
  369. )
  370. return success_response(result, message="创建成功")
  371. except ValueError as e:
  372. return error_response(400, str(e))
  373. @router.put("/pilot-infusion-schedule/update")
  374. async def api_update_schedule(
  375. body: InfusionScheduleUpdateBody,
  376. user: dict = Depends(require_admin_or_super_admin),
  377. db: Session = Depends(get_db),
  378. ):
  379. try:
  380. tenant_no = _get_tenant_no(db, user)
  381. data = {}
  382. if body.infusion_time is not None:
  383. data["infusionTime"] = body.infusion_time
  384. if body.remark is not None:
  385. data["remark"] = body.remark
  386. if body.attachments is not None:
  387. data["attachments"] = body.attachments
  388. result = pilot_application.update_schedule(
  389. db, schedule_id=body.id, data=data, tenant_no=tenant_no
  390. )
  391. return success_response(result, message="更新成功")
  392. except ValueError as e:
  393. return error_response(400, str(e))
  394. @router.delete("/pilot-infusion-schedule/delete")
  395. async def api_delete_schedule(
  396. body: IdBody,
  397. user: dict = Depends(require_admin_or_super_admin),
  398. db: Session = Depends(get_db),
  399. ):
  400. try:
  401. tenant_no = _get_tenant_no(db, user)
  402. pilot_application.soft_delete_schedule(
  403. db, schedule_id=body.id, tenant_no=tenant_no
  404. )
  405. return success_response(None, message="删除成功")
  406. except ValueError as e:
  407. return error_response(400, str(e))
  408. @router.post("/pilot-infusion-schedule/mark-complete")
  409. async def api_mark_complete(
  410. body: IdBody,
  411. user: dict = Depends(require_admin_or_super_admin),
  412. db: Session = Depends(get_db),
  413. ):
  414. try:
  415. tenant_no = _get_tenant_no(db, user)
  416. result = pilot_application.mark_schedule_complete(
  417. db, schedule_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id")
  418. )
  419. return success_response(result, message="操作成功")
  420. except ValueError as e:
  421. return error_response(400, str(e))
  422. # ---------------------------------------------------------------------------
  423. # 出库生产工单 / 出库质检工单 联动
  424. # ---------------------------------------------------------------------------
  425. class ScheduleIdBody(BaseModel):
  426. scheduleId: str
  427. @router.post("/pilot-infusion-schedule/create-production-work-order")
  428. async def api_create_outbound_production(
  429. body: ScheduleIdBody,
  430. user: dict = Depends(require_admin_or_super_admin),
  431. db: Session = Depends(get_db),
  432. ):
  433. try:
  434. tenant_no = _get_tenant_no(db, user)
  435. from app.crud import production_work_order as pwo_svc
  436. wo = pwo_svc.create_outbound_production_work_order(
  437. db,
  438. schedule_id=body.scheduleId,
  439. tenant_no=tenant_no,
  440. user_id=user.get("user_id"),
  441. create_dept_id=user.get("dept_id", ""),
  442. )
  443. pilot_application.mark_schedule_complete(
  444. db, schedule_id=body.scheduleId, tenant_no=tenant_no, user_id=user.get("user_id")
  445. )
  446. return success_response(pwo_svc._wo_to_dict(wo), message="创建出库生产工单成功")
  447. except ValueError as e:
  448. return error_response(400, str(e))
  449. except Exception as e:
  450. db.rollback()
  451. return error_response(400, str(e))
  452. class OutboundQcBody(BaseModel):
  453. scheduleId: str
  454. cellProductId: str = ""
  455. @router.post("/pilot-infusion-schedule/create-qc-work-order")
  456. async def api_create_outbound_qc(
  457. body: OutboundQcBody,
  458. user: dict = Depends(require_admin_or_super_admin),
  459. db: Session = Depends(get_db),
  460. ):
  461. try:
  462. tenant_no = _get_tenant_no(db, user)
  463. from app.crud import qc_work_order as qc_svc
  464. wo = qc_svc.create_outbound_qc_work_order(
  465. db,
  466. schedule_id=body.scheduleId,
  467. cell_product_id=body.cellProductId,
  468. tenant_no=tenant_no,
  469. user_id=user.get("user_id"),
  470. create_dept_id=user.get("dept_id", ""),
  471. )
  472. return success_response(qc_svc._wo_to_dict(wo), message="创建出库质检工单成功")
  473. except ValueError as e:
  474. return error_response(400, str(e))
  475. except Exception as e:
  476. db.rollback()
  477. return error_response(400, str(e))