先说结论
Odoo 的任务依赖不是画一条线那么简单。
从 project.task 和官方 test_task_dependencies.py 看,系统真正关心的是三件事:
- 依赖关系不能形成环
- 复制项目或任务树时,依赖要尽量映射到新的副本上
- 如果另一端不在当前复制范围内,依赖关系就不该被粗暴改写
所以最准确的总结是:
Odoo 任务依赖的核心不是“显示关系”,而是“保证关系仍然可执行”。
为什么循环依赖必须被硬性阻止
_check_no_cyclic_dependencies() 里直接调用 _has_cycle('depend_on_ids'),发现环就抛 ValidationError。
这一步不能妥协。
因为一旦允许:
- A 依赖 B
- B 又依赖 A
任务系统就无法再可靠判断:
- 谁先做
- 谁被阻塞
- 哪个状态变化应触发谁
在协同系统里,环路不是小瑕疵,而是把执行顺序彻底打烂的结构性错误。
为什么 is_blocked_by_dependences() 只盯 closed states
源码里判断阻塞时,并不是看“依赖任务有没有开始”,而是看依赖任务是否都进入 closed states。
这背后的语义很清楚:
- 依赖任务只要没关闭,就仍然算前置未完成
- 阻塞关系关注的是“能不能开始下一步”,不是“对方看起来忙没忙”
这种定义虽然朴素,但非常稳定。
复制项目时,为什么依赖要尽量指向新副本
官方测试 test_duplicate_project_with_task_dependencies() 明确要求:
- 原项目里 task1 依赖 task2
- 复制整个项目后,副本里的 task1_copy 应依赖 task2_copy
也就是说,复制范围内的依赖关系应尽量闭合在新世界里。
这是对的。
否则复制项目的结果会非常别扭:
- 新项目任务还依赖旧项目任务
- 两个项目的执行链被粘在一起
- 后续统计和责任边界全部混乱
为什么跨复制边界的依赖不能乱改
同一组测试也验证了另一件事:
如果依赖另一端根本不在这次复制范围里,就不要强行重定向。
这说明 Odoo 的策略不是“见依赖就全部重写”,而是:
- 在复制闭包内部,尽量映射到新副本
- 超出复制闭包的,保留原关系
这个边界判断非常成熟,因为它保住了语义真实。
为什么子任务树复制时也要重建依赖链
test_duplicate_project_with_subtask_dependencies() 展示了一个很实际的场景:
- 父任务下面有节点树
- 节点之间有 dependency
- 复制整棵树后,依赖要重建在新节点之间
这说明 Odoo 不是只会复制层级关系,还会尽量把执行关系一起带过去。
对 SOP 类任务模板尤其关键。
否则你得到的只是看起来像的结构,真正的先后约束却全丢了。
最容易踩的误区
误区一:把依赖当视觉装饰
真正麻烦的是后端语义,不是前端画线。
误区二:复制项目时忘记检查依赖是否仍指向旧任务
如果自定义 copy 逻辑破坏了映射,项目会出现隐性跨项目耦合。
误区三:允许环路,只想靠人工约定解决
一旦规模上来,人工约定几乎一定失效。
排错顺序
如果用户说“任务一直被阻塞”或“复制后依赖乱了”,建议查:
- 项目是否启用了 allow_task_dependencies
- 依赖链里是否存在环路或被自定义数据写坏
- 复制范围内任务是否都生成了新副本
- 新副本依赖是否正确指向 copy,而不是旧项目原任务
- 跨项目残留依赖是否本来就应该存在
一句话记忆
Odoo 任务依赖的难点从来不是连线,而是让依赖在校验、复制和执行中始终保持真实。
DISCUSSION
评论区