Files
zkxw/openspec/changes/production-material-inventory/design.md
T
2026-05-23 12:51:22 +08:00

6.7 KiB
Raw Blame History

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 模式)。

算法:

  1. 查出该步骤当前所有 source_type=step_save 的变动记录
  2. 按变动记录逐条回退库存(加回扣减量),软删变动记录
  3. 遍历新的步骤耗材/试剂列表,按 actual_usage 扣减库存,生成新变动记录
  4. 整体在同一事务中

理由:

  • 步骤保存频率不高,全量回退+重算简单可靠
  • 避免复杂的逐项 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

  1. 数据库迁移:ALTER TABLE 加字段(有默认值) + CREATE TABLE 建变动记录表
  2. 后端部署:模型 → CRUD → API 顺序部署
  3. 前端部署:扩展现有页面
  4. 无需数据迁移:现有耗材/试剂的 stock_qty 默认为 0,warning_qty 默认为 0,status 默认为 '1'
  5. 回滚方案:ALTER TABLE 删除新增字段,DROP TABLE 变动记录表,回滚代码