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 的长期关联,并将审批及后续付款逻辑放到持久模型中。
备注:内容仅供参考。
