PAYATHON 2026

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 或 ObjectModel override;
  • 数据库升级或迁移后,索引、字段类型、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;

如果记录已经存在,但缺少编号或数据不完整,需要先确认它是失败流程留下的记录,还是有效的历史发票。不要直接删除发票记录,尤其是已经用于财务或税务归档的记录。

推荐的定位顺序

  1. 在测试环境中复现一笔 bankwire 或 cheque 订单。
  2. 在 save() 返回 false 后,立即记录 getNumberError() 和 getMsgError()。
  3. 执行 validateFields(false, true),排除对象字段校验失败。
  4. 比较问题订单与正常订单的金额、地址、承运商和税费字段。
  5. 检查 ps_order_invoice 的主键、AUTO_INCREMENT、索引和字段类型。
  6. 检查 Order、OrderInvoice、ObjectModel override。
  7. 根据明确的 SQL 错误修复 schema、非法数据或业务代码。
  8. 移除临时调试代码,关闭生产环境开发模式,再测试订单状态变更和 PDF 下载。

排查时应先取得数据库返回的原始错误。save() 返回 false 只是失败结果,无法单凭这个返回值判断数据库是否达到容量限制。拿到具体的 MySQL/MariaDB 错误编号和消息后,通常就能区分表结构异常、字段值非法、唯一键冲突、存储空间不足以及 override 引发的问题。

备注:内容仅供参考。