项目深度

Odoo 任务依赖为什么不只是“前置任务”:Blocked By、等待状态与复制依赖到底怎么协同

Odoo 的任务依赖不是简单连一条箭头。源码里它会改任务状态、拦截循环依赖,还会在复制任务树时重建依赖关系。本文把这套机制讲透。

Odoo 开发 项目
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 7 阅读

先说结论

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 By
  • dependent_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_done
  • 1_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_idsdependent_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

评论区

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