Odoo V16 API 创建 Payment on Account 后未计入客户余额
结论
通过 API 直接创建 account.payment 时,Odoo 界面中的 onchange 不会自动补全字段。处理客户收款时,需要确保:
destination_account_id指向该客户的应收账款科目;payment_method_line_id属于目标日记账的 inbound payment method;- 创建付款后调用
action_post正式过账。
客户余额由已过账凭证中的 account.move.line 计算,而不是直接根据 account.payment 记录计算。相关分录行必须带有该客户,并使用应收账款类型的科目。即使付款已经出现在 Payments 和 Journal Entries 中,只要生成的分录没有正确记入客户应收科目,就不会影响 due balance。
原因
在 Odoo 界面中选择客户、日记账和付款类型时,客户端会触发多个 onchange,并自动确定:
- 收款方式;
- 收款日记账对应的 outstanding receipts 或银行科目;
- 客户的应收账款科目;
- 公司和币种;
- 分录行上的客户信息。
通过 XML-RPC、JSON-RPC 或第三方 PHP 封装直接调用 create 时,这些界面层的 onchange 通常不会执行。只传入以下字段:
journal_id
partner_type
payment_type
partner_id
amount
date
虽然可能成功创建付款记录,但不一定能生成与界面操作相同的会计分录。
一笔尚未核销的客户预收款,其分录通常类似:
借:Outstanding Receipts / Bank
贷:Accounts Receivable
贷方的应收账款分录必须带有正确的 partner_id。之后,这笔贷方余额可以作为客户的未分配贷项,用于核销未来的发票。
解决步骤
1. 读取客户的应收账款科目
从 res.partner 读取:
property_account_receivable_id
该字段是 company-dependent property。调用 API 时,应使用与付款日记账相同的公司上下文,否则可能读到其他公司的应收科目。
2. 获取日记账的 inbound payment method line
Odoo 16 使用 payment_method_line_id。需要从 account.payment.method.line 中查找 journal_id = 8 且适用于 inbound payment 的记录。
不要写死属于其他日记账的 payment method line,否则付款分录可能使用错误的中间科目,也可能在过账时被拒绝。
3. 创建付款时显式传入关键字段
下面的 PHP 调用方式需要根据当前 $odoo 封装库调整,字段结构可以写成:
$partner = $odoo->get('res.partner', 'read', array(
array($invoice['partner_id']),
array('property_account_receivable_id')
));
$receivableAccountId =
$partner[0]['property_account_receivable_id'][0];
$methodLineIds = $odoo->get(
'account.payment.method.line',
'search',
array(array(
array('journal_id', '=', 8),
array('payment_type', '=', 'inbound')
))
);
if (empty($methodLineIds)) {
throw new RuntimeException(
'No inbound payment method line is configured for journal 8.'
);
}
$paymentId = $odoo->get('account.payment', 'create', array(array(
'date' => date('Y-m-d', $invoice['date']),
'journal_id' => 8,
'partner_type' => 'customer',
'payment_type' => 'inbound',
'partner_id' => $invoice['partner_id'],
'destination_account_id' => $receivableAccountId,
'payment_method_line_id' => $methodLineIds[0],
'amount' => abs($invoice['amount']),
'ref' => 'Legacy payment on account'
)));
如果日记账配置了多个 inbound payment method line,不要直接取第一条。应根据导入业务选择对应的方式,例如 Manual、Check 或其他本地化支付方式。
4. 通过模型方法过账
付款创建完成后,调用 action_post,不要直接修改 state:
$odoo->get(
'account.payment',
'action_post',
array(array($paymentId))
);
在标准 Odoo 16 中,付款通常会从 draft 进入 posted。如果系统还有额外的 approve 步骤,这通常来自本地化模块或自定义模块。此时应调用该模块定义的正式业务方法,而不是直接写入状态字段。
如何验证分录是否正确
过账后,读取付款关联的 move_id,再检查对应的 line_ids。应收账款分录至少要满足以下条件:
partner_id等于客户;account_id是客户的应收账款科目;- 对于客户 inbound payment,该行通常产生贷方金额;
- 分录状态为
posted; company_id与客户应收科目、付款日记账一致;- 未分配付款的应收行尚未完全核销,即
reconciled = false。
如果付款仍然没有影响余额,可以读取分录行进行对比:
$payment = $odoo->get('account.payment', 'read', array(
array($paymentId),
array('move_id', 'state')
));
$moveId = $payment[0]['move_id'][0];
$lines = $odoo->get('account.move.line', 'search_read', array(
array(array('move_id', '=', $moveId)),
array(
'account_id',
'partner_id',
'debit',
'credit',
'balance',
'amount_residual',
'reconciled'
)
));
将这些分录行与通过界面创建的正常付款逐项比较,通常很快就能找到差异。需要比较的是最终生成的 account.move.line,而不是 account.payment 的表面字段。
注意事项
extract_state 与客户余额入账没有直接关系。它主要用于付款单据提取流程。除非已安装的模块明确要求,否则导入普通付款时通常不需要设置。
如果 destination_account_id 已经正确,但余额仍未变化,可以继续检查:
- API 用户当前公司的
allowed_company_ids; - 客户的
property_account_receivable_id是否属于同一公司; journal_id = 8是否配置了正确的 outstanding receipts account;- 分录的应收行是否带有客户;
- 当前查看的是商业实体还是子联系人的余额;
- 所谓 due balance 是否来自只统计发票的自定义报表。
未分配付款无需绑定 invoice_ids,也能进入客户应收账。它会先作为应收账款贷项存在,之后再通过 reconciliation 与发票核销。付款必须使用正确的收款方式正式过账,并生成带有客户信息的应收账款分录。
备注:内容仅供参考。