2026-05-26-outbound-production-work-order-design.md 11 KB

出库生产工单设计文档

日期: 2026-05-26 状态: 待实施 方案: A(在现有生产工单页面扩展 tab)

1. 背景与目标

现状

  • 生产工单界面有 3 个 tab:入库生产工单、临时生产工单、全部生产工单
  • 后端 CRUD 层已存在 create_outbound_production_work_order() 函数,通用列表 API 已支持 workOrderType=outbound 过滤
  • 出库工单目前只能通过输注计划管理页面的"生成出库工单"按钮手动触发创建
  • 中试服务申请单、体检管理、输注计划管理三个界面已调通

目标

  • 在生产工单页面新增「出库生产工单」tab
  • 输注计划创建时自动创建出库生产工单
  • 输注计划删除时关联出库工单状态变为已作废
  • 出库工单详情展示关联的中试申请单信息和输注计划信息
  • 全部生产工单 tab 隐藏操作列

2. 需求确认

# 需求项 确认结果
1 出库工单生成时机 输注计划创建时立即自动创建
2 输注计划删除时出库工单行为 保留工单,状态变为已作废(yzf)
3 业务类型字段 新增 biz_type 字段区分中试服务(pilot_service)/临床应用(clinical_application)
4 详情弹窗 复用现有 SystemViewDialog,增加申请单信息和输注计划信息区块
5 操作按钮 查看详情、编辑(仅备注)、删除、编辑任务、派发任务、完成工单
6 列表列 工单号、业务类型、申请单号、细胞类型、规格、输注时间、备注、工单状态、创建时间
7 全部 tab 操作列 隐藏

3. 数据库变更

3.1 biz_production_work_order 表新增字段

ALTER TABLE biz_production_work_order
  ADD COLUMN biz_type VARCHAR(20) NULL COMMENT '业务类型: pilot_service=中试服务, clinical_application=临床应用' AFTER infusion_schedule_id;

3.2 工单状态枚举扩展

现有:dpz(待配置), jxz(进行中), ywc(已完成) 新增:yzf(已作废)

无需 ALTER TABLE,VARCHAR(16) 已足够容纳。仅需更新模型 comment:

ALTER TABLE biz_production_work_order
  MODIFY COLUMN work_order_status VARCHAR(16) NOT NULL DEFAULT 'dpz' COMMENT '工单状态 dpz待配置 jxz进行中 ywc已完成 yzf已作废';

3.3 模型 comment 更新

ALTER TABLE biz_production_work_order
  MODIFY COLUMN work_order_type VARCHAR(20) NOT NULL DEFAULT 'inbound' COMMENT '工单类型: inbound=入库, temporary=临时, outbound=出库';

4. 后端变更

4.1 模型层 — backend/app/models/models.py

BizProductionWorkOrder 新增:

biz_type = Column(String(20), nullable=True, comment="业务类型: pilot_service=中试服务, clinical_application=临床应用")

更新 work_order_type 和 work_order_status 字段 comment。

4.2 CRUD 层 — backend/app/crud/production_work_order.py

4.2.1 状态映射扩展

_WORK_ORDER_STATUS_LABELS = {
    "dpz": "待配置",
    "jxz": "进行中",
    "ywc": "已完成",
    "yzf": "已作废",   # 新增
}

4.2.2 _wo_to_dict() 增加字段

返回值新增:

"bizType": getattr(wo, "biz_type", None) or None,
"bizTypeLabel": {"pilot_service": "中试服务", "clinical_application": "临床应用"}.get(getattr(wo, "biz_type", None), ""),

4.2.3 create_outbound_production_work_order() 增加 biz_type 参数

函数签名新增 biz_type: str = "pilot_service" 参数,创建时写入 biz_type 字段。

4.2.4 列表增强 — outbound 类型工单附加业务数据

