先说结论
Odoo 里的子任务,不是“父任务下面再缩进几行”。
从 /home/ubuntu/odoo-temp/addons/project/models/project_task.py 看,parent_id 一旦建立,系统会一起处理至少四件事:
- 子任务要不要继承父任务的
project_id - 子任务要不要在项目主视图里单独显示
- 子任务在没有客户时,要不要继承父任务或项目的客户
- 父任务的子任务数量、已关闭数量、完成百分比怎么汇总
所以它本质上不是 UI 层的小功能,而是一套 任务树语义。
在 Odoo 里,子任务不是“备注式分解”,而是会改数据归属和统计口径的正式结构。
第一层:父子关系不只决定层级,还决定项目归属
源码里 project_id 不是简单手填字段,而是带有 _compute_project_id()。
关键逻辑是:
- 如果任务本来不该单独显示在项目里
- 它又有父任务
- 且父任务有项目
那么当前任务会跟着父任务的 project_id 走。
这意味着什么?
意味着 Odoo 默认把很多子任务当成“父任务内部拆分”,而不是独立项目对象。
对业务人员来说,这个设计很合理:
- 一个大的交付任务拆成若干技术子任务
- 子任务本质仍属于同一个项目
- 你只是想拆工作,不是想把它们变成新的项目节点
所以 parent_id 在这里不是装饰,而是一个“归属继承器”。
第二层:为什么有些子任务不会在项目主列表里重复出现
源码里还有一个很关键但常被忽略的字段:display_in_project。
_compute_display_in_project() 的思路很直接:
- 如果没有项目,显示
- 如果没有父任务,显示
- 如果有父任务,但项目和父任务项目不同,也显示
- 只有当它是父任务下面、并且仍属于同一项目时,才可能不单独显示
配合 _inverse_parent_id() 的逻辑你会发现:
Odoo 在刻意避免“父任务和其同项目子任务同时把项目看板刷满”。
这就是为什么一些子任务看起来像“挂在父任务里面”,而不是项目主面板上再来一份。
这不是显示 bug,而是官方刻意控制的展示层级。
第三层:子任务客户为什么经常会“自己带出来”
partner_id 的逻辑也很能说明问题。
源码的 _compute_partner_id() 明确写了两层默认来源:
- 优先项目的
partner_id - 否则看父任务的
partner_id
而 _get_default_partner_id() 甚至把优先顺序写得更清楚:
- 如果有父任务,且父任务有客户,用父任务客户
- 否则如果项目有客户,用项目客户
这说明 Odoo 对子任务的理解不是“完全独立的一条记录”,而是:
- 它通常是父任务上下文里的延伸
- 没必要每拆一个子任务就重新选一遍客户
所以你看到子任务自动带出客户,不是偶然方便,而是官方默认的业务假设。
第四层:父任务的子任务统计不是 Python for 循环硬算,而是分组汇总
在 _compute_subtask_count() 里,Odoo 没有对每个父任务逐条遍历 child_ids 去算。
它用了 _read_group() 做数据库层汇总:
- 按
parent_id分组 - 统计总数
__count - 聚合
state:array_agg - 再算出关闭数量
这样做有两个好处:
1. 性能更稳
一批父任务一起打开时,不会轻易退化成大量小查询。
2. 统计口径更统一
subtask_count 和 closed_subtask_count 是正式计算字段,不是前端临时拼出来的数字。
这意味着父任务上的“已完成 x/y”并不是视觉糖,而是模型层认可的统计结果。
第五层:完成度为什么只是“关闭数量 / 总数量”
_compute_subtask_completion_percentage() 很简洁:
- 如果
subtask_count不为 0 - 就用
closed_subtask_count / subtask_count
这揭示了 Odoo 的一个设计取舍:
子任务完成度是按“任务是否关闭”统计,不是按工时权重、优先级权重或复杂度权重统计。
这会带来两个很现实的结果:
- 3 个很小的子任务做完,完成度可能一下跳很高
- 1 个巨大子任务没关,完成度也不会细腻反映真实投入
所以项目经理要明白:
- 这个百分比适合看流程推进
- 不适合当精确工作量燃尽图
如果你要按工时权重算,就该走 timesheet / allocated hours 那条链,而不是误读 subtask completion。
第六层:子任务和“私有任务 / 跨项目任务”边界是分开的
display_in_project 的设计还隐含了一个常见误区:
很多人以为“子任务一定不在项目主列表出现”。
其实不是。
如果子任务和父任务项目不同,或者本身就是需要单独展示的记录,它仍然可以显示在项目层。
也就是说,Odoo 不是简单把所有子任务都藏起来,而是区分:
- 同项目内的拆分 → 更像父任务内部结构
- 跨项目或独立承担的子任务 → 仍可作为独立项目项被看见
这个边界很适合复杂实施场景。
新手最容易误解的 4 件事
1. 以为子任务只是树形展示
不对。它还会影响 project_id、partner_id 和统计字段。
2. 以为子任务一定是完全独立的项目记录
不对。很多时候它会继承父任务的项目上下文,而且默认不单独刷到项目主视图。
3. 以为父任务完成度是按工作量算的
不对。默认就是“关闭子任务数 / 子任务总数”。
4. 以为客户要每张子任务手填
不对。没有显式客户时,系统会从父任务或项目往下带。
实战开发最该注意什么
1. 自定义报表时,不要把 child_ids 数量当场现算
既然官方已经有 subtask_count、closed_subtask_count、subtask_completion_percentage,能复用就复用。
2. 改子任务展示逻辑时,要一并理解 display_in_project
否则很容易出现:
- 列表重复
- 父子结构错位
- 用户误以为数据多了一倍
3. 如果你要做按工时衡量的进度,不要误用 subtask completion
那应该走工时和 timesheet 的逻辑,而不是改这里的百分比公式硬塞业务含义。
一句话记忆法
Odoo 子任务不是“缩进后的任务卡”,而是会继承归属、继承客户、控制显示层级并回写父任务统计的一套正式结构。
DISCUSSION
评论区