6.7 KiB
Context
生产部门当前仅有耗材目录表(biz_consumable)和试剂目录表(biz_reagent),管理编码、名称、规格、标准用量等基础信息,无库存管理。市场部门和质检部门已有完整的库存管理体系,采用「主表含库存字段 + 变动记录表」的双表模式。
现有耗材/试剂被 10 张下游关联表(耗材包、工艺、工艺路线、生产任务步骤)引用,但全部使用快照复制(denormalized code/name),无 FK 依赖,对主表加字段零影响。
现有参照实现
- 市场物料:
biz_material(含 stock_qty)+biz_material_stock_log(变动记录,rk/ck,正数) - 质检物料:
biz_qc_material(含 stock_qty)+biz_qc_material_usage(变动记录,in/use,正负数,含 task_complete 自动扣减 + revert 回退)
步骤保存挂钩点
生产任务步骤的耗材/试剂保存入口:
PUT /production-tasks/{task_id}/steps/{step_id}/reagents→replace_step_reagents()PUT /production-tasks/{task_id}/steps/{step_id}/consumables→replace_step_consumables()
这两个函数使用"软删旧 + 插入新"模式,需在此处加入 diff 结算逻辑。
Goals / Non-Goals
Goals:
- 为耗材和试剂增加库存管理能力(库存量、预警量、出入库操作)
- 支持手动单个/批量入库出库,参照质检物料模式
- 支持生产任务步骤保存时按 actual_usage 自动 diff 结算库存
- 变动记录在耗材/试剂页面内以子弹窗和 Tab 形式展示
Non-Goals:
- 不新建独立菜单页面(变动记录嵌入现有页面)
- 不修改现有耗材/试剂目录字段(只加库存字段)
- 不修改下游 10 张关联表
- 不实现仓库/库位管理(单库存维度)
- 不实现库存预警通知(仅记录 warning_qty 字段)
- 不统一耗材/试剂为统一物料表(保持两个独立目录)
Decisions
Decision 1: 在现有目录表上加库存字段,不新建库存主表
选择:ALTER TABLE biz_consumable / biz_reagent 新增 stock_qty、warning_qty、status 字段。
理由:
- 下游 10 张关联表全是快照复制,加字段零影响
- 与市场/质检物料的「主表含库存字段」模式一致
- 避免引入目录表→库存表的间接层和数据同步问题
- 改动最小:复用现有 CRUD/API/前端,只需扩展
替代方案:新建 biz_consumable_stock 表,通过 consumable_id 关联。放弃原因:多一层间接、需维护目录-库存同步、与已有模式不一致。
Decision 2: 新建独立的变动记录表
选择:新建 biz_consumable_stock_log 和 biz_reagent_stock_log,参照 biz_material_stock_log 结构。
理由:
- 变动记录是独立的关注点,不应混在目录表中
- 需要快照字段(code/name)和 before/after 库存
- 需要溯源字段(source_type、step_id、task_id、task_no)
- 参照
biz_qc_material_usage的 source_type 模式:manual_in / manual_out / step_save
关键设计:参照质检物料采用正负数表示方向(入库正、出库负),同时保留 change_type 字段(in/use)。
Decision 3: 步骤保存 diff 结算算法
选择:全量回退 + 重算模式(参照质检物料的 revert + deduct 模式)。
算法:
- 查出该步骤当前所有 source_type=step_save 的变动记录
- 按变动记录逐条回退库存(加回扣减量),软删变动记录
- 遍历新的步骤耗材/试剂列表,按 actual_usage 扣减库存,生成新变动记录
- 整体在同一事务中
理由:
- 步骤保存频率不高,全量回退+重算简单可靠
- 避免复杂的逐项 diff 计算(需处理编码变更等边界情况)
- 与质检物料 deduct_for_task() 模式一致,代码可复用
替代方案:逐项 diff(旧用量 - 新用量 = 差额)。放弃原因:需处理编码变更(物料A改成物料B)、处理删除、新增等复杂场景,容易出 bug。全量回退更简单且已验证。
Decision 4: 变动记录子弹窗 + Tab 双入口
选择:
- 每行操作列「变动记录」按钮 → Dialog 弹窗,按 material_id 筛选
- 页面顶部 Tab「全部变动记录」→ Tab 切换视图,展示所有物料的汇总记录
理由:
- 不新增菜单位,保持生产管理菜单简洁
- 行按钮满足"看某个物料历史"的快速需求
- Tab 视图满足"看所有变动流水"的审计需求
Decision 5: 挂钩点在 replace_step_reagents/consumables
选择:在 replace_step_reagents() 和 replace_step_consumables() 函数中,在 soft delete 旧记录之后、insert 新记录之前,执行库存 diff 结算。
调用链:
API: PUT /production-tasks/{task_id}/steps/{step_id}/consumables
→ replace_step_consumables(db, step_id, tenant_no, user_id, items)
→ 1. 查出步骤信息(step_id → task_id, task_no)
→ 2. 调用 diff_settle_step_consumables(db, step_id, task_id, task_no, tenant_no, user_id)
→ revert step 的所有 step_save 变动记录(回退库存)
→ 3. soft delete 旧步骤耗材子记录
→ 4. insert 新步骤耗材子记录
→ 5. 调用 deduct_step_consumables(db, step_id, task_id, task_no, tenant_no, user_id, new_items)
→ 按 actual_usage 扣减库存,生成 step_save 变动记录
→ 6. commit
Risks / Trade-offs
Risk: 步骤保存时库存不足
影响:任务执行保存步骤时,若耗材/试剂库存不足,保存会失败。 缓解:提供清晰的错误提示("物料「XX」库存不足,当前库存 N,需使用 M"),让用户先去入库。同时前端可在步骤编辑时显示当前库存量供参考。
Risk: actual_usage 为空或非数字
影响:步骤保存时 actual_usage 字段可能为空字符串或非数字。 缓解:diff 结算时跳过 actual_usage 为空或无法转为正数的条目,仅处理有效数据。
Risk: 并发保存同一物料库存
影响:多个步骤同时保存涉及同一耗材/试剂。
缓解:使用 with_for_update() 行锁,与质检物料一致。
Trade-off: 全量回退 vs 逐项 diff
选择全量回退,牺牲少量性能换取代码简洁性和正确性保证。步骤保存频率低,性能影响可忽略。
Migration Plan
- 数据库迁移:ALTER TABLE 加字段(有默认值) + CREATE TABLE 建变动记录表
- 后端部署:模型 → CRUD → API 顺序部署
- 前端部署:扩展现有页面
- 无需数据迁移:现有耗材/试剂的 stock_qty 默认为 0,warning_qty 默认为 0,status 默认为 '1'
- 回滚方案:ALTER TABLE 删除新增字段,DROP TABLE 变动记录表,回滚代码