# 中试服务申请单 Phase 1 设计文档 > 日期:2026-05-24 > 模块:中试服务管理 > 中试服务申请单 > 阶段:Phase 1(核心骨架) > 需求来源:`docs/中科兴皖需求V0.3.md` §2.3.9 ## 1. 概述 实现中试服务申请单的完整 CRUD、患者信息管理、输注方案管理、输注计划管理。采用独立后端模块方案(方案A),与现有 `pilot_service.py` 分离。 ### 分期规划 | 阶段 | 范围 | 状态 | |------|------|------| | Phase 1 | 申请单+患者+输注方案+输注计划 | 本文档 | | Phase 2 | 体检管理+交付交接+费用扣减 | 后续 | | Phase 3 | 出库生产/质检/放行审核联动 | 后续 | ### Phase 1 范围 - ✅ 中试申请单 CRUD + 状态流转(开始/完成/作废) - ✅ 患者信息(关联客户管理表 biz_customer,选填+新建模式) - ✅ 输注方案 CRUD - ✅ 输注计划(按疗程数自动生成 + 手动增删调整 + 标记完成) - ✅ 申请单列表页 + 详情 Tab 页 - ❌ 体检管理(Phase 2) - ❌ 交付/交接模块(Phase 2) - ❌ 出库生产/质检/放行联动(Phase 3) - ❌ 费用扣减逻辑(Phase 2 交付模块中实现) --- ## 2. 数据库变更 ### 2.1 ALTER biz_pilot_application 现有表仅含 `channel_service_order_id` + 公共字段,需增强: ```sql -- 迁移文件: docs/sql/pilot-application-migration.sql ALTER TABLE biz_pilot_application ADD COLUMN application_no VARCHAR(64) DEFAULT NULL COMMENT '申请单号(自动生成,前缀ZSSQ)' AFTER id, ADD COLUMN application_status VARCHAR(16) NOT NULL DEFAULT 'dks' COMMENT 'dks待开始 jxz进行中 ywc已完成 yzf已作废' AFTER application_no, ADD COLUMN channel_id VARCHAR(64) DEFAULT NULL COMMENT '渠道ID(缓存自服务订单)' AFTER channel_service_order_id, ADD COLUMN channel_name VARCHAR(200) DEFAULT NULL COMMENT '渠道名称(缓存)' AFTER channel_id; ALTER TABLE biz_pilot_application ADD UNIQUE KEY uk_application_no (application_no, del_flag); ``` ### 2.2 现有表不变 以下 3 张表结构已满足 Phase 1 需求,无需变更: | 表 | 说明 | |---|---| | `biz_pilot_patient` | 患者信息,含 customer_id 关联 + 完整个人字段 | | `biz_pilot_infusion_plan` | 输注方案,含 cell_type/spec/total_course_count 等 | | `biz_pilot_infusion_schedule` | 输注计划,含 schedule_status/infusion_time 等 | ### 2.3 输注计划状态约定(Phase 1) `biz_pilot_infusion_schedule.schedule_status` 已有值域:dsc/ysc/yjf/yqx。 Phase 1 简化使用: - 创建时 `schedule_status` 留空(前端显示"-") - 用户点击"完成"按钮后更新为 `ywc`(已完成) - Phase 3 出库联动时再启用 dsc/ysc/yjf/yqx 等详细状态 --- ## 3. 后端架构 ### 3.1 Model(models.py 新增) 在 `backend/app/models/models.py` 中新增 4 个 SQLAlchemy model: | Model | 表 | 关键字段 | |-------|---|---------| | `BizPilotApplication` | biz_pilot_application | application_no, application_status, channel_service_order_id, channel_id, channel_name | | `BizPilotPatient` | biz_pilot_patient | pilot_application_id, customer_id, patient_name, sex, age, id_card_no, phone_no, emergency_contact, emergency_phone, clinical_diagnosis, diagnosis_attachments, need_checkup | | `BizPilotInfusionPlan` | biz_pilot_infusion_plan | pilot_application_id, cell_type, spec_text, total_course_count, course_interval, infusion_route, infusion_start_date | | `BizPilotInfusionSchedule` | biz_pilot_infusion_schedule | infusion_plan_id, cell_type, spec_text, infusion_time, schedule_status, attachments | ### 3.2 CRUD(新文件:backend/app/crud/pilot_application.py) #### _row_to_dict 函数 每个实体一个 `_row_to_dict_*` 函数,作为可读字段的单一事实来源: - `_row_to_dict_application(row)` — 含 channel_name、patient_name(子查询)、infusion_plan_count(子查询) - `_row_to_dict_patient(row)` - `_row_to_dict_infusion_plan(row)` — 含 schedule_count(子查询) - `_row_to_dict_infusion_schedule(row)` #### CRUD 函数清单 **Application:** | 函数 | 说明 | |------|------| | `list_applications(db, tenant_no, filters)` | 分页列表,支持按渠道/状态/服务订单筛选 | | `get_application(db, app_id, tenant_no)` | 获取单个申请单详情 | | `create_application(db, data, tenant_no, user_id)` | 创建申请单,校验服务订单存在且生效,缓存渠道信息,生成申请单号 | | `update_application(db, app_id, data, tenant_no)` | 更新申请单(仅 dks 状态可编辑) | | `soft_delete_application(db, app_id, tenant_no)` | 软删除(仅 dks 状态) | | `start_application(db, app_id, tenant_no, user_id)` | 状态:dks→jxz | | `complete_application(db, app_id, tenant_no, user_id)` | 状态:jxz→ywc | | `void_application(db, app_id, tenant_no, user_id)` | 状态:任意→yzf | **Patient:** | 函数 | 说明 | |------|------| | `get_patient_by_application(db, app_id, tenant_no)` | 获取某申请单的患者信息 | | `save_patient(db, app_id, data, tenant_no, user_id)` | 保存/替换患者信息,同步更新 biz_customer 表 | **InfusionPlan:** | 函数 | 说明 | |------|------| | `list_infusion_plans(db, app_id, tenant_no)` | 获取某申请单的输注方案列表 | | `create_infusion_plan(db, app_id, data, tenant_no, user_id)` | 创建输注方案 | | `update_infusion_plan(db, plan_id, data, tenant_no)` | 更新输注方案 | | `soft_delete_infusion_plan(db, plan_id, tenant_no)` | 软删除输注方案(级联删除其下所有计划) | **InfusionSchedule:** | 函数 | 说明 | |------|------| | `list_schedules_by_plan(db, plan_id, tenant_no)` | 获取某方案下的输注计划列表 | | `auto_generate_schedules(db, plan_id, tenant_no, user_id)` | 按方案的 total_course_count 批量生成计划记录 | | `create_schedule(db, plan_id, data, tenant_no, user_id)` | 手动添加单条计划 | | `update_schedule(db, schedule_id, data, tenant_no)` | 更新计划(时间/备注等) | | `soft_delete_schedule(db, schedule_id, tenant_no)` | 软删除单条计划 | | `mark_schedule_complete(db, schedule_id, tenant_no, user_id)` | 标记计划为已完成(schedule_status='ywc') | #### 核心业务逻辑 **create_application:** 1. 查询 `channel_service_order_id` 对应的服务订单,校验存在且状态为生效中(status='sxz') 2. 从服务订单关联的渠道信息缓存 `channel_id` / `channel_name` 到申请单 3. 生成 `application_no`:前缀 ZSSQ + YYYYMMDD + 4位序列号 4. 默认 `application_status = 'dks'` **save_patient(客户关联逻辑):** 1. 如果传入 `customer_id`,从 `biz_customer` 表读取客户信息填充到患者字段 2. 用户可修改已填充的字段 3. 保存时:如果 `customer_id` 存在则更新 `biz_customer` 对应记录;如果不存在则新建 `biz_customer` 记录并回填 `customer_id` 4. 与采集协议单的母亲信息模式一致 **auto_generate_schedules:** 1. 读取方案的 `total_course_count` 2. 批量插入 N 条 `biz_pilot_infusion_schedule` 记录 3. 每条记录继承方案的 `cell_type` / `spec_text` 4. `schedule_status` 留空,`infusion_time` 留空(用户后续手动填写) 5. `order_num` 按序号 1, 2, 3... 排列 6. 返回生成数量 ### 3.3 API(新文件:backend/app/api/pilot_application.py) **路由前缀:** `/web/admin` **Tag:** `中试服务申请单管理` #### Pydantic Schemas(内联定义) ``` ApplicationCreateBody: channel_service_order_id: str remark: Optional[str] ApplicationUpdateBody: remark: Optional[str] PatientSaveBody: customer_id: Optional[str] patient_name: str sex: Optional[str] age: Optional[int] id_card_no: Optional[str] phone_no: Optional[str] emergency_contact: Optional[str] emergency_phone: Optional[str] clinical_diagnosis: Optional[str] diagnosis_attachments: Optional[str] need_checkup: int = 1 InfusionPlanCreateBody: pilot_application_id: str cell_type: str spec_text: Optional[str] total_course_count: int course_interval: Optional[str] infusion_route: Optional[str] infusion_start_date: Optional[str] InfusionPlanUpdateBody: cell_type: Optional[str] spec_text: Optional[str] total_course_count: Optional[int] course_interval: Optional[str] infusion_route: Optional[str] infusion_start_date: Optional[str] InfusionScheduleUpdateBody: infusion_time: Optional[str] remark: Optional[str] attachments: Optional[str] ``` #### API 路由清单 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/pilot-application/list` | 申请单分页列表 | | GET | `/pilot-application/detail` | 申请单详情(含患者+方案+计划) | | POST | `/pilot-application/create` | 创建申请单 | | PUT | `/pilot-application/update` | 更新申请单 | | DELETE | `/pilot-application/delete` | 删除申请单 | | POST | `/pilot-application/start` | 开始(dks→jxz) | | POST | `/pilot-application/complete` | 完成(jxz→ywc) | | POST | `/pilot-application/void` | 作废(→yzf) | | GET | `/pilot-patient/detail` | 获取患者信息 | | POST | `/pilot-patient/save` | 保存患者信息 | | GET | `/pilot-infusion-plan/list` | 输注方案列表 | | POST | `/pilot-infusion-plan/create` | 创建输注方案 | | PUT | `/pilot-infusion-plan/update` | 更新输注方案 | | DELETE | `/pilot-infusion-plan/delete` | 删除输注方案 | | POST | `/pilot-infusion-schedule/auto-generate` | 自动生成计划 | | POST | `/pilot-infusion-schedule/create` | 手动添加计划 | | PUT | `/pilot-infusion-schedule/update` | 更新计划 | | DELETE | `/pilot-infusion-schedule/delete` | 删除计划 | | POST | `/pilot-infusion-schedule/mark-complete` | 标记计划完成 | **路由注册:** 在 `app/api/__init__.py` 和 `app/main.py` 中追加 `pilot_application` router。 **权限:** 使用 `require_admin_or_super_admin` 装饰器(与现有 pilot_service 一致)。 --- ## 4. 前端架构 ### 4.1 路由(sqxp.ts 追加) ```typescript { path: 'pilot-application', name: 'PilotApplication', component: () => import('@views/pilot-service/pilot-application/index.vue'), meta: { title: '中试服务申请单', icon: '...' } }, { path: 'pilot-application/detail', name: 'PilotApplicationDetail', component: () => import('@views/pilot-service/pilot-application/ApplicationDetail.vue'), meta: { title: '申请单详情', hidden: true } } ``` ### 4.2 页面文件 ``` web-admin/src/views/pilot-service/pilot-application/ ├── index.vue ← 申请单列表页 └── ApplicationDetail.vue ← 详情页(Tab布局) ``` ### 4.3 列表页 index.vue **参考模板页:** `src/views/system/order/index.vue`(复杂列表页) **筛选条件:** - 申请单号(文本输入) - 渠道(下拉选择,数据源:已有渠道 API) - 服务订单(下拉选择,联动渠道筛选) - 状态(Tag 筛选按钮组) **表格列:** | 列名 | 字段 | 宽度 | 说明 | |------|------|------|------| | 申请单号 | application_no | 180 | | | 渠道名称 | channel_name | 150 | | | 关联服务订单 | service_order_no | 180 | 显示订单号 + 类型标签 | | 患者姓名 | patient_name | 100 | | | 输注方案数 | infusion_plan_count | 100 | | | 状态 | application_status | 100 | el-tag:dks(info) / jxz(primary) / ywc(success) / yzf(danger) | | 创建时间 | create_time | 160 | | | 操作 | — | 260+ | 查看/编辑/删除 + 状态按钮 | **操作按钮逻辑:** - 新增:弹窗选择渠道 + 服务订单 → 创建成功后跳转详情页 - 状态按钮动态显示: - dks(待开始):显示"开始"按钮 - jxz(进行中):显示"完成"和"作废"按钮 - ywc(已完成) / yzf(已作废):无状态按钮 - 点击行 → router.push 跳转详情页(携带 application_id 参数) ### 4.4 详情页 ApplicationDetail.vue **参考模板页:** `src/views/system/product/index.vue`(含 ViewDialog 的详情展示) **Tab 布局:** | Tab | 内容 | 阶段 | |-----|------|------| | 基本信息 | 申请单信息 + 患者信息 | Phase 1 ✅ | | 输注方案 | 方案列表 + 计划管理 | Phase 1 ✅ | | 体检信息 | 占位(Phase 2 实现) | Phase 2 🔒 | | 交付信息 | 占位(Phase 2 实现) | Phase 2 🔒 | #### 基本信息 Tab **上半区 — 申请单信息(只读):** - 申请单号、渠道名称、关联服务订单、状态、备注 - 状态以 el-tag 展示 **下半区 — 患者信息面板(可编辑):** - "选择客户"按钮 → 弹窗从客户管理表(biz_customer)选取 - 弹窗展示客户列表,支持搜索(姓名/手机号) - 选中后自动填充:姓名、性别、年龄、身份证号、联系电话、紧急联系人、紧急联系人电话 - 已填充字段可手动修改 - "是否需要体检"开关(need_checkup) - 临床诊断文本框 + 诊断附件上传 - "保存患者信息"按钮 - 保存时同步更新 biz_customer 表(与采集协议单母亲信息模式一致) #### 输注方案 Tab **方案列表(表格或卡片):** 每行显示: - 细胞类型(cell_type) - 规格(spec_text) - 总疗程数(total_course_count) - 疗程间隔(course_interval) - 输注途径(infusion_route) - 输注开始日期(infusion_start_date) - 计划数 / 已完成数(如 3/5) - 操作:编辑 / 删除 **展开行 — 输注计划列表:** 每个方案可展开显示其下的输注计划: | 列 | 说明 | |---|---| | 序号 | order_num | | 细胞类型 | cell_type | | 规格 | spec_text | | 输注时间 | infusion_time(可编辑) | | 状态 | "-" 或 "已完成"(el-tag) | | 操作 | 编辑 / 删除 / 标记完成 | **操作流程:** 1. 点击"新增输注方案" → 弹窗填写方案信息 → 保存 2. 保存方案后**自动弹出提示**:"是否立即生成 X 条输注计划?" 3. 确认后调用 auto_generate 接口批量生成 4. 生成后可手动编辑每条计划的时间/备注 5. 可手动新增/删除计划 6. 每条计划有"标记完成"按钮 ### 4.5 API 函数(admin.ts 追加) 在 `web-admin/src/api/admin.ts` 末尾追加以下函数: ```typescript // 中试申请单 fetchPilotApplicationList(params) fetchPilotApplicationDetail(params) createPilotApplication(data) updatePilotApplication(data) deletePilotApplication(data) startPilotApplication(data) completePilotApplication(data) voidPilotApplication(data) // 患者信息 fetchPilotPatient(params) savePilotPatient(data) // 输注方案 fetchPilotInfusionPlanList(params) createPilotInfusionPlan(data) updatePilotInfusionPlan(data) deletePilotInfusionPlan(data) // 输注计划 fetchPilotInfusionScheduleList(params) autoGenerateSchedules(data) updatePilotInfusionSchedule(data) deletePilotInfusionSchedule(data) markScheduleComplete(data) ``` ### 4.6 状态标签注册 在 `web-admin/src/utils/status-tag-registry.ts` 追加: ```typescript // 申请单状态 applicationStatusLabel(status) → '待开始'/'进行中'/'已完成'/'已作废' applicationStatusTagType(status) → 'info'/'primary'/'success'/'danger' ``` --- ## 5. 关键交互流程 ### 5.1 创建申请单流程 ``` 列表页 → 点击"新增" → 弹窗:选择渠道(下拉)→ 联动加载该渠道的服务订单(下拉) → 选择服务订单 → 填写备注 → 提交 → 后端校验订单状态 → 生成申请单号 → 创建成功 → 跳转详情页 ``` ### 5.2 患者信息录入流程 ``` 详情页基本信息Tab → 点击"选择客户" → 弹窗展示客户列表(姓名/手机搜索) → 选中客户 → 自动填充患者字段 → 可手动修改字段 → 填写临床诊断 + 上传诊断附件 → 点击"保存" → 后端同步更新 biz_customer ``` ### 5.3 输注方案+计划管理流程 ``` 详情页输注方案Tab → 点击"新增输注方案" → 弹窗填写:细胞类型、规格、总疗程数(N)、疗程间隔、输注途径、开始日期 → 保存 → ElMessageBox.confirm("是否立即生成 N 条输注计划?") → 确认 → 调用 auto_generate → 生成 N 条计划 → 取消 → 仅创建方案,后续手动添加 → 展开方案 → 查看/编辑每条计划 → 手动新增/删除/编辑计划 → 标记计划完成 ``` ### 5.4 申请单状态流转 ``` dks(待开始) ──开始──→ jxz(进行中) ──完成──→ ywc(已完成) │ │ └────作废──→ yzf(已作废) ←──作废──┘ 约束: - 编辑/删除:仅 dks 状态可操作 - 开始:仅 dks → jxz - 完成:仅 jxz → ywc - 作废:dks 或 jxz → yzf ``` --- ## 6. 文件清单 ### 新增文件 | # | 文件路径 | 说明 | |---|---------|------| | 1 | `docs/sql/pilot-application-migration.sql` | 数据库迁移 SQL | | 2 | `backend/app/crud/pilot_application.py` | 后端 CRUD | | 3 | `backend/app/api/pilot_application.py` | 后端 API 路由 | | 4 | `web-admin/src/views/pilot-service/pilot-application/index.vue` | 列表页 | | 5 | `web-admin/src/views/pilot-service/pilot-application/ApplicationDetail.vue` | 详情页 | ### 修改文件 | # | 文件路径 | 变更 | |---|---------|------| | 6 | `backend/app/models/models.py` | 新增 4 个 Model 类 | | 7 | `backend/app/api/__init__.py` | 注册新 router | | 8 | `backend/app/main.py` | 引入新 router | | 9 | `web-admin/src/api/admin.ts` | 追加 19 个 API 函数 | | 10 | `web-admin/src/utils/status-tag-registry.ts` | 追加申请单状态标签函数 | | 11 | `web-admin/src/router/modules/sqxp.ts` | 追加 2 个路由 | --- ## 7. 不在范围内 以下内容不在 Phase 1 中: - 体检预约/报告/费用管理(biz_pilot_checkup + reserve_item + actual_item) - 交付信息/物料清单/交付方式(biz_pilot_delivery + delivery_material + delivery_method) - 应用确认(biz_pilot_infusion_confirm) - 输注计划→出库生产工单自动联动 - 出库质检工单 - 出库放行审核 - 费用/份数扣减逻辑 - 工作流集成 - 数据权限(使用 require_admin_or_super_admin)