先说结论
在 Odoo 项目里,很多人看到任务上的工时字段,会天然理解成:
allocated_hours= 预算工时effective_hours= 已登记工时remaining_hours= 两者相减
但 /home/ubuntu/odoo-temp/addons/hr_timesheet/models/project_task.py 告诉你,真实逻辑更复杂。
对一个父任务来说,系统真正关心的是:
- 本任务自己登记了多少工时
- 子任务一共花了多少工时
- 预算和实际相比是否超了
- 最终进度百分比要怎样表达
所以 Odoo 的剩余工时不是单任务局部口径,而是 带子任务汇总的执行口径。
第一层:effective_hours 只算本任务自己,不含子任务
_compute_effective_hours() 的逻辑很清楚:
- 按
account.analytic.line对task_id分组 - 求
unit_amount:sum - 写回
effective_hours
这意味着 effective_hours 表达的是:
这张任务自己名下的 timesheet 总工时
它不是“整棵任务树的真实耗时”。
所以如果你看到一个父任务 effective_hours=2h,并不能说明这个工作包只花了 2 小时;它可能只是父任务自己没怎么记工时,而大量工时被记在子任务上。
第二层:子任务工时会递归汇总成另一个字段
官方没有强行把子任务工时混进 effective_hours,而是单独做了:
subtask_effective_hours
_compute_subtask_effective_hours() 的核心写法是:
- 对每个任务
- 累加每个子任务的
effective_hours + subtask_effective_hours
这代表一种递归汇总语义:
- 直接子任务的工时要算
- 孙任务、曾孙任务的工时也要被一路卷上来
也就是说,一个父任务虽然没直接登记任何工时,只要下面任务树在持续记工时,父任务的汇总视角仍然能看到真实投入。
这对于项目管理非常关键。
第三层:remaining_hours 为什么不是 allocated_hours 减 effective_hours
_compute_remaining_hours() 真正用的是:
allocated_hours - effective_hours - subtask_effective_hours
这就是很多人误解的来源。
因为在直觉里,你可能只想减掉本任务工时;但在 Odoo 的任务树语义里,父任务往往代表一个工作包。
既然它是工作包,剩余工时就不该只看父任务自己记了多少,而要看:
- 这个包下面整体已经消耗了多少
所以:
父任务 remaining_hours 的默认语义,是“整个任务包剩多少”,不是“父卡片自己还剩多少”。
这点如果没看源码,很容易看错报表。
第四层:progress 和 overtime 也都建立在汇总口径上
_compute_progress_hours() 继续沿用了同一个思路。
当 allocated_hours > 0 时:
task_total_hours = effective_hours + subtask_effective_hoursovertime = max(task_total_hours - allocated_hours, 0)progress = round(task_total_hours / allocated_hours, 2)
这说明两个重要事实:
1. progress 不是任务状态进度
这里的 progress 是工时进度,不是看板流程进度。
所以一个任务状态还没 done,也可能 progress > 1,因为它只是表示工时已经超预算。
2. overtime 也不是员工加班时长
这里的 overtime 是任务预算超耗时,不是劳动法意义上的个人加班。
很多中文使用者很容易把这个词误读掉。
第五层:为什么 total_hours_spent 还要单独再算一次
源码里还有一个:
total_hours_spent = effective_hours + subtask_effective_hours
表面看像重复,实际上它是在给“总耗时”一个明确字段语义。
这样前端、报表或其他模块需要展示总耗时时,不必每次临时把两部分再加一遍。
这是一种典型 ERP 设计:
- 基础原子字段保留
- 常用业务口径再封装成明确字段
所以不要觉得它多余;它是在稳定消费口径。
第六层:为什么查看子任务工时还有单独动作
action_view_subtask_timesheet() 也能看出官方思路。
它会:
- 找出所有相关子任务
- 拼出 timesheet action
- 让你直接按任务树范围查看工时
这再次证明一个事实:
Odoo 不是把父任务和子任务工时关系当作“后台计算细节”,而是把它当作用户应当直接查看的管理视角。
换句话说,子任务工时汇总不是隐藏实现,而是产品能力的一部分。
新手最容易误解的 4 件事
1. 以为 effective_hours 就是整个任务包耗时
不对。它只算本任务自身 timesheet。
2. 以为 remaining_hours 只减本任务已登记工时
不对。父任务会把子任务递归工时也扣掉。
3. 以为 progress 是看板状态进度
不对。这里的 progress 是工时消耗比例。
4. 以为 overtime 是员工个人加班
不对。这里表示任务超预算工时。
实战上最该注意什么
1. 做项目工时报表时,一定要区分“本任务工时”和“任务包总工时”
别把 effective_hours 和 total_hours_spent 混为一谈。
2. 如果父任务只做工作包容器,就别拿它自己的 effective_hours 判断投入
更该看 subtask_effective_hours、remaining_hours 和 progress。
3. 如果你要做预算预警,优先用 overtime / remaining_hours 的汇总口径
这比盯单条 timesheet 更接近项目经理真正关心的问题。
一句话记忆法
Odoo 任务剩余工时不是“预算减本任务工时”,而是“预算减整棵子任务树已消耗工时”的执行视角。
DISCUSSION
评论区