Files
bj_power/临时.md
T
SunYF 05b0b8a909 feat: 完成客户第12条需求+多系统改造
1. 新增预警表添加工单号字段,支持按工单号检索预警
2. 添加工单过滤在制品/绩效报表/预警中心查询
3. 补全WMS台账字段口径与权限配置
4. 修复备料单与数量不符处理流程
5. 新增配送进度/库位联动/自动出库功能
6. 修复AGV配送状态更新逻辑
7. 优化退料与库存管理细节
2026-09-19 17:04:58 +08:00

23 KiB
Raw Blame History

出库与配送闭环 · 最终方案(2026-09-19 定稿)

本文为唯一版本,覆盖此前所有草稿;外部建议(DeepSeek 两轮)仅作参考,逐条给了采纳/否决理由。 全部结论基于真实代码逐行核实,位置标注 文件:行本文是方案,未改任何代码。全部改动需授权后执行。


0. 结论先行

  1. 现状不是"缺功能",而是"同一份料的库存被扣两次、同一个字段被两个语义写"。 这是数据正确性缺陷,必须先于补功能修 —— 否则功能越全,账越乱。
  2. 外部方案描述的「四条链路」约 70% 已在代码里跑着(工单物料台账、AGV 任务、接料确认、退库、手动出库、接驳台主数据、叫料、分批出库、工位级拆单、工序-物料绑定全部已实现)。照它重建会推翻现有可用结构。
  3. 唯一真空白:数量不符的处理侧(上报有、处理无)。已确认 material_qty_report.resolved_at从未被写入的死字段
  4. 1 个真 bug:叫料单不写工位号 → 工位终端「待接料」查不到自己叫的料(本轮逐行复核再次确认)。
  5. 改进方向:修 1 处重复扣减、拆 2 个被污染的字段、补 1 处空白、修 1 个 bug、语义化 1 个枚举
  6. 已定调:库存扣减走 A(出库扣、反冲只核销)。但 A 有三个前提必须先落,否则会从"双扣"变成"漏扣"(§3.2)。
  7. 已否决:在 MES 建「线边库存」实体表(客户从未提出、可完全派生、会引出第三本账)。

1. 根因:三个"唯一性"缺失

1.1 inventories.quantity 的全部写入点(事实核查)

# 位置 触发动作 上限校验
1 bj_power_wms/internal/handler/outbound.go:107 工单出库 仓管发料 409
2 bj_power_wms/internal/handler/outbound.go:436 通用出库 仓管发料 409
3 bj_power_wms/internal/handler/outbound.go:deductStockHandler/api/internal/stock/deduct MES 备料自动出库 409
4 bj_power_wms/internal/handler/backflush.go:192 fifoConsume 报工反冲 无任何上限

第 3 条是关键MES logic/material.go:231 的备料自动出库走的正是 /api/internal/stock/deduct已经扣过一次库存;随后 MES 报工 logic/workpiece.go:379/api/internal/material/backflush同一份料又被 fifoConsume 扣一次

料只出一次门,库存扣两次。 且反冲按 FIFO 从"当前在库任意合格批次"扣,不认工位实领的那批 —— 领料后若有新批次入库,还会扣错批。

1.2 out_qty / sent_qty 的多写者

WMS order_material_ledger.out_qty(字段注释"累计已出库数量")有 3 个写者,其中 1 个语义完全不同:

写者 语义 校验
outbound.go 工单出库 已发出
outbound.go:deductStockHandler 已发出
backflush.go:100 newOut := ledger.OutQty + consumed 已消耗 无上限 → 可 > total_qty

MES material_request.sent_qty 有 2 个写者,语义互斥:

写者 语义 后果
logic/material.go:241 AddSentQty(sent)(自动出库) 已发出 同时是 :192 need = ReqQty SentQty 的幂等基准
logic/material.go:383 SetSentQty(newSent)(接料) 已接料 把 sent_qty 顶到 ReqQty → need 恒为 0 → 后续补发永久失效;且 add<=0 时算出的实数不落库 → 实收量根本没记