在 _enrich_wo_items() 或列表返回后,对 workOrderType == "outbound" 的记录:

  • 通过 infusion_schedule_id 关联 BizPilotInfusionSchedule
  • 通过 infusion_plan_id 关联 BizPilotInfusionPlan
  • 通过 pilot_application_id 关联 BizPilotApplication
  • 附加字段:applicationNo, applicationName, cellType, specText, infusionTime, infusionLocation

4.2.5 详情增强 — outbound 类型工单详情

在 _wo_to_response_dict() 中对 outbound 类型,增加返回:

"pilotApplication": {
    "applicationNo": "...",
    "applicationName": "...",
    "applicationStatus": "...",
},
"infusionSchedule": {
    "cellType": "...",
    "specText": "...",
    "infusionTime": "...",
    "infusionLocation": "...",
    "infusionMethod": "...",
    "scheduleStatus": "...",
}

4.3 自动创建钩子 — backend/app/crud/pilot_application.py

在 create_schedule() 函数末尾(db.commit() 之前),增加:

# 自动创建出库生产工单
from app.crud import production_work_order as pwo_svc
pwo_svc.create_outbound_production_work_order(
    db,
    schedule_id=schedule_id,
    tenant_no=tenant_no,
    user_id=user_id,
    create_dept_id=dept_id,
    biz_type="pilot_service",
)

注意:create_outbound_production_work_order 内部已有幂等检查(通过 infusion_schedule_id 查重),重复调用不会产生重复工单。

4.4 自动作废钩子 — backend/app/crud/pilot_application.py

在 soft_delete_schedule() 函数中,row.del_flag = 1 之前,增加:

# 关联的出库生产工单标记为已作废
outbound_wo = (
    db.query(BizProductionWorkOrder)
    .filter(
        BizProductionWorkOrder.infusion_schedule_id == schedule_id,
        BizProductionWorkOrder.tenant_no == tenant_no,
        BizProductionWorkOrder.del_flag == 0,
        BizProductionWorkOrder.work_order_type == "outbound",
    )
    .first()
)
if outbound_wo:
    outbound_wo.work_order_status = "yzf"
    outbound_wo.update_time = datetime.now()

需在文件头部导入 BizProductionWorkOrder。

4.5 Schema 层 — backend/app/schemas/production_work_order.py

ProductionWorkOrderListQuery 如需支持按 biz_type 筛选,新增可选字段。当前需求未要求筛选,暂不增加。

5. 前端变更

5.1 Tab 结构 — web-admin/src/views/production/production-work-order/index.vue

类型定义扩展

type WorkOrderTypeTab = 'inbound' | 'temporary' | 'outbound' | 'all'

Tab 面板

<el-tabs v-model="activeTypeTab" class="work-order-type-tabs" @tab-change="handleTypeTabChange">
  <el-tab-pane label="入库生产工单" name="inbound" />
  <el-tab-pane label="临时生产工单" name="temporary" />
  <el-tab-pane label="出库生产工单" name="outbound" />
  <el-tab-pane label="全部生产工单" name="all" />
</el-tabs>

5.2 列表列配置

工具栏

  • outbound tab 不显示新增按钮
  • 条件:v-if="activeTypeTab !== 'all' && activeTypeTab !== 'outbound'"

关键词搜索框 placeholder

const searchPlaceholder = computed(() => {
  if (activeTypeTab.value === 'temporary') return '工单号'
  if (activeTypeTab.value === 'outbound') return '工单号/申请单号'
  return '工单号/协议号/样本编码'
})

表格列

新增出库工单专属列(仅在 outbound tab 显示):

  • 业务类型(tag):v-if="activeTypeTab === 'outbound'"
  • 申请单号:v-if="activeTypeTab === 'outbound'"
  • 细胞类型:v-if="activeTypeTab === 'outbound'"
  • 规格:v-if="activeTypeTab === 'outbound'"
  • 输注时间:v-if="activeTypeTab === 'outbound'"

全部 tab 的工单类型 Tag

更新 tag 映射:

