Files
zkxw/docs/superpowers/plans/2026-06-10-basic-experiment.md
T
2026-06-11 15:42:58 +08:00

21 KiB
Raw Blame History

基础实验业务 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 — ALTER biz_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_experiment
  • backend/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_TYPES
  • backend/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_experiment variant
  • web-admin/src/views/pilot-service/pilot-application/index.vue — Tab/卡片按 config 渲染
  • web-admin/src/views/pilot-service/pilot-application/PilotApplicationTab.vue
  • web-admin/src/views/pilot-service/pilot-application/ApplicationFormDialog.vue — 基础实验表单 + 售卖产品子表
  • web-admin/src/views/pilot-service/pilot-application/ApplicationViewDialog.vue
  • web-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 prop
  • web-admin/src/views/pilot-service/outbound-release-review/index.vue
  • web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue
  • web-admin/src/views/production/production-work-order/index.vue
  • web-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-schedules API 批量创建计划

  • Step 3: PilotApplicationTab 列表列

基础实验列表展示:申请单号、课题名称、客户名称、状态(隐藏渠道列)。

  • Step 4: ApplicationViewDialog 只读态对齐

  • Step 5: 手动验证

  1. 打开 /#/basic-experiment/application
  2. 新增申请单,添加 2 行产品不同提货时间
  3. 保存后在售卖计划 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_delivery API)

  • 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?