一张订单已经付款,创建 Shipment 时却报 Source Deduction 失败。很多人第一反应是直接改库存数量,让发货先通过。这样做可能暂时消掉错误,却把 MSI 的两本账弄得更乱。
先记住这个关系:下单主要影响 Reservation,它改变可售数量;发货主要影响 Source Item,它从实际来源扣减。订单取消、退款或补偿又会追加相反方向的 Reservation。报错发生在发货阶段,并不代表下单时那笔 Reservation 没有成功。
用一张订单还原库存轨迹
| 时点 | Reservation | Source quantity |
|---|---|---|
| 下单 | 写入负数,降低 salable qty | 通常不变 |
| 发货 | 写入补偿记录 | 从选中的 Source 扣减 |
| 取消未发货订单 | 写入补偿记录 | 不应扣减 |
因此要同时查订单 SKU 的 Reservation 链和 Shipment 选择的 Source。只看后台商品总库存,会把两个层次混在一起。
Source 为什么会扣不下去
我会按下面几类证据判断,而不是反复点“发货”:
- 来源不匹配:订单属于某个 Stock,但 Shipment 选择了不在该 Stock 销售渠道中的 Source;
- 数量已经变化:下单后仓库同步把 Source quantity 降低,发货时不再允许扣减;
- 重复处理:ERP 已创建 Shipment 或发送过扣减消息,重试又走了一遍;
- SKU 映射错误:外部系统传的是旧 SKU、父 SKU,或大小写/空格不同;
- 自定义插件破坏扩展属性:Shipment source code 没被正确带到扣减请求。
先做只读检查
SELECT reservation_id, stock_id, sku, quantity, metadata
FROM inventory_reservation
WHERE sku = :sku
ORDER BY reservation_id;
SELECT source_code, sku, quantity, status
FROM inventory_source_item
WHERE sku = :sku;
php bin/magento inventory:reservation:list-inconsistencies
metadata 能帮助确认 Reservation 来自哪张订单和哪类事件。不要把查到的负数当作错误;下单 Reservation 本来就是负的。真正要看的是同一订单的事件是否完整、总和是否符合它当前的业务状态。
三种修法,不能混用
仓库实际有货,只是 Source 选错
回到 Source Selection,选择实际履约的来源,再创建 Shipment。若由 ERP 指定来源,修复映射和幂等键,避免下一单继续选错。
库存同步晚到,实际已经无货
不要伪造 quantity。应拆单、换来源、等待补货或取消未能履约的数量,并让取消流程产生正确的补偿 Reservation。
历史 Reservation 不一致
先使用 Magento 提供的不一致检查和补偿命令在测试环境验证输出,明确会影响哪些订单,再按受控变更执行。直接删除 inventory_reservation 行会破坏审计链,后续很难判断某个补偿到底对应哪次业务事件。
最终验收不能只看 Shipment 创建成功。我会同时核对 Source quantity、目标 Stock 的 salable quantity、Reservation 总和、订单已发数量和 ERP 状态,并再做一次相同回调重放,确认幂等逻辑不会二次扣减。

