多公司例外

Odoo 多公司里为什么共享记录最容易让人误判:_check_company_domain、company_id=False 与 root company 例外

不再泛讲 _check_company_auto,而是只解释 _check_company_domain 怎么把 company_id=False 共享记录、res.company 自身记录与普通业务单据区分开,帮助开发者读懂哪些跨公司关联其实是被设计允许的。

Odoo 开发 框架
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

先把问题收窄

很多人一看到多公司校验,就会把它理解成一句话:

只允许当前公司记录,别家公司一律不行。

这句话只对了一半。

真正让新手最容易误判的,不是 _check_company_auto 有没有开,而是 Odoo 明明在做公司一致性校验,却又故意放行两类看起来像“例外”的记录

  • company_id = False 的共享记录
  • res.company 自身的一些特殊场景

如果你没把这两个口子看懂,项目里就会经常出现两种相反误判:

  • 以为系统漏拦了,其实那是官方允许的共享主数据
  • 以为某条关系一定能挂,结果 root company 约束又把你挡回来了

这篇只讲这一件事:_check_company_domain() 到底把谁放进“合法集合”,以及这个集合为什么不是你想象中的“只有当前公司”。

_check_company_domain() 真正返回的是什么

odoo/orm/models.py 里,_check_company_domain() 的默认逻辑非常短:

  • 没有 company 时,只接受 company_id = False
  • 有 company 时,接受 company_id in allowed_companies + [False]

也就是说,Odoo 的默认答案从一开始就不是:

  • 只能等于当前公司

而是:

  • 当前允许公司可以
  • company_id=False 也可以

这就是很多共享主数据能跨公司挂接的根本原因。

比如某些全局模板、公共配置、共享基础资料,看起来像“没公司”,实际上是 被框架明确定义成可跨公司引用

所以你在调试时看到一条记录跨公司没报错,第一反应不该是“权限失效了”,而应该先问:

这条记录是不是本来就属于共享记录语义?

为什么 company_id=False 不是漏洞

很多实现者对 False 有天然警惕,觉得“没公司归属”就是脏数据。

但在 Odoo 多公司设计里,False 很多时候是一个明确业务语义:

  • 这不是“丢了公司”
  • 而是“对所有公司共享”

所以 _check_company_domain()[False] 加到允许集合,不是放水,而是框架层面的折中:

  • 既保护公司隔离
  • 又不把公共主数据全部切碎

如果没有这条规则,很多跨公司共用的基础对象都会变得非常难用; 如果你把这条规则忘了,又会误把共享数据当成越权。

_check_company() 为什么仍然会把你拦住

虽然允许集合里有 [False],但 _check_company() 仍然会严格比较:

  • 当前记录所属公司
  • 关联字段实际指向的记录公司
  • 以及该记录是否满足 _check_company_domain()

也就是说,Odoo 并不是“只要 related record 有 company_id 就随便放”。

真正的判定逻辑是:

  1. 先确定当前记录对应哪组公司
  2. 再对关联记录套 _check_company_domain()
  3. 只有落在允许域里的记录才合法

因此下面两种情况会得到不同结果:

  • 关联到别家公司记录 → 一般拦截
  • 关联到共享记录(company_id=False)→ 默认允许

看似都不是“本公司记录”,但语义完全不同。

res.company 为什么又是一个特殊分支

fields_relational.py_description_domain() 里还有一个很容易被忽略的细节:

  • 如果字段所在模型本身就是 res.company
  • 生成的 company domain 会直接用当前记录的 id

也就是源码注释写的那个意思:

  • res.company 上使用 check_company=True
  • company 不是来自 company_id 字段
  • 而是来自当前公司记录本身

这说明 Odoo 没有把 res.company 当成普通业务模型处理。

因为公司模型自己就是“公司边界”的定义者,它不能再机械地依赖一个额外的 company_id 字段来判定自己属于谁。

这也是为什么多公司开发里,公司主数据自身的关系字段 往往不能用普通业务单据那套直觉去理解。

前端 domain 为什么也会体现这个例外

很多人只在后端 _check_company() 里找答案,但 fields_relational.py_description_domain() 其实会把相同思路提前注入到字段描述里。

如果模型有 company_idcompany_ids,它会生成类似:

  • 有公司时用 _check_company_domain(公司集合)
  • 没公司时退回 _check_company_domain('')

这个设计的意义是:

  • 前端下拉候选尽量别把明显不合法的记录展示出来
  • 但共享记录仍应出现在可选列表里

所以你在界面里看到“没有 company_id 的记录也能选”,不要立刻怀疑 domain 写错了。很多时候,这正是 Odoo 对共享记录的标准行为。

最容易踩的 3 个误判

1)把 company_id=False 当成脏数据

不一定。它可能是共享记录的正式语义。

2)把 res.company 上的关系字段当普通业务字段看

不行。公司模型自己就是边界定义者,源码里有单独分支。

3)看到前端 domain 放行共享记录,就以为后端不会校验

也不对。后端仍然会用 _check_company() 兜底,只是允许集合本来就包含共享记录。

一套更靠谱的排查顺序

如果你遇到“这条跨公司关系到底该不该过”,建议这样查:

  1. 关联字段是否 check_company=True
  2. 关联对象的 company_id 是当前公司、别家公司,还是 False
  3. 当前模型是不是 res.company
  4. 字段描述里的默认 domain 有没有把共享记录放进候选
  5. 最终 _check_company() 套上的 allowed companies 是哪一组

按这个顺序查,很多“看起来像特例”的行为都会变得很可解释。

总结

Odoo 多公司校验里最容易让人误判的,不是“为什么它会报错”,而是:

  • 为什么有些跨公司关系居然没报错
  • 为什么 company_id=False 反而是合法候选
  • 为什么 res.company 自己又像走了另一套路

如果只记一句,就记这句:

_check_company_domain() 从来就不是“只允许当前公司”,而是“允许当前公司 + 共享记录,并在公司模型上保留特殊语义”。

把这层边界看清以后,多公司关系里的很多“例外”,其实都不是例外,而是设计本意。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。