11 KiB
11 KiB
出库放行审核 · 出库物料准备(台账)设计
日期: 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. 测试要点
- 保存后:
biz_cell_product状态/库位不变;biz_qc_material.stock_qty不变。 - 编辑替换细胞/物料行:仅子表与快照更新。
- 两计划登记同一细胞:均允许保存(台账)。
- 查看弹窗正确展示快照与附件。
- 主数据后续变更:已保存台账仍显示保存时快照。
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 衔接,避免在本界面混入库存事务。