18 KiB
中试服务申请单 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 + 公共字段,需增强:
-- 迁移文件: 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:
- 查询
channel_service_order_id对应的服务订单,校验存在且状态为生效中(status='sxz') - 从服务订单关联的渠道信息缓存
channel_id/channel_name到申请单 - 生成
application_no:前缀 ZSSQ + YYYYMMDD + 4位序列号 - 默认
application_status = 'dks'
save_patient(客户关联逻辑):
- 如果传入
customer_id,从biz_customer表读取客户信息填充到患者字段 - 用户可修改已填充的字段
- 保存时:如果
customer_id存在则更新biz_customer对应记录;如果不存在则新建biz_customer记录并回填customer_id - 与采集协议单的母亲信息模式一致
auto_generate_schedules:
- 读取方案的
total_course_count - 批量插入 N 条
biz_pilot_infusion_schedule记录 - 每条记录继承方案的
cell_type/spec_text schedule_status留空,infusion_time留空(用户后续手动填写)order_num按序号 1, 2, 3... 排列- 返回生成数量
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 追加)
{
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) |
| 操作 | 编辑 / 删除 / 标记完成 |
操作流程:
- 点击"新增输注方案" → 弹窗填写方案信息 → 保存
- 保存方案后自动弹出提示:"是否立即生成 X 条输注计划?"
- 确认后调用 auto_generate 接口批量生成
- 生成后可手动编辑每条计划的时间/备注
- 可手动新增/删除计划
- 每条计划有"标记完成"按钮
4.5 API 函数(admin.ts 追加)
在 web-admin/src/api/admin.ts 末尾追加以下函数:
// 中试申请单
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 追加:
// 申请单状态
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)