1.3 三处结构性缺陷

# 缺陷 证据
A 反冲被实现成了"第二次出库" backflush.go:63 创建 outbound_type=backflush 出库单 + :192 扣库存 + :100 累加 out_qty。已核实该出库单无任何消费方(列表/导出按 type 过滤,导出 outbound.go:641 只认 workorder,反冲单被归入"通用出库")→ 纯账目污染
B 退库不回收缺口 return_order.go:59/72 建单 PENDING → :137 收货 RECEIVED → :158 AddQuantity(ro.Qty) 回库存,但不回写台账。来源确认是工位退料MES logic/station_ops.go:128wmsclient.CreateReturnOrder,注释「工位退料回库房」)
C 台账缺字段 schema/order_material_ledger.go 仅有 total_qty / out_qty / status / target_station / completed_at / remarkconsumed_qty、无 returned_qty → 消耗与退回无处安放,只能挤进 out_qty

1.4 "退库 → 补发"死锁(本轮新发现,外部建议与上一版方案均未点到)

outbound.godeductStockHandler 的超量判定都是 newOutQty > TotalQty → 409,而退库不回写台账:

步骤 total_qty out_qty 结果
出库 100 100 100 领料完结
工位退料 20(多发) 100 100 库存 +20台账缺口仍为 0
工位再叫料 20 100 120 > 100 409 拒绝 → 补料永远发不出去

→ 必须引入 returned_qty,缺口与完结判定改净额

1.5 其他

  • 补料与备料同表同状态无来源标记(无 source),AGV 待发料列表无法按"工位急件优先"排序。
  • 接料实收量不落库,与"数量不符"无法自动对账。
  • 出库单need_agv,分不清"这批走没走车"。
  • 数据现状(硬证据,查库):order_material_ledgers 0 行outbound_orders 0 行 → 隐患尚未触发,库里无坏数据。属"上线即坏",非"已经坏了"。

2. 目标态:三条铁律

铁律 1 —— 一次物理位移 = 一次库存账变

inventories.quantity 只在两处变:出库(−)、退库收货(+)。消耗不动库存。

铁律 2 —— 一个字段只有一个写者

字段 唯一写者 语义
inventories.quantity 出库单 / 退库收货 仓库实物
order_material_ledger.out_qty 出库(含自动出库) 已发出
order_material_ledger.returned_qty 🆕 退库收货(仅挂工单的) 已退回
order_material_ledger.consumed_qty 🆕 报工反冲 已消耗
material_request.sent_qty 自动出库 已发出
material_request.received_qty 🆕 工位接料 已接料

铁律 3 —— 不新增账本,在途/线边全部派生

缺口 gap        = total_qty  (out_qty  returned_qty)      ← 净额,修 1.4 死锁
在途 in_transit = out_qty  received_qty
线边 line_side  = received_qty  consumed_qty  工位退回量
恒等式:入库总量 = 在库 + 在途 + 线边 + 已消耗 + 退库回仓 + 盘点差异

派生依据全部现成:在途 → agv_task(已有 DISPATCHED/DELIVERING/DONEarrived_at);线边 → material_request;已消耗 → ledger.consumed_qty不需要新表。

四时点口径表(对外沟通用)

时点 触发 写什么 动库存
T1 出库提交 仓管 / MES 自动出库 ledger.out_qty += nmr.sent_qty += n 唯一减点
T2 AGV 送达 RCS 回调 agv_task.status / arrived_at
T3 工位接料 工位终端扫码 mr.received_qty += n
T4 报工消耗 MES 报工 ledger.consumed_qty += n
T5 退库收货 WMS 退库确认 ledger.returned_qty += n 加点

3. 方案

3.1 P0(必须先修)—— 数据正确性:不改,库存与台账本身就是错的

0-1 反冲停扣库存、停写 out_qty、停产反冲出库单

