Prestashop 1.6 更改订单状态后未生成 PDF Invoice
结论
问题不在 PDF 生成器本身。$order_invoice->save() 返回 false,表示发票记录没有成功写入 ps_order_invoice 表。对象因此拿不到 id_order_invoice,后续也就无法生成 PDF Invoice。
数据库容量达到上限只是其中一种可能,而且概率不高。如果同时期的其他支付方式仍能正常创建发票,应优先检查:
- 数据库返回的具体 SQL 错误;
ps_order_invoice表结构是否与 PrestaShop 1.6.1.6 一致;- bankwire、cheque 订单中是否有字段包含非法值;
- 是否存在
OrderInvoice、Order或ObjectModeloverride; - 数据库升级或迁移后,索引、字段类型、
AUTO_INCREMENT是否异常。
只查看 $order_invoice 对象内容无法确定原因。必须在 save() 失败后立即读取数据库错误。
先捕获真实的数据库错误
可以临时将保存逻辑改为:
$result = $order_invoice->save();
if (!$result) {
$db = Db::getInstance();
die(
'Order invoice save failed.' . PHP_EOL .
'MySQL error number: ' . $db->getNumberError() . PHP_EOL .
'MySQL error message: ' . $db->getMsgError()
);
}
也可以把错误写入 PrestaShop 日志,以免直接显示给后台用户:
$result = $order_invoice->save();
if (!$result) {
$db = Db::getInstance();
PrestaShopLogger::addLog(
sprintf(
'OrderInvoice save failed for order #%d. MySQL error %s: %s',
(int) $this->id,
$db->getNumberError(),
$db->getMsgError()
),
3
);
throw new PrestaShopException(
'Unable to save OrderInvoice for order #' . (int) $this->id
);
}
必须在 save() 返回后立即读取错误。如果随后执行了其他 SQL 查询,当前连接中的错误信息可能会被覆盖。
这类调试代码不应长期留在核心类中,也不要在生产环境前台直接输出数据库错误。
开启 PrestaShop 开发模式
可以在测试环境中临时编辑 config/defines.inc.php:
define('_PS_MODE_DEV_', true);
再次执行订单状态变更,然后查看:
- 页面显示的异常;
- PrestaShop 日志;
- PHP error log;
- Web 服务器日志;
- MySQL/MariaDB error log。
排查完成后,应关闭生产环境的开发模式:
define('_PS_MODE_DEV_', false);
开发模式有时会直接显示 DbQuery、字段校验或 override 中的异常,比只观察 save() 的返回值更容易定位问题。
在保存前检查对象字段
OrderInvoice::save() 最终会通过 ObjectModel 完成字段校验和数据库写入。保存前可以先检查字段是否符合模型定义:
$validation = $order_invoice->validateFields(false, true);
if ($validation !== true) {
die('OrderInvoice validation failed: ' . print_r($validation, true));
}
if (!$order_invoice->save()) {
$db = Db::getInstance();
die(
'Database error ' .
$db->getNumberError() .
': ' .
$db->getMsgError()
);
}
重点比较正常订单与 bankwire、cheque 订单中的以下字段:
var_dump([
'id_order' => $order_invoice->id_order,
'number' => $order_invoice->number,
'total_discount_tax_excl' => $order_invoice->total_discount_tax_excl,
'total_discount_tax_incl' => $order_invoice->total_discount_tax_incl,
'total_paid_tax_excl' => $order_invoice->total_paid_tax_excl,
'total_paid_tax_incl' => $order_invoice->total_paid_tax_incl,
'total_products' => $order_invoice->total_products,
'total_products_wt' => $order_invoice->total_products_wt,
'total_shipping_tax_excl' => $order_invoice->total_shipping_tax_excl,
'total_shipping_tax_incl' => $order_invoice->total_shipping_tax_incl,
'shipping_tax_computation_method' =>
$order_invoice->shipping_tax_computation_method,
'total_wrapping_tax_excl' => $order_invoice->total_wrapping_tax_excl,
'total_wrapping_tax_incl' => $order_invoice->total_wrapping_tax_incl,
]);
检查数值字段中是否有:
- 空字符串
''; null;- 非数字字符串;
- 超出数据库字段精度的金额;
INF或NAN;- 不存在的订单 ID;
- 超出字段长度限制的文本。
如果数据库启用了严格 SQL 模式,以前会被自动转换的空字符串或非法数值可能直接导致 INSERT 失败。
检查 ps_order_invoice 表结构
商店使用的表前缀不一定是 ps_,执行时应替换为实际前缀。
SHOW CREATE TABLE ps_order_invoice;
SHOW TABLE STATUS LIKE 'ps_order_invoice';
CHECK TABLE ps_order_invoice;
重点确认主键字段是否仍有 AUTO_INCREMENT:
SHOW COLUMNS
FROM ps_order_invoice
LIKE 'id_order_invoice';
正常情况下,id_order_invoice 是整数主键,由数据库自动递增。如果数据库迁移或手工导入时丢失了 AUTO_INCREMENT,新对象保存后就无法取得正确的 ID。
还可以查询当前 ID 范围:
SELECT
MIN(id_order_invoice) AS min_id,
MAX(id_order_invoice) AS max_id,
COUNT(*) AS invoice_count
FROM ps_order_invoice;
在确认表结构差异和数据库错误之前,不要直接执行 ALTER TABLE。应先将当前表结构与相同 PrestaShop 版本和版本分支的干净数据库进行比较,再按实际差异修复。
根据数据库错误处理
常见错误可以按以下方向排查。
Duplicate entry
如果错误中包含:
Duplicate entry ... for key ...
表示主键或唯一索引发生冲突。需要检查:
id_order_invoice的AUTO_INCREMENT值;- 是否有重复的发票编号;
- override 是否手工设置了对象 ID;
- 数据迁移后,自增计数是否小于现有最大 ID。
不要删除唯一索引来绕过错误。应先确定具体冲突的字段,以及冲突数据来自哪里。
Incorrect decimal value 或 Column cannot be null
这通常表示某个金额字段是空值、格式错误或 null。可以比较正常订单与问题订单在 ps_orders 中的金额数据,并追查 bankwire、cheque 模块或相关 override 在创建订单时写入的值。
例如,查询问题订单:
SELECT
id_order,
module,
payment,
total_discounts_tax_excl,
total_discounts_tax_incl,
total_paid_tax_excl,
total_paid_tax_incl,
total_products,
total_products_wt,
total_shipping_tax_excl,
total_shipping_tax_incl,
total_wrapping_tax_excl,
total_wrapping_tax_incl,
id_carrier,
id_address_invoice,
id_address_delivery
FROM ps_orders
WHERE id_order = 123;
将 123 替换为实际订单 ID,再与能够正常生成发票的订单进行比较。
不建议将关闭严格 SQL 模式作为正式修复方案。更可靠的办法是修正产生非法数据的模块、override 或历史订单数据。
Table is full 或磁盘空间错误
只有当错误中包含:
The table ... is full
或者:
No space left on device
才需要检查:
- 数据库服务器磁盘空间;
- MySQL 临时目录空间;
- 主机数据库配额;
- InnoDB 表空间;
- 云数据库存储上限。
如果其他支付方式仍能实时创建发票,存储空间通常不是首要排查方向,但最终仍要以数据库返回的具体错误为准。
Unknown column 或字段没有默认值
这类错误通常说明代码与数据库结构不一致,常见情况包括:
- 只更新了 PrestaShop 文件,没有执行数据库升级;
- 恢复了旧数据库;
- 使用了其他版本的核心文件;
- 模块或 override 依赖了不存在的字段;
- 数据迁移时遗漏了表结构。
核心代码、数据库 schema 和 override 必须对应同一个 PrestaShop 版本。
检查 override 和模块差异
问题只出现在 bankwire 和 cheque 订单上,因此需要检查以下 override:
override/classes/order/Order.php
override/classes/order/OrderInvoice.php
override/classes/ObjectModel.php
也要检查相关模块是否被修改:
modules/bankwire/
modules/cheque/
如果删除或调整了 override,需要清理 PrestaShop 类索引缓存后再测试。缓存文件的具体位置可能因安装方式和版本补丁而异。不要直接在生产环境中删除无法确认的文件,应先备份,再通过后台清理缓存,或者在测试副本中操作。
还需要比较两类支付流程触发发票创建的时间:
- 在线支付模块通常在创建订单时直接进入已付款状态;
- bankwire、cheque 通常先创建待付款订单,之后由管理员手动改为已付款;
- 手动变更状态会在后续请求中调用发票创建逻辑。
因此,问题不一定由 bankwire 或 cheque 模块直接造成。也可能是”延迟创建发票”这条执行路径遇到了历史订单数据、状态配置或 override 异常。
确认订单状态配置
目标订单状态必须启用”生成发票”选项,否则状态变化不会触发发票创建。不过,当前调试已经执行到 setInvoiceDetails(),说明发票流程确实已经启动,所以状态配置不是这次 save() 返回 false 的直接原因。
仍应确认:
payment accepted对应的状态已启用发票;remote payment accepted对应的状态已启用发票;- 状态没有被重复创建或错误映射;
- 问题订单此前没有残缺的发票记录。
可以查询某个订单是否已有发票记录:
SELECT *
FROM ps_order_invoice
WHERE id_order = 123;
如果记录已经存在,但缺少编号或数据不完整,需要先确认它是失败流程留下的记录,还是有效的历史发票。不要直接删除发票记录,尤其是已经用于财务或税务归档的记录。
推荐的定位顺序
- 在测试环境中复现一笔 bankwire 或 cheque 订单。
- 在
save()返回false后,立即记录getNumberError()和getMsgError()。 - 执行
validateFields(false, true),排除对象字段校验失败。 - 比较问题订单与正常订单的金额、地址、承运商和税费字段。
- 检查
ps_order_invoice的主键、AUTO_INCREMENT、索引和字段类型。 - 检查
Order、OrderInvoice、ObjectModeloverride。 - 根据明确的 SQL 错误修复 schema、非法数据或业务代码。
- 移除临时调试代码,关闭生产环境开发模式,再测试订单状态变更和 PDF 下载。
排查时应先取得数据库返回的原始错误。save() 返回 false 只是失败结果,无法单凭这个返回值判断数据库是否达到容量限制。拿到具体的 MySQL/MariaDB 错误编号和消息后,通常就能区分表结构异常、字段值非法、唯一键冲突、存储空间不足以及 override 引发的问题。
备注:内容仅供参考。
