21 KiB
基础实验业务 Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 新增 biz_type=basic_experiment 基础实验业务线:B2B 细胞产品售卖、一产品一售卖计划、独立缴费/出库/交付闭环。
Architecture: 复用 biz_pilot_* 表与中试/临床出库链路;申请单扩展课题/客户字段;售卖方案保存时按产品行批量 create_schedule(每行 → 1 条 infusion_schedule + 工单 + 缴费)。前端通过 pilot-biz-type.ts 策略配置 + 路由 props.bizType 隔离 UI。
Tech Stack: FastAPI + SQLAlchemy + Pydantic v2(后端),Vue 3 + Element Plus + TypeScript(前端),MySQL 5.7
Design Spec: docs/superpowers/specs/2026-06-10-basic-experiment-design.md
File Structure
SQL — New
docs/sql/migrations/2026-06-10-basic-experiment.sql— ALTERbiz_pilot_application、扩展biz_type注释、biz_pilot_payment注释docs/sql/migrations/2026-06-10-basic-experiment-menu.sql—sys_menu基础实验菜单(可选,与menu.py对齐)
Backend — Modified
backend/app/models/models.py—BizPilotApplication新增客户/课题字段;biz_type注释扩展backend/app/crud/pilot_application.py— JCSY 前缀、basic_experiment校验、售卖计划批量创建、_row_to_dict新字段backend/app/api/pilot_application.py— Create/Update schema 新字段backend/app/crud/clinical_payment.py—auto_create_payment/ 列表支持basic_experimentbackend/app/api/clinical_payment.py— 列表接口bizType过滤backend/app/crud/outbound_release_review.py—_BIZ_TYPE_LABELS、详情聚合backend/app/crud/pilot_outbound_status_sync.py—_OUTBOUND_BIZ_TYPESbackend/app/crud/qc_work_order.py— 出库详情basic_experiment上下文backend/app/crud/production_work_order.py— 出库详情basic_experiment上下文backend/app/crud/pilot_infusion_schedule_mgmt.py— 详情/列表basic_experiment字段backend/app/api/menu.py— 基础实验菜单树
Backend — New Tests
backend/tests/test_basic_experiment_application.py— 申请单创建、多产品多计划、工单/缴费联动
Frontend — New
web-admin/src/config/pilot-biz-type.ts—bizType策略配置表
Frontend — Modified
web-admin/src/router/modules/sqxp.ts— 基础实验一级菜单 + 子路由web-admin/src/utils/status-tag-registry.ts—basic_experiment标签web-admin/src/utils/outbound-qc-pilot-view-sections.ts—basic_experimentvariantweb-admin/src/views/pilot-service/pilot-application/index.vue— Tab/卡片按 config 渲染web-admin/src/views/pilot-service/pilot-application/PilotApplicationTab.vueweb-admin/src/views/pilot-service/pilot-application/ApplicationFormDialog.vue— 基础实验表单 + 售卖产品子表web-admin/src/views/pilot-service/pilot-application/ApplicationViewDialog.vueweb-admin/src/views/pilot-service/pilot-application/ApplicationDetailDialog.vue— 交付 Tab 显隐web-admin/src/views/pilot-service/pilot-application/InfusionScheduleTab.vue— 售卖计划文案/列web-admin/src/views/pilot-service/pilot-application/ClinicalPaymentTab.vue—bizType列展示web-admin/src/views/pilot-service/clinical-payment-confirm/index.vue— 接收bizTypepropweb-admin/src/views/pilot-service/outbound-release-review/index.vueweb-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vueweb-admin/src/views/production/production-work-order/index.vueweb-admin/src/api/business.ts— 类型/请求参数(若需)
Task 1: 数据库迁移
Files:
-
Create:
docs/sql/migrations/2026-06-10-basic-experiment.sql -
Modify:
docs/sql/zkxw.sql(全量基线同步) -
Step 1: 编写迁移 SQL
-- 基础实验:biz_pilot_application 扩展
ALTER TABLE biz_pilot_application
MODIFY COLUMN biz_type VARCHAR(20) NOT NULL DEFAULT 'pilot_service'
COMMENT '业务类型: pilot_service=中试服务, clinical_application=临床应用, basic_experiment=基础实验';
ALTER TABLE biz_pilot_application
ADD COLUMN project_title VARCHAR(200) DEFAULT NULL COMMENT '课题名称(基础实验)' AFTER application_name,
ADD COLUMN customer_name VARCHAR(200) DEFAULT NULL COMMENT '客户名称(基础实验)' AFTER project_title,
ADD COLUMN customer_contact VARCHAR(100) DEFAULT NULL COMMENT '客户联系人(基础实验)' AFTER customer_name,
ADD COLUMN customer_phone VARCHAR(32) DEFAULT NULL COMMENT '客户联系方式(基础实验)' AFTER customer_contact;
ALTER TABLE biz_pilot_payment
MODIFY COLUMN biz_type VARCHAR(20) NOT NULL DEFAULT 'clinical_application'
COMMENT '业务类型: clinical_application=临床应用, basic_experiment=基础实验';
- Step 2: 在测试库执行迁移
Run: mysql -u root -p zkxw < docs/sql/migrations/2026-06-10-basic-experiment.sql
Expected: 无报错;DESCRIBE biz_pilot_application 可见四个新字段。
-
Step 3: 同步
docs/sql/zkxw.sql中对应表定义 -
Step 4: Commit
git add docs/sql/migrations/2026-06-10-basic-experiment.sql docs/sql/zkxw.sql
git commit -m "feat: add basic experiment DB migration for application customer fields"
Task 2: 后端模型与 _row_to_dict
Files:
-
Modify:
backend/app/models/models.py -
Modify:
backend/app/crud/pilot_application.py -
Step 1: 在
BizPilotApplication增加四列
project_title = Column(String(200), nullable=True, comment="课题名称(基础实验)")
customer_name = Column(String(200), nullable=True, comment="客户名称(基础实验)")
customer_contact = Column(String(100), nullable=True, comment="客户联系人(基础实验)")
customer_phone = Column(String(32), nullable=True, comment="客户联系方式(基础实验)")
更新 biz_type 列 comment 含 basic_experiment。
- Step 2: 扩展
_row_to_dict_application
"projectTitle": row.project_title or "",
"customerName": row.customer_name or "",
"customerContact": row.customer_contact or "",
"customerPhone": row.customer_phone or "",
- Step 3: 扩展
_generate_application_no
if biz_type == "clinical_application":
prefix = f"LCSQ{today_str}"
elif biz_type == "basic_experiment":
prefix = f"JCSY{today_str}"
else:
prefix = f"ZSSQ{today_str}"
- Step 4: Commit
git add backend/app/models/models.py backend/app/crud/pilot_application.py
git commit -m "feat: extend pilot application model for basic experiment fields"
Task 3: 申请单创建/更新 — basic_experiment 校验
Files:
-
Modify:
backend/app/crud/pilot_application.py -
Modify:
backend/app/api/pilot_application.py -
Step 1: 写失败测试
Create backend/tests/test_basic_experiment_application.py:
import pytest
from app.crud import pilot_application as crud
def test_generate_application_no_basic_experiment_prefix():
# mock db session 或集成测试:biz_type=basic_experiment 时前缀 JCSY
assert "JCSQ" not in "JCSY202606100001" # placeholder; 实现后用真实调用断言
实现时改为:调用 _generate_application_no 或 create API 返回 applicationNo.startswith("JCSY")。
- Step 2: 修改
create_application分支
biz_type = data.get("bizType") or "pilot_service"
if biz_type == "basic_experiment":
if not (data.get("projectTitle") or "").strip():
raise ValueError("请填写课题名称")
if not (data.get("customerName") or "").strip():
raise ValueError("请填写客户名称")
if not (data.get("customerContact") or "").strip():
raise ValueError("请填写客户联系人")
if not (data.get("customerPhone") or "").strip():
raise ValueError("请填写客户联系方式")
channel_service_order_id = ""
channel_id = ""
channel_name = ""
elif biz_type == "clinical_application":
# 保持现有逻辑
...
else:
if not channel_service_order_id:
raise ValueError("请选择渠道服务订单")
创建 BizPilotApplication 时写入 project_title/customer_*;consent_form_urls=None。
-
Step 3: 修改
update_application支持新字段 -
Step 4: API schema
CreateBody/UpdateBody增加
project_title: Optional[str] = Field(None, alias="projectTitle")
customer_name: Optional[str] = Field(None, alias="customerName")
customer_contact: Optional[str] = Field(None, alias="customerContact")
customer_phone: Optional[str] = Field(None, alias="customerPhone")
- Step 5: 运行测试
Run: cd backend && source venv/bin/activate && pytest tests/test_basic_experiment_application.py -v
- Step 6: Commit
Task 4: 售卖计划批量创建(一产品一计划)
Files:
-
Modify:
backend/app/crud/pilot_application.py -
Modify:
backend/app/api/pilot_application.py -
Step 1: 新增
create_basic_experiment_sale_schedules
在 pilot_application.py 中实现(或扩展现有 save infusion API):
def create_basic_experiment_sale_schedules(
db: Session,
application_id: str,
product_lines: list,
tenant_no: str,
user_id: str,
dept_id: str,
) -> list:
"""每个产品行调用一次 create_schedule,cellProducts 仅含一行。"""
plan = _get_or_create_infusion_plan(db, application_id, tenant_no, user_id, dept_id)
results = []
for idx, line in enumerate(product_lines):
schedule_data = {
"cellType": line.get("cellTypeName") or "",
"specText": line.get("specText") or "",
"infusionTime": line.get("pickupTime"), # 提货时间 → infusion_time
"amount": line.get("amount"),
"sortNo": idx + 1,
"cellProducts": [{
"priceId": line.get("priceId"),
"projectCode": line.get("projectCode"),
"projectName": line.get("projectName"),
"cellTypeName": line.get("cellTypeName"),
"specText": line.get("specText"),
"agreementPrice": line.get("agreementPrice"),
"actualPrice": line.get("actualPrice"),
"quantity": line.get("quantity") or 1,
}],
}
results.append(
create_schedule(db, plan.id, schedule_data, tenant_no, user_id, dept_id)
)
return results
- Step 2: 扩展
create_schedule缴费自动创建
将条件从仅 clinical_application 扩展为:
if application and application.biz_type in ("clinical_application", "basic_experiment") and data.get("amount"):
auto_create_payment(..., biz_type=application.biz_type)
- Step 3: API 端点
新增或扩展 POST /pilot-application/{id}/sale-schedules(body: productLines: [...]),仅 biz_type=basic_experiment 可用。
- Step 4: 测试 — 2 行产品生成 2 条 schedule + 2 组工单
def test_basic_experiment_two_products_create_two_schedules(db_session, ...):
# 创建申请单后提交 2 行产品
# 断言 schedule 数量 == 2
# 断言 production_work_order / qc_work_order 各 2 条
# 断言 biz_pilot_payment 2 条
- Step 5: Commit
Task 5: 缴费模块扩展
Files:
-
Modify:
backend/app/crud/clinical_payment.py -
Modify:
backend/app/api/clinical_payment.py -
Step 1:
auto_create_payment增加biz_type参数
默认 clinical_application;基础实验传 basic_experiment。
- Step 2: 列表查询支持
bizType过滤
所有 list_* 函数增加 biz_type: Optional[str],过滤 BizPilotPayment.biz_type。
-
Step 3: 列表
_row_to_dict关联申请单输出projectTitle/customerName -
Step 4: Commit
Task 6: 出库链路扩展
Files:
-
Modify:
backend/app/crud/pilot_outbound_status_sync.py -
Modify:
backend/app/crud/outbound_release_review.py -
Modify:
backend/app/crud/qc_work_order.py -
Modify:
backend/app/crud/production_work_order.py -
Modify:
backend/app/crud/pilot_infusion_schedule_mgmt.py -
Step 1: 元组/字典加入
basic_experiment
_OUTBOUND_BIZ_TYPES = ("pilot_service", "clinical_application", "basic_experiment")
_BIZ_TYPE_LABELS = {..., "basic_experiment": "基础实验"}
- Step 2: 出库详情接口 —
patient对basic_experiment返回 null
详情 application 含 projectTitle/customerName/customerContact/customerPhone。
- Step 3:
pilot_infusion_schedule_mgmt完成逻辑 — 跳过渠道扣费
与 clinical_application 相同:biz_type == basic_experiment 时不扣服务订单。
- Step 4: Commit
Task 7: 菜单与路由
Files:
-
Modify:
web-admin/src/router/modules/sqxp.ts -
Modify:
backend/app/api/menu.py -
Create:
docs/sql/migrations/2026-06-10-basic-experiment-menu.sql(可选) -
Step 1:
sqxp.ts新增一级菜单「基础实验」
{
id: 'BEX00000000000000000000000000001',
name: 'BasicExperiment',
path: '/basic-experiment',
component: '/index/index',
meta: { title: '基础实验', icon: 'ri:flask-line', keepAlive: false, roles: ['admin'] },
children: [
{
id: 'BEX00000000000000000000000000002',
name: 'BasicExperimentApplication',
path: 'application',
component: '/pilot-service/pilot-application/index',
meta: { title: '基础实验申请单', keepAlive: true, menuCode: 'basic-experiment:application:list' },
props: { bizType: 'basic_experiment' }
},
{
id: 'BEX00000000000000000000000000003',
name: 'BasicExperimentPaymentConfirm',
path: 'payment-confirm',
component: '/pilot-service/clinical-payment-confirm/index',
meta: { title: '缴退费财务确认', keepAlive: true, menuCode: 'basic-experiment:payment-confirm:list' },
props: { bizType: 'basic_experiment' }
},
{
id: 'BEX00000000000000000000000000004',
name: 'BasicExperimentOutboundRelease',
path: 'outbound-release-review',
component: '/pilot-service/outbound-release-review/index',
meta: { title: '出库放行审核', keepAlive: true, menuCode: 'basic-experiment:outbound-release-review:list' },
props: { bizType: 'basic_experiment' }
}
]
}
-
Step 2: 同步
backend/app/api/menu.py -
Step 3: Commit
Task 8: 前端 pilot-biz-type.ts 策略配置
Files:
-
Create:
web-admin/src/config/pilot-biz-type.ts -
Step 1: 定义配置表
export type PilotBizType = 'pilot_service' | 'clinical_application' | 'basic_experiment'
export const PILOT_BIZ_TYPE_CONFIG: Record<PilotBizType, {
label: string
applicationNoPrefix: string
showChannel: boolean
showPatient: boolean
showConsent: boolean
showOtherAttachments: boolean
showStandardAttachments: boolean
showCheckup: boolean
showInfusionConfirm: boolean
showDelivery: boolean
showPayment: boolean
planLabel: string
scheduleLabel: string
pickupTimeLabel: string
}> = {
pilot_service: { /* 现有默认 */ showStandardAttachments: false, ... },
clinical_application: { showStandardAttachments: false, ... },
basic_experiment: {
label: '基础实验',
applicationNoPrefix: 'JCSY',
showChannel: false,
showPatient: false,
showConsent: false,
showOtherAttachments: false,
showStandardAttachments: true,
showCheckup: false,
showInfusionConfirm: false,
showDelivery: true,
showPayment: true,
planLabel: '售卖方案',
scheduleLabel: '售卖计划',
pickupTimeLabel: '提货时间',
}
}
export function getPilotBizConfig(bizType?: string) {
return PILOT_BIZ_TYPE_CONFIG[(bizType as PilotBizType) || 'pilot_service']
}
-
Step 2:
status-tag-registry.ts增加basic_experiment -
Step 3: Commit
Task 9: 申请单表单与列表
Files:
-
Modify:
web-admin/src/views/pilot-service/pilot-application/index.vue -
Modify:
web-admin/src/views/pilot-service/pilot-application/PilotApplicationTab.vue -
Modify:
web-admin/src/views/pilot-service/pilot-application/ApplicationFormDialog.vue -
Modify:
web-admin/src/views/pilot-service/pilot-application/ApplicationViewDialog.vue -
Step 1:
index.vue用getPilotBizConfig控制 Tab 卡片
基础实验:申请单 | 售卖计划 | 缴费信息(无体检)。
-
Step 2:
ApplicationFormDialog.vue— 基础实验区块 -
显示:课题名称、客户名称/联系人/联系方式、标准附件(单上传区,图片/PDF,卡片布局)
-
隐藏:渠道、服务订单、知情同意书、其他附件、患者
-
售卖产品子表:细胞产品(
biz_cell_product_price)、参考价、实际价(可改)、份数、提货时间、金额 -
保存:先 create/update application,再调
sale-schedulesAPI 批量创建计划 -
Step 3:
PilotApplicationTab列表列
基础实验列表展示:申请单号、课题名称、客户名称、状态(隐藏渠道列)。
-
Step 4:
ApplicationViewDialog只读态对齐 -
Step 5: 手动验证
- 打开
/#/basic-experiment/application - 新增申请单,添加 2 行产品不同提货时间
- 保存后在售卖计划 Tab 看到 2 条计划
- Step 6: Commit
Task 10: 售卖计划 Tab + 缴费
Files:
-
Modify:
web-admin/src/views/pilot-service/pilot-application/InfusionScheduleTab.vue -
Modify:
web-admin/src/views/pilot-service/pilot-application/ClinicalPaymentTab.vue -
Modify:
web-admin/src/views/pilot-service/clinical-payment-confirm/index.vue -
Step 1:
InfusionScheduleTab文案与列 -
scheduleLabel/pickupTimeLabel从 config 读取 -
隐藏输注地点/方式/输注状态(
basic_experiment) -
展示:细胞产品、金额、提货时间、缴费状态
-
Step 2:
ClinicalPaymentTab+ 缴退费页 -
请求带
bizType: 'basic_experiment' -
列:课题名称、客户名称(替代患者/渠道)
-
Step 3: Commit
Task 11: 出库查看弹窗 + 放行审核
Files:
-
Modify:
web-admin/src/utils/outbound-qc-pilot-view-sections.ts -
Modify:
web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue -
Modify:
web-admin/src/views/production/production-work-order/index.vue -
Modify:
web-admin/src/views/pilot-service/outbound-release-review/index.vue -
Step 1: 扩展
OutboundViewContextVariant
basic_experiment: {
application: '基础实验申请信息',
schedule: '售卖计划信息'
}
新增 section builder:申请信息含课题/客户;无患者;计划含提货时间/金额;产品明细表。
- Step 2: QC/生产查看传入
variant='basic_experiment'
当 detail.application.bizType === 'basic_experiment'。
-
Step 3: 放行审核列表
bizType过滤 + 查看弹窗 -
Step 4: Commit
Task 12: 交付确认
Files:
-
Modify:
web-admin/src/views/pilot-service/pilot-application/ApplicationDetailDialog.vue -
Step 1:
getPilotBizConfig(bizType).showDelivery控制交付 Tab -
Step 2: 隐藏输注确认 Tab(
showInfusionConfirm: false) -
Step 3: 按 schedule 粒度操作交付(复用现有
pilot_deliveryAPI) -
Step 4: Commit
Task 13: 字段一致性检查与收尾
Files:
-
Run:
backend/scripts/check_field_consistency.py -
Run:
./scripts/update-graph.sh frontend+backend -
Step 1: 运行字段一致性脚本并修复遗漏
Run: cd backend && python scripts/check_field_consistency.py
- Step 2: 更新知识图谱
Run: ./scripts/update-graph.sh all
- Step 3: 验收清单(对照 design spec §8)
| # | 验收项 | 通过 |
|---|---|---|
| 1 | 侧栏「基础实验」三菜单可见 | |
| 2 | JCSY 申请单 + 课题/客户/标准附件 | |
| 3 | 2 行产品 → 2 条售卖计划 | |
| 4 | 每计划自动工单 + 待缴费 | |
| 5 | QC/生产查看弹窗无患者、有基础实验信息 | |
| 6 | 放行/交接/交付可用 | |
| 7 | 缴退费流程可用且数据隔离 | |
| 8 | 中试/临床列表无 JCSY |
- Step 4: Commit
git commit -m "feat: complete basic experiment business line"
Spec Coverage Self-Review
| Spec 章节 | 对应 Task |
|---|---|
| §2 业务流程 / 一产品一计划 | Task 4 |
| §3 数据库 | Task 1–2 |
| §4.1 申请单 API | Task 3–4 |
| §4.2 缴费 | Task 5 |
| §4.3–4.6 出库/交付 | Task 6, 11–12 |
| §5 前端菜单/表单/查看 | Task 7–12 |
| §6 权限 menuCode | Task 7 |
| §8 验收标准 | Task 13 |
无 TBD/占位;类型命名统一 camelCase API / snake_case DB。
Execution Handoff
Plan complete and saved to docs/superpowers/plans/2026-06-10-basic-experiment.md. Two execution options:
1. Subagent-Driven (recommended) — 每个 Task 派发独立 subagent,任务间做代码审查,迭代快
2. Inline Execution — 本会话按 Task 顺序直接实现,每 2–3 个 Task 设检查点
Which approach?