2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2014 OpenERP S.A. (<http://openerp.com).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
24 # cr.execute('delete from wkf_triggers where model=%s and res_id=%s', (res_type,res_id))
29 from openerp.workflow.helpers import Session
30 from openerp.workflow.helpers import Record
31 from openerp.workflow.helpers import WorkflowActivity
33 logger = logging.getLogger(__name__)
36 from openerp.tools.safe_eval import safe_eval as eval
38 class Environment(dict):
40 Dictionary class used as an environment to evaluate workflow code (such as
41 the condition on transitions).
43 This environment provides sybmols for cr, uid, id, model name, model
44 instance, column names, and all the record (the one obtained by browsing
45 the provided ID) attributes.
47 def __init__(self, session, record):
49 self.uid = session.uid
50 self.model = record.model
52 self.ids = [record.id]
53 self.obj = openerp.registry(self.cr.dbname)[self.model]
54 self.columns = self.obj._columns.keys() + self.obj._inherit_fields.keys()
56 def __getitem__(self, key):
57 if (key in self.columns) or (key in dir(self.obj)):
58 res = self.obj.browse(self.cr, self.uid, self.id)
61 return super(Environment, self).__getitem__(key)
64 class WorkflowItem(object):
65 def __init__(self, session, record, work_item_values):
66 assert isinstance(session, Session)
67 assert isinstance(record, Record)
68 self.session = session
71 if not work_item_values:
74 assert isinstance(work_item_values, dict)
75 self.workitem = work_item_values
78 def create(cls, session, record, activity, instance_id, stack):
79 assert isinstance(session, Session)
80 assert isinstance(record, Record)
81 assert isinstance(activity, dict)
82 assert isinstance(instance_id, (long, int))
83 assert isinstance(stack, list)
86 cr.execute("select nextval('wkf_workitem_id_seq')")
87 id_new = cr.fetchone()[0]
88 cr.execute("insert into wkf_workitem (id,act_id,inst_id,state) values (%s,%s,%s,'active')", (id_new, activity['id'], instance_id))
89 cr.execute('select * from wkf_workitem where id=%s',(id_new,))
90 work_item_values = cr.dictfetchone()
91 logger.info('Created workflow item in activity %s',
93 extra={'ident': (session.uid, record.model, record.id)})
95 workflow_item = WorkflowItem(session, record, work_item_values)
96 workflow_item.process(stack=stack)
99 def create_all(cls, session, record, activities, instance_id, stack):
100 assert isinstance(activities, list)
102 for activity in activities:
103 cls.create(session, record, activity, instance_id, stack)
105 def process(self, signal=None, force_running=False, stack=None):
106 assert isinstance(force_running, bool)
107 assert stack is not None
111 cr.execute('select * from wkf_activity where id=%s', (self.workitem['act_id'],))
112 activity = cr.dictfetchone()
115 if self.workitem['state'] == 'active':
117 if not self._execute(activity, stack):
120 if force_running or self.workitem['state'] == 'complete':
121 ok = self._split_test(activity['split_mode'], signal, stack)
122 triggers = triggers and not ok
125 cr.execute('select * from wkf_transition where act_from=%s', (self.workitem['act_id'],))
126 for trans in cr.dictfetchall():
127 if trans['trigger_model']:
128 ids = self.wkf_expr_eval_expr(trans['trigger_expr_id'])
130 cr.execute('select nextval(\'wkf_triggers_id_seq\')')
132 cr.execute('insert into wkf_triggers (model,res_id,instance_id,workitem_id,id) values (%s,%s,%s,%s,%s)', (trans['trigger_model'],res_id, self.workitem['inst_id'], self.workitem['id'], id))
136 def _execute(self, activity, stack):
137 """Send a signal to parenrt workflow (signal: subflow.signal_name)"""
142 if (self.workitem['state']=='active') and activity['signal_send']:
144 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=%s)", (self.workitem['inst_id'],))
145 for instance_id, model_name, record_id in cr.fetchall():
146 record = Record(model_name, record_id)
147 signal_todo.append((instance_id, record, activity['signal_send']))
150 if activity['kind'] == WorkflowActivity.KIND_DUMMY:
151 if self.workitem['state']=='active':
152 self._state_set(activity, 'complete')
153 if activity['action_id']:
154 res2 = self.wkf_expr_execute_action(activity)
159 elif activity['kind'] == WorkflowActivity.KIND_FUNCTION:
161 if self.workitem['state']=='active':
162 self._state_set(activity, 'running')
163 returned_action = self.wkf_expr_execute(activity)
164 if type(returned_action) in (dict,):
165 stack.append(returned_action)
166 if activity['action_id']:
167 res2 = self.wkf_expr_execute_action(activity)
168 # A client action has been returned
172 self._state_set(activity, 'complete')
174 elif activity['kind'] == WorkflowActivity.KIND_STOPALL:
175 if self.workitem['state']=='active':
176 self._state_set(activity, 'running')
177 cr.execute('delete from wkf_workitem where inst_id=%s and id<>%s', (self.workitem['inst_id'], self.workitem['id']))
178 if activity['action']:
179 self.wkf_expr_execute(activity)
180 self._state_set(activity, 'complete')
182 elif activity['kind'] == WorkflowActivity.KIND_SUBFLOW:
184 if self.workitem['state']=='active':
186 self._state_set(activity, 'running')
187 if activity.get('action', False):
188 id_new = self.wkf_expr_execute(activity)
190 cr.execute('delete from wkf_workitem where id=%s', (self.workitem['id'],))
192 assert type(id_new)==type(1) or type(id_new)==type(1L), 'Wrong return value: '+str(id_new)+' '+str(type(id_new))
193 cr.execute('select id from wkf_instance where res_id=%s and wkf_id=%s', (id_new, activity['subflow_id']))
194 id_new = cr.fetchone()[0]
196 inst = instance.WorkflowInstance(self.session, self.record)
197 id_new = inst.create(activity['subflow_id'])
199 cr.execute('update wkf_workitem set subflow_id=%s where id=%s', (id_new, self.workitem['id']))
200 self.workitem['subflow_id'] = id_new
202 if self.workitem['state']=='running':
203 cr.execute("select state from wkf_instance where id=%s", (self.workitem['subflow_id'],))
204 state = cr.fetchone()[0]
205 if state=='complete':
206 self._state_set(activity, 'complete')
208 for instance_id, record, signal_send in signal_todo:
209 wi = instance.WorkflowInstance(self.session, record, {'id': instance_id})
210 wi.validate(signal_send, force_running=True)
214 def _state_set(self, activity, state):
215 self.session.cr.execute('update wkf_workitem set state=%s where id=%s', (state, self.workitem['id']))
216 self.workitem['state'] = state
217 logger.info('Changed state of work item %s to "%s" in activity %s',
218 self.workitem['id'], state, activity['id'],
219 extra={'ident': (self.session.uid, self.record.model, self.record.id)})
221 def _split_test(self, split_mode, signal, stack):
223 cr.execute('select * from wkf_transition where act_from=%s', (self.workitem['act_id'],))
226 alltrans = cr.dictfetchall()
228 if split_mode in ('XOR', 'OR'):
229 for transition in alltrans:
230 if self.wkf_expr_check(transition,signal):
232 transitions.append((transition['id'], self.workitem['inst_id']))
233 if split_mode=='XOR':
237 for transition in alltrans:
238 if not self.wkf_expr_check(transition, signal):
241 cr.execute('select count(*) from wkf_witm_trans where trans_id=%s and inst_id=%s', (transition['id'], self.workitem['inst_id']))
242 if not cr.fetchone()[0]:
243 transitions.append((transition['id'], self.workitem['inst_id']))
245 if test and transitions:
246 cr.executemany('insert into wkf_witm_trans (trans_id,inst_id) values (%s,%s)', transitions)
247 cr.execute('delete from wkf_workitem where id=%s', (self.workitem['id'],))
248 for t in transitions:
249 self._join_test(t[0], t[1], stack)
253 def _join_test(self, trans_id, inst_id, stack):
255 cr.execute('select * from wkf_activity where id=(select act_to from wkf_transition where id=%s)', (trans_id,))
256 activity = cr.dictfetchone()
257 if activity['join_mode']=='XOR':
258 WorkflowItem.create(self.session, self.record, activity, inst_id, stack=stack)
259 cr.execute('delete from wkf_witm_trans where inst_id=%s and trans_id=%s', (inst_id,trans_id))
261 cr.execute('select id from wkf_transition where act_to=%s', (activity['id'],))
262 trans_ids = cr.fetchall()
264 for (id,) in trans_ids:
265 cr.execute('select count(*) from wkf_witm_trans where trans_id=%s and inst_id=%s', (id,inst_id))
266 res = cr.fetchone()[0]
271 for (id,) in trans_ids:
272 cr.execute('delete from wkf_witm_trans where trans_id=%s and inst_id=%s', (id,inst_id))
273 WorkflowItem.create(self.session, self.record, activity, inst_id, stack=stack)
275 def wkf_expr_eval_expr(self, lines):
277 Evaluate each line of ``lines`` with the ``Environment`` environment, returning
278 the value of the last line.
280 assert lines, 'You used a NULL action in a workflow, use dummy node instead.'
282 for line in lines.split('\n'):
288 elif line == 'False':
291 env = Environment(self.session, self.record)
292 result = eval(line, env, nocopy=True)
295 def wkf_expr_execute_action(self, activity):
297 Evaluate the ir.actions.server action specified in the activity.
300 'active_model': self.record.model,
301 'active_id': self.record.id,
302 'active_ids': [self.record.id]
305 ir_actions_server = openerp.registry(self.session.cr.dbname)['ir.actions.server']
306 result = ir_actions_server.run(self.session.cr, self.session.uid, [activity['action_id']], context)
310 def wkf_expr_execute(self, activity):
312 Evaluate the action specified in the activity.
314 return self.wkf_expr_eval_expr(activity['action'])
316 def wkf_expr_check(self, transition, signal):
318 Test if a transition can be taken. The transition can be taken if:
320 - the signal name matches,
321 - the uid is SUPERUSER_ID or the user groups contains the transition's
323 - the condition evaluates to a truish value.
325 if transition['signal'] and signal != transition['signal']:
328 if self.session.uid != openerp.SUPERUSER_ID and transition['group_id']:
329 registry = openerp.registry(self.session.cr.dbname)
330 user_groups = registry['res.users'].read(self.session.cr, self.session.uid, [self.session.uid], ['groups_id'])[0]['groups_id']
331 if transition['group_id'] not in user_groups:
334 return self.wkf_expr_eval_expr(transition['condition'])
336 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: