pilot_application.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. """中试服务申请单管理 API(申请单、患者信息、输注方案、输注计划、出库联动)"""
  2. from typing import Optional
  3. from fastapi import APIRouter, Depends, Query
  4. from pydantic import BaseModel, ConfigDict, Field
  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. model_config = ConfigDict(populate_by_name=True)
  25. id: str
  26. class PlanIdBody(BaseModel):
  27. model_config = ConfigDict(populate_by_name=True)
  28. plan_id: str = Field(..., alias="infusionPlanId")
  29. class ApplicationCreateBody(BaseModel):
  30. model_config = ConfigDict(populate_by_name=True)
  31. channel_service_order_id: str = Field(..., alias="channelServiceOrderId")
  32. remark: Optional[str] = None
  33. class ApplicationUpdateBody(BaseModel):
  34. model_config = ConfigDict(populate_by_name=True)
  35. id: str
  36. remark: Optional[str] = None
  37. class PatientSaveBody(BaseModel):
  38. model_config = ConfigDict(populate_by_name=True)
  39. application_id: str = Field(..., alias="pilotApplicationId")
  40. customer_id: Optional[str] = Field(None, alias="customerId")
  41. patient_name: str = Field(..., alias="patientName")
  42. sex: Optional[str] = None
  43. age: Optional[int] = None
  44. id_card_no: Optional[str] = Field(None, alias="idCardNo")
  45. phone_no: Optional[str] = Field(None, alias="phoneNo")
  46. emergency_contact: Optional[str] = Field(None, alias="emergencyContact")
  47. emergency_phone: Optional[str] = Field(None, alias="emergencyPhone")
  48. clinical_diagnosis: Optional[str] = Field(None, alias="clinicalDiagnosis")
  49. diagnosis_attachments: Optional[str] = Field(None, alias="diagnosisAttachments")
  50. need_checkup: int = Field(1, alias="needCheckup")
  51. class InfusionPlanCreateBody(BaseModel):
  52. model_config = ConfigDict(populate_by_name=True)
  53. pilot_application_id: str = Field(..., alias="pilotApplicationId")
  54. cell_type: str = Field(..., alias="cellType")
  55. spec_text: Optional[str] = Field(None, alias="specText")
  56. total_course_count: int = Field(..., alias="totalCourseCount")
  57. course_interval: Optional[str] = Field(None, alias="courseInterval")
  58. infusion_route: Optional[str] = Field(None, alias="infusionRoute")
  59. infusion_start_date: Optional[str] = Field(None, alias="infusionStartDate")
  60. class InfusionPlanUpdateBody(BaseModel):
  61. model_config = ConfigDict(populate_by_name=True)
  62. id: str
  63. cell_type: Optional[str] = Field(None, alias="cellType")
  64. spec_text: Optional[str] = Field(None, alias="specText")
  65. total_course_count: Optional[int] = Field(None, alias="totalCourseCount")
  66. course_interval: Optional[str] = Field(None, alias="courseInterval")
  67. infusion_route: Optional[str] = Field(None, alias="infusionRoute")
  68. infusion_start_date: Optional[str] = Field(None, alias="infusionStartDate")
  69. class InfusionScheduleUpdateBody(BaseModel):
  70. model_config = ConfigDict(populate_by_name=True)
  71. id: str
  72. infusion_time: Optional[str] = Field(None, alias="infusionTime")
  73. remark: Optional[str] = None
  74. attachments: Optional[str] = None
  75. class ScheduleCreateBody(BaseModel):
  76. model_config = ConfigDict(populate_by_name=True)
  77. plan_id: str = Field(..., alias="infusionPlanId")
  78. infusion_time: Optional[str] = Field(None, alias="infusionTime")
  79. remark: Optional[str] = None
  80. # --- 复合保存 Schema(参考采集协议单一次性保存模式)---
  81. class PatientNestedBody(BaseModel):
  82. model_config = ConfigDict(populate_by_name=True)
  83. customer_id: Optional[str] = Field(None, alias="customerId")
  84. patient_name: Optional[str] = Field(None, alias="patientName")
  85. sex: Optional[str] = None
  86. age: Optional[int] = None
  87. id_card_no: Optional[str] = Field(None, alias="idCardNo")
  88. phone_no: Optional[str] = Field(None, alias="phoneNo")
  89. emergency_contact: Optional[str] = Field(None, alias="emergencyContact")
  90. emergency_phone: Optional[str] = Field(None, alias="emergencyPhone")
  91. clinical_diagnosis: Optional[str] = Field(None, alias="clinicalDiagnosis")
  92. diagnosis_attachments: Optional[str] = Field(None, alias="diagnosisAttachments")
  93. need_checkup: int = Field(1, alias="needCheckup")
  94. class ScheduleProductNestedBody(BaseModel):
  95. model_config = ConfigDict(populate_by_name=True)
  96. id: Optional[str] = None
  97. price_id: Optional[str] = Field(None, alias="priceId")
  98. project_code: Optional[str] = Field(None, alias="projectCode")
  99. project_name: Optional[str] = Field(None, alias="projectName")
  100. cell_type_name: Optional[str] = Field(None, alias="cellTypeName")
  101. spec_text: Optional[str] = Field(None, alias="specText")
  102. agreement_price: Optional[float] = Field(None, alias="agreementPrice")
  103. actual_price: Optional[float] = Field(None, alias="actualPrice")
  104. quantity: Optional[int] = 1
  105. status: Optional[str] = None
  106. remark: Optional[str] = None
  107. class InfusionScheduleNestedBody(BaseModel):
  108. model_config = ConfigDict(populate_by_name=True)
  109. id: Optional[str] = None
  110. sort_no: Optional[int] = Field(None, alias="sortNo")
  111. infusion_time: Optional[str] = Field(None, alias="infusionTime")
  112. infusion_location: Optional[str] = Field(None, alias="infusionLocation")
  113. infusion_method: Optional[str] = Field(None, alias="infusionMethod")
  114. cell_products: Optional[list[ScheduleProductNestedBody]] = Field(None, alias="cellProducts")
  115. class InfusionPlanNestedBody(BaseModel):
  116. model_config = ConfigDict(populate_by_name=True)
  117. id: Optional[str] = None
  118. cell_type: Optional[str] = Field(None, alias="cellType")
  119. spec_text: Optional[str] = Field(None, alias="specText")
  120. total_course_count: Optional[int] = Field(None, alias="totalCourseCount")
  121. course_interval: Optional[str] = Field(None, alias="courseInterval")
  122. infusion_route: Optional[str] = Field(None, alias="infusionRoute")
  123. infusion_start_date: Optional[str] = Field(None, alias="infusionStartDate")
  124. channel_agreement_id: Optional[str] = Field(None, alias="channelAgreementId")
  125. class ApplicationSaveBody(BaseModel):
  126. """一揽子保存:申请单 + 患者信息 + 输注方案 + 输注计划(含细胞产品)"""
  127. model_config = ConfigDict(populate_by_name=True)
  128. id: Optional[str] = None
  129. application_name: Optional[str] = Field(None, alias="applicationName")
  130. channel_service_order_id: str = Field(..., alias="channelServiceOrderId")
  131. remark: Optional[str] = None
  132. patient: Optional[PatientNestedBody] = None
  133. infusion_plan: Optional[InfusionPlanNestedBody] = Field(None, alias="infusionPlan")
  134. infusion_schedules: Optional[list[InfusionScheduleNestedBody]] = Field(
  135. None, alias="infusionSchedules"
  136. )
  137. infusion_plans: Optional[list[InfusionPlanNestedBody]] = Field(None, alias="infusionPlans")
  138. # ---------------------------------------------------------------------------
  139. # 1. 申请单 CRUD + 状态变更
  140. # ---------------------------------------------------------------------------
  141. @router.get("/pilot-application/list")
  142. async def api_list_applications(
  143. page: int = Query(1, ge=1),
  144. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  145. keyword: Optional[str] = Query(None, alias="keyword"),
  146. channelId: Optional[str] = Query(None, alias="channelId"),
  147. applicationStatus: Optional[str] = Query(None, alias="applicationStatus"),
  148. channelServiceOrderId: Optional[str] = Query(None, alias="channelServiceOrderId"),
  149. user: dict = Depends(require_admin_or_super_admin),
  150. db: Session = Depends(get_db),
  151. ):
  152. tenant_no = _get_tenant_no(db, user)
  153. result = pilot_application.list_applications(
  154. db,
  155. tenant_no=tenant_no,
  156. page=page,
  157. page_size=pageSize,
  158. channel_id=channelId,
  159. application_status=applicationStatus,
  160. channel_service_order_id=channelServiceOrderId,
  161. keyword=keyword,
  162. )
  163. return success_response(result)
  164. @router.get("/pilot-application/detail")
  165. async def api_get_application(
  166. id: str = Query(...),
  167. user: dict = Depends(require_admin_or_super_admin),
  168. db: Session = Depends(get_db),
  169. ):
  170. tenant_no = _get_tenant_no(db, user)
  171. result = pilot_application.get_application(db, app_id=id, tenant_no=tenant_no)
  172. if result is None:
  173. return error_response(404, "申请单不存在")
  174. return success_response(result)
  175. @router.post("/pilot-application/create")
  176. async def api_create_application(
  177. body: ApplicationCreateBody,
  178. user: dict = Depends(require_admin_or_super_admin),
  179. db: Session = Depends(get_db),
  180. ):
  181. try:
  182. tenant_no = _get_tenant_no(db, user)
  183. data = {
  184. "channelServiceOrderId": body.channel_service_order_id,
  185. "remark": body.remark,
  186. }
  187. result = pilot_application.create_application(
  188. db,
  189. data=data,
  190. tenant_no=tenant_no,
  191. user_id=user.get("user_id"),
  192. dept_id=user.get("region_id", ""),
  193. )
  194. return success_response(result, message="创建成功")
  195. except ValueError as e:
  196. return error_response(400, str(e))
  197. @router.put("/pilot-application/update")
  198. async def api_update_application(
  199. body: ApplicationUpdateBody,
  200. user: dict = Depends(require_admin_or_super_admin),
  201. db: Session = Depends(get_db),
  202. ):
  203. try:
  204. tenant_no = _get_tenant_no(db, user)
  205. data = {}
  206. if body.remark is not None:
  207. data["remark"] = body.remark
  208. result = pilot_application.update_application(
  209. db, app_id=body.id, data=data, tenant_no=tenant_no
  210. )
  211. return success_response(result, message="更新成功")
  212. except ValueError as e:
  213. return error_response(400, str(e))
  214. @router.post("/pilot-application/save")
  215. async def api_save_application(
  216. body: ApplicationSaveBody,
  217. user: dict = Depends(require_admin_or_super_admin),
  218. db: Session = Depends(get_db),
  219. ):
  220. try:
  221. tenant_no = _get_tenant_no(db, user)
  222. user_id = user.get("user_id")
  223. dept_id = user.get("region_id", "")
  224. is_edit = bool(body.id)
  225. app_data: dict = {
  226. "channelServiceOrderId": body.channel_service_order_id,
  227. "remark": body.remark,
  228. }
  229. if body.application_name is not None:
  230. app_data["applicationName"] = body.application_name
  231. if is_edit:
  232. app_result = pilot_application.update_application(
  233. db,
  234. app_id=body.id,
  235. data=app_data,
  236. tenant_no=tenant_no,
  237. )
  238. app_id = body.id
  239. else:
  240. app_result = pilot_application.create_application(
  241. db,
  242. data=app_data,
  243. tenant_no=tenant_no,
  244. user_id=user_id,
  245. dept_id=dept_id,
  246. )
  247. app_id = app_result.get("id")
  248. if not app_id:
  249. return error_response(500, "创建申请单失败,未获取到ID")
  250. if body.patient:
  251. pilot_application.save_patient(
  252. db,
  253. app_id=app_id,
  254. data=body.patient.model_dump(by_alias=True),
  255. tenant_no=tenant_no,
  256. user_id=user_id,
  257. dept_id=dept_id,
  258. )
  259. if body.infusion_plan is not None or body.infusion_schedules:
  260. plan_dict = (
  261. body.infusion_plan.model_dump(by_alias=True) if body.infusion_plan else {}
  262. )
  263. schedules = [
  264. s.model_dump(by_alias=True) for s in (body.infusion_schedules or [])
  265. ]
  266. if is_edit:
  267. pilot_application.upsert_application_infusion(
  268. db,
  269. app_id=app_id,
  270. plan_data=plan_dict,
  271. schedules_data=schedules,
  272. tenant_no=tenant_no,
  273. user_id=user_id,
  274. dept_id=dept_id,
  275. )
  276. else:
  277. pilot_application.save_application_infusion(
  278. db,
  279. app_id=app_id,
  280. plan_data=plan_dict,
  281. schedules_data=schedules,
  282. tenant_no=tenant_no,
  283. user_id=user_id,
  284. dept_id=dept_id,
  285. replace_existing=False,
  286. )
  287. elif body.infusion_plans:
  288. for plan_data in body.infusion_plans:
  289. plan_result = pilot_application.create_infusion_plan(
  290. db,
  291. app_id=app_id,
  292. data=plan_data.model_dump(by_alias=True),
  293. tenant_no=tenant_no,
  294. user_id=user_id,
  295. dept_id=dept_id,
  296. )
  297. plan_id = plan_result.get("id")
  298. if plan_id and (plan_data.total_course_count or 0) > 0:
  299. pilot_application.auto_generate_schedules(
  300. db,
  301. plan_id=plan_id,
  302. tenant_no=tenant_no,
  303. user_id=user_id,
  304. dept_id=dept_id,
  305. )
  306. app_result = pilot_application.get_application(db, app_id=app_id, tenant_no=tenant_no)
  307. return success_response(app_result, message="保存成功")
  308. except ValueError as e:
  309. return error_response(400, str(e))
  310. except Exception as e:
  311. return error_response(500, str(e))
  312. @router.delete("/pilot-application/delete")
  313. async def api_delete_application(
  314. body: IdBody,
  315. user: dict = Depends(require_admin_or_super_admin),
  316. db: Session = Depends(get_db),
  317. ):
  318. try:
  319. tenant_no = _get_tenant_no(db, user)
  320. pilot_application.soft_delete_application(db, app_id=body.id, tenant_no=tenant_no)
  321. return success_response(None, message="删除成功")
  322. except ValueError as e:
  323. return error_response(400, str(e))
  324. @router.post("/pilot-application/start")
  325. async def api_start_application(
  326. body: IdBody,
  327. user: dict = Depends(require_admin_or_super_admin),
  328. db: Session = Depends(get_db),
  329. ):
  330. try:
  331. tenant_no = _get_tenant_no(db, user)
  332. result = pilot_application.start_application(
  333. db, app_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id")
  334. )
  335. return success_response(result, message="操作成功")
  336. except ValueError as e:
  337. return error_response(400, str(e))
  338. @router.post("/pilot-application/complete")
  339. async def api_complete_application(
  340. body: IdBody,
  341. user: dict = Depends(require_admin_or_super_admin),
  342. db: Session = Depends(get_db),
  343. ):
  344. try:
  345. tenant_no = _get_tenant_no(db, user)
  346. result = pilot_application.complete_application(
  347. db, app_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id")
  348. )
  349. return success_response(result, message="操作成功")
  350. except ValueError as e:
  351. return error_response(400, str(e))
  352. @router.post("/pilot-application/void")
  353. async def api_void_application(
  354. body: IdBody,
  355. user: dict = Depends(require_admin_or_super_admin),
  356. db: Session = Depends(get_db),
  357. ):
  358. try:
  359. tenant_no = _get_tenant_no(db, user)
  360. result = pilot_application.void_application(
  361. db, app_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id")
  362. )
  363. return success_response(result, message="操作成功")
  364. except ValueError as e:
  365. return error_response(400, str(e))
  366. # ---------------------------------------------------------------------------
  367. # 2. 患者信息
  368. # ---------------------------------------------------------------------------
  369. @router.get("/pilot-patient/detail")
  370. async def api_get_patient(
  371. application_id: str = Query(..., alias="pilotApplicationId"),
  372. user: dict = Depends(require_admin_or_super_admin),
  373. db: Session = Depends(get_db),
  374. ):
  375. tenant_no = _get_tenant_no(db, user)
  376. result = pilot_application.get_patient_by_application(
  377. db, app_id=application_id, tenant_no=tenant_no
  378. )
  379. if result is None:
  380. return success_response(None)
  381. return success_response(result)
  382. @router.post("/pilot-patient/save")
  383. async def api_save_patient(
  384. body: PatientSaveBody,
  385. user: dict = Depends(require_admin_or_super_admin),
  386. db: Session = Depends(get_db),
  387. ):
  388. try:
  389. tenant_no = _get_tenant_no(db, user)
  390. data = {
  391. "customerId": body.customer_id,
  392. "patientName": body.patient_name,
  393. "sex": body.sex,
  394. "age": body.age,
  395. "idCardNo": body.id_card_no,
  396. "phoneNo": body.phone_no,
  397. "emergencyContact": body.emergency_contact,
  398. "emergencyPhone": body.emergency_phone,
  399. "clinicalDiagnosis": body.clinical_diagnosis,
  400. "diagnosisAttachments": body.diagnosis_attachments,
  401. "needCheckup": body.need_checkup,
  402. }
  403. result = pilot_application.save_patient(
  404. db,
  405. app_id=body.application_id,
  406. data=data,
  407. tenant_no=tenant_no,
  408. user_id=user.get("user_id"),
  409. dept_id=user.get("region_id", ""),
  410. )
  411. return success_response(result, message="保存成功")
  412. except ValueError as e:
  413. return error_response(400, str(e))
  414. # ---------------------------------------------------------------------------
  415. # 3. 输注方案
  416. # ---------------------------------------------------------------------------
  417. @router.get("/pilot-infusion-plan/list")
  418. async def api_list_plans(
  419. application_id: str = Query(..., alias="pilotApplicationId"),
  420. user: dict = Depends(require_admin_or_super_admin),
  421. db: Session = Depends(get_db),
  422. ):
  423. tenant_no = _get_tenant_no(db, user)
  424. result = pilot_application.list_infusion_plans(
  425. db, app_id=application_id, tenant_no=tenant_no
  426. )
  427. return success_response(result)
  428. @router.post("/pilot-infusion-plan/create")
  429. async def api_create_plan(
  430. body: InfusionPlanCreateBody,
  431. user: dict = Depends(require_admin_or_super_admin),
  432. db: Session = Depends(get_db),
  433. ):
  434. try:
  435. tenant_no = _get_tenant_no(db, user)
  436. data = {
  437. "cellType": body.cell_type,
  438. "specText": body.spec_text,
  439. "totalCourseCount": body.total_course_count,
  440. "courseInterval": body.course_interval,
  441. "infusionRoute": body.infusion_route,
  442. "infusionStartDate": body.infusion_start_date,
  443. }
  444. result = pilot_application.create_infusion_plan(
  445. db,
  446. app_id=body.pilot_application_id,
  447. data=data,
  448. tenant_no=tenant_no,
  449. user_id=user.get("user_id"),
  450. dept_id=user.get("region_id", ""),
  451. )
  452. return success_response(result, message="创建成功")
  453. except ValueError as e:
  454. return error_response(400, str(e))
  455. @router.put("/pilot-infusion-plan/update")
  456. async def api_update_plan(
  457. body: InfusionPlanUpdateBody,
  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. data = {}
  464. if body.cell_type is not None:
  465. data["cellType"] = body.cell_type
  466. if body.spec_text is not None:
  467. data["specText"] = body.spec_text
  468. if body.total_course_count is not None:
  469. data["totalCourseCount"] = body.total_course_count
  470. if body.course_interval is not None:
  471. data["courseInterval"] = body.course_interval
  472. if body.infusion_route is not None:
  473. data["infusionRoute"] = body.infusion_route
  474. if body.infusion_start_date is not None:
  475. data["infusionStartDate"] = body.infusion_start_date
  476. result = pilot_application.update_infusion_plan(
  477. db, plan_id=body.id, data=data, tenant_no=tenant_no
  478. )
  479. return success_response(result, message="更新成功")
  480. except ValueError as e:
  481. return error_response(400, str(e))
  482. @router.delete("/pilot-infusion-plan/delete")
  483. async def api_delete_plan(
  484. body: IdBody,
  485. user: dict = Depends(require_admin_or_super_admin),
  486. db: Session = Depends(get_db),
  487. ):
  488. try:
  489. tenant_no = _get_tenant_no(db, user)
  490. pilot_application.soft_delete_infusion_plan(
  491. db, plan_id=body.id, tenant_no=tenant_no
  492. )
  493. return success_response(None, message="删除成功")
  494. except ValueError as e:
  495. return error_response(400, str(e))
  496. # ---------------------------------------------------------------------------
  497. # 4. 输注计划
  498. # ---------------------------------------------------------------------------
  499. @router.get("/pilot-infusion-schedule/list")
  500. async def api_list_schedules(
  501. plan_id: str = Query(..., alias="infusionPlanId"),
  502. user: dict = Depends(require_admin_or_super_admin),
  503. db: Session = Depends(get_db),
  504. ):
  505. tenant_no = _get_tenant_no(db, user)
  506. result = pilot_application.list_schedules_by_plan(
  507. db, plan_id=plan_id, tenant_no=tenant_no
  508. )
  509. return success_response(result)
  510. @router.post("/pilot-infusion-schedule/auto-generate")
  511. async def api_auto_generate(
  512. body: PlanIdBody,
  513. user: dict = Depends(require_admin_or_super_admin),
  514. db: Session = Depends(get_db),
  515. ):
  516. try:
  517. tenant_no = _get_tenant_no(db, user)
  518. result = pilot_application.auto_generate_schedules(
  519. db,
  520. plan_id=body.plan_id,
  521. tenant_no=tenant_no,
  522. user_id=user.get("user_id"),
  523. dept_id=user.get("region_id", ""),
  524. )
  525. return success_response(result, message="生成成功")
  526. except ValueError as e:
  527. return error_response(400, str(e))
  528. @router.post("/pilot-infusion-schedule/create")
  529. async def api_create_schedule(
  530. body: ScheduleCreateBody,
  531. user: dict = Depends(require_admin_or_super_admin),
  532. db: Session = Depends(get_db),
  533. ):
  534. try:
  535. tenant_no = _get_tenant_no(db, user)
  536. data = {
  537. "infusionTime": body.infusion_time,
  538. "remark": body.remark,
  539. }
  540. result = pilot_application.create_schedule(
  541. db,
  542. plan_id=body.plan_id,
  543. data=data,
  544. tenant_no=tenant_no,
  545. user_id=user.get("user_id"),
  546. dept_id=user.get("region_id", ""),
  547. )
  548. return success_response(result, message="创建成功")
  549. except ValueError as e:
  550. return error_response(400, str(e))
  551. @router.put("/pilot-infusion-schedule/update")
  552. async def api_update_schedule(
  553. body: InfusionScheduleUpdateBody,
  554. user: dict = Depends(require_admin_or_super_admin),
  555. db: Session = Depends(get_db),
  556. ):
  557. try:
  558. tenant_no = _get_tenant_no(db, user)
  559. data = {}
  560. if body.infusion_time is not None:
  561. data["infusionTime"] = body.infusion_time
  562. if body.remark is not None:
  563. data["remark"] = body.remark
  564. if body.attachments is not None:
  565. data["attachments"] = body.attachments
  566. result = pilot_application.update_schedule(
  567. db, schedule_id=body.id, data=data, tenant_no=tenant_no
  568. )
  569. return success_response(result, message="更新成功")
  570. except ValueError as e:
  571. return error_response(400, str(e))
  572. @router.delete("/pilot-infusion-schedule/delete")
  573. async def api_delete_schedule(
  574. body: IdBody,
  575. user: dict = Depends(require_admin_or_super_admin),
  576. db: Session = Depends(get_db),
  577. ):
  578. try:
  579. tenant_no = _get_tenant_no(db, user)
  580. pilot_application.soft_delete_schedule(
  581. db, schedule_id=body.id, tenant_no=tenant_no
  582. )
  583. return success_response(None, message="删除成功")
  584. except ValueError as e:
  585. return error_response(400, str(e))
  586. @router.post("/pilot-infusion-schedule/mark-complete")
  587. async def api_mark_complete(
  588. body: IdBody,
  589. user: dict = Depends(require_admin_or_super_admin),
  590. db: Session = Depends(get_db),
  591. ):
  592. try:
  593. tenant_no = _get_tenant_no(db, user)
  594. result = pilot_application.mark_schedule_complete(
  595. db, schedule_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id")
  596. )
  597. return success_response(result, message="操作成功")
  598. except ValueError as e:
  599. return error_response(400, str(e))
  600. # ---------------------------------------------------------------------------
  601. # 出库生产工单 / 出库质检工单 联动
  602. # ---------------------------------------------------------------------------
  603. class ScheduleIdBody(BaseModel):
  604. scheduleId: str
  605. @router.post("/pilot-infusion-schedule/create-production-work-order")
  606. async def api_create_outbound_production(
  607. body: ScheduleIdBody,
  608. user: dict = Depends(require_admin_or_super_admin),
  609. db: Session = Depends(get_db),
  610. ):
  611. try:
  612. tenant_no = _get_tenant_no(db, user)
  613. from app.crud import production_work_order as pwo_svc
  614. wo = pwo_svc.create_outbound_production_work_order(
  615. db,
  616. schedule_id=body.scheduleId,
  617. tenant_no=tenant_no,
  618. user_id=user.get("user_id"),
  619. create_dept_id=user.get("dept_id", ""),
  620. )
  621. db.commit()
  622. db.refresh(wo)
  623. return success_response(pwo_svc._wo_to_dict(wo), message="创建出库生产工单成功")
  624. except ValueError as e:
  625. return error_response(400, str(e))
  626. except Exception as e:
  627. db.rollback()
  628. return error_response(400, str(e))
  629. class OutboundQcBody(BaseModel):
  630. scheduleId: str
  631. cellProductId: str = ""
  632. @router.post("/pilot-infusion-schedule/create-qc-work-order")
  633. async def api_create_outbound_qc(
  634. body: OutboundQcBody,
  635. user: dict = Depends(require_admin_or_super_admin),
  636. db: Session = Depends(get_db),
  637. ):
  638. try:
  639. tenant_no = _get_tenant_no(db, user)
  640. from app.crud import qc_work_order as qc_svc
  641. wo = qc_svc.create_outbound_qc_work_order(
  642. db,
  643. schedule_id=body.scheduleId,
  644. cell_product_id=body.cellProductId,
  645. tenant_no=tenant_no,
  646. user_id=user.get("user_id"),
  647. create_dept_id=user.get("dept_id", ""),
  648. )
  649. return success_response(qc_svc._wo_to_dict(wo), message="创建出库质检工单成功")
  650. except ValueError as e:
  651. return error_response(400, str(e))
  652. except Exception as e:
  653. db.rollback()
  654. return error_response(400, str(e))