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
docs/sql/migrations/2026-06-10-basic-experiment.sql — ALTER biz_pilot_application、扩展 biz_type 注释、biz_pilot_payment 注释docs/sql/migrations/2026-06-10-basic-experiment-menu.sql — sys_menu 基础实验菜单(可选,与 menu.py 对齐)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/tests/test_basic_experiment_application.py — 申请单创建、多产品多计划、工单/缴费联动web-admin/src/config/pilot-biz-type.ts — bizType 策略配置表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_experiment variantweb-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 — 接收 bizType propweb-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 — 类型/请求参数(若需)Files:
docs/sql/migrations/2026-06-10-basic-experiment.sqlModify: 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=基础实验';
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"
_row_to_dictFiles:
backend/app/models/models.pyModify: 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。
_row_to_dict_application"projectTitle": row.project_title or "",
"customerName": row.customer_name or "",
"customerContact": row.customer_contact or "",
"customerPhone": row.customer_phone or "",
_generate_application_noif 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}"
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"
basic_experiment 校验Files:
backend/app/crud/pilot_application.pyModify: 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")。
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")
Run: cd backend && source venv/bin/activate && pytest tests/test_basic_experiment_application.py -v
Files:
backend/app/crud/pilot_application.pyModify: 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
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)
新增或扩展 POST /pilot-application/{id}/sale-schedules(body: productLines: [...]),仅 biz_type=basic_experiment 可用。
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 条
Files:
backend/app/crud/clinical_payment.pyModify: backend/app/api/clinical_payment.py
[ ] Step 1: auto_create_payment 增加 biz_type 参数
默认 clinical_application;基础实验传 basic_experiment。
bizType 过滤所有 list_* 函数增加 biz_type: Optional[str],过滤 BizPilotPayment.biz_type。
[ ] Step 3: 列表 _row_to_dict 关联申请单输出 projectTitle/customerName
[ ] Step 4: Commit
Files:
backend/app/crud/pilot_outbound_status_sync.pybackend/app/crud/outbound_release_review.pybackend/app/crud/qc_work_order.pybackend/app/crud/production_work_order.pyModify: 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": "基础实验"}
patient 对 basic_experiment 返回 null详情 application 含 projectTitle/customerName/customerContact/customerPhone。
pilot_infusion_schedule_mgmt 完成逻辑 — 跳过渠道扣费与 clinical_application 相同:biz_type == basic_experiment 时不扣服务订单。
Files:
web-admin/src/router/modules/sqxp.tsbackend/app/api/menu.pyCreate: 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
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
Files:
web-admin/src/views/pilot-service/pilot-application/index.vueweb-admin/src/views/pilot-service/pilot-application/PilotApplicationTab.vueweb-admin/src/views/pilot-service/pilot-application/ApplicationFormDialog.vueModify: 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-schedules API 批量创建计划
[ ] Step 3: PilotApplicationTab 列表列
基础实验列表展示:申请单号、课题名称、客户名称、状态(隐藏渠道列)。
[ ] Step 4: ApplicationViewDialog 只读态对齐
[ ] Step 5: 手动验证
/#/basic-experiment/applicationFiles:
web-admin/src/views/pilot-service/pilot-application/InfusionScheduleTab.vueweb-admin/src/views/pilot-service/pilot-application/ClinicalPaymentTab.vueModify: 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
Files:
web-admin/src/utils/outbound-qc-pilot-view-sections.tsweb-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vueweb-admin/src/views/production/production-work-order/index.vueModify: web-admin/src/views/pilot-service/outbound-release-review/index.vue
[ ] Step 1: 扩展 OutboundViewContextVariant
basic_experiment: {
application: '基础实验申请信息',
schedule: '售卖计划信息'
}
新增 section builder:申请信息含课题/客户;无患者;计划含提货时间/金额;产品明细表。
variant='basic_experiment'当 detail.application.bizType === 'basic_experiment'。
[ ] Step 3: 放行审核列表 bizType 过滤 + 查看弹窗
[ ] Step 4: Commit
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_delivery API)
[ ] Step 4: Commit
Files:
backend/scripts/check_field_consistency.pyRun: ./scripts/update-graph.sh frontend + backend
[ ] Step 1: 运行字段一致性脚本并修复遗漏
Run: cd backend && python scripts/check_field_consistency.py
Run: ./scripts/update-graph.sh all
| # | 验收项 | 通过 |
|---|---|---|
| 1 | 侧栏「基础实验」三菜单可见 | |
| 2 | JCSY 申请单 + 课题/客户/标准附件 | |
| 3 | 2 行产品 → 2 条售卖计划 | |
| 4 | 每计划自动工单 + 待缴费 | |
| 5 | QC/生产查看弹窗无患者、有基础实验信息 | |
| 6 | 放行/交接/交付可用 | |
| 7 | 缴退费流程可用且数据隔离 | |
| 8 | 中试/临床列表无 JCSY |
git commit -m "feat: complete basic experiment business line"
| 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。
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?