本文目录导读:

Odoo中的工作流(Workflow)与状态(State)是用于控制业务对象(如报价单、采购订单、报销单)在生命周期内流转的核心机制,虽然Odoo在不同版本(特别是从v8到v13之后)经历了从“活动工作流引擎”到“基于状态字段+业务逻辑”的重大变革,但理解其核心思想对于定制和实施系统至关重要。
下面从基础概念、演进历史、当前最佳实践(Odoo 14+)以及实现范例来详解。
核心概念
-
状态 (State):
- 通常是一个存储在数据库中的
Selection字段。 - 表示一个记录当前所处的“阶段”,
draft,confirmed,done,cancel。 - 它是声明式的,仅仅是一个标签,本身没有逻辑。
- 通常是一个存储在数据库中的
-
工作流 (Workflow)(Odoo 8-13 的经典定义):
- 是一个可视化、可配置的引擎。
- 定义了状态如何以及在什么条件下转换到下一个状态。
- 包含了节点 (Nodes)(定义在特定状态需要执行的操作,如发送邮件、创建任务)和转换 (Transitions)(定义从哪个状态到哪个状态,以及触发的条件:例如按钮点击、条件满足、超时自动触发)。
-
Odoo 13+ 的 “基于模型的方法”:
- 核心变化:Odoo 13移除了旧的“自带的UI工作流视图”和“wkf_activity”表,工作流不再是独立于Python代码的可视化配置。
- 新理念:工作流的逻辑被更紧密地整合到模型方法和状态字段中,业务逻辑被写在Python方法里,通过条件判断和抛出异常来阻止或允许状态变化。
历史演进 (为什么会有变化)
这是一个重要的背景信息:
- Odoo 8-12:有自己的
base_workflow模块,允许通过Web界面拖拽创建、编辑“工作流图”,有wkf(工作流定义)、wkf_node、wkf_transition等表,这个系统强大但复杂、性能有问题、难以调试和版本控制。 - Odoo 13+:为了简化、性能、可维护性、版本控制友好,Odoo正式弃用了旧工作流引擎,不再提供可视化的UI图,开发者必须用Python代码来实现逻辑,但这个“代码内的自动流程”仍然被称为“工作流”或“状态机”。
简单地讲: Odoo 13之前,你需要画一个图,Odoo 13之后,你要写代码。
Odoo 14 / 15 / 16 / 17+ 的现代实现方式
我们通过模型(Model)、状态字段(State)、按钮方法(Button Method) 和自动操作(Automated Actions) 来实现工作流。
模型中的状态字段定义
# models/my_model.py
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
class MyModel(models.Model):
_name = 'my.model'
_description = 'My Custom Workflow Model'
_rec_name = 'name'
name = fields.Char(string='名称', required=True)
state = fields.Selection([
('draft', '草稿'),
('confirmed', '已确认'),
('in_progress', '进行中'),
('done', '已完成'),
('cancelled', '已取消'),
], string='状态', default='draft', tracking=True)
# tracking=True 会记录状态变更日志 (Audit Trail)
定义业务逻辑方法(核心)
这是现代工作流的核心:在方法中用if语句控制逻辑。
def action_confirm(self):
"""从草稿转换到已确认"""
for record in self:
if record.state != 'draft':
raise UserError(_('只有处于草稿状态的记录才能被确认。'))
# 执行一些确认时的业务逻辑
record.write({'state': 'confirmed'})
# 可以触发其它操作,如创建任务、发送邮件
# record.message_post(body=_("记录已被确认"))
return True
def action_start(self):
"""从已确认或重新开始转换到进行中"""
for record in self:
if record.state not in ['confirmed', 'cancelled']:
raise UserError(_('只有已确认或已取消的记录才能开始流程。'))
record.write({'state': 'in_progress'})
return True
def action_done(self):
"""结束流程"""
for record in self:
if record.state != 'in_progress':
raise UserError(_('只有进行中的记录才能完成。'))
# 执行结束时的验证 (例如检查必填项)
if not record.name:
raise ValidationError(_('必须填写名称才能完成。'))
record.write({'state': 'done'})
return True
def action_cancel(self):
"""取消记录"""
for record in self:
if record.state not in ['draft', 'confirmed', 'in_progress']:
raise UserError(_('记录当前状态不允许取消。'))
record.write({'state': 'cancelled'})
return True
def action_set_to_draft(self):
"""重置为草稿 (常用于打回)"""
for record in self:
if record.state != 'cancelled':
raise UserError(_('只有已取消的记录可以重置为草稿。'))
record.write({'state': 'draft'})
return True
在视图中添加按钮
在视图的XML文件中,根据state控制按钮可见性。
<!-- views/my_model_views.xml -->
<form string="我的模型">
<header>
<!-- 只有 state 是 'draft' 时才显示 '确认' 按钮 -->
<button name="action_confirm" string="确认" type="object"
class="btn-primary" states="draft"/>
<!-- 只有 state 是 'draft' 或 'confirmed' 或 'cancelled' 时才显示 '开始' 按钮 -->
<button name="action_start" string="开始" type="object"
class="btn-primary" attrs="{'invisible': [('state', 'not in', ['draft', 'confirmed', 'cancelled'])]}"/>
<!-- 只有 state 是 'in_progress' 时才显示 '完成' 按钮 -->
<button name="action_done" string="完成" type="object"
class="btn-success" states="in_progress"/>
<!-- 取消按钮:在非 done/cancelled 状态下可见 -->
<button name="action_cancel" string="取消" type="object"
class="btn-danger" attrs="{'invisible': [('state', 'in', ['done','cancelled'])]}"/>
<field name="state" widget="statusbar" statusbar_visible="draft,confirmed,in_progress,done"/>
</header>
<!-- 表单主体 -->
<sheet>
<group>
<field name="name"/>
<field name="state" readonly="1"/>
</group>
</sheet>
</form>
使用自动操作 (Automated Actions) 实现无界面行动
当无法或不想写Python方法时,可以使用 设置 -> 技术 -> 自动操作创建一个记录规则。
- 模型:选择
my.model - 触发条件:修改记录引起的
- 触发时机:在修改记录前/后
- 条件过滤:
[("state", "=", "confirmed")](只在状态变为已确认时执行) - 动作:
- 添加关注者
- 发送邮件
- 执行Python代码
这个功能对于不想开发自定义模块的非程序员很有用,但对于核心业务逻辑,建议使用上述代码方法。
从旧版迁移到新版(图形化到代码化)
如果你升级旧模块:
- 移除
workflow字段定义:从模型中删除workflow和_workflow_buttons。 - 创建状态字段:如果还没有,添加一个
Selection字段。 - 创建按钮方法:为每个转换状态写一个方法(如上所述)。
- 删除XML视图中的旧版
workflow相关代码:states属性在按钮上可能不再使用(但仍然支持),需要改为attrs或states。 - 迁移数据:升级模块时,调用
ir.actions.server或更新查询来填充新的state字段。
高级技巧与最佳实践
- 安全性与不可逆性:使用
UserError来拒绝不合法操作,不要直接write修改状态。 - 审计追踪:给状态字段加上
tracking=True;或者在关键转换方法里调用record.message_post(body=_("状态从 %s 变更为 %s", old_state, new_state))。 - 结构化工作流:对于复杂的多步骤工作流(订单->审核->采购->入库->开票),使用多模型(如:采购订单模型,验收单模型,发票模型)而不是在同一个模型上疯狂增加状态,让每个模型完成一个领域的工作。
- 使用
@api.onchange谨慎转换状态:onchange是UI前端事件,不能保证执行,不要在其中做关键的业务逻辑,应该在按钮方法或自动动作中做。 - 使用状态图替代(无代码工具):对于业务分析师,在Odoo 16+ 企业版中,有一个“工作流”应用(基于
base_automation),它允许通过界面配置简单的状态机,但功能有限,适合轻量级流程,重业务逻辑仍需编码。
总结对比表
| 特性 | 旧版 (Odoo 8-12) | 新版 (Odoo 13+ 标准做法) |
|---|---|---|
| 定义方式 | XML 定义 workflow, 代码中标记 state |
纯 Python 代码 + state Selection 字段 |
| 可视化编辑 | 有 wkf_manager 图形界面 |
无 (或使用瘦版“工作流”应用) |
| 状态转移控制 | 在 wkf_transition 设置条件 |
在 action_xxx() 方法中用 if 逻辑控制 |
| 执行动作 | 在 wkf_node 定义多种活动(邮件、代码等) |
按钮方法里直接调用ORM方法,或使用自动操作 |
| 性能 | 较慢 (每步都查数据库计算条件) | 很快 (直接在内存中执行) |
| 调试难度 | 困难 (代码 + 引擎 分离) | 容易 (代码内断点) |
| 版本控制 | 差 (数据库记录难迁移) | 极好 (迁移脚本即可) |
| 适用人群 | 超级管理员/实施顾问 | 开发者 (但可以有简化工具) |
一句话结论:
在Odoo 13及以后,忘掉可视化的工作流引擎,在你的模型上定义一个
state字段,然后为每个业务流程写清晰、可测试的Python方法,通过UserError来控制状态转换的合法性,这是最现代、最高效的做法。
这个方案适用于几乎所有Odoo实现场景,包括销售、采购、库存、项目、制造、财务审批等。