物理库存没有出库,但订单取消后Salable Quantity继续下降。查inventory_reservation会看到下单时的负记录,却没有对应取消的正记录;或者有补偿,但stock_id、sku或metadata中的订单引用不一致,汇总后仍然不为零。MSI的Reservation是不可变流水,正确修复是追加补偿,不是删除旧行。

先针对一张订单重建事件链

SELECT reservation_id,stock_id,sku,quantity,metadata
FROM inventory_reservation
WHERE metadata LIKE '%"object_id":"100000123"%'
ORDER BY reservation_id;

正常情况下,下单产生负数;取消、退款未发货数量或发货扣减Source后会产生相应事件。不同流程事件名不同,但同一SKU最终汇总必须符合实际未履约数量。

确认订单是怎么被“取消”的

如果ERP插件只是 setState/setStatus 然后save,没有调用OrderManagementInterface::cancel,就可能绕过取消事件和库存补偿。后台手动取消正常、ERP取消异常时,几乎可以直接沿这条线查。

$order = $orderRepository->get($orderId);
if ($order->canCancel()) {
    $orderManagement->cancel($orderId);
}

不要在Observer里重复调用cancel。服务层应负责一次完整状态转换,外部回调用幂等键避免重复消费。

用官方工具生成补偿前先预览

bin/magento inventory:reservation:list-inconsistencies -r > /tmp/reservation-inconsistencies.txt

逐条核对订单状态、已发货数量、退款数量与Stock。确认无误后,再把输出交给补偿命令。大批量执行前先在预生产复制数据演练,并备份数据库。

cat /tmp/reservation-inconsistencies.txt | bin/magento inventory:reservation:create-compensations

命令参数在不同版本可能有差异,以当前 bin/magento help 输出为准。核心原则是让系统追加带来源说明的补偿流水。

为什么不建议DELETE

  • 删除会失去库存审计链
  • 并发下单可能在清理期间写入新Reservation
  • 删错stock_id会影响另一个销售渠道
  • 后续升级和一致性检查无法解释数量来源

修复代码后做四种状态回归

  • 下单后直接取消
  • 部分发货后取消剩余商品
  • 全额退款已发货商品
  • 支付回调与取消回调乱序到达

每个用例都记录source quantity、reservation sum和salable quantity。满足近似公式:可售量 = Source可售汇总 + Reservation汇总 - 阈值调整。若启用backorder或自定义Source Selection,还要把相应配置计入。

补偿历史数据只是第一步。若ERP仍用错误方式改订单状态,今晚修好的库存明天还会继续变负。