# -*- coding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # ############################################################################## import logging from operator import itemgetter import os from openerp import report, tools _logger = logging.getLogger(__name__) def graph_get(cr, graph, wkf_ids, nested, workitem, witm_trans, processed_subflows): import pydot cr.execute('select * from wkf_activity where wkf_id in ('+','.join(['%s']*len(wkf_ids))+')', wkf_ids) nodes = cr.dictfetchall() activities = {} actfrom = {} actto = {} for n in nodes: activities[n['id']] = n if n['subflow_id'] and nested and n['subflow_id'] not in processed_subflows: processed_subflows.add(n['subflow_id']) # don't create multiple times the same cluster. cr.execute('select * from wkf where id=%s', (n['subflow_id'],)) wkfinfo = cr.dictfetchone() graph2 = pydot.Cluster('subflow'+str(n['subflow_id']), fontsize='12', label = "\"Subflow: %s\\nOSV: %s\"" % ( n['name'], wkfinfo['osv']) ) (s1,s2) = graph_get(cr, graph2, [n['subflow_id']], True, workitem, witm_trans, processed_subflows) graph.add_subgraph(graph2) actfrom[n['id']] = s2 actto[n['id']] = s1 else: args = {} if n['flow_start'] or n['flow_stop']: args['style']='filled' args['color']='lightgrey' args['label']=n['name'] workitems = '' if n['id'] in workitem: workitems = '\\nx ' + str(workitem[n['id']]) args['label'] += workitems args['color'] = "red" args['style']='filled' if n['subflow_id']: args['shape'] = 'box' if nested and n['subflow_id'] in processed_subflows: cr.execute('select * from wkf where id=%s', (n['subflow_id'],)) wkfinfo = cr.dictfetchone() args['label'] = \ '\"Subflow: %s\\nOSV: %s\\n(already expanded)%s\"' % \ (n['name'], wkfinfo['osv'], workitems) args['color'] = 'green' args['style'] ='filled' graph.add_node(pydot.Node(n['id'], **args)) actfrom[n['id']] = (n['id'],{}) actto[n['id']] = (n['id'],{}) node_ids = tuple(map(itemgetter('id'), nodes)) cr.execute('select * from wkf_transition where act_from IN %s ORDER BY sequence,id', (node_ids,)) transitions = cr.dictfetchall() for t in transitions: if not t['act_to'] in activities: continue args = { 'label': str(t['condition']).replace(' or ', '\\nor ') .replace(' and ','\\nand ') } if t['signal']: args['label'] += '\\n'+str(t['signal']) args['style'] = 'bold' if activities[t['act_from']]['split_mode']=='AND': args['arrowtail']='box' elif str(activities[t['act_from']]['split_mode'])=='OR ': args['arrowtail']='inv' if activities[t['act_to']]['join_mode']=='AND': args['arrowhead']='crow' if t['id'] in witm_trans: args['color'] = 'red' activity_from = actfrom[t['act_from']][1].get(t['signal'], actfrom[t['act_from']][0]) activity_to = actto[t['act_to']][1].get(t['signal'], actto[t['act_to']][0]) graph.add_edge(pydot.Edge( str(activity_from) ,str(activity_to), fontsize='10', **args)) cr.execute('select * from wkf_activity where flow_start=True and wkf_id in ('+','.join(['%s']*len(wkf_ids))+')', wkf_ids) start = cr.fetchone()[0] cr.execute("select 'subflow.'||name,id from wkf_activity where flow_stop=True and wkf_id in ("+','.join(['%s']*len(wkf_ids))+')', wkf_ids) stop = cr.fetchall() if stop: stop = (stop[0][1], dict(stop)) else: stop = ("stop",{}) return (start, {}), stop def graph_instance_get(cr, graph, inst_id, nested=False): cr.execute('select wkf_id from wkf_instance where id=%s', (inst_id,)) inst = cr.fetchall() def workitem_get(instance): cr.execute('select act_id,count(*) from wkf_workitem where inst_id=%s group by act_id', (instance,)) workitems = dict(cr.fetchall()) cr.execute('select subflow_id from wkf_workitem where inst_id=%s', (instance,)) for (subflow_id,) in cr.fetchall(): workitems.update(workitem_get(subflow_id)) return workitems def witm_get(instance): cr.execute("select trans_id from wkf_witm_trans where inst_id=%s", (instance,)) return set(t[0] for t in cr.fetchall()) processed_subflows = set() graph_get(cr, graph, [x[0] for x in inst], nested, workitem_get(inst_id), witm_get(inst_id), processed_subflows) # # TODO: pas clean: concurrent !!! # class report_graph_instance(object): def __init__(self, cr, uid, ids, data): try: import pydot except Exception,e: _logger.warning( 'Import Error for pydot, you will not be able to render workflows.\n' 'Consider Installing PyDot or dependencies: http://dkbza.org/pydot.html.') raise e self.done = False try: cr.execute('select * from wkf where osv=%s limit 1', (data['model'],)) wkfinfo = cr.dictfetchone() if not wkfinfo: ps_string = '''%PS-Adobe-3.0 /inch {72 mul} def /Times-Roman findfont 50 scalefont setfont 1.5 inch 15 inch moveto (No workflow defined) show showpage''' else: cr.execute('select i.id from wkf_instance i left join wkf w on (i.wkf_id=w.id) where res_id=%s and osv=%s',(data['id'],data['model'])) inst_ids = cr.fetchall() if not inst_ids: ps_string = '''%PS-Adobe-3.0 /inch {72 mul} def /Times-Roman findfont 50 scalefont setfont 1.5 inch 15 inch moveto (No workflow instance defined) show showpage''' else: graph = pydot.Dot(graph_name=data['model'].replace('.','_'), fontsize='16', label="""\\\n\\nWorkflow: %s\\n OSV: %s""" % (wkfinfo['name'],wkfinfo['osv']), size='7.3, 10.1', center='1', ratio='auto', rotate='0', rankdir='TB', ) for inst_id in inst_ids: inst_id = inst_id[0] graph_instance_get(cr, graph, inst_id, data.get('nested', False)) ps_string = graph.create(prog='dot', format='ps') except Exception: _logger.exception('Exception in call:') # string is in PS, like the success message would have been ps_string = '''%PS-Adobe-3.0 /inch {72 mul} def /Times-Roman findfont 50 scalefont setfont 1.5 inch 15 inch moveto (No workflow available) show showpage''' if os.name == "nt": prog = 'ps2pdf.bat' else: prog = 'ps2pdf' args = (prog, '-', '-') input, output = tools.exec_command_pipe(*args) input.write(ps_string) input.close() self.result = output.read() output.close() self.done = True def is_done(self): return self.done def get(self): if self.done: return self.result else: return None class report_graph(report.interface.report_int): def __init__(self, name, table): report.interface.report_int.__init__(self, name) self.table = table def result(self): if self.obj.is_done(): return True, self.obj.get(), 'pdf' else: return False, False, False def create(self, cr, uid, ids, data, context=None): self.obj = report_graph_instance(cr, uid, ids, data) return self.obj.get(), 'pdf' report_graph('report.workflow.instance.graph', 'ir.workflow') # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: