2026-06-11-qc-material-batch-inbound-design.md 23 KB

质检物料库 · 入库批次管理设计

日期: 2026-06-11
状态: 已确认(2026-06-11 补充)
父模块: 质量检测 → 质检物料库
supersede: docs/superpowers/specs/2026-05-20-qc-material-library-design.md 中「不做批次管理」约束

1. 背景与目标

现状

质检物料库采用 物资维度 的简单库存模式:

  • biz_qc_material:一种物资一行,汇总 stock_qty
  • biz_qc_material_usage:变动流水,入库仅记录数量与时间
  • 「物料列表」Tab 展示物资主数据;入库弹窗只有数量 + 入库时间
  • 质检任务「填写检测结果」中物料使用按 物料名称 下拉选择(materialId)

客户诉求

  1. 每次入库需记录:入库日期、入库编号、货号、批号、厂家、单价、金额
  2. 同一物资可多次入库,每批次的编号/货号/批号/厂家/单价/金额不同
  3. 「物料列表」需展示上述批次信息(一行 = 一条入库批次)
  4. 出库 / 质检任务扣减须 手动选择具体入库批次(入库编号),不再只按物资名称扣减

已确认决策

问题 选项 含义
出库/扣减批次策略 B 每次手动选择扣哪一批(按入库编号)
物料列表展示粒度 A 一行 = 一条入库批次(同一物资可出现多行)
入库编号来源 B 用户手工录入,系统做租户内唯一性校验
入库批次字段必填 是 货号、批号、厂家、单价(元)、金额(元)均必填(前端 + 后端校验)
零库存批次展示 不隐藏 批次列表默认展示全部批次(含 remaining_qty=0),便于查看历史出库后的批次行

非目标

  • 先进先出(FIFO)自动扣减
  • 有效期 / 库位管理
  • 出库放行「出库物料准备」台账改为批次级(仍为物资级台账,可后续扩展)
  • 市场物料库 / 生产耗材 / 试剂模块同步改造

2. 数据模型

2.1 保留 biz_qc_material(物资目录)

职责:物资主数据,不再在列表 Tab 直接展示(无批次时不出现在批次列表)。

字段 说明
material_code / material_name / spec_model / unit 不变
stock_qty 汇总库存 = Σ 下属批次 remaining_qty(写入时同步维护)
warning_qty / status 不变,预警仍按物资汇总库存判断

2.2 新增 biz_qc_material_stock_batch(入库批次 / 库存明细)

每次入库产生一条批次记录,是库存扣减的 最小粒度。

