2026-05-27-outbound-release-material-design.md 11 KB

出库放行审核 · 出库物料准备(台账)设计

日期: 2026-05-27
状态: 待审阅
父模块: 中试服务管理 → 出库放行审核
关联: openspec/changes/outbound-release-review、方案 A(独立弹窗 + 子表)
库存策略(已确认): 台账模式 — 出库物料准备界面仅记录,不联动库存

1. 背景与目标

现状

  • 出库放行审核列表/查看/审核已实现,审核记录存于 biz_outbound_release_review。
  • 每条输注计划尚无「出库物料准备」维护入口。
  • 细胞产品库、质检物料库已有独立库存/状态管理(生产任务、质检任务等场景会真实扣减)。

目标

针对每条输注计划,提供「出库物料准备」界面,用于登记拟出库内容(台账),保存时不改变细胞产品状态、不扣质检物料库存:

区块 数据来源 保存行为
出库细胞 细胞产品库勾选 写入关联 ID + 字段快照,不改 biz_cell_product
使用的物料 质检物料库勾选 + 用量 写入关联 ID + 用量 + 快照,不扣 stock_qty
其他纸质材料 附件上传 仅存 URL

非目标

  • 不在本界面做真实出库(不改细胞状态、不释库位、不写 biz_qc_material_usage)。
  • 不与「交付物料清单」(biz_pilot_delivery_material)合并或自动同步。
  • 批量审核弹窗不涉及出库物料(仍按条维护)。
  • 真实出库若后续需要,另开能力/节点实现(本 spec 不覆盖)。

2. 架构决策

项 决策
UI 定位 「出库物料准备」— 台账登记,非库存事务
UI 列表操作列「出库物料」→ OutboundReleaseMaterialDialog.vue
数据 1 主表 + 2 子表;纸质附件放主表
保存语义 全量替换:软删旧子表行 → 写入新行 + 主数据快照
库存 零联动;前端可展示当前库存/状态作参考,保存时不校验库存是否足够
复杂度 无 revert/apply、无 qc_material_usage 扩展、无库位占用变更

3. 数据库设计

SQL 文件:docs/sql/outbound_release_material.sql

3.1 biz_outbound_release_material(主表,1:1 输注计划)

