通过 API 将指定 Outstanding payment 添加到 Odoo 发票
结论
在 Odoo 15 中,“Ajouter / Add”按钮调用的是 account.move 模型的以下方法:
js_assign_outstanding_line(line_id)
这里的 line_id 指的不是 account.payment ID,而是付款生成的应收或应付 account.move.line ID。
完整的处理步骤如下:
- 确认 payment 已过账;
- 确认 invoice 已过账;
- 找到 invoice 的应收或应付科目;
- 在 payment 对应的会计分录中,找到相同科目的未核销行;
- 在 invoice 上调用
js_assign_outstanding_line。
XML-RPC 示例
以下示例假设 XML-RPC 已连接,并且已经取得:
models:xmlrpc.client.ServerProxy;db、uid、password;payment_id;invoice_id。
# 1. 读取付款及其对应的会计分录
payment = models.execute_kw(
db,
uid,
password,
'account.payment',
'read',
[[payment_id]],
{
'fields': ['state', 'move_id', 'partner_id', 'company_id']
}
)[0]
if payment['state'] != 'posted':
models.execute_kw(
db,
uid,
password,
'account.payment',
'action_post',
[[payment_id]]
)
payment_move_id = payment['move_id'][0]
接着查找 invoice 的应收或应付行:
invoice_lines = models.execute_kw(
db,
uid,
password,
'account.move.line',
'search_read',
[[
('move_id', '=', invoice_id),
('account_id.user_type_id.type', 'in', ['receivable', 'payable']),
]],
{
'fields': [
'id',
'account_id',
'partner_id',
'company_id',
'reconciled',
'amount_residual',
],
'limit': 1,
}
)
if not invoice_lines:
raise RuntimeError(
'未找到 invoice 的应收/应付行,请确认 invoice 已过账'
)
invoice_line = invoice_lines[0]
invoice_account_id = invoice_line['account_id'][0]
再到 payment 的会计分录中查找同一科目的未核销行:
payment_lines = models.execute_kw(
db,
uid,
password,
'account.move.line',
'search_read',
[[
('move_id', '=', payment_move_id),
('account_id', '=', invoice_account_id),
('reconciled', '=', False),
]],
{
'fields': [
'id',
'account_id',
'partner_id',
'company_id',
'debit',
'credit',
'amount_residual',
'amount_residual_currency',
],
}
)
payment_lines = [
line
for line in payment_lines
if line['amount_residual'] != 0
]
if len(payment_lines) != 1:
raise RuntimeError(
f'无法唯一确定付款的 outstanding line,找到 {len(payment_lines)} 条候选记录'
)
outstanding_line_id = payment_lines[0]['id']
最后,模拟界面上的“Ajouter”操作:
result = models.execute_kw(
db,
uid,
password,
'account.move',
'js_assign_outstanding_line',
[[invoice_id], outstanding_line_id]
)
print(result)
其中最核心的调用可以简化为:
models.execute_kw(
db,
uid,
password,
'account.move',
'js_assign_outstanding_line',
[[invoice_id], outstanding_line_id]
)
为什么不能直接传 payment_id
account.payment 是付款业务对象。付款过账后,Odoo 会生成相应的会计分录,通常包括:
- 银行或现金科目行;
- 客户应收或供应商应付科目行。
发票核销使用的是第二类会计行,也就是 account.move.line。界面中的 outstanding credits 列表展示的同样是这些尚未核销的应收或应付行,而不是 account.payment 记录。
对应的调用关系如下:
account.payment
→ move_id
→ account.move.line
→ js_assign_outstanding_line(line_id)
使用前提
付款和发票通常需要满足这些条件:
- 两者都已过账,状态为
posted; - 属于同一公司;
- 客户或供应商一致;
- 使用相同的应收或应付科目;
- payment 的会计行仍有未核销余额;
- payment 与 invoice 的余额方向相反,可以互相核销;
- 当前 XML-RPC 用户具有相应的会计读取和核销权限。
调用前还可以核对客户和公司:
payment_line = payment_lines[0]
if payment_line['company_id'][0] != invoice_line['company_id'][0]:
raise RuntimeError('付款和发票不属于同一公司')
if payment_line['partner_id'][0] != invoice_line['partner_id'][0]:
raise RuntimeError('付款和发票的客户或供应商不一致')
如果系统使用商业伙伴层级,还需要根据实际配置比较 commercial_partner_id,以免联系人与母公司的关系导致误判。
注意事项
js_assign_outstanding_line 是 Odoo 15 中供发票界面调用的服务端方法。它会将指定的 outstanding line 与发票上同一应收或应付科目的未核销行进行 reconciliation。
不要直接操作 invoice_payments_widget 或 invoice_outstanding_credits_debits_widget。这些字段主要用于界面展示,通常是计算字段,并不是添加付款的写入入口。
付款已经部分核销时,仍可使用剩余金额,但最终可能只完成部分核销。如果币种不同、存在汇率差异或金额不完全相等,Odoo 还可能生成部分核销关系或汇兑差额分录。调用完成后,应重新读取 invoice 的以下字段:
payment_state
amount_residual
invoice_payments_widget
根据这些字段确认最终的核销状态。
备注:内容仅供参考。