// 工单类型 tag(全部 tab 显示)
const woTypeTag = (type: string) => {
  const map: Record<string, { label: string; type: string }> = {
    inbound: { label: '入库', type: 'success' },
    temporary: { label: '临时', type: 'warning' },
    outbound: { label: '出库', type: 'primary' },
  }
  return map[type] || { label: type, type: 'info' }
}

全部 tab 隐藏操作列

操作列 <el-table-column> 添加条件:

<el-table-column
  v-if="activeTypeTab !== 'all'"
  label="操作"
  ...
>

工单状态 Tag

const woStatusTag = (status: string) => {
  const map: Record<string, { label: string; type: string }> = {
    dpz: { label: '待配置', type: 'info' },
    jxz: { label: '进行中', type: 'warning' },
    ywc: { label: '已完成', type: 'success' },
    yzf: { label: '已作废', type: 'danger' },   // 新增
  }
  return map[status] || { label: status, type: 'info' }
}

状态筛选下拉

增加已作废选项:

<el-option label="已作废" value="yzf" />

5.3 详情弹窗 — viewSections computed

对 outbound 类型(isOutbound = d.workOrderType === 'outbound'):

基本信息 section:

  • 工单号、工单类型(出库 tag=primary)、业务类型(中试服务 tag=primary)、工单状态、备注、创建时间
  • 不显示:协议号、协议状态、样本编码、来源类型、样本类型

中试申请信息 section(新增,仅 outbound):

{
  title: '中试申请信息',
  fields: [
    { label: '申请单号', value: d.pilotApplication?.applicationNo || '—' },
    { label: '申请名称', value: d.pilotApplication?.applicationName || '—' },
    { label: '申请状态', value: applicationStatusLabel(d.pilotApplication?.applicationStatus), tagType: ... },
  ]
}

输注计划信息 section(新增,仅 outbound):

{
  title: '输注计划信息',
  fields: [
    { label: '细胞类型', value: d.infusionSchedule?.cellType || '—' },
    { label: '规格', value: d.infusionSchedule?.specText || '—' },
    { label: '输注时间', value: d.infusionSchedule?.infusionTime || '—' },
    { label: '输注地点', value: d.infusionSchedule?.infusionLocation || '—' },
    { label: '输注方式', value: d.infusionSchedule?.infusionMethod || '—' },
  ]
}

不显示:运输信息 section、样本接收 section(这些是入库工单特有的)

5.4 操作按钮逻辑

outbound tab 的操作按钮与 inbound/temporary 基本一致:

  • 查看详情:始终显示
  • 编辑:始终显示(仅改备注)
  • 编辑任务:始终显示
  • 派发任务:状态为 dpz/jxz 时显示
  • 完成工单:状态为 dpz/jxz 时显示
  • 删除:非 ywc 状态时显示(yzf 状态也可删除)

5.5 showReceptionCols 逻辑

const showReceptionCols = computed(() => activeTypeTab.value !== 'temporary' && activeTypeTab.value !== 'outbound')

6. 影响文件清单

文件 改动类型 说明
docs/sql/2026-05-26-outbound-work-order.sql 新增 ALTER TABLE SQL 脚本
backend/app/models/models.py 修改 BizProductionWorkOrder 新增 biz_type 字段,更新 comment
backend/app/crud/production_work_order.py 修改 状态映射、biz_type 字段、出库工单创建参数、列表增强、详情增强
backend/app/crud/pilot_application.py 修改 create_schedule 自动创建钩子、soft_delete_schedule 作废钩子
web-admin/src/views/production/production-work-order/index.vue 修改 新增 tab、列配置、操作按钮逻辑、详情弹窗、状态映射

7. 不涉及的改动

  • 不新增路由或菜单项(复用现有生产工单页面)
  • 不新增 API 端点(复用现有通用列表/详情 API)
  • 不修改输注计划管理页面的"生成出库工单"按钮(保持兼容,该按钮仍可手动触发,幂等逻辑保证不重复创建)
  • 不修改生产任务相关的弹窗组件(派发、执行、完成等逻辑通用,无需改动)