先说结论
Odoo 里的任务依赖,不是“任务 A 前面加一行说明:等任务 B 做完”。
从 project.task 源码看,它其实是一套真正参与状态计算的机制:
depend_on_ids表示“我被谁卡住”dependent_ids表示“谁被我卡住”_compute_state()会根据依赖任务是否关闭,把当前任务自动切到04_waiting_normal_check_no_cyclic_dependencies()会阻止互相套娃- 复制任务树时,
_resolve_copied_dependencies()还会把新旧依赖重新对齐
一句话说:
Odoo 的任务依赖不是备注层功能,而是直接影响任务状态与任务树复制行为的业务语义。
第一层:依赖在模型里就是双向关系,不是单字段
在 project.task 里,依赖不是一个“前置任务”字段糊过去,而是两组 Many2many:
depend_on_ids:Blocked Bydependent_ids:Block
而且它们共用同一张关系表 task_dependencies_rel。
这意味着 Odoo 不是只关心“我依赖谁”,它也关心“谁依赖我”。
这点很重要,因为项目管理里常见的两个入口:
- 我这张卡为什么还不能做
- 我做完之后会解锁哪些卡
在 Odoo 里对应的就是两套视角。
第二层:依赖不只是显示,它真的会改状态
最关键的逻辑在 _compute_state()。
源码会检查:
- 当前项目是否开启
allow_task_dependencies depend_on_ids里是否还有任务不在CLOSED_STATES
只要还有未关闭的阻塞任务,当前任务如果本身还没关闭,就会被自动设成:
04_waiting_normal
如果阻塞条件消失,而且任务本身又不是 done / canceled,状态会回到:
01_in_progress
所以任务依赖不是看板上的灰色小图标,而是真正参与状态机的。
依赖一旦成立,任务不是“提醒你先别做”,而是系统层面把它放进 waiting。
第三层:为什么 Odoo 用“关闭态”而不是“Done”单独判断
源码里判断是否解锁,看的不是某一个固定 done 值,而是 CLOSED_STATES:
1_done1_canceled
这说明 Odoo 的设计语义不是“前置任务必须成功完成”,而是:
只要前置任务已经结束,不再占着流程位,它就不再继续阻塞别人。
这很符合真实业务。
因为有些任务不是做完才结束,而是明确取消、放弃、合并到别处处理。对下游来说,只要这个前置节点不再悬着,流程就该继续。
第四层:为什么它必须阻止循环依赖
源码里有一个很关键的约束:
_check_no_cyclic_dependencies()
底层通过 _has_cycle('depend_on_ids') 检查依赖环,一旦出现互相依赖,就抛出:
Two tasks cannot depend on each other.
这不是小题大做。
因为只要出现环:
- A 等 B
- B 又等 A
那 _compute_state() 就会把两边都卡死,整个流程永远解不开。
所以循环依赖校验,本质上是在保护状态机不被自己打爆。
第五层:复制任务时,依赖关系为什么不能直接 copy
这部分很容易被忽略,但其实很见功力。
depend_on_ids 和 dependent_ids 都设了 copy=False。乍一看像是“复制时不带依赖”。
但源码并没有就这样放弃。
Odoo 在 copy() 后会调用:
_create_task_mapping()_resolve_copied_dependencies()
它会先建立“原任务 → 新任务”的映射,再把原来依赖树里那些也被一起复制的任务 id,替换成新任务 id。
这说明官方很清楚一个现实问题:
复制一整棵任务结构时,依赖如果还指向旧任务,新的项目模板就废了。
所以依赖不是盲拷贝,也不是干脆丢掉,而是“有条件重建”。
这就是成熟业务系统和简单看板工具的区别。
第六层:为什么界面里既有“Blocked By”,又有“Blocked Tasks”按钮
在 project sharing 视图和普通任务动作里,源码都给了“查看被我阻塞的任务”的入口,比如:
action_open_blocking()action_dependent_tasks()
这说明 Odoo 不把依赖当成静态元数据,而是把它当成可导航的工作流网络。
对项目经理来说,这比“看到一条依赖线”更重要。
因为真正要问的是:
- 我今天把这张卡关掉,会释放几个后续任务?
- 哪个节点是当前项目的堵点?
而 dependent_tasks_count 正是在回答这个问题。
新手最容易误解的 4 件事
1. 以为依赖只影响展示,不影响状态
不对。源码里它直接进入 _compute_state()。
2. 以为前置任务必须 done,cancel 不算结束
不对。Odoo 判断的是 closed state,不是单独 done。
3. 以为复制项目后依赖天然会跟过去
不会自动“天然正确”。Odoo 专门写了重建映射逻辑。
4. 以为依赖只是单向说明
不对。系统同时维护“我依赖谁”和“谁依赖我”两种视角。
实战上最该注意什么
如果你在做项目实施或二开,最该注意这三点:
1. 不要自己手搓一个“前置任务文本字段”代替官方依赖
那样不会进入状态计算,也不会进入复制重建逻辑。
2. 自定义状态时,要理解 closed state 的边界
你如果改了任务结束语义,却没对齐 closed state,依赖解锁逻辑就会跑偏。
3. 做模板复制或批量建项目时,要特别检查依赖重建结果
因为这类 bug 表面看只是“几张卡状态怪怪的”,本质上可能是复制后依赖还指向旧记录。
一句话记忆法
Odoo 任务依赖不是“前置说明”,而是“会把任务推入 waiting、阻止循环、并在复制后重建引用”的正式流程机制。
DISCUSSION
评论区