先说结论
Odoo 里的项目燃尽图,不是简单把“当前看板里有多少任务”按日期倒着画出来。
如果你去看 /home/ubuntu/odoo-temp/addons/project/report/project_task_burndown_chart_report.py,会发现官方做的是一件更重但也更靠谱的事情:
- 先从
project.task找任务集合 - 再去
mail_message+mail_tracking_value里回放stage_id的变更历史 - 把每段“某任务在某阶段停留的时间区间”重建出来
- 最后用
generate_series在时间轴上展开,得到每个阶段在每个周期的任务数量和预估工时
所以这张燃尽图的本质不是“看当前状态”,而是:
根据阶段变更日志,回放任务曾经怎么流过各阶段。
第一层:为什么燃尽图不能直接查当前 project.task
如果只查当前任务表,你最多只能知道:
- 任务现在在哪个阶段
- 任务当前分配了多少工时
- 任务现在是否关闭
但燃尽图要回答的是另一个问题:
- 上个月这个项目还压了多少任务在 To Do
- 哪个阶段什么时候开始堆积
- 测试阶段的任务数量是逐步下降还是突然清空
这类问题都不是“当前值”能回答的,而是历史轨迹问题。
所以官方没有偷懒直接查 project.task,而是从阶段追踪日志里恢复时间切片。
第二层:Odoo 依赖的是邮件追踪,不是额外建一张阶段流水表
在 project.task.burndown.chart.report 的 SQL 里,核心数据源不是单独的审计表,而是:
mail_messagemail_tracking_value
当任务 stage_id 发生变化时,邮件追踪机制会留下旧值、新值以及对应时间点。燃尽图正是利用这套现成的 tracking 基础设施,把每次阶段切换当成一段历史边界。
这说明官方的设计思路很明确:
不再维护一套“项目专用阶段历史表”,而是复用 mail tracking 作为状态时间线的数据来源。
这也解释了为什么 addons/project/models/mail_message.py 里还专门为燃尽图准备了索引:它不是附带功能,而是一个确实会吃历史数据的报表。
第三层:为什么 SQL 里要同时处理“有过变更的任务”和“从没变更过的任务”
这是这张报表最容易被忽略、但最见功力的一段。
官方在 SQL 里分了两类任务:
1. 有阶段变更记录的任务
这种任务可以通过 mail_message / mail_tracking_value 拿到:
- 上一个阶段从什么时候开始
- 什么时候切到下一个阶段
于是系统就能还原它在各阶段停留的区间。
2. 从创建后就没变过阶段的任务
如果一个任务一直停在创建时的默认阶段,没有任何跟踪记录,那燃尽图也不能把它漏掉。
所以 SQL 又专门处理了一类“没有 tracking 记录的任务”,把:
create_date当成开始时间- 当前阶段当成整个区间的阶段
这才保证图表不会只统计“动过的任务”,而漏掉那些长期静止但真实存在的积压项。
第四层:为什么要区分 stage_id 和 is_closed
源码里除了阶段,还会额外算一个字段:
is_closed,值为open或closed
而这个 closed/open 的判断并不是看阶段折叠,而是结合任务 state 是否处于关闭态。
这很关键,因为燃尽图有两种常见分析方式:
- 按具体阶段分层看积压
- 按 open / closed 看关闭趋势
也就是说,Odoo 并不把“阶段列位置”和“业务上是否结束”混为一谈。
这和很多实现只看 kanban 列的轻量工具很不一样。官方是在告诉你:
燃尽图既要回答任务在哪一列,也要回答任务是不是已经真正结束。
第五层:为什么 allocated_hours 会跟着一起进图
这张模型不只数任务数量,还会汇总:
allocated_hours
所以燃尽图并不是单纯的 ticket count chart,它还可以站在预估工时的角度看趋势。
这意味着你在项目里看到的燃尽,不只是“卡片少了多少”,而是还能进一步回答:
- 工作包的预计工作量有没有下降
- 哪个阶段堆的不是任务数,而是大工时任务
- 任务数量变少,但剩余工时是不是依旧很高
这个设计很值钱,因为真正的项目管理里,10 个小任务和 2 个超大任务,对交付压力完全不是一回事。
第六层:generate_series 在这里到底解决了什么问题
SQL 里最像“魔法”的部分,是最后把每段阶段停留区间丢给 generate_series。
它做的事可以理解成:
- 某任务在 2 月 10 日到 4 月 9 日期间位于 In Progress
- 那就为每个统计周期生成一个落点
- 这样图表才能在月、周、季度维度上看到这段区间的存在
换句话说,Odoo 不是把事件点直接画成图,而是把阶段停留区间展开成时间序列数据。
这一步决定了它能真正做出阶段堆叠燃尽,而不是只给你一个“阶段切换日志列表”。
新手最容易误解的 4 件事
1. 以为燃尽图就是当前任务列表的统计投影
不对,它依赖阶段历史回放。
2. 以为只有改过阶段的任务才会进入图表
不对,没改过阶段的任务也会按创建日期和当前阶段补进去。
3. 以为图表只关心任务数量
不对,allocated_hours 也被一并聚合。
4. 以为阶段和关闭态是同一个概念
不对,源码明确把 stage_id 和 is_closed 分开处理。
实战里最该注意什么
1. 不要轻易关闭 task stage 的 tracking 或乱改 mail tracking 机制
因为燃尽图就建立在这套历史上。你把 tracking 语义改坏,后面图表就会失真。
2. 自定义阶段流转时,要考虑历史报表还能不能解释
你新增再多阶段都没问题,但要明白燃尽图会把它们当成时间区间状态,而不是一次性标签。
3. 如果客户抱怨燃尽图“不准”,先查是不是历史缺失,而不是先怀疑图表组件
很多时候问题在于:
- 任务数据是批量导入的
- 阶段被直接 SQL 改过
- tracking 记录不完整
图表本身只是忠实呈现它能拿到的历史。
一句话记忆法
Odoo 的项目燃尽图不是按当前任务状态倒推出来的,而是基于
stage_id的邮件追踪历史重建阶段区间,再把区间展开成真正可分析的时间序列。
DISCUSSION
评论区