CREATE TABLE `biz_qc_material_stock_batch` (
  `id` varchar(64) NOT NULL COMMENT '主键ID',
  `material_id` varchar(64) NOT NULL COMMENT '关联 biz_qc_material.id',
  `material_code` varchar(64) DEFAULT NULL COMMENT '物料编码快照',
  `material_name` varchar(200) DEFAULT NULL COMMENT '物料名称快照',
  `spec_model` varchar(200) DEFAULT NULL COMMENT '规格型号快照',
  `unit` varchar(32) DEFAULT NULL COMMENT '单位快照',
  `stock_in_no` varchar(64) NOT NULL COMMENT '入库编号(用户录入,租户内唯一)',
  `stock_in_date` date NOT NULL COMMENT '入库日期',
  `catalog_no` varchar(128) NOT NULL COMMENT '货号',
  `batch_no` varchar(128) NOT NULL COMMENT '批号',
  `manufacturer` varchar(200) NOT NULL COMMENT '厂家',
  `unit_price` decimal(18,2) NOT NULL COMMENT '单价(元)',
  `amount` decimal(18,2) NOT NULL COMMENT '金额(元)',
  `in_qty` decimal(18,4) NOT NULL COMMENT '入库数量',
  `remaining_qty` decimal(18,4) NOT NULL COMMENT '剩余数量',
  `tenant_no` varchar(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
  `order_num` int(11) DEFAULT NULL COMMENT '排序号',
  `remark` varchar(512) DEFAULT NULL COMMENT '备注',
  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人id',
  `create_dept_id` varchar(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_by` varchar(64) DEFAULT NULL COMMENT '更新人id',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_qc_batch_tenant_stock_in_no` (`tenant_no`, `stock_in_no`, `del_flag`),
  KEY `idx_qc_batch_material` (`material_id`, `del_flag`),
  KEY `idx_qc_batch_tenant_date` (`tenant_no`, `stock_in_date`, `del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='质检物料入库批次';

字段规则:

  • stock_in_no:必填,trim 后租户内唯一(del_flag=0 维度)
  • catalog_no / batch_no / manufacturer / unit_price / amount:全部必填;入库弹窗与批量入库每行均 required
  • amount:打开弹窗时默认 unit_price × in_qty 自动计算;用户可手工改金额;改单价或数量时若用户未手工改过金额则重新计算
  • remaining_qty:初始 = in_qty;出库/任务扣减时递减;撤销时回加;为 0 时批次行仍保留并在列表中展示
  • 快照字段(material_code 等)在入库时从主表复制,后续主表改名不影响历史

2.3 扩展 biz_qc_material_usage(变动流水)

新增批次关联与快照字段:

新增字段 说明
batch_id 关联 biz_qc_material_stock_batch.id(入库/出库/任务扣减均有值)
stock_in_no 入库编号快照
catalog_no 货号快照
batch_no 批号快照
manufacturer 厂家快照
unit_price 单价快照
amount 金额快照(入库写正数;出库不写或写按量折算,见 3.3)
stock_before 变动前库存(补齐,与耗材模块一致)

stock_after 语义调整为 物资汇总库存(biz_qc_material.stock_qty),与现网一致;批次剩余量查批次表。


3. 业务流程

3.1 物资目录管理(不变)

新增物资 → 填写编码/名称/规格/单位/预警量 → stock_qty=0,尚无批次行
编辑 / 停用 / 删除(仅 stock_qty=0 且无未删批次时可删)

3.2 入库(单条 / 批量)

入口(仅工具栏,行内无入库按钮): 物料列表 Tab 工具栏「入库」「批量入库」。

每条入库必填:

  • 关联物资(material_id)
  • 入库编号(stock_in_no,手工,唯一校验)
  • 入库日期(stock_in_date)
  • 入库数量(in_qty > 0)
  • 货号(catalog_no)、批号(batch_no)、厂家(manufacturer)、单价(unit_price)、金额(amount)— 全部必填

事务内:

  1. 校验 stock_in_no 租户唯一
  2. INSERT biz_qc_material_stock_batch(remaining_qty = in_qty)
  3. UPDATE biz_qc_material.stock_qty += in_qty
  4. INSERT biz_qc_material_usage(change_type=in, source_type=manual_in, batch_id=…)

批量入库:同一事务,任一行失败全部回滚。

3.3 出库(单条 / 批量)

入口(仅工具栏,行内无出库按钮): 物料列表 Tab 工具栏「批量出库」(单条出库 = 批量出库弹窗内只填一行)。

粒度: 按 入库批次 出库(Q1=B);明细行选择 batchId(入库编号),非物资维度。

  • 校验 remaining_qty >= 出库数量
  • UPDATE 批次 remaining_qty
  • UPDATE 物资 stock_qty
  • INSERT usage(change_type=use, source_type=manual_out, batch_id=…)

出库单头(整单共用): 领用人(必填)、用途、备注、出库时间。

3.4 质检任务 · 物料使用(重点改造)

现状: QcWorkOrderTaskSubList.vue 按 materialId 选物料,禁止同一物料重复行。

改造后:

列 说明
入库编号 下拉选择 batchId,选项文案:{stockInNo} / {materialName} / 批号:{batchNo} / 剩:{remainingQty}{unit}
物料名称 选中批次后自动带出(只读)
规格型号 自动带出
批号 / 货号 / 厂家 自动带出(只读,便于核对)
剩余数量 自动带出
用量 校验 0 < qty <= remainingQty
备注 选填

重复规则: 同一 batchId 不可重复添加(原为同一 materialId 不可重复)。

提交 API materialUsages 结构:

{
  "batchId": "uuid",
  "qty": 1.5,
  "remark": "可选"
}

后端 deduct_for_task:

  1. revert_task_material_usages 按 batch_id 回加 remaining_qty 与 stock_qty
  2. 对每行 SELECT batch FOR UPDATE,校验剩余量
  3. 扣减 batch.remaining_qty 与 material.stock_qty
  4. 写 usage,快照 stock_in_no / batch_no 等

查看态: 任务查看弹窗、工单任务子表展示「入库编号、物料名称、批号、用量、单位」。

3.5 变动记录 Tab(QcMaterialUsageTab.vue)

变动记录是 全量流水审计,与批次列表互补:批次列表看「每批还剩多少」,变动记录看「每次入/出库发生了什么」。

筛选区调整:

筛选项 说明
关键词 新增,placeholder「物料编码/名称/入库编号/批号/货号」;替代仅按物资下拉的单维筛选(保留「物料筛选」下拉可选)
入库编号 新增独立输入框(可选,与关键词二选一或叠加,实现时 keyword 已覆盖即可)
物料筛选 保留,按 material_id
变动类型 保留:入库 / 出库 / 任务使用
时间范围 保留

列表列调整(在物料编码/名称之后、变动类型之前插入批次列):

列 说明
入库编号 stockInNo;出库/任务类来自 usage 快照
入库日期 仅 change_type=in 时有值(stockInDate);出库/任务类显示「—」
货号 catalogNo
批号 batchNo
厂家 manufacturer
单价(元) 入库行展示快照单价,formatMoneyDisplay
金额(元) 入库行展示快照金额;出库/任务类显示「—」或按业务不写
变动类型 不变
变动数量 / 变动前库存 / 变动后库存 不变;变动后库存仍为 物资汇总 stock_after
关联任务号 / 来源类型 / 领用人 / 用途 / 领用时间 不变
备注 / 操作人 / 变动时间 / 操作 不变

查看弹窗(SystemViewDialog)调整:

在「变动信息」区块增加字段:入库编号、入库日期(入库类)、货号、批号、厂家、单价、金额(入库类);出库/任务类展示入库编号 + 批号 + 货号 + 厂家(来自快照,便于追溯扣的是哪一批)。

后端 list_usages / _row_to_dict:

返回新增字段:batchId、stockInNo、stockInDate(入库类从批次表 join 或快照)、catalogNo、batchNo、manufacturer、unitPrice、amount;stockBefore 优先读库字段,无则 stockAfter - changeQty 兼容旧数据。

API 筛选扩展:

GET /qc-material-usages?keyword=&materialId=&changeType=&startTime=&endTime=&stockInNo=

keyword 匹配 material_code / material_name / stock_in_no / batch_no / catalog_no。

3.6 盘存关系

  • 物料列表 Tab(批次列表) = 全部入库批次(含零库存),按入库日期或 create_time 倒序;零库存行 remaining_qty 显示 0,仍可点「查看」追溯该批历史
  • 变动记录 Tab = 全量入/出库/任务扣减流水,按批次字段可筛选、可查看每次出库对应的入库编号

4. API 设计

4.1 批次列表(替代原物料列表 Tab 数据源)

GET /api/admin/web/admin/qc-material-batches
  Query: page, pageSize, keyword(编码/名称/入库编号/批号/货号), materialId, status(物资状态)
  Response: 分页,**默认返回全部批次(含 remaining_qty=0)**;每行含物资字段 + 批次字段 + 汇总 stockQty + 库存预警状态

4.2 批次下拉(质检任务 / 手动出库)

GET /api/admin/web/admin/qc-material-batches/options
  Query: keyword, materialId(可选)
  Filter: status=1 物资且 remaining_qty > 0
  Response: [{ id, stockInNo, materialId, materialCode, materialName, specModel, unit, batchNo, catalogNo, manufacturer, remainingQty, label }]

保留 GET /qc-materials/options 供「新增入库时搜索物资」使用;扣减场景改用 batches/options。

4.3 入库

POST /api/admin/web/admin/qc-materials/{materialId}/stock-in
Body: {
  stockInNo, stockInDate, catalogNo, batchNo, manufacturer,
  unitPrice, amount, qty, remark?, stockInTime?  // stockInTime 仍映射 create_time,stockInDate 为业务日期
}
POST /api/admin/web/admin/qc-material-batches/batch-stock-in
Body: {
  stockInTime?, remark?,
  items: [{ materialId, stockInNo, stockInDate, catalogNo, batchNo, manufacturer, unitPrice, amount, qty, remark? }]
}

4.4 出库

POST /api/admin/web/admin/qc-material-batches/{batchId}/stock-out
Body: { qty, receiver, purpose?, remark?, stockOutTime? }

批量出库 items 元素改为 { batchId, qty, remark? }。

4.5 质检任务

POST /qc-tasks/{id}/complete-with-result
  materialUsages: [{ batchId, qty, remark? }]   // 替代 materialId

GET /qc-tasks/{id}/material-usages
  返回含 batchId, stockInNo, batchNo, catalogNo, manufacturer, ...

4.6 物资 CRUD

GET/POST/PUT/DELETE /qc-materials 保留,供目录维护;列表 Tab 改调 batches 接口。


5. 前端改造

5.1 质检物料库 qc-material/index.vue

5.1.1 工具栏与列表操作列

工具栏(从左到右,「已选 N 项」在最前):

按钮 类型 说明
新增物料 primary 维护物资目录(编码/名称/规格等)
入库 ghost 打开 单条入库 弹窗,填写一条完整入库信息
批量入库 ghost 打开 批量入库 弹窗,列表模式多行录入
批量出库 ghost 打开 批量出库 弹窗,列表模式选批次并填数量

移除行内「入库」「出库」按钮;操作列仅保留:查看、编辑(物资)、删除(物资),或收拢至「更多」下拉(与现网 dropdown 一致)。行内 不提供 入/出库入口。

列表列(批次明细): 见 §4.1;含入库编号、入库日期、货号、批号、厂家、单价、金额、入库数量、剩余数量等;零库存批次仍展示。

5.1.2 单条入库弹窗(工具栏「入库」)

  • 标题:物料入库
  • 宽度:约 720px,dialog-form-layout
  • 表单模式(非表格):一次提交一条批次
  • 字段(除备注外均必填):
    • 物资:远程搜索下拉(materialId,选物资目录)
    • 入库编号、入库日期
    • 货号、批号、厂家
    • 单价(AmountInput)、金额(AmountInput,默认单价×入库数量)
    • 入库数量
    • 备注
  • 可选:入库时间(stockInTime,映射 usage.create_time;默认可当前时间)
  • 底部:取消 / 确认入库

5.1.3 批量入库弹窗(工具栏「批量入库」)

  • 标题:批量入库
  • 宽度:约 1200px+(列多时可横向滚动 inner-table)
  • 列表录入模式:明细区为 el-table,「新增一行」 增加空行,每行 = 一条独立入库信息(不同入库编号/批号/厂家/单价/金额)
  • 表头区(整单,可选):
    • 入库时间(整单默认,行内未指定时用)
    • 整单备注(行备注为空时作为默认)
  • 明细表列(均可编辑,除自动带出列):
列 说明
序号 index
物资 下拉/远程搜索选 materialId,带出编码/名称/规格/单位(只读列)
入库编号 手工输入,行内唯一性提交时校验
入库日期 date-picker
货号 / 批号 / 厂家 输入
单价 / 金额 AmountInput,行内联动
入库数量 input-number
备注 可选
操作 删除行
  • 工具栏:新增一行(主按钮旁或明细区标题旁);不再使用「搜索添加一条物料」作为唯一入口(可保留搜索快速填充一行,但必须有「新增一行」)
  • 至少 1 行方可提交;同一事务批量写入

5.1.4 批量出库弹窗(工具栏「批量出库」;单条出库 = 只填一行)

  • 标题:批量出库
  • 宽度:约 1000px+
  • 单头(整单共用,form-grid-2):
字段 必填
出库时间 是
领用人 是(远程搜索用户)
用途 否
备注 否(整单默认;行备注优先)
  • 明细列表模式:「新增一行」 或搜索选择 入库批次(batchId,文案含入库编号/物料名/批号/剩余量);仅可选 remaining_qty > 0 的批次
  • 明细表列:
列 说明
序号 index
入库编号 选择批次后带出(或选择列即 batch)
物料编码 / 名称 / 规格 / 单位 自动带出
批号 / 货号 / 厂家 自动带出(只读,便于核对)
剩余数量 只读
出库数量 input-number,max = 剩余数量
备注 可选
操作 删除行
  • 同一 batchId 不可重复添加两行
  • 单条出库:用户只新增 1 行即可,交互与批量相同

5.1.5 移除的 UI

  • 删除单条出库弹窗(原 stockOutVisible 行内触发)— 统一走批量出库弹窗
  • 删除行内 handleStockIn(row) / handleStockOut(row) 及对应按钮
  • 单条入库弹窗不再从行内打开;若保留 stockInVisible 组件,仅由工具栏「入库」打开且 不预填 materialId(或支持预填,但不绑行操作)

5.1.6 交互参考

  • 布局与 class:material-batch-stock-dialog、batch-stock-move-lines 等沿用 index.vue / production/consumable 批量入出库弹窗
  • 按钮须带图标(OpenSpec ui-conventions)
  • 金额字段:AmountInput + formatMoneyDisplay

5.2 质检任务 QcWorkOrderTaskSubList.vue

  • MaterialUsageRow:batchId 替代 materialId 为主键字段
  • 下拉:fetchQcMaterialBatchOptions
  • 校验:重复 batchId、用量 ≤ remainingQty
  • 提交:materialUsages: { batchId, qty, remark? }[]
  • 查看 slot / 父级 index.vue / qc-task-view-dialog 同步展示入库编号

5.3 变动记录 Tab QcMaterialUsageTab.vue(专项)

对照 §3.5,本 Tab 与批次列表 同步改造,不可遗漏:

  1. api/qc-material-usage.ts:QcMaterialUsage 类型扩展 batchId、stockInNo、stockInDate、catalogNo、batchNo、manufacturer、unitPrice、amount;列表请求增加 keyword / stockInNo 参数
  2. 筛选区:增加「关键词」输入;hint 文案改为「按物料、入库编号、批号、变动类型和时间范围查询变动记录」
  3. 表格列:在物料名称后插入入库编号、入库日期、货号、批号、厂家、单价、金额列;金额/单价用 formatMoneyDisplay
  4. 查看弹窗 viewSections:补充上述批次字段
  5. 路由联动:自批次列表跳转变动记录时,除 materialId 外支持 stockInNo query 预填筛选

5.4 其他只读展示

文件 改动
qc-work-order/index.vue 物料使用子表增加入库编号、批号列
OutboundQcViewDialog.vue 同上
qc-task-view-dialog/index.vue 同上

不在本次范围: OutboundReleaseMaterialDialog.vue(台账模式,仍选物资)。


6. 数据策略:不迁移旧数据

决策(2026-06-11): 现有质检物料库数据 不做迁移,功能开发完成后由 DBA/实施人员在库中手动执行脚本。

6.1 交付物(代码完成后提供)

脚本 路径(计划) 用途
清除旧数据 docs/sql/migrations/2026-06-11-qc-material-batch-clear-data.sql 软删或物理清理 biz_qc_material / biz_qc_material_usage 旧行(及上线前若已存在的批次表数据)
新测试数据 docs/sql/migrations/2026-06-11-qc-material-batch-test-data.sql 插入物资目录、多条入库批次、若干变动记录,覆盖单物资多批次、零库存批次、入出库流水等验收场景

清除脚本范围:

  • biz_qc_material_usage(变动记录)
  • biz_qc_material_stock_batch(入库批次,新表)
  • biz_qc_material(物资目录)

默认采用 软删(del_flag=1)或 DELETE 全表(脚本内注释说明,按环境选择);不在应用代码中自动清库。

测试数据脚本内容(示意):

  • 2~3 种物资(不同规格/单位)
  • 同一物资至少 2 条入库批次(不同入库编号、批号、厂家、单价、金额)
  • 其中 1 条批次 remaining_qty=0(已出清,用于验证列表仍展示 + 变动记录可查)
  • 对应 biz_qc_material_usage 入库/出库/(可选)任务扣减样例行

执行顺序: 先跑 DDL(2026-06-11-qc-material-batch.sql)→ 部署代码 → 清除旧数据 → 导入测试数据。


7. 影响范围

后端

文件 改动
models/qc_material.py 新增 BizQcMaterialStockBatch;扩展 BizQcMaterialUsage
crud/qc_material_stock_batch.py 新建:list/options/stock_in/stock_out
crud/qc_material.py 入库/出库委托批次层;汇总 stock_qty 同步
crud/qc_material_usage.py deduct/revert 按 batch_id;_row_to_dict 扩展
api/qc.py 新路由 + 调整 complete-with-result Body
docs/sql/migrations/2026-06-11-qc-material-batch.sql DDL + 迁移

前端

文件 改动
api/qc-material.ts 批次类型与 API
api/qc-task-result.ts materialUsages 改 batchId
qc-material/index.vue 列表/入出库弹窗
QcMaterialUsageTab.vue 筛选、列表列、查看弹窗、API 类型(见 §5.3)
api/qc-material-usage.ts 类型与列表参数扩展
QcWorkOrderTaskSubList.vue 物料使用区核心改造
qc-work-order/index.vue 等 查看态列

一致性

实现后运行 python scripts/check_field_consistency.py 覆盖新 CRUD。


8. 测试要点

  1. 同一物资两次入库,列表两行,入库编号不同
  2. 入库编号重复 → 400
  3. 批次出库超 remaining_qty → 失败
  4. 质检任务选批次 A 扣减 → 批次 A remaining 减、物资 stock_qty 减
  5. 重新填写检测结果 → revert 回加批次 A
  6. 同一任务不可重复选同一 batchId
  7. 清除旧数据 + 导入测试数据后,列表与变动记录展示正确
  8. 批次出清至 remaining_qty=0 后,物料列表仍可见该行;变动记录可查到该批次的出库流水
  9. 入库缺少货号/批号/厂家/单价/金额任一项 → 前后端均拒绝

9. 已确认产品规则(2026-06-11)

项 结论
货号 / 批号 / 厂家 / 单价 / 金额 全部必填(元字段用 AmountInput + formatMoneyDisplay)
零库存批次 默认展示,不隐藏;出库仅在批量出库弹窗中选批次,仅展示 remaining_qty > 0
入出库入口 仅工具栏;列表行内无入库/出库按钮
批量入库/出库 列表模式 +「新增一行」;出库单头含领用人/用途/备注/出库时间
金额与单价联动 默认 单价 × 入库数量;用户手工改金额后不再自动覆盖
变动记录 Tab 必须同步改造(§3.5、§5.3)
旧数据 不迁移;交付清除脚本 + 测试数据脚本,由人工在库中执行

Spec 已按上述确认更新。若无其他修改,可进入 writing-plans 编写分 Task 实施计划。