多公司一致性

Odoo 的 _check_company_auto 究竟拦住了什么:多公司关联字段的一道自动护栏

很多人知道字段上能写 check_company=True,却没真正搞明白什么时候会自动报错、什么时候只是界面 domain 收窄。本文结合 odoo/orm/models.py 和 fields_relational.py,讲清 _check_company_auto、_check_company 与 check_company=True 是怎么一起工作的。

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

结论先行

在 Odoo 多公司开发里,check_company=True 不是完整答案。

真正的组合关系是:

  • 字段上 check_company=True:告诉系统“这个关联字段需要做公司一致性检查”
  • 模型上 _check_company_auto = True:告诉 ORM“在 create / write 后自动调用 _check_company()
  • 字段描述阶段的 domain 注入:让界面下拉先尽量只看到合法候选
  • 后端 _check_company():兜底拦截跨公司脏数据

所以它不是单点机制,而是 前端收窄 + 后端校验 + ORM 自动触发 三层一起工作。


第一层:很多人只记住了字段上的 check_company=True

/home/ubuntu/odoo-temp/odoo/orm/fields_relational.py 里,关联字段如果带 check_company=True,Odoo 会在字段描述里自动拼公司 domain。

核心逻辑大意是:

  • 如果模型有 company_id,就按当前记录的 company_id 限定可选值
  • 如果模型有 company_ids,就按这些公司限定
  • 如果是 company_dependent 属性字段,则按当前环境公司限定
  • 还会允许 company_id = False 的共享记录

这一步的意义是:

先尽量别让用户在界面上选到明显不合法的记录。

但这还不够,因为:

  • 导入可以绕开部分界面限制
  • Python 代码可以直接写值
  • RPC 调用也可能送来不一致的数据

所以前端 domain 只是第一道筛子,不是最终裁判。


第二层:真正拍板的是 _check_company()

/home/ubuntu/odoo-temp/odoo/orm/models.py 里,_check_company() 会扫描当前模型里带 check_company=True 的关联字段。

它大致做两类检查:

1)普通关联字段

如果记录本身有 company_id,就要求关联过去的记录满足:

  • company_id = False
  • company_id = 当前记录公司

也就是常说的:

共享记录可以挂,自己公司记录可以挂,别家公司记录不行。

2)company_dependent / property 字段

这类字段不是按“当前业务记录所属公司”判断,而是按“当前环境公司”判断。

源码里专门把普通字段和 property 字段分成两路处理,这也是很多人第一次读源码才意识到的点。


第三层:_check_company_auto = True 才会自动触发

模型类属性里默认是:

_check_company_auto: bool = False

也就是说,不是所有模型都会在 create / write 后自动跑公司一致性检查

后面 create / write 流程里可以看到两处关键钩子:

if self._check_company_auto:
    self._check_company(list(vals))

以及:

if self._check_company_auto:
    records._check_company()

这意味着:

  • 你给字段写了 check_company=True
  • 但模型没开 _check_company_auto
  • 那自动兜底检查未必会按你想象发生

这是很多自定义模块多公司 bug 的源头之一。


一个很容易踩的误区:界面能选,不代表后端一定能过;界面不能选,也不代表代码层完全没机会写进去

因为这套机制本来就是分层的:

  • 字段 domain 负责“尽量别选错”
  • _check_company() 负责“就算你绕过了 UI,也别想写脏数据”

所以开发里要避免两种误判:

误判 1:下拉里已经过滤了,所以后端不用管

错。导入、脚本、测试、第三方 API 都可能绕开下拉。

误判 2:我在代码里 write 成功过一次,所以字段没问题

也不一定。可能只是模型没启 _check_company_auto,或者你那次运行环境正好用了共享记录。


为什么 company_id=False 的共享记录常常能通过

看源码你会发现,_check_company_domain() 默认会把 False 也放进允许集合:

return Domain('company_id', 'in', to_record_ids(companies) + [False])

这背后的设计意图很明确:

  • 有些主数据就是全公司共享
  • 不应该因为多公司校验把共享配置全拦死

所以你看到某些税、单位、参数、模板能跨公司挂,不一定是漏洞,而可能是它本来就是共享记录。


实战开发里最该记住的 4 件事

1)check_company=True 主要是“字段声明”

它声明这个字段需要公司一致性语义。

2)_check_company_auto=True 才是“自动执法开关”

没开它,很多人期待的自动校验不会完整发生。

3)前端 domain 和后端一致性校验不是一回事

前者偏体验,后者偏数据正确性。

4)property/company_dependent 字段要单独理解

它不是简单跟着业务记录的 company_id 走,而是和当前环境公司有关系。


一个够用的排查顺序

如果你遇到跨公司关联报错,建议按这个顺序查:

  1. 字段有没有 check_company=True
  2. 模型有没有 _check_company_auto=True
  3. 相关记录的 company_id 是不是别家公司
  4. 该关联对象是不是共享记录(company_id=False
  5. 当前是不是 property/company_dependent 场景
  6. 是界面选值报错,还是后端 create/write 报错

这几步一过,问题一般就不神秘了。


总结

Odoo 的多公司一致性机制,不是某一个布尔参数的魔法,而是一套配合关系:

  • 字段用 check_company=True 声明约束语义
  • 字段描述自动注入公司 domain,先收窄候选
  • 模型用 _check_company_auto=True 开启 ORM 自动检查
  • _check_company() 在后端真正拦下跨公司脏关联

真正理解这套链路之后,你就不会再把“下拉过滤”“共享记录”“property 字段”“后端校验”混成一团了。

DISCUSSION

评论区

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