协同办公

Odoo 任务状态为什么不只是“改个字段”:approved、changes requested、waiting 的 subtype 传播链讲透

项目任务里的批准、打回和等待,并不只是 state 值变化。Odoo 源码把这些状态翻译成 subtype,再通过父子子类型向项目层传播,形成真正可协作的状态语义。

协同办公
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

很多人看项目任务状态时,会把它理解成一件纯数据层的事:

  • state 改了
  • 看板列或按钮变了
  • 任务显示成已完成 / 已批准 / 待修改

addons/project/models/project_task.pyaddons/project/data/mail_message_subtype_data.xml 结合起来看, Odoo 真正关心的并不只是“状态值改没改”,而是:

这个状态变化应该如何被翻译成协作语义,并让任务层与项目层的参与者都在正确粒度上感知到它。

因此 approved、changes requested、waiting 并不只是状态码, 它们还是 subtype、通知语义,以及父子对象传播链的一部分。


一、为什么任务状态不能只当成字段枚举

如果任务状态只是界面显示值, 那做法会很简单:

  • state
  • 页面展示新状态
  • 结束

但真实项目协作不是这样。

因为状态变化往往意味着责任关系改变:

  • approved 说明这项工作已经通过某种确认
  • changes requested 说明执行者需要返工
  • waiting 说明当前卡在依赖或外部阻塞上
  • done 说明这项协作可以往后流转

这些都不是“字段改了”那么轻。

它们是团队对下一步协作关系的理解。

所以 Odoo 需要一套机制,把状态变化从数据库事件升级成沟通事件。


二、_track_subtype() 才是状态语义翻译器

project.task 里,最关键的方法之一就是 _track_subtype()

源码里它建立了一张非常明确的映射:

  • 1_doneproject.mt_task_done
  • 1_canceledproject.mt_task_canceled
  • 01_in_progressproject.mt_task_in_progress
  • 03_approvedproject.mt_task_approved
  • 02_changes_requestedproject.mt_task_changes_requested
  • 04_waiting_normalproject.mt_task_waiting

而当 stage_id 改变时,又会走 project.mt_task_stage

这说明 Odoo 的项目任务不是简单记录“哪个字段变了”, 而是在主动把变化翻译成:

  • 完成
  • 取消
  • 进行中
  • 已批准
  • 请求修改
  • 等待中
  • 阶段变化

这些词本质上就是协作语言。

所以 _track_subtype() 可以理解成:

任务状态变化通往协作通知的翻译器。


三、为什么 approved / changes requested 特别像“审批协作”,却又不是重型工作流

用户要求里特别提到“审批与消息协同”。

项目任务这里就是一个很好的例子。

从源码看,Odoo 确实给任务准备了:

  • approved
  • changes requested

这样的状态语义, 并且为它们配了独立 subtype。

这意味着系统承认:

  • 任务推进有时会经过一种轻审批 / 评审动作
  • 这个动作不应该只体现在字段上
  • 它还应该成为一条明确的消息语义

但另一方面,Odoo 又没有把它做成一套沉重的 BPM 引擎。

它更像是:

  • 用状态表达流程节点
  • 用 subtype 表达沟通语义
  • 用 Chatter 把评审结果留痕并扩散给相关人

这正好体现了 Odoo 在协同办公上的风格:

  • 不一定重流程
  • 但一定要让协作信号可见、可传递、可回顾

四、为什么 waiting subtype 还要配合权限和依赖边界

project.task._mail_get_message_subtypes() 里还有一段很容易被忽略的逻辑:

  • mt_task_waiting 这个 subtype 不总是可用
  • 如果项目不允许任务依赖,或者用户没有依赖相关权限,它会被剔除

这件事很有代表性。

因为“等待中”不是一个中性的装饰状态, 它常常在表达:

  • 我被别的任务卡住了
  • 我在等上游
  • 我在等批准或输入