CREATE TABLE `biz_outbound_release_material` (
  `id` varchar(64) NOT NULL COMMENT '主键ID',
  `infusion_schedule_id` varchar(64) NOT NULL COMMENT '输注计划ID',
  `paper_attachments` varchar(4000) DEFAULT NULL COMMENT '其他纸质材料附件(逗号分隔URL)',
  `tenant_no` varchar(64) NOT NULL DEFAULT 'zkxw',
  `order_num` int(11) DEFAULT NULL,
  `remark` varchar(512) DEFAULT NULL,
  `create_by` varchar(64) DEFAULT NULL,
  `create_dept_id` varchar(64) NOT NULL DEFAULT '',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_by` varchar(64) DEFAULT NULL,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `del_flag` tinyint(4) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_orm_schedule` (`infusion_schedule_id`, `del_flag`),
  KEY `idx_orm_tenant` (`tenant_no`, `del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出库放行-出库物料准备主表';

3.2 biz_outbound_release_cell(出库细胞行)

CREATE TABLE `biz_outbound_release_cell` (
  `id` varchar(64) NOT NULL,
  `material_id` varchar(64) NOT NULL COMMENT '关联 biz_outbound_release_material.id',
  `infusion_schedule_id` varchar(64) NOT NULL COMMENT '冗余,便于查询',
  `cell_product_id` varchar(64) NOT NULL COMMENT '细胞产品ID',
  `product_code` varchar(128) DEFAULT NULL COMMENT '保存时快照',
  `product_name` varchar(200) DEFAULT NULL,
  `product_type_name` varchar(100) DEFAULT NULL,
  `spec_name` varchar(100) DEFAULT NULL,
  `passage` varchar(32) DEFAULT NULL,
  `batch_no` varchar(64) DEFAULT NULL,
  `storage_slot_name` varchar(200) DEFAULT NULL,
  `product_status` varchar(32) DEFAULT NULL COMMENT '保存时状态快照(只读展示)',
  `tenant_no` varchar(64) NOT NULL DEFAULT 'zkxw',
  `order_num` int(11) DEFAULT NULL,
  `remark` varchar(512) DEFAULT NULL,
  `create_by` varchar(64) DEFAULT NULL,
  `create_dept_id` varchar(64) NOT NULL DEFAULT '',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_by` varchar(64) DEFAULT NULL,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `del_flag` tinyint(4) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_orc_schedule_product` (`infusion_schedule_id`, `cell_product_id`, `del_flag`),
  KEY `idx_orc_product` (`cell_product_id`, `del_flag`),
  KEY `idx_orc_material` (`material_id`, `del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出库放行-出库细胞(台账)';

不需要 product_status_before / storage_slot_id 回滚字段。

3.3 biz_outbound_release_qc_material(质检物料行)

CREATE TABLE `biz_outbound_release_qc_material` (
  `id` varchar(64) NOT NULL,
  `material_id` varchar(64) NOT NULL COMMENT '关联 biz_outbound_release_material.id',
  `infusion_schedule_id` varchar(64) NOT NULL,
  `qc_material_id` varchar(64) NOT NULL,
  `qty` decimal(18,4) NOT NULL COMMENT '计划用量(台账)',
  `material_code` varchar(64) DEFAULT NULL,
  `material_name` varchar(200) DEFAULT NULL,
  `spec_model` varchar(200) DEFAULT NULL,
  `unit` varchar(32) DEFAULT NULL,
  `tenant_no` varchar(64) NOT NULL DEFAULT 'zkxw',
  `order_num` int(11) DEFAULT NULL,
  `remark` varchar(512) DEFAULT NULL,
  `create_by` varchar(64) DEFAULT NULL,
  `create_dept_id` varchar(64) NOT NULL DEFAULT '',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_by` varchar(64) DEFAULT NULL,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `del_flag` tinyint(4) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_orqm_schedule_material` (`infusion_schedule_id`, `qc_material_id`, `del_flag`),
  KEY `idx_orqm_material` (`material_id`, `del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出库放行-质检物料(台账)';

不关联 biz_qc_material_usage;不扩展 source_type。

4. 核心业务流程

4.1 保存出库物料准备(单事务)

save_outbound_release_material(schedule_id, payload):
  1. 校验输注计划存在 + 租户 + 数据权限
  2. 校验 payload:
       - cells 内 cell_product_id 不重复
       - qcMaterials 内 material_id 不重复
       - qty > 0
       - 关联主数据存在且未软删(产品/物料)
  3. upsert 主表(paper_attachments、remark)
  4. 软删本 schedule 下旧 cell / qc_material 子行
  5. 对每个 cell_product_id:读主数据 → 写快照 → insert 子行
  6. 对每个 qc_material + qty:读主数据 → 写快照 → insert 子行
  7. db.commit()

明确不做:

  • 不调用 cell_product.update / 不改 product_status
  • 不调用 occupancy_svc.remove_by_item
  • 不调用 qc_material_usage.deduct_*
  • 不校验质检库存是否 ≥ 用量(用量仅作计划登记)

4.2 编辑

  • 再次保存 = 全量替换子表(软删旧行 + 插入新行)。
  • 无库存回滚逻辑。

4.3 业务校验(台账级)

规则 说明
同计划细胞不重复 同一 infusion_schedule_id 下 cell_product_id 唯一
同计划物料不重复 同一 schedule 下 qc_material_id 唯一
跨计划可重复引用 不同输注计划可登记同一细胞/物料(台账互不排斥)
主数据存在性 保存时产品/物料须存在;已软删的主数据不允许新增引用
细胞状态 不限制可选状态;选择器展示状态供人判断,保存时不拦截

4.4 细胞产品选择范围

  • 从细胞产品库分页/搜索勾选(复用 TaskInputEnhancedTable 选择弹窗模式)。
  • 列表展示编码、名称、类型、代次、储物格、当前状态(参考信息)。
  • 可选增强(v1.1):默认筛选与本计划出库生产工单产出相关的产品,仍允许从全库补选。

5. API 设计

前缀:/web/admin/pilot-release-reviews

方法 路径 说明
GET /{scheduleId}/outbound-materials 获取出库物料准备(主表 + 细胞行 + 质检行)
PUT /{scheduleId}/outbound-materials 全量保存(仅写台账)

PUT Body:

{
  "paperAttachments": "url1,url2",
  "remark": "",
  "cells": [{ "cellProductId": "..." }],
  "qcMaterials": [{ "materialId": "...", "qty": 1.5, "remark": "" }]
}
  • cells / qcMaterials 可为空数组。
  • GET .../detail 响应增加 outboundMaterial 域(查看弹窗只读)。

6. 前端设计

6.1 模式

  • P22 审核列表 + 子表编辑弹窗。
  • 弹窗标题建议:「出库物料准备」(与「真实出库」区分)。
  • 参考:
    • 细胞选择:TaskInputEnhancedTable.vue
    • 质检物料:qc-task/index.vue 物料使用区(去掉库存不足拦截)
    • 纸质附件:OutboundReleaseReviewDialog.vue 卡片上传

6.2 页面改动

文件 改动
outbound-release-review/index.vue 操作列「出库物料」
OutboundReleaseMaterialDialog.vue 新建
OutboundReleaseViewDialog.vue Section「出库物料准备」只读
api/outbound-release-review.ts fetch/save API

6.3 交互规则

  • 保存成功:「出库物料准备已保存」(不提库存更新)。
  • 质检物料表可展示「当前库存」列作参考,不因库存不足阻止保存。
  • 删除行:前端移除,点保存后生效。
  • 操作列宽度:约 260px(查看 / 审核 / 出库物料)。

7. 后端模块划分

文件 职责
app/crud/outbound_release_material.py 查询、全量 save、主数据快照
app/schemas/outbound_release_material.py Pydantic
app/api/outbound_release_review.py 两个端点
app/models/models.py 三个 ORM 模型

不修改: qc_material_usage.py、cell_product.py 库存/状态逻辑。

8. 错误处理

场景 HTTP 说明
细胞/物料不存在 400 ID 无效或已删除
同计划重复勾选 400 同一产品/物料不可重复
qty ≤ 0 400 用量须大于 0
输注计划不存在 404 —

9. 测试要点

  1. 保存后:biz_cell_product 状态/库位不变;biz_qc_material.stock_qty 不变。
  2. 编辑替换细胞/物料行:仅子表与快照更新。
  3. 两计划登记同一细胞:均允许保存(台账)。
  4. 查看弹窗正确展示快照与附件。
  5. 主数据后续变更:已保存台账仍显示保存时快照。

10. OpenSpec 增量

  • outbound-release-material-api: GET/PUT outbound-materials(台账 CRUD)
  • outbound-release-material-ui: 出库物料准备弹窗 + 查看 Section

11. 工作量估算

项 人天
SQL + Model + CRUD 1.0
API + 详情聚合 0.5
前端弹窗 + 查看 1.5
联调 0.5
合计 ~3.5

附录:策略变更记录

版本 策略 说明
v0(讨论稿) B 真实出库 保存时改状态 + 扣库存 + 回滚
v1(当前) 台账模式 出库物料准备界面仅记录;与交付清单类似,不联动库存

若后续需要在「审核通过」或独立「确认出库」节点做真实出库,应新建 spec,与本台账表通过 infusion_schedule_id 衔接,避免在本界面混入库存事务。