一句话先说透
company_dependent=True 的字段,不是“每个公司一列”,也不是“切公司时动态算一遍”。
它更接近:
同一个字段按公司保存值;当前公司没值时,再回退到该公司的默认值。
很多多公司 bug,不是权限问题,而是开发者没有把这个读取链想清楚。
第一层:company_dependent 不是任意字段都能开
在 odoo/orm/fields.py 的 _get_attrs() 里,Odoo 会对 company_dependent 做一串约束检查:
- 不能
required - 不能
translate - 类型必须在允许范围里
- 默认
copy=False - 默认加索引
prefetch走company_dependent_depends_context自动包含company
这几条其实已经暴露出它的设计意图:
1)它不是普通业务字段
因为值不再只依赖记录本身,还依赖当前公司上下文。
2)它不能要求“永远有值”
因为当前公司可能根本没配置专属值,只能走 fallback。
第二层:底层列类型变成了 jsonb
源码里 column_type 对 company-dependent 字段直接返回 ('jsonb', 'jsonb')。
这很关键。
它意味着数据库层不是:
- company A 一列
- company B 一列
而是更像:
- 同一列里按公司 id 存一份映射
所以它本质是“一个字段 + 按公司切片的值”,而不是“多个字段拼装出来的幻象”。
第三层:当前公司没值时,Odoo 会走 fallback
get_company_dependent_fallback() 的逻辑非常值得记住:
- 它会去
ir.default - 用
SUPERUSER_ID - 且明确
with_company(records.env.company) - 读取该模型在当前公司下的默认值
这说明 company-dependent 字段不是简单的“没配置就 False”。
而是:
- 先看该记录在当前公司有没有专属值
- 没有的话,再看当前公司的模型默认值
所以你切公司后看到值变了,不一定是这条记录被改了,可能只是 fallback 来源变了。
第四层:读 SQL 时其实就是 COALESCE 当前公司值 + fallback
在 to_sql() 里,Odoo 对 company-dependent 字段会生成一段关键逻辑:
- 先从 jsonb 里取当前 company id 对应的值
- 取不到,就
COALESCE到 fallback
这个设计特别像应用层常见的配置继承:
- 有本公司专属值 → 用专属值
- 没有 → 用该公司的默认值
所以搜索、排序、读组这些看似“数据库动作”的地方,实际也会受到当前公司上下文影响。
这就是为什么两个公司打开同一列表,看到的筛选结果可能不一样。
第五层:最容易误判的不是“写错”,而是“读对了但理解错了”
比如一个价格、科目、账户属性或策略字段,在 A 公司和 B 公司显示不同。
很多人第一反应是:
- 是不是谁写脏了
- 是不是缓存乱了
- 是不是 record rule 过滤了
但更常见的真相是:
- A 公司这条记录有显式值
- B 公司没有显式值,只读到了默认值
从界面上看,两边都“像是字段自己的值”,其实来源不同。
开发实战里最容易踩的 3 个坑
坑 1:忘了 company 上下文
如果你的逻辑在后台任务、cron、sudo 流程里跑,当前 env.company 和用户界面可能不是同一个。
结果就是:
- 用户在界面看到的是公司 A 的值
- 代码在后台读到的是公司 B 的值
表面像“字段自己跳了”,实则是上下文不同。
坑 2:把 company_dependent 当成全局稳定值去缓存
因为它依赖 company,上下文不同,值就可能不同。
所以这类字段天然不适合被你自己做“跨公司静态缓存”。
坑 3:以为没专属值就是 False
真实逻辑可能是 fallback 到 ir.default。如果你调试时只查记录本身,却不查默认值来源,就会得出错误结论。
排错时该怎么查
如果一个 company-dependent 字段在多公司表现异常,按这个顺序最快:
- 当前代码运行时
env.company是谁 - 这条记录在该公司是否已有显式值
- 当前公司下
ir.default是否设置了 fallback - 查询 / 排序是否发生在不同公司上下文里
- 是否有 sudo / with_company / allowed_company_ids 改写了环境
这比一上来翻权限规则高效得多。
适合拿来做什么,不适合拿来做什么
适合
- 多公司独立配置
- 同模型在不同公司有不同财务 / 策略属性
- 希望“未配置时走公司默认值”的字段
不适合
- 要求全局绝对一致的字段
- 强依赖 required 语义的字段
- 你自己都无法解释“当前公司到底是谁”的流程
结论
company-dependent 字段最值得记住的,不是“它支持多公司”,而是:
它把字段值变成了“记录 + 当前公司 + 默认值回退”三者共同决定的结果。
一旦你把它当成普通列去理解,就会不断遇到“明明是同一条记录,为什么显示不一样”的诡异问题。
DISCUSSION
评论区