很多人第一次接触 company_dependent=True 时,会把它理解成:
- 这个字段在不同公司有不同值
- 所以数据库里大概就是一列加点公司过滤
这个理解抓到了表面现象,但没抓到 Odoo 真正的设计。
如果你去看 /home/ubuntu/odoo-temp/odoo/orm/fields.py、addons/base/models/ir_property.py,再对照 test_fields.py 里的多公司测试,会发现它背后的核心不是“普通字段按公司分片”,而是:
字段值由 property 存储、字段默认回退、当前公司上下文以及读取/搜索翻译逻辑共同决定。
这也是为什么这类字段总让人觉得“像普通字段,但又不完全像”。
一、company_dependent=True 不是简单列覆盖
从语义上看,它表达的是:
- 同一个业务字段
- 在不同公司下允许拥有不同值
但 Odoo 并没有把这件事做成“在原表多放几列”。
因为这样会马上遇到几个问题:
- 公司数不固定
- 默认值与记录级值怎么合并
- 多公司切换时怎样统一读写接口
- 搜索域怎样表达“当前公司值 + 回退值”
所以框架采用的是 property 机制,而不是暴力扩列。
二、真正的值入口不只一个:当前公司值 + fallback
在 fields.py 相关逻辑里,company dependent 字段并不是永远要求当前公司一定有一条专属值。
Odoo 还会考虑 fallback。
这个 fallback 可能来自:
- 字段自身默认
- 属性默认
- 更通用层级的属性值
这意味着读取 company dependent 字段时,系统脑子里想的不是:
- 当前公司有没有一条值?
而是:
- 当前公司有没有专属值?
- 没有的话,该往哪一级回退?
所以你在多公司现场看到:
- A 公司改了后只影响 A
- B 公司没配时却还能读到一个“像默认值”的结果
这不是偶然,而是回退链设计的一部分。
三、with_company() 改的不只是展示公司,而是读取语义
很多开发者把 with_company() 只当成一种“切换当前公司”的语法糖。
对 property 字段来说,这个动作非常实质。
因为 company dependent 字段的取值,本来就和当前公司上下文绑定。
所以:
- 同一条记录
- 同一个字段
- 在
record.with_company(company_a)下 - 和在
record.with_company(company_b)下
完全可以合法地读出不同结果。
这里最容易误解的一点是:
变的不是记录本身,而是读取这条记录的公司视角。
因此这类字段问题,常常不是“数据写错了”,而是“你在错误的公司上下文里读对了数据”。
四、为什么搜索也不像普通字段那样简单
普通字段搜索,很多人脑中默认模型是:
- 域条件翻成 SQL where
- 直接查业务表列
company dependent 字段没这么直接。
因为你要搜索的不是一列裸值,而是:
- 当前公司是否有 property 值
- 没有时 fallback 是否命中
- 某些情况下默认值是否也应算匹配
也就是说,搜索语义必须先把“字段条件”翻译成“属性解析语义”,再落到查询层。
这也是为什么这类字段出问题时,现象往往很怪:
- 表单里看着是这个值
- 搜索结果却不像你想的那样筛
- 换公司后又像恢复正常
根因不是 UI,而是搜索本来就不是在查一列简单字段。
五、为什么 Odoo 非要这样设计
因为它解决的是一个很现实的问题:
- 字段接口要像普通字段一样用
- 业务上又允许公司间差异
- 同时还要保留默认值 / 回退值语义
如果全交给业务模块自己维护,你就会在每个模型里重复写:
- 当前公司取值
- 默认回退
- 搜索翻译
- 写入覆盖
- 多公司切换
这不仅难统一,还极易出 bug。
所以 Odoo 把它抽成 property 机制,本质上是在用框架换一致性。
六、最常见的误区
误区 1:以为它就是“多公司版普通字段”
表面用法像,底层解析链完全不同。
误区 2:看到不同公司不同值,就以为数据重复写进了业务表
很多时候真正起作用的是 property 存储,不是原表列本身。
误区 3:以为 with_company() 只是界面上下文切换
对 company dependent 字段,它直接改变取值语义。
误区 4:以为搜索不准就是索引问题
很多时候是你把 property 字段按普通列的脑回路去理解了。
七、实战排错顺序
如果你想查“为什么这个字段在不同公司下不一样 / 搜索结果不对”,建议按这个顺序:
- 确认字段是否
company_dependent=True - 确认当前代码运行在哪个公司上下文
- 检查当前公司有没有专属 property 值
- 没有的话,实际命中的 fallback 是什么
- 如果是搜索问题,别只盯业务表列,要看属性解析语义
这个顺序通常比“直接查表看值”更接近真相。
结语
company_dependent 真正值得理解的,不是“一个字段可以因公司不同而不同”这么一句表层结论,而是它背后的设计选择:
- 不把问题粗暴塞进业务表结构
- 用
ir.property承接公司特定值 - 用 fallback 保证未配置公司也有合理结果
- 用上下文公司决定当前读取视角
- 用额外翻译逻辑把搜索语义补完整
所以这类字段从来不是“多公司版普通列”,而是:
一条由 property 存储、公司上下文与回退机制共同完成的字段解析链。
看懂这条链后,多公司字段的大部分“诡异行为”其实都不诡异了。
DISCUSSION
评论区