Files
zkxw/docs/superpowers/specs/2026-05-24-pilot-application-phase1-design.md
T
2026-05-25 15:14:06 +08:00

504 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 中试服务申请单 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)