PAYATHON 2026

Odoo 16 模型关联时 self.id 显示 _unknown(43,)

支付阿杰

结论

_unknown(43,) 是因为 Many2one 指向了一个不存在的模型:

fields.Many2one('account.register.payment')

Odoo 16 中正确的模型技术名称是:

account.payment.register

但只改正模型名称,还不足以解决整个审批流程的问题。account.payment 是持久模型,account.payment.register 则是 TransientModel。系统会定期清理 Wizard 记录,因此它不适合作为付款记录的长期关联对象。Odoo 也不允许持久模型通过 Many2one 稳定地指向瞬态模型。

这个流程应改为:

  • Wizard 通过 payment_id 临时指向 account.payment。
  • 不要在 account.payment 上保存指向 Wizard 的 Many2one。
  • 将审批完成后需要执行的业务逻辑放到持久模型中,不要在稍后重新调用原 Wizard。
  • 如果需要追踪来源,可以保存原始发票、会计分录、付款批次,或者使用自定义的持久审批记录。

为什么会出现 _unknown(43,)

创建 Many2one 字段时,Odoo 会根据字段声明中的模型技术名称查找关联模型:

related_payment_id = fields.Many2one(
    'account.register.payment',
    string='Related Wizard',
)

这里误将 account.payment.register 写成了 account.register.payment。ORM 找不到对应模型时,会使用内部的 _unknown 占位模型。数据库中可能保存了整数 43,但读取结果不是正常的 Wizard recordset,而是:

_unknown(43,)

先检查并改正所有相关的模型名称:

'account.payment.register'

模块的 __manifest__.py 也必须依赖定义该模型的模块,通常至少需要:

'depends': ['account']

修改字段定义后,重启 Odoo 并升级自定义模块,让注册表和数据库字段元数据重新加载。

为什么不能长期保存 Wizard 的关联

account.payment.register 的定义如下:

class AccountPaymentRegister(models.TransientModel):
    _name = 'account.payment.register'

TransientModel 用于短期交互,有以下特点:

  • Wizard 关闭后,它的记录不代表正式业务数据。
  • 自动清理任务可能删除这些记录。
  • 审批可能在数小时或数天后才完成,此时原 Wizard 很可能已经不存在。
  • Wizard 的 action_create_payments() 通常依赖创建时的上下文,例如 active_model、active_ids 和待处理的会计分录行。
  • 持久模型不应通过 Many2one 指向瞬态模型。

因此,审批结束后再查找原 Wizard 并调用 action_create_payments(),并不是可靠的流程。即使短时间测试可以运行,之后仍可能因为 Wizard 被清理或上下文丢失而失败。

推荐实现:把审批数据保存在付款记录上

Wizard 只负责收集输入,并创建待审批的持久记录。审批通过后,后续处理直接在 account.payment 或自定义持久审批模型上完成。

Wizard 只需保留指向付款的字段:

from odoo import fields, models


class CustomPaymentRegister(models.TransientModel):
    _inherit = 'account.payment.register'

    payment_id = fields.Many2one(
        'account.payment',
        string='Created Payment',
        readonly=True,
    )

    def action_submit_for_approval(self):
        self.ensure_one()

        payment_vals = self._collect_payment_vals()
        payment = self.env['account.payment'].create(payment_vals)

        self.payment_id = payment
        payment.action_submit_for_approval()

        return {
            'type': 'ir.actions.act_window',
            'res_model': 'account.payment',
            'res_id': payment.id,
            'view_mode': 'form',
            'target': 'current',
        }

后续流程需要的数据保存在 account.payment 上,例如原始会计分录:

from odoo import fields, models


class CustomPayment(models.Model):
    _inherit = 'account.payment'

    source_move_id = fields.Many2one(
        'account.move',
        string='Source Invoice',
        readonly=True,
        copy=False,
    )

    approval_state = fields.Selection(
        [
            ('draft', 'Draft'),
            ('waiting_approval', 'Waiting Approval'),
            ('approved', 'Approved'),
            ('rejected', 'Rejected'),
            ('posted', 'Posted'),
        ],
        default='draft',
        required=True,
        copy=False,
    )

如果一次付款可能涉及多张发票,保存 account.move.line 更合适:

source_line_ids = fields.Many2many(
    'account.move.line',
    string='Source Journal Items',
    copy=False,
)

