项目深度

Odoo 任务剩余工时为什么不只是 allocated_hours 减已登记:子任务工时汇总、进度百分比与 overtime 逻辑讲透

很多人看到 Odoo 任务上的剩余工时,会以为只是预算工时减去本任务工时。但 hr_timesheet 源码里还会把子任务工时递归汇总进来,并进一步影响 progress、overtime 和总耗时。本文把这条链讲透。

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

先说结论

在 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.linetask_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_hours
  • overtime = 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_hourstotal_hours_spent 混为一谈。

2. 如果父任务只做工作包容器,就别拿它自己的 effective_hours 判断投入

更该看 subtask_effective_hoursremaining_hoursprogress

3. 如果你要做预算预警,优先用 overtime / remaining_hours 的汇总口径

这比盯单条 timesheet 更接近项目经理真正关心的问题。


一句话记忆法

Odoo 任务剩余工时不是“预算减本任务工时”,而是“预算减整棵子任务树已消耗工时”的执行视角。

DISCUSSION

评论区

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