文件bj_power_wms/internal/handler/backflush.go

  • 删除 :63-77 反冲出库单创建(已核实无任何消费方,删除零影响);outboundNo 随之去掉。
  • 删除 :86fifoConsume 的调用;:145 fifoConsume 整体删除;其中写 inventories:174 SetStatus("出库"):192 AddQuantity(-take))与 OutboundDetail.Create:180/:200)的分支全部删除。
  • 台账改写新字段:SetConsumedQty(ledger.ConsumedQty + n);完结判定改净额(见 0-2)。
  • 新增核销上限可核销量 = (out_qty returned_qty) consumed_qty,实际核销 min(应核销, 可核销量);超出部分不写台账,落差异记录 + 预警(material_over_consume),不阻断报工(生产线不能被系统卡死)。响应体保留 consumed(实际核销)与 shortfall(未核销额)。

0-2 台账加两列 + 退库回收缺口

文件bj_power_wms/schema/order_material_ledger.gointernal/handler/return_order.gointernal/db/db.go

  • schema 加 consumed_qty(默认 0,「累计已消耗数量(报工反冲)」)、returned_qty(默认 0,「累计已退回数量」)。
  • ent 重生成:cd bj_power_wms && go run -tags entgenerate ./tools
  • internal/db/db.goapplyColumnPatches 登记两列(带默认值 → 无需进 applyPreMigratePatches,不阻断启动)。
  • return_order.go 收货(:137 RECEIVED)事务内:若退库单挂工单ledger.returned_qty += qty;不挂工单(仓库自身退料)不动台账。

0-3 所有超量判定改净额

文件bj_power_wms/internal/handler/outbound.go

  • deductStockHandler 与工单出库的 409 判定:newOutQty > TotalQtynewOutQty > TotalQty + ReturnedQty
  • 「领料完结」判定同步改净额。
  • 错误文案中文化:出库数量超过工单需求(需求 X,已退 Y,累计将达 Z)

0-4 MES 接料改写 received_qty

文件bj_power_mes/schema/material_request.gointernal/logic/material.go:366-395

  • schema 加 received_qty(默认 0)、received_at(可选)、receive_by(可选)。
  • ReceiveMaterialRequest:383-387 改为 SetReceivedQty + SetReceivedAt + SetReceiveBy(operator)删掉 SetSentQtyadd<=0 的兜底改用 ReqQty ReceivedQty 计算。
  • sent_qty 语义锁定为「已发出」(唯一写者 = 自动出库 :241),need = ReqQty SentQty 保持成立。
  • 「DONE」推进改由 received_qty >= req_qty 触发。
  • MES 侧 internal/db/db.goschemaPatchSQL 登记三个新列。

0-5 出库类型语义化

  • outbound_type 最终三值:workorder(备料/工单领料)/ refill(补料)/ general(手动出库),移除 backflush
  • 导出与列表类型文案同步(outbound.go:641if ob.OutboundType == "workorder" 分支补 refill)。

P0 验收:连续跑「备料自动出库 → 报工反冲」,inventories.quantity 只减一次;out_qty 不因反冲变化;out_qty returned_qtyconsumed_qty 均可解释。

3.2 A 方案的三个前提(已采纳 A,但前提不落则变"漏扣")

# 前提 不做的后果
1 反冲核销有上限 = net_out_qty consumed_qty 工位自取的料(辅料/胶水/现场替换)被全额核销,但库存从没扣过 → 库存永不下降
2 退库必须回收缺口returned_qty §1.4 死锁:退料后补料被 409 永久拦死
3 出库扣到实领批次/SN 只解决"扣几次",不解决"扣哪批"(现有出库本就按批次/SN 指定,已满足)

3.3 P1(该做,但不动现有数据)—— 功能空白与真 bug

1-1 修 bug:叫料单补工位号(零成本,必做)

文件bj_power_mes/internal/logic/line.go CallMaterial

  • 已逐行复核:建单只有 SetRequestNo/OrderNo/PlanDate/MaterialCode/MaterialName/Unit/ManageMode/ReqQty/Status/TargetDock/OperatorSetStationNo、无 SetProcessCodestation_no=0
  • 而工位终端「待接料」按 stationNo=本工位 查询 → 工位看不见自己叫的料(扫码接料直查备料单号,不受影响,所以隐蔽)。
  • 修法:SetStationNo(...) + SetProcessCode(...)(按该物料在本工位的装配工序取,取不到则 0)+ SetSource("REFILL")
  • 一次性数据修复:历史 stationNo=0 的叫料单按 target_dock → dock.station_no 映射回填(幂等)。

