PAYATHON 2026

Odoo Invoice 跳过验证并直接 Register Payment

支付阿杰

结论

自定义按钮需要按顺序完成两项操作:

  1. 触发发票验证或过账流程,将发票状态从 draft 改为 open。
  2. return invoice_pay_customer() 生成的窗口动作,打开 Register Payment 弹窗。

只调用 invoice_pay_customer() 不会自动验证发票。如果不返回该方法生成的 action,客户端也无法显示付款窗口。

推荐写法

根据代码中的 account.invoice 和旧式方法签名,下面的方案适用于仍采用发票工作流的旧版 Odoo,例如部分 Odoo 8 环境:

from openerp import models
from openerp import netsvc


class AccountInvoice(models.Model):
    _inherit = 'account.invoice'

    def button_custom(self, cr, uid, ids, context=None):
        context = context or {}

        if len(ids) != 1:
            raise Warning('Please select exactly one invoice.')

        workflow = netsvc.LocalService('workflow')
        workflow.trg_validate(
            uid,
            'account.invoice',
            ids[0],
            'invoice_open',
            cr
        )

        return self.invoice_pay_customer(
            cr,
            uid,
            ids,
            context=context
        )

按钮需要使用 type="object":

<button
    name="button_custom"
    string="Validate and Register Payment"
    type="object"
    states="draft"
    class="oe_highlight"
/>

invoice_open 是验证发票的工作流信号。工作流成功执行后,发票会进入 open 状态。随后,invoice_pay_customer() 返回打开付款登记向导所需的 action。

使用新 API 的旧版 Odoo

如果当前版本支持 recordset API,可以这样写:

from openerp import api, models
from openerp.exceptions import UserError


class AccountInvoice(models.Model):
    _inherit = 'account.invoice'

    @api.multi
    def button_custom(self):
        self.ensure_one()

        if self.state == 'draft':
            self.signal_workflow('invoice_open')

        if self.state != 'open':
            raise UserError(
                'The invoice could not be validated. '
                'Register Payment can only be opened for an open invoice.'
            )

        return self.invoice_pay_customer()

不同 Odoo 小版本的工作流接口可能有所不同。如果模型提供 action_invoice_open(),优先调用这个业务方法:

@api.multi
def button_custom(self):
    self.ensure_one()

    if self.state == 'draft':
        self.action_invoice_open()

    if self.state != 'open':
        raise UserError('The invoice could not be validated.')

    return self.invoice_pay_customer()

不要同时调用 action_invoice_open() 和 signal_workflow('invoice_open'),以免重复触发验证逻辑。根据当前版本实际提供的接口选择一种即可。

原代码没有生效的原因

原方法有两个问题:

self.invoice_pay_customer(cr, uid, ids, context=context)

第一个问题是没有把方法的返回值交给 Web 客户端。invoice_pay_customer() 通常会返回一个 ir.actions.act_window 字典,用来打开 Register Payment 弹窗,所以必须写成:

return self.invoice_pay_customer(cr, uid, ids, context=context)

第二个问题是,invoice_pay_customer() 只负责准备付款登记界面,不会把草稿发票验证为 open。如果在草稿状态下直接调用,可能会因状态、会计分录或上下文不完整而无法正常运行。

注意事项

  • 验证发票可能因缺少会计科目、税务配置、账期、发票日期或凭证序列而失败。不要捕获后忽略这些异常,否则可能出现付款界面已经打开,但发票实际上尚未完成过账的情况。
  • Register Payment 向导通常会根据单张发票填写默认金额、币种和往来单位,因此按钮方法最好使用 ensure_one(),或明确要求 len(ids) == 1。
  • account.invoice 是旧版模型。较新的 Odoo 使用 account.move,通常通过 action_post() 完成过账,再通过相应的 payment register action 打开付款向导,不能直接套用上面的代码。
  • 如果继承视图后没有显示按钮,请检查 XML 是否已经加载、按钮是否位于正确的 <header> 中,以及 states="draft" 是否与发票当前状态一致。

备注:内容仅供参考。