后台点击 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 三方金额;重复执行同一回调不得生成第二张发票,部分开票订单仍能只开剩余数量。

