PAYATHON 2026

如何通过 NetSuite Suitelet Query 查询 Invoice 及相关 Payments

支付阿杰

结论

可以查询,但不能直接读取 Invoice 页面中的 Related Records > Payments 子列表。该子列表由 NetSuite UI 根据交易应用关系动态生成,并不是 Invoice 上可直接查询的字段。

使用 N/query 执行 SuiteQL 时,需要通过交易关联表查询:

  • Transaction:Invoice 和 Customer Payment 的交易头
  • NextTransactionLineLink:从 Invoice 查询后续应用交易
  • PreviousTransactionLineLink:从当前交易反查被应用的交易
  • TransactionLine、TransactionAccountingLine:按需获取行级数据或记账数据

如果只需要取得 Invoice 对应的 Customer Payment,通常查询 NextTransactionLineLink 即可。

SuiteQL 查询示例

以下查询从 Invoice 出发,通过 NextTransactionLineLink 找到应用到该 Invoice 的 Customer Payment:

SELECT DISTINCT
    inv.id AS invoice_id,
    inv.tranid AS invoice_number,
    inv.trandate AS invoice_date,
    BUILTIN.DF(inv.entity) AS customer,

    pay.id AS payment_id,
    pay.tranid AS payment_number,
    pay.trandate AS payment_date,
    pay.foreigntotal AS payment_foreign_total,

    link.foreignamount AS applied_foreign_amount,
    link.linktype AS link_type
FROM
    Transaction inv
INNER JOIN
    NextTransactionLineLink link
        ON link.previousdoc = inv.id
INNER JOIN
    Transaction pay
        ON pay.id = link.nextdoc
WHERE
    inv.type = 'CustInvc'
    AND pay.type = 'CustPymt'
ORDER BY
    inv.trandate,
    inv.tranid,
    pay.trandate

如果要查询指定的 Invoice,可以增加以下条件:

AND inv.id = ?

NetSuite 账户中实际可查询的列,应以当前账户的 Records Catalog 为准。如果 foreignamount 或其他列不可用,需要将其从查询中移除,或者改用 Records Catalog 中对应的字段。

在 Suitelet 中执行

Suitelet 只是脚本入口,查询实际由 N/query 模块执行:

/**
 * @NApiVersion 2.1
 * @NScriptType Suitelet
 */
define(['N/query'], (query) => {
    const onRequest = (context) => {
        const invoiceId = Number(context.request.parameters.invoiceId);

        if (!Number.isInteger(invoiceId) || invoiceId <= 0) {
            context.response.write({
                output: JSON.stringify({
                    error: 'A valid invoiceId is required.'
                })
            });
            return;
        }

        const sql = `
            SELECT DISTINCT
                inv.id AS invoice_id,
                inv.tranid AS invoice_number,
                inv.trandate AS invoice_date,
                BUILTIN.DF(inv.entity) AS customer,
                pay.id AS payment_id,
                pay.tranid AS payment_number,
                pay.trandate AS payment_date,
                pay.foreigntotal AS payment_foreign_total,
                link.foreignamount AS applied_foreign_amount,
                link.linktype AS link_type
            FROM
                Transaction inv
            INNER JOIN
                NextTransactionLineLink link
                    ON link.previousdoc = inv.id
            INNER JOIN
                Transaction pay
                    ON pay.id = link.nextdoc
            WHERE
                inv.type = 'CustInvc'
                AND pay.type = 'CustPymt'
                AND inv.id = ?
            ORDER BY
                pay.trandate,
                pay.tranid
        `;

        const results = query.runSuiteQL({
            query: sql,
            params: [invoiceId]
        }).asMappedResults();

        context.response.setHeader({
            name: 'Content-Type',
            value: 'application/json; charset=utf-8'
        });

        context.response.write({
            output: JSON.stringify(results)
        });
    };

    return { onRequest };
});

请求参数必须通过 params 绑定,不能直接拼接到 SQL 中。

查询所有已支付 Invoice

如果只需要查询状态为“已全额支付”的 Invoice,可以加入状态条件。由于内部状态值和显示方式可能受记录类型及账户环境影响,建议先查询当前账户实际返回的值:

SELECT DISTINCT
    status,
    BUILTIN.DF(status) AS status_name
FROM
    Transaction
WHERE
    type = 'CustInvc'

确认结果后再添加条件,例如:

AND inv.status = 'CustInvc:B'

