后台点击 Invoice 后提示 You can’t create an invoice without products,含义是 Magento 计算出的本次可开票数量全部为 0。先确认这张订单是否真的还有未开票商品,不能通过修改 state/status 强行显示按钮。

查询订单行的数量关系

SELECT item_id, parent_item_id, sku, product_type,
       qty_ordered, qty_invoiced, qty_canceled,
       qty_refunded, row_total
FROM sales_order_item
WHERE order_id = 1234
ORDER BY item_id;

通常可开票数量由 ordered、invoiced 和 canceled 等字段计算。Configurable、Bundle 和 Downloadable 存在父子行,真正开票数量可能在子行或父行,不能把每行都手工设为 1。

确认是否已经有发票

SELECT entity_id, increment_id, state, grand_total, created_at
FROM sales_invoice
WHERE order_id = 1234;

SELECT invoice_id, order_item_id, sku, qty, row_total
FROM sales_invoice_item
WHERE order_item_id IN (
  SELECT item_id FROM sales_order_item WHERE order_id = 1234
);

支付回调重试或 ERP 同步可能已创建发票,只是后台邮件/状态没有更新。不要创建第二张发票来修界面。

支付方式决定 Online Capture 是否可用

查看订单 Payment 的 method、amount_authorized、amount_paid、last_trans_id,以及 transaction 表中的 authorization/capture。在线开票可能触发 Capture;如果网关已经在外部 Capture,本地再次 Capture 会重复扣款。

SELECT method, amount_authorized, amount_paid,
       amount_refunded, last_trans_id
FROM sales_order_payment
WHERE parent_id = 1234;

SELECT txn_id, parent_txn_id, txn_type, is_closed
FROM sales_payment_transaction
WHERE order_id = 1234;

自定义代码创建 Invoice 的正确边界

if (!$order->canInvoice()) {
    throw new MagentoFrameworkExceptionLocalizedException(
        __('Order has no invoiceable items.')
    );
}

$invoice = $invoiceService->prepareInvoice($order, $qtys);
if (!$invoice->getTotalQty()) {
    throw new RuntimeException('Prepared invoice is empty');
}

$qtys 的键必须是 order_item_id,不是 product_id。传错 ID 时 prepareInvoice 会得到空发票。保存 Invoice、Order 与 Transaction 应放在一致事务中,并为外部事件建立幂等键。

不要直接更新 qty_invoiced

这些字段与 Invoice 实体、支付交易、税、折扣和订单总额有关。直接 SQL 改数量会让 Credit Memo 与对账继续错误。应从漏掉的业务动作入手,通过支付模块/服务层补偿。

修复后对比订单、发票、网关 Capture 三方金额;重复执行同一回调不得生成第二张发票,部分开票订单仍能只开剩余数量。