diff options
| author | pinky <> | 2006-12-07 13:41:40 +0000 |
|---|---|---|
| committer | pinky <> | 2006-12-07 13:41:40 +0000 |
| commit | 004a0b996ff8f269451e07346f71a129a1f3fbaf (patch) | |
| tree | 48f43c1fc2ca4c1f4d5648e7e73553d3f4ccae06 /bin/workflow | |
New trunk
bzr revid: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
Diffstat (limited to 'bin/workflow')
| -rw-r--r-- | bin/workflow/__init__.py | 31 | ||||
| -rw-r--r-- | bin/workflow/common.py | 32 | ||||
| -rw-r--r-- | bin/workflow/instance.py | 81 | ||||
| -rw-r--r-- | bin/workflow/wkf_expr.py | 91 | ||||
| -rw-r--r-- | bin/workflow/wkf_logs.py | 35 | ||||
| -rw-r--r-- | bin/workflow/wkf_service.py | 98 | ||||
| -rw-r--r-- | bin/workflow/workitem.py | 184 |
7 files changed, 552 insertions, 0 deletions
diff --git a/bin/workflow/__init__.py b/bin/workflow/__init__.py new file mode 100644 index 00000000000..2f7247b929c --- /dev/null +++ b/bin/workflow/__init__.py @@ -0,0 +1,31 @@ +############################################################################## +# +# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved. +# Fabien Pinckaers <fp@tiny.Be> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + + +import wkf_service + diff --git a/bin/workflow/common.py b/bin/workflow/common.py new file mode 100644 index 00000000000..152485ffd48 --- /dev/null +++ b/bin/workflow/common.py @@ -0,0 +1,32 @@ +############################################################################## +# +# copyright (c) 2004 TINY SPRL. (http://tiny.be) +# copyright (c) 2004 Fabien Pinckaers <fp@tiny.be> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contact a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +class except_wkf(Exception): + def __init__(self, name, value): + self.name = name + self.value = value diff --git a/bin/workflow/instance.py b/bin/workflow/instance.py new file mode 100644 index 00000000000..0701ccbcec7 --- /dev/null +++ b/bin/workflow/instance.py @@ -0,0 +1,81 @@ +############################################################################## +# +# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved. +# Fabien Pinckaers <fp@tiny.Be> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import wkf_logs +import workitem + +import netsvc +import pooler + +def create(cr, ident, wkf_id): + (uid,res_type,res_id) = ident + cr.execute("select nextval('wkf_instance_id_seq')") + id_new = cr.fetchone()[0] + cr.execute('insert into wkf_instance (id,res_type,res_id,uid,wkf_id) values (%d,%s,%s,%s,%s)', (id_new,res_type,res_id,uid,wkf_id)) + cr.execute('select * from wkf_activity where flow_start=True and wkf_id=%d', (wkf_id,)) + res = cr.dictfetchall() + workitem.create(cr, res, id_new, ident) + update(cr, id_new, ident) + return id_new + +def delete(cr, ident): + (uid,res_type,res_id) = ident + cr.execute('delete from wkf_instance where res_id=%d and res_type=%s', (res_id,res_type)) + +def validate(cr, inst_id, ident, signal, force_running=False): + cr.execute("select * from wkf_workitem where inst_id=%d", (inst_id,)) + for witem in cr.dictfetchall(): + workitem.process(cr, witem, ident, signal, force_running) + return _update_end(cr, inst_id, ident) + +def update(cr, inst_id, ident): + cr.execute("select * from wkf_workitem where inst_id=%d", (inst_id,)) + for witem in cr.dictfetchall(): + workitem.process(cr, witem, ident) + return _update_end(cr, inst_id, ident) + +def _update_end(cr, inst_id, ident): + cr.execute('select wkf_id from wkf_instance where id=%d', (inst_id,)) + wkf_id = cr.fetchone()[0] + cr.execute('select state,flow_stop from wkf_workitem w left join wkf_activity a on (a.id=w.act_id) where w.inst_id=%d', (inst_id,)) + ok=True + for r in cr.fetchall(): + if (r[0]<>'complete') or not r[1]: + ok=False + break + if ok: + cr.execute('select distinct a.name from wkf_activity a left join wkf_workitem w on (a.id=w.act_id) where w.inst_id=%d', (inst_id,)) + act_names = cr.fetchall() + cr.execute("update wkf_instance set state='complete' where id=%d", (inst_id,)) + cr.execute("update wkf_workitem set state='complete' where subflow_id=%d", (inst_id,)) + cr.execute("select i.id,w.osv,i.res_id from wkf_instance i left join wkf w on (i.wkf_id=w.id) where i.id in (select inst_id from wkf_workitem where subflow_id=%d)", (inst_id,)) + for i in cr.fetchall(): + for act_name in act_names: + validate(cr, i[0], (ident[0],i[1],i[2]), 'subflow.'+act_name[0]) + return ok + diff --git a/bin/workflow/wkf_expr.py b/bin/workflow/wkf_expr.py new file mode 100644 index 00000000000..0245e9e0bd6 --- /dev/null +++ b/bin/workflow/wkf_expr.py @@ -0,0 +1,91 @@ +############################################################################## +# +# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved. +# +# $Id: wkf_expr.py 1304 2005-09-08 14:35:42Z nicoe $ +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## +import sys +import netsvc +import osv as base +import pooler + +def _inst_value_get(ident, field): + service = netsvc.LocalService("object_proxy") + res = service.execute(ident[0], ident[1], 'read', [ident[2]], [field]) + return res[0][field] + +# +# TODO: improve evaluation expression +# VERY SLOW !!! read all !!! +# +class _eval_call(object): + def __init__(self,wf_service,d_arg): + self.wf_service=wf_service + self.d_arg=d_arg + def __call__(self,*args): + arg=self.d_arg+args + return self.wf_service.execute_cr(*arg) + +def _eval_expr(cr, ident, workitem, action): + ret=False + for line in action.split('\n'): + uid=ident[0] + model=ident[1] + ids=[ident[2]] + if line =='True': + ret=True + elif line =='False': + ret=False + else: + wf_service = netsvc.LocalService("object_proxy") + res=wf_service.execute_cr(cr, uid, model, 'read', ids, None) + if res: + env=res[0] + if '(' in line: + pool=pooler.get_pool(cr.dbname) + obj=pool.get(model) + for i in dir(obj): + attr=getattr(obj,i) + if not i.startswith('_') and callable(attr): + env[i]=_eval_call(wf_service,(cr,uid,model,i,ids)) + ret=eval(line,env) + return ret + +def execute(cr, ident, workitem, activity): + return _eval_expr(cr, ident, workitem, activity['action']) + +def check(cr, workitem, ident, transition, signal): + ok = True + if transition['signal']: + ok = (signal==transition['signal']) + + if transition['role_id']: + uid = ident[0] + serv = netsvc.LocalService('object_proxy') + user_roles = serv.execute_cr(cr, uid, 'res.users', 'read', [uid], ['roles_id'])[0]['roles_id'] + ok = ok and serv.execute_cr(cr, uid, 'res.roles', 'check', user_roles, transition['role_id']) + ok = ok and _eval_expr(cr, ident, workitem, transition['condition']) + return ok + diff --git a/bin/workflow/wkf_logs.py b/bin/workflow/wkf_logs.py new file mode 100644 index 00000000000..abefc6348ba --- /dev/null +++ b/bin/workflow/wkf_logs.py @@ -0,0 +1,35 @@ +############################################################################## +# +# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved. +# Fabien Pinckaers <fp@tiny.Be> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +# +# May be uncommented to logs workflows modifications +# + +def log(cr,ident,act_id,info=''): + pass + #cr.execute('insert into wkf_logs (res_type, res_id, uid, act_id, time, info) values (%s,%d,%d,%d,current_time,%s)', (ident[1],int(ident[2]),int(ident[0]),int(act_id),info)) diff --git a/bin/workflow/wkf_service.py b/bin/workflow/wkf_service.py new file mode 100644 index 00000000000..707ce97eb9c --- /dev/null +++ b/bin/workflow/wkf_service.py @@ -0,0 +1,98 @@ +############################################################################## +# +# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved. +# Fabien Pinckaers <fp@tiny.Be> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import wkf_logs +import workitem +import instance + +import netsvc +import pooler + +class workflow_service(netsvc.Service): + def __init__(self, name='workflow', audience='*'): + netsvc.Service.__init__(self, name, audience) + self.exportMethod(self.trg_write) + self.exportMethod(self.trg_delete) + self.exportMethod(self.trg_create) + self.exportMethod(self.trg_validate) + self.exportMethod(self.trg_redirect) + self.exportMethod(self.trg_trigger) + + def trg_write(self, uid, res_type, res_id, cr): + ident = (uid,res_type,res_id) + cr.execute('select id from wkf_instance where res_id=%d and res_type=%s and state=%s', (res_id,res_type, 'active')) + for (id,) in cr.fetchall(): + instance.update(cr, id, ident) + + def trg_trigger(self, uid, res_type, res_id, cr): + cr.execute('select instance_id from wkf_triggers where res_id=%d and model=%s', (res_id,res_type)) + res = cr.fetchall() + for (instance_id,) in res: + cr.execute('select uid,res_type,res_id from wkf_instance where id=%d', (instance_id,)) + ident = cr.fetchone() + instance.update(cr, instance_id, ident) + + def trg_delete(self, uid, res_type, res_id, cr): + ident = (uid,res_type,res_id) + instance.delete(cr, ident) + + def trg_create(self, uid, res_type, res_id, cr): + ident = (uid,res_type,res_id) + cr.execute('select id from wkf where osv=%s and on_create=True', (res_type,)) + for (wkf_id,) in cr.fetchall(): + instance.create(cr, ident, wkf_id) + + def trg_validate(self, uid, res_type, res_id, signal, cr): + ident = (uid,res_type,res_id) + # ids of all active workflow instances for a corresponding resource (id, model_nam) + cr.execute('select id from wkf_instance where res_id=%d and res_type=%s and state=%s', (res_id, res_type, 'active')) + for (id,) in cr.fetchall(): + instance.validate(cr, id, ident, signal) + + # make all workitems which are waiting for a (subflow) workflow instance + # for the old resource point to the (first active) workflow instance for + # the new resource + def trg_redirect(self, uid, res_type, res_id, new_rid, cr): + # get ids of wkf instances for the old resource (res_id) +#CHECKME: shouldn't we get only active instances? + cr.execute('select id, wkf_id from wkf_instance where res_id=%d and res_type=%s', (res_id, res_type)) + for old_inst_id, wkf_id in cr.fetchall(): + # first active instance for new resource (new_rid), using same wkf + cr.execute( + 'SELECT id '\ + 'FROM wkf_instance '\ + 'WHERE res_id=%d AND res_type=%s AND wkf_id=%d AND state=%s', + (new_rid, res_type, wkf_id, 'active')) + new_id = cr.fetchone() + if new_id: + # select all workitems which "wait" for the old instance + cr.execute('select id from wkf_workitem where subflow_id=%d', (old_inst_id,)) + for (item_id,) in cr.fetchall(): + # redirect all those workitems to the wkf instance of the new resource + cr.execute('update wkf_workitem set subflow_id=%d where id=%d', (new_id[0], item_id)) +workflow_service() diff --git a/bin/workflow/workitem.py b/bin/workflow/workitem.py new file mode 100644 index 00000000000..b01c9fbed3d --- /dev/null +++ b/bin/workflow/workitem.py @@ -0,0 +1,184 @@ +############################################################################## +# +# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved. +# Fabien Pinckaers <fp@tiny.Be> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +# +# TODO: +# cr.execute('delete from wkf_triggers where model=%s and res_id=%d', (res_type,res_id)) +# + +import netsvc +import instance + +import wkf_expr +import wkf_logs + +def create(cr, act_datas, inst_id, ident): + for act in act_datas: + cr.execute("select nextval('wkf_workitem_id_seq')") + id_new = cr.fetchone()[0] + cr.execute("insert into wkf_workitem (id,act_id,inst_id,state) values (%d,%s,%s,'active')", (id_new, act['id'], inst_id)) + cr.execute('select * from wkf_workitem where id=%d',(id_new,)) + res = cr.dictfetchone() + wkf_logs.log(cr,ident,act['id'],'active') + process(cr, res, ident) + +def process(cr, workitem, ident, signal=None, force_running=False): + cr.execute('select * from wkf_activity where id=%d', (workitem['act_id'],)) + activity = cr.dictfetchone() + + triggers = False + if workitem['state']=='active': + triggers = True + if not _execute(cr, workitem, activity, ident): + return False + + if workitem['state']=='running': + pass + + if workitem['state']=='complete' or force_running: + ok = _split_test(cr, workitem, activity['split_mode'], ident, signal) + triggers = triggers and not ok + + if triggers: + cr.execute('select * from wkf_transition where act_from=%d', (workitem['act_id'],)) + alltrans = cr.dictfetchall() + for trans in alltrans: + if trans['trigger_model']: + ids = wkf_expr._eval_expr(cr,ident,workitem,trans['trigger_expr_id']) + for id in ids: + cr.execute('insert into wkf_triggers (model,res_id,instance_id,workitem_id) values (%s,%d,%d,%d)', (trans['trigger_model'],id,workitem['inst_id'], workitem['id'])) + + return True + + +# ---------------------- PRIVATE FUNCS -------------------------------- + +def _state_set(cr, workitem, activity, state, ident): + cr.execute('update wkf_workitem set state=%s where id=%d', (state,workitem['id'])) + workitem['state'] = state + wkf_logs.log(cr,ident,activity['id'],state) + +def _execute(cr, workitem, activity, ident): + # + # send a signal to parent workflow (signal: subflow.signal_name) + # + if (workitem['state']=='active') and activity['signal_send']: + cr.execute("select i.id,w.osv,i.res_id from wkf_instance i left join wkf w on (i.wkf_id=w.id) where i.id in (select inst_id from wkf_workitem where subflow_id=%d)", (workitem['inst_id'],)) + for i in cr.fetchall(): + instance.validate(cr, i[0], (ident[0],i[1],i[2]), activity['signal_send'], force_running=True) + + + + if activity['kind']=='dummy': + if workitem['state']=='active': + _state_set(cr, workitem, activity, 'complete', ident) + elif activity['kind']=='function': + if workitem['state']=='active': + _state_set(cr, workitem, activity, 'running', ident) + wkf_expr.execute(cr, ident, workitem, activity) + _state_set(cr, workitem, activity, 'complete', ident) + elif activity['kind']=='stopall': + if workitem['state']=='active': + _state_set(cr, workitem, activity, 'running', ident) + cr.execute('delete from wkf_workitem where inst_id=%d and id<>%d', (workitem['inst_id'], workitem['id'])) + if activity['action']: + wkf_expr.execute(cr, ident, workitem, activity) + _state_set(cr, workitem, activity, 'complete', ident) + elif activity['kind']=='subflow': + if workitem['state']=='active': + _state_set(cr, workitem, activity, 'running', ident) + if activity.get('action', False): + id_new = wkf_expr.execute(cr, ident, workitem, activity) + if not (id_new): + cr.execute('delete from wkf_workitem where id=%s', (workitem['id'],)) + return False + assert type(id_new)==type(1) or type(id_new)==type(1L), 'Wrong return value: '+str(id_new)+' '+str(type(id_new)) + cr.execute('select id from wkf_instance where res_id=%d and wkf_id=%d', (id_new,activity['subflow_id'])) + id_new = cr.fetchone()[0] + else: + id_new = instance.create(cr, ident, activity['subflow_id']) + cr.execute('update wkf_workitem set subflow_id=%d where id=%s', (id_new, workitem['id'])) + workitem['subflow_id'] = id_new + if workitem['state']=='running': + cr.execute("select state from wkf_instance where id=%d", (workitem['subflow_id'],)) + state= cr.fetchone()[0] + if state=='complete': + _state_set(cr, workitem, activity, 'complete', ident) + return True + +def _split_test(cr, workitem, split_mode, ident, signal=None): + cr.execute('select * from wkf_transition where act_from=%d', (workitem['act_id'],)) + test = False + transitions = [] + alltrans = cr.dictfetchall() + if split_mode=='XOR' or split_mode=='OR': + for transition in alltrans: + if wkf_expr.check(cr, workitem, ident, transition,signal): + test = True + transitions.append((transition['id'], workitem['inst_id'])) + if split_mode=='XOR': + break + else: + test = True + for transition in alltrans: + if not wkf_expr.check(cr, workitem, ident, transition,signal): + test = False + break + cr.execute('select count(*) from wkf_witm_trans where trans_id=%d and inst_id=%d', (transition['id'], workitem['inst_id'])) + if not cr.fetchone()[0]: + transitions.append((transition['id'], workitem['inst_id'])) + if not test: + pass + if test and len(transitions): + cr.executemany('insert into wkf_witm_trans (trans_id,inst_id) values (%d,%d)', transitions) + cr.execute('delete from wkf_workitem where id=%d', (workitem['id'],)) + for t in transitions: + _join_test(cr, t[0], t[1], ident) + return True + return False + +def _join_test(cr, trans_id, inst_id, ident): + cr.execute('select * from wkf_activity where id=(select act_to from wkf_transition where id=%d)', (trans_id,)) + activity = cr.dictfetchone() + if activity['join_mode']=='XOR': + create(cr,[activity], inst_id, ident) + cr.execute('delete from wkf_witm_trans where inst_id=%d and trans_id=%d', (inst_id,trans_id)) + else: + cr.execute('select id from wkf_transition where act_to=%d', (activity['id'],)) + trans_ids = cr.fetchall() + ok = True + for (id,) in trans_ids: + cr.execute('select count(*) from wkf_witm_trans where trans_id=%d and inst_id=%d', (id,inst_id)) + res = cr.fetchone()[0] + if not res: + ok = False + break + if ok: + for (id,) in trans_ids: + cr.execute('delete from wkf_witm_trans where trans_id=%d and inst_id=%d', (id,inst_id)) + create(cr, [activity], inst_id, ident) |