1-2 补空白:数量不符处理侧 —— 真源留在 MES,处理页也放 MES

理由(关键,与上一版方案的结论相反)

  • 上报链路、表、预警全在 MES:工位终端 InspectPanel ⑥/api/qty-report → 代理 MES /api/internal/station/qty-reportlogic.CreateQtyReportlogic/station_ops.go:64)落 material_qty_report + 预警 type=qty_diff
  • 处理动作在 MES 侧可完全闭环:补发 = 生成 source=REFILL 备料单 → 走已有自动出库链路;退库 = 走已有 wmsclient.CreateReturnOrderMES→WMS 方向已存在);调整 = 本地记账。
  • 仓管本来就在 MES 操作备料(MaterialRequest.vue 已含「按日排产生成备料」「一键自动出库」)。
  • 放 WMS 虽技术可行(bj_power_wms/internal/mesclient/client.go 已具备反向调用能力),但需新增 3 个接口,且补发要绕 WMS→MES→WMS 一圈,没有理由多绕

实施

  • 表:material_qty_report 已有 status(PENDING/RESOLVED)、resolved_at当前死字段)→ 只需补 handle_typeREFILL/RETURN/ADJUST)、handle_notehandled_byhandle_ref_no
  • 接口:GET /api/v1/qty-diffs(状态/工位/物料/时间筛选 + 后端 SQL 真分页)、GET /api/v1/qty-diffs/:id(明细,配 el-drawer 懒加载)、POST /api/v1/qty-diffs/:id/handle
  • 处理分支:
    • REFILL → 生成 source=REFILL 备料单(可走自动出库)→ 回写 handle_ref_no
    • RETURNwmsclient.CreateReturnOrder → 回写退库单号
    • ADJUST → 仅记账关单
  • 照片:走统一附件体系 biz_type=QTY_DIFF + biz_id=差异记录ID不加列
  • 权限:新增 produce.qtydiff:view / produce.qtydiff:handle同时登记进 logic/seed.go 的 menus 与 buttonshandler/perm.gopathPermMap(本项目栽过一次"菜单码未登记 → 任何角色永远授不了权")。
  • 前端:MES 新增「数量不符处理」页 + MainLayout 菜单 + help.js
  • 客户 DOC 同步seed.go:165 migrateLegacyQtyReport 留的「明细由 WMS 处理」改为「在 MES 数量不符处理页处理」。

1-3 补料来源标记

  • material_requestsource(默认 PLAN);叫料与差异补发写 REFILL
  • AutoOutboundRequests 与 WMS 待发料列表按 source=REFILL 优先排序(不新建单据类型、不新建优先级字段)。
  • 补料出库默认叫 AGV(沿用现有 AGV 下发条件,不新增 need_agv 字段)。

1-4 只读「配送进度」聚合

  • 不给出库单加五态(状态分散在 agv_task / material_request,硬塞会互斗)。改一条只读接口按出库单号聚合:出库单(已出库)+ AGV 任务(配送中/已送达)+ 备料单(已接料/完结)。

3.4 P2(可有可无)—— 可选增强

说明
「物料去向」只读对账页 按物料展示 在库/在途/线边/已消耗,直接验 §2 恒等式
在途对账预警 out_qty received_qty 长期 > 0(滞留在途)或 < 0(未发先接)→ 预警
消耗超接料预警 consumed_qty > received_qty → 提示"未接料先消耗",不阻断

4. 明确不做(含理由)