这些都是持久记录,不会像 Wizard 一样被系统自动清理。

审批完成后不要重新调用原 Wizard

柜员审批方法应直接处理持久付款记录:

def action_cashier_approval(self):
    self.ensure_one()

    company = self.env.company
    if self.env.user not in company.po_fourth_approver_ids:
        raise ValidationError(
            _("Seul %s peut approver !")
            % company.po_fourth_approver_ids.display_name
        )

    if self.approval_state != 'approved':
        raise ValidationError(_("The payment has not completed approval."))

    # 根据实际业务调用 account.payment 上的正式动作。
    # 具体方法应以当前 Odoo 16 安装及自定义模块中的实现为准。
    self.action_post()
    self.approval_state = 'posted'

如果业务要求审批通过后才能创建付款,可以先建立一个自定义的持久审批模型:

class PaymentApproval(models.Model):
    _name = 'payment.approval'
    _description = 'Payment Approval'

    amount = fields.Monetary(required=True)
    currency_id = fields.Many2one('res.currency', required=True)
    partner_id = fields.Many2one('res.partner', required=True)
    journal_id = fields.Many2one('account.journal', required=True)
    source_line_ids = fields.Many2many('account.move.line')
    payment_id = fields.Many2one('account.payment', readonly=True, copy=False)
    state = fields.Selection(
        [
            ('draft', 'Draft'),
            ('waiting', 'Waiting Approval'),
            ('approved', 'Approved'),
            ('rejected', 'Rejected'),
            ('done', 'Done'),
        ],
        default='draft',
        required=True,
    )

由 Wizard 创建 payment.approval。审批完成后,再通过这个持久模型创建和处理 account.payment,整个流程便不再依赖可能已经消失的 Wizard。

如果只想修正当前字段

从模型名称来看,正确的字段声明是:

related_payment_id = fields.Many2one(
    'account.payment.register',
    string='Related Wizard',
)

读取字段时也不需要再次调用 browse():

related_payment = self.related_payment_id

if related_payment.exists():
    related_payment.action_create_payments()

以下写法没有必要:

self.env['account.payment.register'].browse(self.related_payment_id)

因为 self.related_payment_id 本身已经是 recordset。只有手中的是整数 ID 时,才需要使用:

wizard = self.env['account.payment.register'].browse(wizard_id)

不过,持久模型到 TransientModel 的 Many2one 不适合作为最终方案。即使改正了模型名称,仍可能遇到持久模型不能关联瞬态模型的注册错误。

如果必须临时保存 Wizard ID

如果流程非常短暂、同步执行,并且确定不会跨越自动清理周期,可以保存一个普通整数:

wizard_id = fields.Integer(
    string='Temporary Wizard ID',
    copy=False,
)

赋值:

payment.wizard_id = self.id

读取:

wizard = self.env['account.payment.register'].browse(self.wizard_id).exists()

if not wizard:
    raise UserError(_("The original payment wizard no longer exists."))

这种方法只能避开 Many2one 引发的 _unknown 问题,无法保证 Wizard 仍然存在,也无法恢复创建 Wizard 时的上下文。对于需要人工审批的异步流程,不建议这样处理。

还需要修正的两个风险

不要覆盖 account.payment.state

account.payment 已经有核心字段 state。如果重新声明:

state = fields.Selection([...])

可能会破坏付款过账、取消、界面状态以及其他会计逻辑。审批状态应使用单独的字段,例如:

approval_state = fields.Selection(...)

会计状态仍由 Odoo 原生的 state 管理。

不要手工拼出不完整的付款参数

当前 _collect_payment_vals() 只收集了部分字段。创建 account.payment 时,合法参数会受到付款方向、合作伙伴类型、目标账户、币种和付款方式等因素影响。account.payment.register 已经包含批次计算和付款创建逻辑,只复制几个界面字段,不一定等同于标准的 action_create_payments()。

如果审批必须发生在付款创建之前,应保存一份持久的付款申请。审批通过后,再复用或适配 Odoo 16 的标准付款创建流程,不要假设原 Wizard 在整个审批期间都会存在。

归纳起来,错误的模型技术名称导致了 _unknown。要让流程长期可靠,还需要取消 account.payment → account.payment.register 的长期关联,并将审批及后续付款逻辑放到持久模型中。

备注:内容仅供参考。