先说结论
Odoo 的组织架构图并不是“拿 parent_id 递归展开一下”这么简单。
hr_org_chart 源码真正解决的是两个同时成立的问题:
- 你要能看到 直接下属 和 间接下属
- 你又不能因为现实组织里出现回环、兼岗、跨部门挂靠,就把整棵树算死循环
所以它的关键不是树形展示,而是:
在不完全理想的数据里,尽量稳定地算出‘谁属于谁的下属链’。
第一层:组织图展示的从来不只是 child_ids
在 hr_org_chart 扩展里,除了原本的 child_ids,还新增了:
subordinate_idschild_countchild_all_countis_subordinate
这已经说明官方不是只关心“一层直属关系”,而是同时关心:
- 当前人有多少直属下属
- 整个下属链总共有多少人
- 当前登录用户是不是某人的上级链条中的一环
如果只靠简单的 one2many 展开,这些都很难稳定算对。
第二层:_get_subordinates() 的核心不是递归,而是“递归时记住已经走过谁”
/home/ubuntu/odoo-temp/addons/hr_org_chart/models/hr_org_chart_mixin.py 里的 _get_subordinates() 很值得看。
它在递归过程中维护了一个 parents 集合,并且每次计算直属下属时,会先做:
direct_subordinates = self.child_ids - parents
这一步非常关键。
它等于在说:
- 当前节点的直属下属里
- 如果已经在上游路径里出现过
- 就不再继续往下算
换句话说,Odoo 防的不是“没有树”,而是“现实里的层级未必是一棵纯净无环树”。
第三层:为什么官方注释会直接提 CEO/CTO 互相绕回的场景
源码注释举了一个很现实的例子:
- CEO 管所有人
- 但 CEO 又可能是某个研发部门成员
- 研发部门又由 CTO 管
- CTO 又向 CEO 汇报
这类数据在企业里不算少见,尤其在:
- 矩阵组织
- 临时项目挂靠
- 兼岗管理
- 历史数据迁移
如果你把组织架构强行当纯树,一旦出现这种回路,间接下属统计、可见性判断、组织图接口都会被拖垮。
第四层:child_count 和 child_all_count 为什么要分开
child_count 是直属下属数量,child_all_count 是全链路数量。
这两个数字分开,不只是为了 UI 好看,而是因为它们服务的决策不同:
child_count更接近真实带人跨度child_all_count更接近影响范围或组织深度
如果只保留一个总人数,很多管理视图会误导。
比如一个 VP 可能直属只带 4 个人,但全链路覆盖 150 人;一个 Team Lead 可能直属带 9 人,但没有二级下属。两者管理结构完全不同。
第五层:is_subordinate 为什么要按当前用户算,而不是全局字段
is_subordinate 的计算依赖当前用户的 employee 记录。
也就是说,某员工是不是“我的下属”,不是全局真值,而是 相对于当前登录人 的结果。
这很重要,因为组织图除了展示,还有访问与筛选用途:
- 哪些人应该在我的团队列表里
- 哪些审批或管理入口对我可见
- 哪些统计该按我的管理半径展开
所以这是一个“相对关系字段”,不是静态标签。
第六层:为什么组织图问题经常不是前端 bug,而是层级数据脏了
很多实施遇到的问题是:
- 组织图人数不对
- 某人下属重复
- 某位经理看到了不该看到的人
第一反应往往是怀疑前端组件。
但按源码设计,更多时候应该先查:
parent_id有没有形成环- 员工是否同时被挂到多个异常链路
- 历史离职/返聘记录有没有造成层级残留
- 用户自己的
employee_id是否正确
因为只要底层层级关系脏了,再漂亮的组织图都只能把脏数据画出来。
最容易踩的 4 个坑
1)把 parent_id 当成唯一真相
实际企业里的“汇报对象”和“组织归属”并不总是一回事。
2)假设层级永远无环
源码都专门防了,你就别在实施里当它绝不可能发生。
3)只看直属人数,不看全链路人数
这会误判管理跨度。
4)把组织图异常全部甩给前端
多数问题要回到员工层级数据本身排查。
排错顺序
- 先查目标员工的
parent_id - 再查上级链是否形成回路
- 查
child_ids与subordinate_ids是否逻辑一致 - 用当前登录用户视角核对
is_subordinate - 最后再看前端组织图渲染
最后一句话
Odoo 的组织架构不是在画一棵完美的树,而是在脏现实里尽量稳定地维护一条上下级链。懂了这一点,你就知道问题通常出在层级建模,而不是图本身。
DISCUSSION
评论区