不做 理由
MES「线边库存」实体表 ① 客户《软件系统问题-hd.doc》全文检索「线边/工位库存/工位上的料/余料」命中 0,只提过仓库盘点(第 102 段)→ 为未提出的需求建表即过度设计;② 会在 WMS 库存 + WMS 台账 + MES 备料单 + MES 线边四处同步,本项目已栽过一次跨系统对不上;③ 线边量可由 received_qty consumed_qty 完全派生唯一例外:客户明确要求"盘工位上的料"时再建,并接受同步成本(列入待确认)
出库单五态(待出库→已出库→已送达→已接料→已完成) 状态分散在 agv_task / material_request,硬塞进出库单会产生三份互相打架的状态副本。改只读聚合(1-4)
新建 difference_report material_qty_report 已存在且字段齐全(含 resolved_at 死字段),补 4 列即可,不造同义表
双写两份账本 违反单一真源。sent_qty 的教训已经很清楚
接料"可选"、AGV"可选" 接料确认是消耗核销的唯一依据,保持强校验
补料另建单据类型 source 字段标记即可,避免两套单据各自维护缺口
OA 对接 客户已定:永久不做,不再提

5. 影响面与风险

改动 影响 处置
反冲单不再产生 出库列表/导出少掉 backflush 类单据 已核实无消费方;导出文案补 refill
反冲不再扣库存 若存在"从未出库的料被消耗"(工位自取/辅料) 由 0-1 的核销上限兜住:超出部分落差异+预警,不写台账、不阻断报工
sent_qty 语义锁定 MES 页面/看板若把 sent_qty 当"已接料"展示 一并改为读 received_qty
台账新增两列 存量 0 行,无回填 直接加列
material_request 新增列 存量可能有数据 均带默认值,安全

6. 验收标准

  1. 库存账备料自动出库 → 报工反冲 连续两轮,inventories.quantity 只在出库时减一次;反冲前后库存不变。
  2. 台账账out_qty 只由出库写;反冲只增 consumed_qty;超量报工不使 out_qty > total_qty
  3. 死锁解除:出库满额 → 工位退料 → 再补料,不再被 409 拦住
  4. 接料账received_qty 落库;接料后 need = ReqQty SentQty 不被污染,补发仍可执行。
  5. 叫料 bug:工位终端叫料后,该工位「待接料」列表能看到自己叫的料
  6. 数量不符闭环:工位上报 → 处理页可见 → 补发/退库/调整 → 差异关闭,生成单据可追溯。
  7. 负向验证:非 admin 角色在缺权限时看不到/点不动处理按钮(防"菜单码未登记"老坑)。

7. 待确认

  1. refill 补料是否允许超出工单 BOM 需求?现规则是硬 409(含 0-3 净额修正)。补料语义上可能确实需要超额(损耗/报废),那就必须走"数量不符/额外需求"通道而非工单领料通道 —— 需业务确认。本方案默认仍受净额上限约束。
  2. 是否要求「工位线边盘点」?要 → 建 line_stock 实体表并接受四处同步成本;不要 → 线边量走派生视图(默认)。
  3. 数量不符处理页放 MES 与客户 DOC 原写「明细由 WMS 处理」不一致 → 建议改 DOC(理由见 1-2)。
  4. outbound_type 由三值变四值(新增 refill)是否影响历史统计口径(现存量 workorder 中是否混有旧叫料出库)。
  5. 环境依赖:AGV 真机联调需海康 RCS 地址与 appKey/appSecret(现 Agv.EnableMock: true);附件归档需外部备份路径(现 ArchiveDir 为空)。

附:实施顺序(一次性做完,不分阶段交付)

【编号说明】P0=必须先修(P0-1..P0-6)P1=该做(P1-1..P1-4)P2=可选。

