如何在 Odoo v15 CE 中用 Python 为 invoice 注册 payment
结论
在 Odoo v15 CE 中,account.move.action_register_payment() 只会返回“注册付款”向导对应的界面动作,不会直接创建付款。
通过 Python 自动注册付款时,需要:
- 创建并过账 invoice;
- 将该 invoice 放入
active_ids上下文,并创建account.payment.register临时记录; - 调用向导的
action_create_payments()。
核心代码如下:
payment_register = self.env['account.payment.register'].with_context(
active_model='account.move',
active_id=invoice.id,
active_ids=invoice.ids,
).create({
'journal_id': payment_journal.id,
'payment_date': fields.Date.context_today(self),
})
payment_register.action_create_payments()
为什么不能直接调用 action_register_payment()
account.move 上的 action_register_payment() 是一个 UI action,负责准备窗口动作和上下文,供 Odoo 前端打开 account.payment.register 向导。
例如:
invoice.action_register_payment()
这段代码只会返回一个 action 字典,不会创建 account.payment,也不会核销 invoice。
付款的创建、过账以及应收账款核销,实际由 account.payment.register 向导完成。
完整实现示例
下面的示例会为每个销售订单创建 invoice,将草稿 invoice 过账,再按剩余应付金额登记全额付款。
from odoo import fields, models
from odoo.exceptions import UserError
class SaleInvoicePaymentWizard(models.TransientModel):
_name = 'sale.invoice.payment.wizard'
_description = 'Create Invoice and Register Payment'
def create_invoices(self):
sale_orders = self.env['sale.order'].browse(
self.env.context.get('active_ids', [])
)
payment_journal = self.env['account.journal'].search([
('type', 'in', ('bank', 'cash')),
('company_id', '=', self.env.company.id),
], limit=1)
if not payment_journal:
raise UserError('未找到可用于收款的银行或现金日记账。')
for order in sale_orders:
invoices = order._create_invoices()
for invoice in invoices:
if invoice.state == 'draft':
invoice.action_post()
if invoice.state != 'posted':
continue
if invoice.payment_state in ('paid', 'in_payment'):
continue
if not invoice.amount_residual:
continue
payment_register = self.env[
'account.payment.register'
].with_context(
active_model='account.move',
active_id=invoice.id,
active_ids=invoice.ids,
dont_redirect_to_payments=True,
).create({
'journal_id': payment_journal.id,
'payment_date': fields.Date.context_today(self),
})
payment_register.action_create_payments()
return {'type': 'ir.actions.act_window_close'}
这里不需要显式传入 amount。account.payment.register 会根据上下文中的 invoice 计算默认付款金额,通常就是 amount_residual。让向导自行计算,也能减少退款、外币和部分核销场景中的正负号问题。
如果业务明确要求指定付款金额,也可以传入:
payment_register = self.env[
'account.payment.register'
].with_context(
active_model='account.move',
active_id=invoice.id,
active_ids=invoice.ids,
dont_redirect_to_payments=True,
).create({
'journal_id': payment_journal.id,
'payment_date': fields.Date.context_today(self),
'amount': invoice.amount_residual,
})
这种写法适合普通客户 invoice 的全额收款。遇到退款、供应商账单或外币付款时,建议优先让向导计算金额。
使用指定的付款方式
Odoo v15 使用 account.payment.method.line 表示日记账上的付款方式。如果银行日记账配置了多个入站付款方式,可以明确传入 payment_method_line_id:
payment_method_line = payment_journal.inbound_payment_method_line_ids[:1]
if not payment_method_line:
raise UserError('所选日记账没有配置入站付款方式。')
payment_register = self.env[
'account.payment.register'
].with_context(
active_model='account.move',
active_id=invoice.id,
active_ids=invoice.ids,
dont_redirect_to_payments=True,
).create({
'journal_id': payment_journal.id,
'payment_method_line_id': payment_method_line.id,
'payment_date': fields.Date.context_today(self),
})
payment_register.action_create_payments()
客户 invoice 一般使用 inbound_payment_method_line_ids,供应商账单付款通常使用 outbound_payment_method_line_ids。
也可以复用 action_register_payment() 的上下文
如果想沿用 Odoo 原生 action 生成的上下文,可以先取得 action,再创建向导:
action = invoice.action_register_payment()
payment_context = dict(
self.env.context,
**action.get('context', {})
)
payment_register = self.env[
'account.payment.register'
].with_context(payment_context).create({
'journal_id': payment_journal.id,
'payment_date': fields.Date.context_today(self),
})
payment_register.action_create_payments()
不过在服务端代码中,直接设置 active_model、active_id 和 active_ids 往往更清楚。
注意事项
使用 _create_invoices() 的返回值
不要在创建完成后再次遍历:
order.invoice_ids
这个字段可能包含以前创建的 invoice、退款单或其他历史记录。直接使用本次创建并返回的记录更稳妥:
invoices = order._create_invoices()
必须先过账
account.payment.register 只能处理已经过账,并且存在待核销应收或应付款项的会计凭证。因此,创建向导前必须执行:
invoice.action_post()
日记账必须属于正确公司
在多公司环境中,付款日记账应与 invoice 属于同一家公司。相比固定使用 self.env.company,按 invoice 的公司查找更加严谨:
payment_journal = self.env['account.journal'].search([
('type', 'in', ('bank', 'cash')),
('company_id', '=', invoice.company_id.id),
], limit=1)
不要直接调用私有方法
有些代码示例会调用:
payment_register._create_payments()
这个私有方法在 Odoo v15 中确实参与付款创建,但业务代码更适合调用向导按钮对应的公开方法:
payment_register.action_create_payments()
私有方法以下划线开头,升级时接口更容易发生变化。
注册付款不等于银行对账
上述流程会创建并过账 account.payment,同时核销 invoice 的应收账款。完成全额付款后,invoice 通常会进入 paid 状态,或者进入与当前付款流程配置相符的状态。
这并不一定表示银行流水已经完成对账。如果日记账配置了未结收款账户,后续仍可能需要通过银行对账,将付款与实际银行流水匹配。
备注:内容仅供参考。
