先说结论
Odoo 的项目状态更新不是一段自由发挥的周报文本。
从 project.update 源码看,它更像一次带结构的项目快照,至少会处理四件事:
- 默认继承上一次 update 的 progress 与 status
- 自动构造一段包含 milestone 信息的默认 description
- 创建时把当下的 task_count / closed_task_count 冻结进 update
- 删除旧 update 后,回退项目的
last_update_id
所以最简洁的理解是:
project.update 是项目在某一时刻的结构化快照,而不是随手写的一段汇报。
为什么默认值会继承上一次状态
default_get() 会优先从项目的 last_update_id 和 last_update_status 里取值。
这有两个明显好处:
- 用户每次写 update 不必从零开始
- 状态序列天然形成连续叙事
对协作管理来说,这比“每周重填一张空表”成熟得多。
因为真正的管理节奏通常是:
- 默认延续上周判断
- 只改发生变化的部分
而不是每周重新发明一套状态。
默认描述为什么要自动拼 milestone
_build_description() 会用 QWeb 模板渲染描述,而 _get_template_values() 里最值得看的是 milestone 数据准备。
它会整理:
- 当前 milestone 列表
- 自上次 update 后被更新的 milestone
- 自上次 update 后新创建的 milestone
这意味着默认描述不是空白富文本框,而是带上下文的报告草稿。
对项目经理来说,这很实用:
- 重要节点自动摆在眼前
- 不必回忆“上次之后到底改过什么”
- update 天然围绕交付节奏展开
为什么 create() 要把任务数冻结下来
create() 后会把:
task_countclosed_task_count
写进 update 自身。
注意,这不是 related 字段实时联动,而是创建当下的快照值。
这很关键。
因为状态更新的意义本来就是:在某个时间点,项目看起来如何。
如果过去的 update 还跟着今天的任务数量实时变化,那历史记录就失真了。
milestone 变化为什么要从 tracking 里抓
_get_last_updated_milestone() 不是简单比较当前值和旧值,而是去查:
mail_messagemail_tracking_valueproject.milestone.deadline
这说明 Odoo 很重视“上次 update 之后究竟发生了什么变化”这个时间边界。
它要的不是静态列表,而是:
- 哪个里程碑改了 deadline
- 原来是什么
- 现在变成什么
这比手工周报里常见的“里程碑有调整”高级得多,因为它能追出具体变化链路。
为什么删除 update 还要回滚 last_update_id
unlink() 之后会重新找该项目最新的一条 update 设回去。
这说明 last_update_id 不是一个可有可无的显示字段,而是整个项目状态连续性的锚点。
如果删记录不回滚,后面所有默认值、项目摘要、统计都会指向一个已经不存在的节点。
最容易踩的误区
误区一:把 project.update 当纯文本周报
那会错过它最有价值的结构化上下文。
误区二:希望 update 的任务数据永远实时
实时适合 dashboard,不适合历史快照。
误区三:里程碑只看当前列表,不看上次之后的变化
这样写出来的 update 很难有连续性。
排错顺序
如果用户说“默认描述不对”或“状态继承怪怪的”,建议查:
- 项目当前
last_update_id指向哪条记录 - 项目是否开启里程碑,以及 milestone 数据是否可读
- 上次 update 之后有没有真正发生 tracking 变化
- 是否有人删过 update,导致 last_update 锚点回退
一句话记忆
Odoo 的项目状态更新,本质上是在定期给项目拍一张结构化快照,而不是写一段任意发挥的周报。
DISCUSSION
评论区