简介
工作流程系统在OpenERP里是非常有用的机制,可以用于及时描述文件(模型)的演进过程。
工作流程是完全可以定制的,这些流程可以调整适用于几乎所有公司的作业流程和交易逻辑。 这个工作流程系统使OpenERP非常有弹性, 而且可以不用编程增加新功能,就可以支持不断变化的需要。
目标
- 及时描述文件的演进
- 符合某些条件时自动触发动作
- 管理公司里人员的角色和验证步骤
- 管理不同物件/模块间的互动
- 为文件流程可视化提供图形化的工具
以下三个例子说明了工作流程系统的用途:
例子1 : 订单折扣
第一个流程图代表了一种非常基本的订单流程:
![../_images/Workflow_bc1.png](http://oedoc601.mrshelly.com/_images/Workflow_bc1.png)
订单一开始在输入但是还没有被核准前,是在’草稿’状态。当使用者按下’确认’,系统会生成请款单,而且订单会变成’已确认’状态。
然后, 可能有2个动作:
- 订单完成(已出货)
- 订单被取消
假设公司有一个需求还没有在 OpenERP 里实现,例如,销售人员只能同意到最多 15% 的折扣,所有折扣大于 15% 的订单必须由销售经理核准。
这种业务流程的修改需求不需要更改任何 Python 编码! 只需要一个简单的工作流程修改就可以让我们把这个新的需求列入考虑,并且多增加一个验证步骤。
![../_images/Workflow_bc2.png](http://oedoc601.mrshelly.com/_images/Workflow_bc2.png)
工作流程依上面的图里修改以后,订单就会依所要求的方式反应。然后我们只需要修改订单的表单显示方式,在想要的位置加上验证的按键。
然后我们可以再深入强化这个工作流程-当订单进入 ‘待验证’ 状态,主动通知销售经理审核订单。工作流程的节点可以执行物件的程序;只要增加2行 Python 编码,就可以通知销售经理审核或是取消订单。
例子2: 业务订单产生请款单及出货通知
![../_images/Workflow_sale.png](http://oedoc601.mrshelly.com/_images/Workflow_sale.png)
例子3: 客户请款单基本工作流程
![../_images/Acount_inv_wkf.jpg](http://oedoc601.mrshelly.com/_images/Acount_inv_wkf.jpg)
定义工作流程
工作流程是在以下档案里定义的 server/addons/base/ir/workflow/workflow.py。 在这个档案里定义的前3个级别(class)是 workflow,wkf_activity 和 wkf_transition。他们分别对应了工作流程里3种需要的资源:
- workflow : 工作流程,
- wkf_activity : 工作流程的活动(节点),
- wkf_transition : 在不同活动间的转变。
工作流程 XML 档案的一般结构
工作流程 XML 档案的一般结构如下:
<?xml version="1.0"?>
<openerp>
<data>
<record model="workflow" id=workflow_id>
<field name="name">workflow.name</field>
<field name="osv">resource.model</field>
<field name="on_create" eval='True|False' />
</record>
</data>
</openerp>
这里的
- id (就是 “workflow_id”) 是工作流程的识别码。每个工作流程必须有一个唯一的识别码。
- name (就是 “workflow.name”) 是工作流程的名称。工作流程的名称必须符合 OpenERP 文法的 “带点名称” 要求。
- osv (就是 “resource.model”) 是我们当做模型使用的物件名称 [-(请记得 OpenERP 物件是从 osv.osv 继承属性,所以 ‘<字段名称=”osv”>’)-]。
- on_create 如果为 True,在创建 resource.model 时会自动将 workflow.name 实体化,如果是 False 则相反。
范例
定义在 addons/sale/sale_workflow.xml 里的工作流程 sale.order.basic 完全依照这个模式,工作流程标签的编码如下:
<record model="workflow" id="wkf_sale">
<field name="name">sale.order.basic</field>
<field name="osv">sale.order</field>
<field name="on_create" eval='True' />
</record>
Activity(活动)
简介
wkf_activity 这个级别(class)代表工作流程的节点。这些节点就是要被执行的行动。
字段
split_mode(拆分模式)
![../_images/Wkf_split.png](http://oedoc601.mrshelly.com/_images/Wkf_split.png)
可能的值:
- XOR: 一个必须的转变, 取找到的第一个转变(预设值)。
- OR: 只依顺序取有效的转变(可能是0个或是多个)。
- AND: 所有有效的转变都会同时出现(分叉)。
在 OR 和 AND 分离模式,可能会产生一些工作时间。
在 AND 模式,活动会等到所有转变都生效才会开始进行;即使是有小部分转变还没有生效,活动也不会开始进行。所有活动是同时被触发的。
join_mode(结合模式)
![../_images/Wkf_join.png](http://oedoc601.mrshelly.com/_images/Wkf_join.png)
可能的值:
- XOR: 进行目标的活动前,还必须继续一个转变(预设值)。
- AND: 等待所有转变都生效,才能执行活动。
kind(种类)
可能的值:
- dummy: 不作任何事(预设值)。
- function: 执行一个行为选择的功能。
- subflow: 执行一个子工作流程 SUBFLOW_ID(子工作流程代码)。这个行为程序必须回复子工作流程所需要的资源代码。如果这个行为程序回复 False,这个工作项目就会消失。
- stopall: 全部停止
当某一个活动是 SUBFLOW 形态时,就会执行子工作流程。当子工作流程结束时活动也会结束。当子工作流程在作用中,这个活动的工作项目会被冻结。
action(行动)
行动是指当某一个工作项目来到这个活动时,所要执行的程序方法。这些程序方法必须在这个工作流程里的物件里有定义,而且具有以下特征:
def object_method(self, cr, uid, ids):
在实际行动里,这些程序方法会被以下的叙述方式呼叫:
object_method()
signal_send(送出信号)
这个字段是用于指定一个信号,当活动在作用中,这个信号就会被送往上一层的工作流程。如果要送出信号,将字段值设定为信号的名字 (去掉开头的 signal. )。
flow_start(流程开始)
标记这个节点是否为开始节点。当创建一个工作流程的实例(instance)时,每一个活动都会启动一个标记为 n``flow_start``n (流程开始)的工作项目。
Warning
-
对所有的布尔型字段来说,当在你的XML资料里写入
<field>
标记时,务必使用
eval
属性,
- 不可以使用文字节点属性。详细说明请参考:ref: eval attribute <eval-attribute-link>。
flow_stop(流程停止)
标记这个节点是否为结束的节点。当一个实例(instance)里所有作用中的工作项目来到标记为``flow_stop``(流程停止)的节点,工作流程将会结束。
Warning
参考上面关于 flow_start (流程开始)的字段说明
-
::
- wkf_id(工作流程识别码)
表示这个活动所属的工作流程。
使用XML档案定义活动
活动记录的一般结构如下
<record model="workflow.activity" id="''activity_id''">
<field name="wkf_id" ref="''workflow_id''"/>
<field name="name">''activity.name''</field>::
<field name="split_mode">XOR | OR | AND</field>
<field name="join_mode">XOR | AND</field>
<field name="kind">dummy | function | subflow | stopall</field>
<field name="action">''(...)''</field>
<field name="signal_send">''(...)''</field>
<field name="flow_start" eval='True | False' />
<field name="flow_stop" eval='True | False' />
</record>
开始的**wkf_id**和**name**是强制要求要有的两个参数。
范例
有太多时候可以从这个定义档案里选择其中一项作为活动的定义,所以我们建议大家看看以下档案里的几个活动定义范例。server/addons/sale/sale_workflow.xml
Transition(转变)
简介
工作流程的转变是指,一个活动要进行到下一个活动前,必须满足的条件。转变是用单向的箭头代表,通常会连接前后两个活动。
条件有以下几种:
- 使用者必须符合某种角色要求
- 在使用界面里按下某个按钮
- 经由某个指定的活动达到这个子流程的结束点
系统是在表达式之前先判断任务或信号是否成立,所以如果任务或信号为伪(false),系统不会进行表达式的判断。
转变的判断可能不会在物件里写入任何值。
字段
act_from(来源活动)
转变的来源活动。当这个(来源)活动结束后,系统会检查这个字段的状态,来确认是不是可以开始进行 ACT_TO 活动。
act_to(目标活动)
转变要进行到的目标活动
condition(状态)
要满足**表达式(Expression)** 才能完成转变。
signal(信号)
当转变的运作是来自于在使用者界面里按下一个按钮,信号会检查被按下的按钮的名称。
如果信号为空值(NULL),表示不需要任何按钮来启动这个转变。
role_id(角色识别码)
使用者必须符合某个**角色**才能启动这个转变
用 XML 档案定义转变
转变记录的一般结构如下
<record model="workflow.transition" id="transition_id">
<field name="act_from" ref="activity_id'_1_'"/>
<field name="act_to" ref="activity_id'_2_'"/>
<field name="signal">(...)</field>
<field name="role_id" ref="role_id'_1_'"/>
<field name="condition">(...)</field>
<field name="trigger_model">(...)</field>
<field name="trigger_expr_id">(...)</field>
</record>
只有**act_from**和**act_to**这两个字段是强制要求要有的。
Expressions(表达式)
表达式是以 Python 写成的:
- True
- 1==1
- ‘hello’ in [‘hello’,’bye’]
工作流程指向的资源里,任何字段都可以用在表达式里。例如,如果想要为伙伴地址建立一个工作流程,可以用类似以下的表达式:
- zip==1400
- phone==mobile
使用者角色
转变可以附加角色要求。如果转变指定角色要求,只有符合角色要求的使用者启动转变,转变才会进行。
每个使用者可以有一个或多个角色。角色会被定义在一个角色树状图里,上层(父层)的角色拥有所有下层(子层)的权力。
范例:
执行长
技术经理
研发组长
- 研发员
- 测试员
- 销售经理
- 广告人员
- ...
假设我们要查找数据库里的错误,需要测试员的角色才能在找到的错误上做标示,在上述范例的角色树里,有以下角色的人都可以在找到的错误上做标示:测试员,研发组长,技术经理,执行长。
错误处理
在以下的叙述中,工作流程里没有包含错误处理。
如果工作流程是批量执行的动作组成的,就不会触发例外状况。为了提升执行效率和尽量不被锁住,工作流程在每一个活动结束时才提交一个结果。这个策略是合理的,因为在每一个动作要求的条件被满足后,活动才会被执行。
唯一可能出现例外状况的问题是编程上的错误;这种状况下,只有属于整个已经执行完成的活动的动作才会被执行,其他的活动会被回退到上个检查点。
创建一个工作流程
以下步骤是用于创建一个名为**mymod**的定制模块,是一个简单的改变状态的工作流程
定义你的物件的状态
第一步是定义你的物件可以有那些状态。我们在物件的栏目(_columns)集合里加上一个 ‘state’ 字段,用于定义物件的状态。
_columns = {
...
'state': fields.selection([
('new','New'),
('assigned','Assigned'),
('negotiation','Negotiation'),
('won','Won'),
('lost','Lost')], 'Stage', readonly=True),
}
定义状态改变的处理方式
在你的物件里增加以下额外的处理方法,我们的工作流程里的按钮会呼叫这些方法。
def mymod_new(self, cr, uid, ids):
self.write(cr, uid, ids, {'state': 'new'})
return True
def mymod_assigned(self, cr, uid, ids):
self.write(cr, uid, ids, {'state': 'assigned'})
return True
def mymod_negotiation(self, cr, uid, ids):
self.write(cr, uid, ids, {'state': 'negotiation'})
return True
def mymod_won(self, cr, uid, ids):
self.write(cr, uid, ids, {'state': 'won'})
return True
def mymod_lost(self, cr, uid, ids):
self.write(cr, uid, ids, {'state': 'lost'})
return True
显然你以后会想把这些方法扩充改成执行更有用的事项!
创建你的工作流程XML档案
我们在 mymod_workflow.xml 这个档案里需要定义3个种类的记录。
- 工作流程标题记录
<record model="workflow" id="wkf_mymod"> <field name="name">mymod.wkf</field> <field name="osv">mymod.mymod</field> <field name="on_create" eval='True' /> </record>
- 工作流程活动记录
这种记录是在定义工作流程到达某个特定状态时,必须执行的动作
<record model="workflow.activity" id="act_new"> <field name="wkf_id" ref="wkf_mymod" /> <field name="flow_start" eval='True' /> <field name="name">new</field> <field name="kind">function</field> <field name="action">mymod_new()</field> </record> <record model="workflow.activity" id="act_assigned"> <field name="wkf_id" ref="wkf_mymod" /> <field name="name">assigned</field> <field name="kind">function</field> <field name="action">mymod_assigned()</field> </record> <record model="workflow.activity" id="act_negotiation"> <field name="wkf_id" ref="wkf_mymod" /> <field name="name">negotiation</field> <field name="kind">function</field> <field name="action">mymod_negotiation()</field> </record> <record model="workflow.activity" id="act_won"> <field name="wkf_id" ref="wkf_mymod" /> <field name="name">won</field> <field name="kind">function</field> <field name="action">mymod_won()</field> <field name="flow_stop" eval='True' /> </record> <record model="workflow.activity" id="act_lost"> <field name="wkf_id" ref="wkf_mymod" /> <field name="name">lost</field> <field name="kind">function</field> <field name="action">mymod_lost()</field> <field name="flow_stop" eval='True' /> </record>
- 工作流程转变记录
这种记录是在定义工作流程的状态间,可能的转变
<record model="workflow.transition" id="t1"> <field name="act_from" ref="act_new" /> <field name="act_to" ref="act_assigned" /> <field name="signal">mymod_assigned</field> </record> <record model="workflow.transition" id="t2"> <field name="act_from" ref="act_assigned" /> <field name="act_to" ref="act_negotiation" /> <field name="signal">mymod_negotiation</field> </record> <record model="workflow.transition" id="t3"> <field name="act_from" ref="act_negotiation" /> <field name="act_to" ref="act_won" /> <field name="signal">mymod_won</field> </record> <record model="workflow.transition" id="t4"> <field name="act_from" ref="act_negotiation" /> <field name="act_to" ref="act_lost" /> <field name="signal">mymod_lost</field> </record>
把 mymod_workflow.xml 加到 __openerp__.py 里
修改你的模块里的 __openerp__.py ;把 "mymod_workflow.xml" 加到 update_xml 阵列,这样下一次 OpenERP 载入你的模块时,就会抓取这些修改。
在你的视图里加上工作流程按钮
最后一个步骤是把需要的按钮加到 mymod_views.xml 档案里。
在你的物件的视图定义里, <form> 部分的最后一段,加上以下程序:
<separator string="Workflow Actions" colspan="4"/> <group colspan="4" col="3"> <button name="mymod_assigned" string="Assigned" states="new" /> <button name="mymod_negotiation" string="In Negotiation" states="assigned" /> <button name="mymod_won" string="Won" states="negotiating" /> <button name="mymod_lost" string="Lost" states="negotiating" /> </group>
测试
现在可以用模块管理员(Module Manager)来安装或更新你的模块。如果你有正确地完成所有事项,应该不会出现任何错误。你可以检查你的工作流程是不是有被安装在菜单里:选择菜单(menuselection):管理(Administration) –> 定制(Customization) –> 工作流程定义(Workflow Definitions).
当你进行测试时,记得新加入的工作流程只会被应用在新创建的记录上。
故障排除
如果你的按钮看起来没有任何作用,或许是因为以下两个问题的其中一个:
- 你正在处理的记录没有连接到工作流程实例(Instance)记录(也许是这笔记录是在你定义你的工作流程以前创建的)
- 你在你的工作流程 XML 档案里没有正确设定 osv 这个字段