结论先行
在 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 走,而是和当前环境公司有关系。
一个够用的排查顺序
如果你遇到跨公司关联报错,建议按这个顺序查:
- 字段有没有
check_company=True - 模型有没有
_check_company_auto=True - 相关记录的
company_id是不是别家公司 - 该关联对象是不是共享记录(
company_id=False) - 当前是不是 property/company_dependent 场景
- 是界面选值报错,还是后端 create/write 报错
这几步一过,问题一般就不神秘了。
总结
Odoo 的多公司一致性机制,不是某一个布尔参数的魔法,而是一套配合关系:
- 字段用
check_company=True声明约束语义 - 字段描述自动注入公司 domain,先收窄候选
- 模型用
_check_company_auto=True开启 ORM 自动检查 _check_company()在后端真正拦下跨公司脏关联
真正理解这套链路之后,你就不会再把“下拉过滤”“共享记录”“property 字段”“后端校验”混成一团了。
DISCUSSION
评论区