多公司属性字段

Odoo property 字段为什么会“同一字段不同公司不同值”:company_dependent、回退值与多公司读取链

结合 Odoo 19 字段源码,讲清 company_dependent 字段并不是魔法,而是按公司存储并带 fallback 的字段语义。理解它,才能少踩多公司配置串味、默认值错读和搜索结果不一致的坑。

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

一句话先说透

company_dependent=True 的字段,不是“每个公司一列”,也不是“切公司时动态算一遍”。

它更接近:

同一个字段按公司保存值;当前公司没值时,再回退到该公司的默认值。

很多多公司 bug,不是权限问题,而是开发者没有把这个读取链想清楚。


第一层:company_dependent 不是任意字段都能开

odoo/orm/fields.py_get_attrs() 里,Odoo 会对 company_dependent 做一串约束检查:

  • 不能 required
  • 不能 translate
  • 类型必须在允许范围里
  • 默认 copy=False
  • 默认加索引
  • prefetchcompany_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”。

而是:

  1. 先看该记录在当前公司有没有专属值
  2. 没有的话,再看当前公司的模型默认值

所以你切公司后看到值变了,不一定是这条记录被改了,可能只是 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 字段在多公司表现异常,按这个顺序最快:

  1. 当前代码运行时 env.company 是谁
  2. 这条记录在该公司是否已有显式值
  3. 当前公司下 ir.default 是否设置了 fallback
  4. 查询 / 排序是否发生在不同公司上下文里
  5. 是否有 sudo / with_company / allowed_company_ids 改写了环境

这比一上来翻权限规则高效得多。


适合拿来做什么,不适合拿来做什么

适合

  • 多公司独立配置
  • 同模型在不同公司有不同财务 / 策略属性
  • 希望“未配置时走公司默认值”的字段

不适合

  • 要求全局绝对一致的字段
  • 强依赖 required 语义的字段
  • 你自己都无法解释“当前公司到底是谁”的流程

结论

company-dependent 字段最值得记住的,不是“它支持多公司”,而是:

它把字段值变成了“记录 + 当前公司 + 默认值回退”三者共同决定的结果。

一旦你把它当成普通列去理解,就会不断遇到“明明是同一条记录,为什么显示不一样”的诡异问题。

DISCUSSION

评论区

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