CustInvc:B 通常表示 Paid In Full,但在当前账户中验证之前,不应认为这个值在所有环境中都固定不变。

还要注意,“已支付”和“存在付款交易”并不是同一个条件:

  • Invoice 可能只支付了一部分。此时已经存在 Customer Payment,但尚未全额支付。
  • 一张 Invoice 可能由多笔 Customer Payment 结清。
  • Invoice 也可能通过 Credit Memo、Customer Deposit 或其他抵扣方式结清。
  • 如果后续取消应用或重新应用,交易关系也可能发生变化。

因此,要查询“所有应用记录”,不能只看 Invoice 状态,还应以交易关联表中的应用关系为准。

反向查询方式

如果报表以 Customer Payment 为主,可以通过 PreviousTransactionLineLink 反查 Payment 应用到了哪些 Invoice:

SELECT DISTINCT
    pay.id AS payment_id,
    pay.tranid AS payment_number,
    pay.trandate AS payment_date,

    inv.id AS invoice_id,
    inv.tranid AS invoice_number,
    inv.trandate AS invoice_date,

    link.foreignamount AS applied_foreign_amount,
    link.linktype AS link_type
FROM
    Transaction pay
INNER JOIN
    PreviousTransactionLineLink link
        ON link.nextdoc = pay.id
INNER JOIN
    Transaction inv
        ON inv.id = link.previousdoc
WHERE
    pay.type = 'CustPymt'
    AND inv.type = 'CustInvc'
ORDER BY
    pay.trandate,
    pay.tranid,
    inv.tranid

两种查询方向处理的是同一类交易关系。具体使用哪一种,取决于报表按 Invoice 还是按 Payment 展开。

与 Realized Exchange Gain/Loss 报表的区别

只查询 Invoice 与 Customer Payment 的关联,不能保证所得数据与 Custom Realized Exchange Gain/Loss 报表完全一致。

已实现汇兑损益通常还涉及:

  • Invoice 的交易币种和汇率
  • Payment 的交易币种和汇率
  • 实际应用金额
  • Invoice 日期与 Payment 日期对应的汇率差异
  • 子公司本位币
  • 多账簿会计
  • Credit Memo、Deposit Application 等其他结算交易
  • 记账行以及系统生成的汇兑损益行

如果需要核对总账层面的已实现汇兑损益,还要研究以下记录:

TransactionAccountingLine
TransactionLine
AccountingBook
AccountingPeriod

可以先查询付款交易产生的会计分录:

SELECT
    t.id AS transaction_id,
    t.tranid,
    t.trandate,
    BUILTIN.DF(t.type) AS transaction_type,
    BUILTIN.DF(tal.account) AS account_name,
    tal.debit,
    tal.credit,
    tal.amount
FROM
    Transaction t
INNER JOIN
    TransactionLine tl
        ON tl.transaction = t.id
INNER JOIN
    TransactionAccountingLine tal
        ON tal.transaction = tl.transaction
        AND tal.transactionline = tl.id
WHERE
    t.id = ?
ORDER BY
    tl.id,
    tal.account

具体字段是否可用,以及是否需要增加 accountingbook 等条件,应根据当前账户的 Records Catalog 和会计配置确定。

注意事项

  • UI 中的 Related List 不能作为 SuiteQL 表或字段直接查询。
  • 使用交易链接表时可能得到重复记录,因为关联关系可能发生在行级。可以使用 DISTINCT,也可以按 previousdoc、nextdoc、linktype 汇总。
  • 不能直接用 Payment 的 foreigntotal 代替应用到某张 Invoice 的金额,因为一笔 Payment 可能同时支付多张 Invoice。
  • 如果还需要包括 Credit Memo 等结算方式,应扩展后续交易类型,不能只筛选 CustPymt。
  • SuiteQL 查询结果受执行 Suitelet 的角色权限影响。如果某些交易或会计字段无法查询,应先检查角色权限和 N/query 的元数据权限模式。
  • 大批量报表应使用 query.runSuiteQLPaged() 分页,避免依赖单次查询的结果数量上限。
  • 要复刻标准汇兑损益报表,建议先建立包含 Invoice、应用交易和应用金额的明细,再与会计分录中的汇兑损益科目逐笔核对。只根据交易头金额自行计算,通常无法完整覆盖 NetSuite 的会计处理。

备注:内容仅供参考。