如何通过 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 的会计处理。
备注:内容仅供参考。
