"""中试服务申请单管理 API(申请单、患者信息、输注方案、输注计划、出库联动)""" from typing import Optional from fastapi import APIRouter, Depends, Query from pydantic import BaseModel from sqlalchemy.orm import Session from app.core.database import get_db from app.core.menu_guard import require_admin_or_super_admin from app.core.response import error_response, success_response from app.crud import pilot_application from app.models.models import SysUser router = APIRouter(prefix="/web/admin", tags=["中试服务申请单管理"]) def _get_tenant_no(db: Session, user: dict) -> str: user_id = user.get("user_id") if not user_id: raise PermissionError("用户身份信息缺失,无法确定租户") sys_user = db.query(SysUser).filter(SysUser.id == user_id).first() if not sys_user or not sys_user.tenant_no: raise PermissionError("用户租户信息缺失") return sys_user.tenant_no # --------------------------------------------------------------------------- # Pydantic Schemas # --------------------------------------------------------------------------- class IdBody(BaseModel): id: str class PlanIdBody(BaseModel): plan_id: str class ApplicationCreateBody(BaseModel): channel_service_order_id: str remark: Optional[str] = None class ApplicationUpdateBody(BaseModel): id: str remark: Optional[str] = None class PatientSaveBody(BaseModel): application_id: str customer_id: Optional[str] = None patient_name: str sex: Optional[str] = None age: Optional[int] = None id_card_no: Optional[str] = None phone_no: Optional[str] = None emergency_contact: Optional[str] = None emergency_phone: Optional[str] = None clinical_diagnosis: Optional[str] = None diagnosis_attachments: Optional[str] = None need_checkup: int = 1 class InfusionPlanCreateBody(BaseModel): pilot_application_id: str cell_type: str spec_text: Optional[str] = None total_course_count: int course_interval: Optional[str] = None infusion_route: Optional[str] = None infusion_start_date: Optional[str] = None class InfusionPlanUpdateBody(BaseModel): id: str cell_type: Optional[str] = None spec_text: Optional[str] = None total_course_count: Optional[int] = None course_interval: Optional[str] = None infusion_route: Optional[str] = None infusion_start_date: Optional[str] = None class InfusionScheduleUpdateBody(BaseModel): id: str infusion_time: Optional[str] = None remark: Optional[str] = None attachments: Optional[str] = None class ScheduleCreateBody(BaseModel): plan_id: str infusion_time: Optional[str] = None remark: Optional[str] = None # --------------------------------------------------------------------------- # 1. 申请单 CRUD + 状态变更 # --------------------------------------------------------------------------- @router.get("/pilot-application/list") async def api_list_applications( page: int = Query(1, ge=1), pageSize: int = Query(10, ge=1, le=100, alias="pageSize"), channelId: Optional[str] = Query(None, alias="channelId"), applicationStatus: Optional[str] = Query(None, alias="applicationStatus"), channelServiceOrderId: Optional[str] = Query(None, alias="channelServiceOrderId"), user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): tenant_no = _get_tenant_no(db, user) result = pilot_application.list_applications( db, tenant_no=tenant_no, page=page, page_size=pageSize, channel_id=channelId, application_status=applicationStatus, channel_service_order_id=channelServiceOrderId, ) return success_response(result) @router.get("/pilot-application/detail") async def api_get_application( id: str = Query(...), user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): tenant_no = _get_tenant_no(db, user) result = pilot_application.get_application(db, app_id=id, tenant_no=tenant_no) if result is None: return error_response(404, "申请单不存在") return success_response(result) @router.post("/pilot-application/create") async def api_create_application( body: ApplicationCreateBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) data = { "channelServiceOrderId": body.channel_service_order_id, "remark": body.remark, } result = pilot_application.create_application( db, data=data, tenant_no=tenant_no, user_id=user.get("user_id"), dept_id=user.get("region_id", ""), ) return success_response(result, message="创建成功") except ValueError as e: return error_response(400, str(e)) @router.put("/pilot-application/update") async def api_update_application( body: ApplicationUpdateBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) data = {} if body.remark is not None: data["remark"] = body.remark result = pilot_application.update_application( db, app_id=body.id, data=data, tenant_no=tenant_no ) return success_response(result, message="更新成功") except ValueError as e: return error_response(400, str(e)) @router.delete("/pilot-application/delete") async def api_delete_application( body: IdBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) pilot_application.soft_delete_application(db, app_id=body.id, tenant_no=tenant_no) return success_response(None, message="删除成功") except ValueError as e: return error_response(400, str(e)) @router.post("/pilot-application/start") async def api_start_application( body: IdBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) result = pilot_application.start_application( db, app_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id") ) return success_response(result, message="操作成功") except ValueError as e: return error_response(400, str(e)) @router.post("/pilot-application/complete") async def api_complete_application( body: IdBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) result = pilot_application.complete_application( db, app_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id") ) return success_response(result, message="操作成功") except ValueError as e: return error_response(400, str(e)) @router.post("/pilot-application/void") async def api_void_application( body: IdBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) result = pilot_application.void_application( db, app_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id") ) return success_response(result, message="操作成功") except ValueError as e: return error_response(400, str(e)) # --------------------------------------------------------------------------- # 2. 患者信息 # --------------------------------------------------------------------------- @router.get("/pilot-patient/detail") async def api_get_patient( application_id: str = Query(...), user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): tenant_no = _get_tenant_no(db, user) result = pilot_application.get_patient_by_application( db, app_id=application_id, tenant_no=tenant_no ) if result is None: return success_response(None) return success_response(result) @router.post("/pilot-patient/save") async def api_save_patient( body: PatientSaveBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) data = { "customerId": body.customer_id, "patientName": body.patient_name, "sex": body.sex, "age": body.age, "idCardNo": body.id_card_no, "phoneNo": body.phone_no, "emergencyContact": body.emergency_contact, "emergencyPhone": body.emergency_phone, "clinicalDiagnosis": body.clinical_diagnosis, "diagnosisAttachments": body.diagnosis_attachments, "needCheckup": body.need_checkup, } result = pilot_application.save_patient( db, app_id=body.application_id, data=data, tenant_no=tenant_no, user_id=user.get("user_id"), dept_id=user.get("region_id", ""), ) return success_response(result, message="保存成功") except ValueError as e: return error_response(400, str(e)) # --------------------------------------------------------------------------- # 3. 输注方案 # --------------------------------------------------------------------------- @router.get("/pilot-infusion-plan/list") async def api_list_plans( application_id: str = Query(...), user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): tenant_no = _get_tenant_no(db, user) result = pilot_application.list_infusion_plans( db, app_id=application_id, tenant_no=tenant_no ) return success_response(result) @router.post("/pilot-infusion-plan/create") async def api_create_plan( body: InfusionPlanCreateBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) data = { "cellType": body.cell_type, "specText": body.spec_text, "totalCourseCount": body.total_course_count, "courseInterval": body.course_interval, "infusionRoute": body.infusion_route, "infusionStartDate": body.infusion_start_date, } result = pilot_application.create_infusion_plan( db, app_id=body.pilot_application_id, data=data, tenant_no=tenant_no, user_id=user.get("user_id"), dept_id=user.get("region_id", ""), ) return success_response(result, message="创建成功") except ValueError as e: return error_response(400, str(e)) @router.put("/pilot-infusion-plan/update") async def api_update_plan( body: InfusionPlanUpdateBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) data = {} if body.cell_type is not None: data["cellType"] = body.cell_type if body.spec_text is not None: data["specText"] = body.spec_text if body.total_course_count is not None: data["totalCourseCount"] = body.total_course_count if body.course_interval is not None: data["courseInterval"] = body.course_interval if body.infusion_route is not None: data["infusionRoute"] = body.infusion_route if body.infusion_start_date is not None: data["infusionStartDate"] = body.infusion_start_date result = pilot_application.update_infusion_plan( db, plan_id=body.id, data=data, tenant_no=tenant_no ) return success_response(result, message="更新成功") except ValueError as e: return error_response(400, str(e)) @router.delete("/pilot-infusion-plan/delete") async def api_delete_plan( body: IdBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) pilot_application.soft_delete_infusion_plan( db, plan_id=body.id, tenant_no=tenant_no ) return success_response(None, message="删除成功") except ValueError as e: return error_response(400, str(e)) # --------------------------------------------------------------------------- # 4. 输注计划 # --------------------------------------------------------------------------- @router.post("/pilot-infusion-schedule/auto-generate") async def api_auto_generate( body: PlanIdBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) result = pilot_application.auto_generate_schedules( db, plan_id=body.plan_id, tenant_no=tenant_no, user_id=user.get("user_id"), dept_id=user.get("region_id", ""), ) return success_response(result, message="生成成功") except ValueError as e: return error_response(400, str(e)) @router.post("/pilot-infusion-schedule/create") async def api_create_schedule( body: ScheduleCreateBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) data = { "infusionTime": body.infusion_time, "remark": body.remark, } result = pilot_application.create_schedule( db, plan_id=body.plan_id, data=data, tenant_no=tenant_no, user_id=user.get("user_id"), dept_id=user.get("region_id", ""), ) return success_response(result, message="创建成功") except ValueError as e: return error_response(400, str(e)) @router.put("/pilot-infusion-schedule/update") async def api_update_schedule( body: InfusionScheduleUpdateBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) data = {} if body.infusion_time is not None: data["infusionTime"] = body.infusion_time if body.remark is not None: data["remark"] = body.remark if body.attachments is not None: data["attachments"] = body.attachments result = pilot_application.update_schedule( db, schedule_id=body.id, data=data, tenant_no=tenant_no ) return success_response(result, message="更新成功") except ValueError as e: return error_response(400, str(e)) @router.delete("/pilot-infusion-schedule/delete") async def api_delete_schedule( body: IdBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) pilot_application.soft_delete_schedule( db, schedule_id=body.id, tenant_no=tenant_no ) return success_response(None, message="删除成功") except ValueError as e: return error_response(400, str(e)) @router.post("/pilot-infusion-schedule/mark-complete") async def api_mark_complete( body: IdBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) result = pilot_application.mark_schedule_complete( db, schedule_id=body.id, tenant_no=tenant_no, user_id=user.get("user_id") ) return success_response(result, message="操作成功") except ValueError as e: return error_response(400, str(e)) # --------------------------------------------------------------------------- # 出库生产工单 / 出库质检工单 联动 # --------------------------------------------------------------------------- class ScheduleIdBody(BaseModel): scheduleId: str @router.post("/pilot-infusion-schedule/create-production-work-order") async def api_create_outbound_production( body: ScheduleIdBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) from app.crud import production_work_order as pwo_svc wo = pwo_svc.create_outbound_production_work_order( db, schedule_id=body.scheduleId, tenant_no=tenant_no, user_id=user.get("user_id"), create_dept_id=user.get("dept_id", ""), ) pilot_application.mark_schedule_complete( db, schedule_id=body.scheduleId, tenant_no=tenant_no, user_id=user.get("user_id") ) return success_response(pwo_svc._wo_to_dict(wo), message="创建出库生产工单成功") except ValueError as e: return error_response(400, str(e)) except Exception as e: db.rollback() return error_response(400, str(e)) class OutboundQcBody(BaseModel): scheduleId: str cellProductId: str = "" @router.post("/pilot-infusion-schedule/create-qc-work-order") async def api_create_outbound_qc( body: OutboundQcBody, user: dict = Depends(require_admin_or_super_admin), db: Session = Depends(get_db), ): try: tenant_no = _get_tenant_no(db, user) from app.crud import qc_work_order as qc_svc wo = qc_svc.create_outbound_qc_work_order( db, schedule_id=body.scheduleId, cell_product_id=body.cellProductId, tenant_no=tenant_no, user_id=user.get("user_id"), create_dept_id=user.get("dept_id", ""), ) return success_response(qc_svc._wo_to_dict(wo), message="创建出库质检工单成功") except ValueError as e: return error_response(400, str(e)) except Exception as e: db.rollback() return error_response(400, str(e))