如果当前项目根本没打开依赖管理能力, 或者当前用户没有这方面语义, 那把 waiting 当成一个普适通知入口反而会制造误导。

所以 Odoo 没有强行把所有 subtype 暴露给所有人, 而是让通知语义和业务能力边界保持一致。


五、为什么项目层还要再定义一套 parent subtype

mail_message_subtype_data.xml 里, 除了任务自己的 subtype, 还有一套项目层 subtype:

  • mt_project_task_new
  • mt_project_task_stage
  • mt_project_task_in_progress
  • mt_project_task_changes_requested
  • mt_project_task_approved
  • mt_project_task_done
  • 等等

这些记录通过:

  • parent_id
  • relation_field=project_id

与任务 subtype 关联。

这背后解决的是一个很现实的协作问题:

  • 任务参与者要看任务细节
  • 但项目参与者未必跟每条任务
  • 他们仍然需要在项目层感知关键任务状态信号

于是 Odoo 选择的不是“把项目成员全关注到每条任务”, 而是:

  • 在任务层定义细粒度语义
  • 在项目层定义这些语义的投影版本

这样通知既能上浮,又不必把所有人都卷进任务细节。


六、为什么 relation_field=project_id 这么关键

很多人第一次看到 relation_field,会觉得只是技术连接字段。

但在这里它其实表达的是:

  • 当任务上发生某类 subtype 事件时
  • 去哪个父对象上投影这个消息语义

在项目任务场景里,答案就是 project_id

也就是说, 任务状态通知之所以能向项目层传播, 不是因为系统“顺手群发一下”, 而是因为模型层明确声明了:

  • 这类子对象事件与哪个父对象存在协作关系

这让通知传播变成一种有结构的设计,而不是临时广播。


七、这套设计为什么特别适合多角色协作

一个项目里,常见会同时存在几类人:

  • 任务执行者
  • 项目经理
  • 评审人 / 批准人
  • 只关心项目整体进度的干系人

如果系统只有任务状态字段, 这些人就会面临两个问题:

  • 要么必须进任务里自己看
  • 要么完全不知道发生了什么

而有了 subtype + parent subtype 传播后, 通知就能更贴近角色分层:

  • 执行者看任务级变化
  • 项目关注者看项目级投影
  • 评审类状态又能作为清晰的协作语义被记录下来

这就是为什么 approved / changes requested 这类状态特别适合用 subtype 表达。

它们天然是多人协作语义,不只是单人字段状态。


八、这对审批与消息协同的启发

很多团队一说审批,就立刻想到:

  • 单独审批模块
  • 审批单
  • 完整流程引擎

但 Odoo 任务这里提供了另一种更轻的路径:

  • 先让任务状态表达评审节点
  • 再让 subtype 把节点变化翻译成消息语义
  • 最后通过任务层与项目层的传播,让相关角色都在正确层级收到信号

这很适合那些不需要独立审批单、 但又确实存在评审 / 打回 / 等待 / 通过等协作动作的场景。

换句话说:

不是所有审批都要做成独立引擎,有些审批更像协作状态,而 subtype 正好适合承载这层表达。


九、实战里最容易踩的坑

1. 只改 state,不关心 _track_subtype()

结果状态有了,协作语义却没有。

2. 把 approved / changes requested 当成本地 UI 标签

忽略它们其实应该成为通知事件。

3. 不理解 waiting subtype 与依赖能力绑定

会误以为系统“有时有、有时没有”是 bug。

4. 想让项目感知任务变化,就把项目成员全部订阅到任务

这通常比 parent subtype 投影更吵。

5. 设计了很多状态,但没有对应通知语义

最后团队还是只能进记录里自己猜发生了什么。


一句话记忆法

Odoo 项目任务里的 approved、changes requested、waiting 等状态,不只是字段值,而是会被 _track_subtype() 翻译成协作事件,再通过 parent subtype 向项目层传播的通知语义。

DISCUSSION

评论区

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