PAYATHON 2026

InvoicePlane:在 Invoice 中列出所有 Payment 交易

支付老李

结论

问题在于传给模板的数据被截断了:

'payment' => (isset($payment[0]) ? $payment[0] : null),

get()->result() 返回对象数组,而 $payment[0] 只取了第一条记录。应把完整结果集传给模板,再通过 foreach 为每条记录生成表格行。

修改 pdf_helper.php

建议使用复数变量名,表明变量中包含多条付款记录:

$CI->load->model('quotes/mdl_quotes');
$quote = $CI->mdl_quotes
    ->where('ip_quotes.invoice_id', $invoice_id)
    ->get()
    ->result();

$CI->load->model('payments/mdl_payments');
$payments = $CI->mdl_payments
    ->where('ip_payments.invoice_id', $invoice_id)
    ->get()
    ->result();

$data = array(
    'invoice' => $invoice,
    'invoice_tax_rates' => $CI->mdl_invoice_tax_rates
        ->where('invoice_id', $invoice_id)
        ->get()
        ->result(),
    'quote' => isset($quote[0]) ? $quote[0] : null,
    'payments' => $payments,
    'items' => $items,
    'payment_method' => $payment_method,
    'output_type' => 'pdf',
    'show_discounts' => $show_discounts,
);

这里不要再对 $payments 取 [0]。即使没有付款记录,result() 通常也会返回空数组,模板可以直接通过 empty($payments) 判断。

如果需要按日期排列付款记录,可以在查询中加入排序:

$payments = $CI->mdl_payments
    ->where('ip_payments.invoice_id', $invoice_id)
    ->order_by('ip_payments.payment_date', 'asc')
    ->order_by('ip_payments.payment_id', 'asc')
    ->get()
    ->result();

第二个排序条件可以让同一天的多笔付款保持稳定顺序。前提是当前版本的付款表以 payment_id 为主键。如果字段名不同,需要根据实际数据库结构调整。

修改 Invoice PDF Template

在 application/views/invoice_templates/pdf/InvoicePlane.php 中使用 foreach 遍历 $payments:

<?php if (!empty($payments)) { ?>
    <table class="item-table">
        <thead>
            <tr>
                <th>Date of Payment</th>
                <th>Payment Method</th>
                <th>Amount Paid</th>
                <th>Payment Journal</th>
            </tr>
        </thead>
        <tbody>
            <?php foreach ($payments as $payment) { ?>
                <tr>
                    <td>
                        <?php echo htmlsc($payment->payment_date); ?>
                    </td>
                    <td>
                        <?php echo htmlsc($payment->payment_method_name); ?>
                    </td>
                    <td>
                        <?php echo htmlsc($payment->payment_amount); ?>
                    </td>
                    <td>
                        <?php echo htmlsc($payment->payment_note); ?>
                    </td>
                </tr>
            <?php } ?>
        </tbody>
    </table>
<?php } ?>

也就是把原来固定的一行:

<tr>
    ...
</tr>

放入下面的循环:

<?php foreach ($payments as $payment) { ?>
    ...
<?php } ?>

查询得到的每一笔 Payment 都会生成一个 <tr>。

保持原变量名的最小改法

如果不想把模板变量从 $payment 改成 $payments,也可以保留原名称,但传递的必须是完整数组:

'payment' => $payment,

模板可以写成:

<?php if (!empty($payment)) { ?>
    <?php foreach ($payment as $payment_row) { ?>
        <tr>
            <td><?php echo htmlsc($payment_row->payment_date); ?></td>
            <td><?php echo htmlsc($payment_row->payment_method_name); ?></td>
            <td><?php echo htmlsc($payment_row->payment_amount); ?></td>
            <td><?php echo htmlsc($payment_row->payment_note); ?></td>
        </tr>
    <?php } ?>
<?php } ?>

这种写法同样有效,不过用 $payments 表示集合、用 $payment 表示单条记录,会更便于长期维护。

日期和金额格式化

直接输出数据库值可以正常工作,但日期和金额的格式可能与 InvoicePlane 其他区域不一致。如果当前版本提供了相关辅助函数,可以沿用现有模板中的格式化方式。例如,InvoicePlane 1.x 模板中可能会使用:

<td><?php echo date_from_mysql($payment->payment_date); ?></td>
<td><?php echo format_currency($payment->payment_amount); ?></td>

完整示例如下:

<?php if (!empty($payments)) { ?>
    <table class="item-table">
        <thead>
            <tr>
                <th>Date of Payment</th>
                <th>Payment Method</th>
                <th>Amount Paid</th>
                <th>Payment Journal</th>
            </tr>
        </thead>
        <tbody>
            <?php foreach ($payments as $payment) { ?>
                <tr>
                    <td><?php echo date_from_mysql($payment->payment_date); ?></td>
                    <td><?php echo htmlsc($payment->payment_method_name); ?></td>
                    <td><?php echo format_currency($payment->payment_amount); ?></td>
                    <td><?php echo nl2br(htmlsc($payment->payment_note)); ?></td>
                </tr>
            <?php } ?>
        </tbody>
    </table>
<?php } ?>

能否直接使用 date_from_mysql()、format_currency() 和 htmlsc(),取决于当前安装版本以及现有 PDF 模板已经加载的辅助函数。如果模板其他位置已经在使用这些函数,通常可以直接复用。

注意事项

  • payment_method_name 通常依赖付款模型对 Payment Method 表的关联查询。当前第一笔记录既然已经能显示该字段,现有模型查询大概率包含了所需关联。如果修改后该字段为空,需要检查 mdl_payments 的查询和关联配置。
  • Payment Note 是用户输入内容,建议通过 htmlsc() 或 htmlspecialchars() 转义,避免特殊字符破坏 PDF HTML。
  • 没有付款记录时,最好隐藏整个表格,以免生成只有表头的空表。
  • 系统自带的 PDF 模板和 helper 文件可能在升级时被覆盖。升级前应备份改动,升级后也要重新核对模型字段和模板接口。
  • 不要同时保留原来读取单条 $payment 的代码,否则模板仍可能把数组当作对象访问,引发 Trying to get property of non-object 或数组属性访问错误。

备注:内容仅供参考。