Odoo工作流与状态

wen PHP项目 4

本文目录导读:

Odoo工作流与状态

  1. 核心概念
  2. 历史演进 (为什么会有变化)
  3. Odoo 14 / 15 / 16 / 17+ 的现代实现方式
  4. 从旧版迁移到新版(图形化到代码化)
  5. 高级技巧与最佳实践
  6. 总结对比表

Odoo中的工作流(Workflow)与状态(State)是用于控制业务对象(如报价单、采购订单、报销单)在生命周期内流转的核心机制,虽然Odoo在不同版本(特别是从v8到v13之后)经历了从“活动工作流引擎”到“基于状态字段+业务逻辑”的重大变革,但理解其核心思想对于定制和实施系统至关重要。

下面从基础概念、演进历史、当前最佳实践(Odoo 14+)以及实现范例来详解。

核心概念

  1. 状态 (State)

    • 通常是一个存储在数据库中的Selection字段。
    • 表示一个记录当前所处的“阶段”,draftconfirmeddonecancel
    • 它是声明式的,仅仅是一个标签,本身没有逻辑。
  2. 工作流 (Workflow)(Odoo 8-13 的经典定义):

    • 是一个可视化、可配置的引擎。
    • 定义了状态如何以及在什么条件下转换到下一个状态
    • 包含了节点 (Nodes)(定义在特定状态需要执行的操作,如发送邮件、创建任务)和转换 (Transitions)(定义从哪个状态到哪个状态,以及触发的条件:例如按钮点击、条件满足、超时自动触发)。
  3. Odoo 13+ 的 “基于模型的方法”

    • 核心变化:Odoo 13移除了旧的“自带的UI工作流视图”和“wkf_activity”表,工作流不再是独立于Python代码的可视化配置。
    • 新理念:工作流的逻辑被更紧密地整合到模型方法状态字段中,业务逻辑被写在Python方法里,通过条件判断和抛出异常来阻止或允许状态变化。

历史演进 (为什么会有变化)

这是一个重要的背景信息:

  • Odoo 8-12:有自己的base_workflow模块,允许通过Web界面拖拽创建、编辑“工作流图”,有wkf(工作流定义)、wkf_nodewkf_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代码

这个功能对于不想开发自定义模块的非程序员很有用,但对于核心业务逻辑,建议使用上述代码方法。

从旧版迁移到新版(图形化到代码化)

如果你升级旧模块:

  1. 移除workflow字段定义:从模型中删除 workflow_workflow_buttons
  2. 创建状态字段:如果还没有,添加一个 Selection 字段。
  3. 创建按钮方法:为每个转换状态写一个方法(如上所述)。
  4. 删除XML视图中的旧版workflow相关代码states 属性在按钮上可能不再使用(但仍然支持),需要改为 attrsstates
  5. 迁移数据:升级模块时,调用 ir.actions.server 或更新查询来填充新的state字段。

高级技巧与最佳实践

  1. 安全性与不可逆性:使用 UserError 来拒绝不合法操作,不要直接 write 修改状态。
  2. 审计追踪:给状态字段加上 tracking=True;或者在关键转换方法里调用 record.message_post(body=_("状态从 %s 变更为 %s", old_state, new_state))
  3. 结构化工作流:对于复杂的多步骤工作流(订单->审核->采购->入库->开票),使用多模型(如:采购订单模型,验收单模型,发票模型)而不是在同一个模型上疯狂增加状态,让每个模型完成一个领域的工作。
  4. 使用 @api.onchange 谨慎转换状态onchange 是UI前端事件,不能保证执行,不要在其中做关键的业务逻辑,应该在按钮方法或自动动作中做。
  5. 使用状态图替代(无代码工具):对于业务分析师,在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实现场景,包括销售、采购、库存、项目、制造、财务审批等。

抱歉,评论功能暂时关闭!