P0-1  WMS schema: ledger 加 consumed_qty / returned_qty → ent 重生成 → applyColumnPatches 登记
P0-2  MES schema: material_request 加 received_qty/received_at/receive_by → ent 重生成 → schemaPatchSQL 登记
P0-3  WMS backflush.go: 删出库单 + 删 fifoConsume + 改 consumed_qty + 加核销上限
P0-4  WMS return_order.go: 收货回写 returned_qty
P0-5  WMS outbound.go: 两处 409 与完结判定改净额
P0-6  MES material.go: ReceiveMaterialRequest 改写 received_qty
P1-1  MES line.go: CallMaterial 补 station_no/process_code/source + 历史回填
P1-2  MES: material_qty_report 加 4 列 + 处理接口(真分页) + 权限(菜单+按钮) + 页面 + help.js
P1-3  MES: material_request 加 source + 排序优先
P1-4  MES: 只读配送进度聚合接口
P2    只读「物料去向」对账页 + 两条对账预警(可选)
DOC   同步客户问题 DOC;改「明细由 WMS 处理」措辞

增强⑧「物料去向对账」最优方案(2026-09-19 定稿,待授权实施)

0. 结论

  1. 对账后端已经 100% 存在:上一轮实施时顺手落了 GET /api/v1/material-requests/progressMES 聚合 WMS ledger/progress + 本地已接料),返回字段正是对账表全部列——需求/已发出/已退回/已接料/已消耗/在途/线边/待发缺口/台账状态。备料单页已有「配送进度」弹窗在读它。
  2. DeepSeek 方案定位对(只读/不建表/不做自动修正),但四个硬伤:①"差异=需求−已出、非0标红"是错的(在产工单缺口天然非0,照做满屏假红);②示例第3行自己算错符号;③需求总量重算 BOM 是重复建设(total_qty 已随台账同步,重算必与备料时点不一致);④工位余公式漏了退库。
  3. 剩余工作量 = 纯前端:把已有进度弹窗升级为对账视图(异常标记 + 明细展开 + 导出)。后端 0 行新代码(明细过滤 /event-logs 已支持 orderNo+eventType)。

1. 公式(纠正版,与后端派生完全一致)

待发缺口 gap     = totalQty  (outQty  returnedQty)   ← 生产中非0是常态,不标红
在途   inTransit = outQty  receivedQty
工位余 lineSide  = receivedQty  consumedQty  returnedQty   ← DeepSeek 漏了退库

恒等式:入库总量 = 在库 + 在途 + 工位余 + 已消耗 + 退库回仓 + 盘点差异

2. 异常判定(三条硬异常,全部可从现有字段判定,前端现算)

异常 判定 含义 标记
超领 netOut > totalQty 实领超过 BOM(超领原因在 WMS 留痕 material.over
送达未接 inTransit > 0 且该工单物料存在 status=已送达 的出库单 料到了工位没人扫码确认
完结剩料 台账状态=领料完结 且 lineSide > 0 工单做完了工位还压着料,没消耗也没退库
在途等待 inTransit > 0 且无已送达出库单 AGV 正在送,正常 黄(提醒)

3. 页面形态(升级既有弹窗,不新建菜单)

  • 位置:MES 备料单页「配送进度」弹窗 → 改名「物料去向对账」。
  • 列:现有 9 列 + 「异常」列(红/黄/无,hover 显示判定原因)。
  • 行展开明细(全部现成数据,无后端改动):
    • 出库明细 → 行内备料单已含出库单号;超领原因在 WMS 出库记录页(按工单筛)查看,不跨系统重复建设;
    • 接料明细 → material_request 行(receivedQty/receiveBy/receivedAt);
    • 消耗明细 → /event-logs?orderNo=xxx&eventType=material.backflush(含工位/数量/缺口)。
  • 导出:xlsx,复用 exportXlsxFetch。
  • 权限:沿用 produce.material(只读,不新增权限码)。

4. 明确不做

  • 不重算 BOM 需求量(读 total_qty);
  • 不做"差异=需求−已出"列与标红(就是 gap 列,完结后必为 0,出现非 0 即数据错,属可发现的脏数据而非业务异常);
  • 不做跨系统明细钻取(WMS 出库/核销明细由 WMS 出库记录页承担,已可按工单筛选);
  • 不做自动对账修正、不建新表、不加权限码。

5. 工作量与顺序

前端单文件(MaterialRequest.vue+ help.js 一条说明,约半天。排在"①~⑦ 编译验证"之后做。