先说结论
在 Odoo 项目里,“任务看起来做完了” 和 “系统把它当成已关闭” 不是一回事。
如果你去看 /home/ubuntu/odoo-temp/addons/project/doc/stage_status.rst 和 addons/project/models/project_task.py,会发现官方把任务推进拆成了两层:
stage_id:你把任务放在哪一列state:系统根据关闭状态、依赖阻塞等规则计算出来的执行语义
这意味着:
- 拖到某个阶段,只是改变流程位置
- 是否计入 closed task、是否停止“腐烂”判断、是否参与关闭时长统计,要看
state - 如果依赖未完成,任务可能被自动推回
Waiting - 一些项目报表和 milestone 逻辑,也是按 open / closed 口径在工作,不是只看列名像不像“完成”
所以正确理解不是“把列拖对就好了”,而是:列负责表达流程位置,状态负责表达执行语义,二者一起构成任务生命周期。
第一层:为什么 Odoo 早就不再把任务状态等同于按钮状态
官方文档 stage_status.rst 里很明确:老的 state 按钮流被清理掉,项目任务改成以 stage_id 为主来表达流程位置。
但这并不等于“状态彻底消失”。
在新版模型里,project.task 仍有一个 state 字段,只是它不再是你理解中的“手工点按钮换状态”,而是更偏向管理语义:
01_in_progress02_changes_requested03_approved1_done1_canceled04_waiting_normal
最关键的是,源码里还有:
CLOSED_STATES = {'1_done', '1_canceled'}is_closed由state in CLOSED_STATES计算
也就是说,系统真正拿来判断“关没关”的,是 closed states 集合,不是阶段名称。
一个列名叫 Done 的阶段,如果没有把任务落到 closed state 语义上,很多统计仍不会按“已关闭”处理。
第二层:Stage 管位置,State 管含义
很多实施项目会把看板列设计得非常业务化,比如:
- 待分析
- 开发中
- 待测试
- 待客户确认
- 已上线
这很好,因为阶段本来就应该服务流程可视化。
但源码里 state 的计算逻辑提醒你:
- Stage 更像“现在在哪个流程节点”
- State 更像“系统怎么看待这个任务的执行状态”
典型后果有三个:
1. 报表看的是 state,不一定只看 stage
project.project 上的 open_task_count、closed_task_count,以及一些里程碑判断,都按 open / closed 状态口径聚合。
2. 关闭时长是业务信号,不是视觉信号
working_hours_close、working_days_close 这种字段要计算从创建到关闭的耗时,本质上依赖的是“什么时候算 close”,而不是用户肉眼觉得“已经挪到最后一列”。
3. 你可以有很多阶段,但关闭语义只能有少数几个
列可以很多,关闭语义却必须收敛。否则经营统计会漂移。
第三层:为什么依赖一存在,任务会自动变成 Waiting
project_task.py 里 _compute_state 的逻辑非常关键:
- 如果开启了
allow_task_dependencies - 且
depend_on_ids中还有未关闭任务 - 当前任务本身又不在 closed states
那么任务会被系统置成:
04_waiting_normal
反过来,只要阻塞项都清掉了,它又会从 waiting 回到:
01_in_progress
这说明 Odoo 的“等待”不是一个纯手工标签,而是依赖网络驱动出来的计算结果。
也正因为这样,很多团队会踩一个误区:
任务明明在“开发中”列,为什么状态却是 Waiting?
答案通常不是界面 bug,而是:
- 流程位置没有变
- 但依赖关系让系统认定它当前不能推进
所以排错顺序应该是:
- 先看有没有开启任务依赖
- 再看
Blocked By是否存在未关闭任务 - 再看是不是有人手动把任务拖到了一个看起来很积极的列
- 最后再确认报表为什么没把它算成正常推进任务
第四层:为什么“Done 列”不等于“已关闭任务数”
很多管理层最先看到的问题是:
- 看板里已经一堆 Done
- 可项目统计里 closed 数不对
- milestone 也没按预期推进
这时不要先怀疑统计报表,先怀疑口径。
在源码里:
is_closed依赖 closed states- 项目上的 closed/open count 也是按 state 聚合
- 里程碑是否还能标记 done,也会看关联任务里 open 与 closed 的数量
这意味着,Done 列只是一个可能与 closed 语义对齐的视觉容器,但它本身不是结算口径。
如果你的自定义把列配得很花哨,却没有同时考虑 state 的意义,就会出现:
- 看板上“似乎完成”
- 统计上“仍然未关闭”
- 时长上“关闭时间不准”
- 里程碑上“为什么还不能结案”
第五层:实施时最容易犯的三个误区
误区一:把阶段当状态字典用
阶段可以无限细分,状态不行。阶段太像状态,会让流程可视化和经营统计搅在一起。
误区二:把等待状态手工化
如果项目启用了依赖,Waiting 本来就有自动计算的一面。你再手工用一个“待前置完成”列去表达同一层意思,往往会造成双重语义。
误区三:把关闭等同于 UI 操作
很多人以为用户完成最后一次拖拽,就是关闭时刻。实际上 Odoo 更关心的是:
- 当前状态是否进入 closed set
- 依赖是否清空
- 统计时是否按 close 口径可聚合
实战建议:如何把规则设计得既清楚又不拧巴
我的建议是:
1. 阶段只表达流程,不表达结算口径
例如:
- 待分析
- 执行中
- 待验收
- 已完成
- 已取消
视觉上没问题,但一定要同步定义哪些才是真关闭语义。
2. 依赖项目里,别再复制一个“等待列”滥用
既然系统已经能因为前置任务未关闭而把任务置为 waiting,就不要再额外造很多看起来类似的手工状态。
3. 所有经营统计,先统一 closed 口径
在做:
- 关闭时长
- 任务完成率
- 里程碑完成判断
- 项目健康度面板
之前,先确认团队内部到底把哪些 state 当 closed。
最后一句
Odoo 任务管理最容易被误读的地方,不是字段多,而是界面上的列太直观,掩盖了底层状态语义。
真正稳的理解应该是:
stage_id解决“现在在哪一步”state解决“系统把它当成什么状态”is_closed解决“统计与生命周期是否结束”- 依赖关系解决“它是不是其实还不能动”
把这四层分清,项目看板、统计、里程碑和排错顺序才会统一。
DISCUSSION
评论区