X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=addons%2Fprocess%2Fprocess.py;h=464cf460616a7067a270993f395f503710f3014a;hb=0d3bf8d1a21cae4d8bb70009fc34ad3520ca224c;hp=013bdba8e7c79d9180aae847edc9b3830dfac386;hpb=ca784ddcce01b8ffa07a8486a2d0098f77b6a9ff;p=odoo%2Fodoo.git diff --git a/addons/process/process.py b/addons/process/process.py index 013bdba..464cf46 100644 --- a/addons/process/process.py +++ b/addons/process/process.py @@ -1,77 +1,109 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- ############################################################################## # -# Copyright (c) 2005-TODAY TINY SPRL. (http://tiny.be) All Rights Reserved. +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). # -# 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 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 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 Affero General Public License for more details. # -# 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. +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . # ############################################################################## -import netsvc -import pooler, tools - +import pooler +import tools from osv import fields, osv class Env(dict): - + def __init__(self, obj, user): self.__obj = obj self.__usr = user - + def __getitem__(self, name): - if name in ('__obj', '__user'): - return super(ExprContext, self).__getitem__(name) - + return super(Env, self).__getitem__(name) if name == 'user': return self.__user - if name == 'object': return self.__obj - return self.__obj[name] class process_process(osv.osv): _name = "process.process" _description = "Process" _columns = { - 'name': fields.char('Name', size=30,required=True), - 'active': fields.boolean('Active'), - 'note': fields.text('Notes'), + 'name': fields.char('Name', size=30,required=True, translate=True), + 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the process without removing it."), + 'model_id': fields.many2one('ir.model', 'Object', ondelete='set null'), + 'note': fields.text('Notes', translate=True), 'node_ids': fields.one2many('process.node', 'process_id', 'Nodes') } _defaults = { 'active' : lambda *a: True, } - def graph_get(self, cr, uid, id, res_model, res_id, scale, context): + def search_by_model(self, cr, uid, res_model, context=None): pool = pooler.get_pool(cr.dbname) - - process = pool.get('process.process').browse(cr, uid, [id])[0] - current_object = pool.get(res_model).browse(cr, uid, [res_id], context)[0] - current_user = pool.get('res.users').browse(cr, uid, [uid], context)[0] - - expr_context = Env(current_object, current_user) - + model_ids = (res_model or None) and pool.get('ir.model').search(cr, uid, [('model', '=', res_model)]) + + domain = (model_ids or []) and [('model_id', 'in', model_ids)] + result = [] + + # search all processes + res = pool.get('process.process').search(cr, uid, domain) + if res: + res = pool.get('process.process').browse(cr, uid, res, context=context) + for process in res: + result.append((process.id, process.name)) + return result + + # else search process nodes + res = pool.get('process.node').search(cr, uid, domain) + if res: + res = pool.get('process.node').browse(cr, uid, res, context=context) + for node in res: + if (node.process_id.id, node.process_id.name) not in result: + result.append((node.process_id.id, node.process_id.name)) + + return result + + def graph_get(self, cr, uid, id, res_model, res_id, scale, context=None): + + pool = pooler.get_pool(cr.dbname) + + process = pool.get('process.process').browse(cr, uid, id, context=context) + + name = process.name + resource = False + state = 'N/A' + + expr_context = {} + states = {} + perm = False + + if res_model: + states = dict(pool.get(res_model).fields_get(cr, uid, context=context).get('state', {}).get('selection', {})) + + if res_id: + current_object = pool.get(res_model).browse(cr, uid, res_id, context=context) + current_user = pool.get('res.users').browse(cr, uid, uid, context=context) + expr_context = Env(current_object, current_user) + resource = current_object.name + if 'state' in current_object: + state = states.get(current_object.state, 'N/A') + perm = pool.get(res_model).perm_read(cr, uid, [res_id], context=context)[0] + + notes = process.note or "N/A" nodes = {} start = [] transitions = {} @@ -81,30 +113,39 @@ class process_process(osv.osv): data['name'] = node.name data['model'] = (node.model_id or None) and node.model_id.model data['kind'] = node.kind + data['subflow'] = (node.subflow_id or False) and [node.subflow_id.id, node.subflow_id.name] data['notes'] = node.note - data['active'] = 0 - data['gray'] = 0 + data['active'] = False + data['gray'] = False + data['url'] = node.help_url + + # get assosiated workflow + if data['model']: + wkf_ids = self.pool.get('workflow').search(cr, uid, [('osv', '=', data['model'])]) + data['workflow'] = (wkf_ids or False) and wkf_ids[0] + + if 'directory_id' in node and node.directory_id: + data['directory_id'] = node.directory_id.id + data['directory'] = self.pool.get('document.directory').get_resource_path(cr, uid, data['directory_id'], data['model'], False) if node.menu_id: data['menu'] = {'name': node.menu_id.complete_name, 'id': node.menu_id.id} - - if node.kind == "state" and node.model_id and node.model_id.model == res_model: - try: - if eval(node.model_states, expr_context): - data['active'] = current_object.name_get(context)[0][1] - except Exception, e: - # waring: invalid state expression - pass - - if not data['active']: - try: - gray = True - for cond in node.condition_ids: - if cond.model_id and cond.model_id.model == res_model: - gray = gray and eval(cond.model_states, expr_context) - data['gray'] = not gray - except: - pass + + try: + gray = True + for cond in node.condition_ids: + if cond.model_id and cond.model_id.model == res_model: + gray = gray and eval(cond.model_states, expr_context) + data['gray'] = not gray + except: + pass + + if not data['gray']: + if node.model_id and node.model_id.model == res_model: + try: + data['active'] = eval(node.model_states, expr_context) + except Exception: + pass nodes[node.id] = data if node.flow_start: @@ -123,24 +164,75 @@ class process_process(osv.osv): button['state'] = b.state button['action'] = b.action buttons.append(button) - data['roles'] = roles = [] + data['groups'] = groups = [] for r in tr.transition_ids: - if r.role_id: - role = {} - role['name'] = r.role_id.name - roles.append(role) + if r.group_id: + groups.append({'name': r.group_id.name}) + for r in tr.group_ids: + groups.append({'name': r.name}) transitions[tr.id] = data + # now populate resource information + def update_relatives(nid, ref_id, ref_model): + relatives = [] + + for dummy, tr in transitions.items(): + if tr['source'] == nid: + relatives.append(tr['target']) + if tr['target'] == nid: + relatives.append(tr['source']) + + if not ref_id: + nodes[nid]['res'] = False + return + + nodes[nid]['res'] = resource = {'id': ref_id, 'model': ref_model} + + refobj = pool.get(ref_model).browse(cr, uid, ref_id, context=context) + fields = pool.get(ref_model).fields_get(cr, uid, context=context) + + # check for directory_id from inherited from document module + if nodes[nid].get('directory_id', False): + resource['directory'] = self.pool.get('document.directory').get_resource_path(cr, uid, nodes[nid]['directory_id'], ref_model, ref_id) + + resource['name'] = refobj.name_get(context)[0][1] + resource['perm'] = pool.get(ref_model).perm_read(cr, uid, [ref_id], context)[0] + + for r in relatives: + node = nodes[r] + if 'res' not in node: + for n, f in fields.items(): + if node['model'] == ref_model: + update_relatives(r, ref_id, ref_model) + + elif f.get('relation') == node['model']: + rel = refobj[n] + if rel and isinstance(rel, list) : + rel = rel[0] + try: # XXX: rel has been reported as string (check it) + _id = (rel or False) and rel.id + _model = node['model'] + update_relatives(r, _id, _model) + except: + pass + + if res_id: + for nid, node in nodes.items(): + if not node['gray'] and (node['active'] or node['model'] == res_model): + update_relatives(nid, res_id, res_model) + break + + # calculate graph layout g = tools.graph(nodes.keys(), map(lambda x: (x['source'], x['target']), transitions.values())) g.process(start) - #g.scale(100, 100, 180, 120) - g.scale(*scale) + g.scale(*scale) #g.scale(100, 100, 180, 120) graph = g.result_get() - miny = -1 + # fix the height problem + miny = -1 for k,v in nodes.items(): - x = graph[k]['y'] - y = graph[k]['x'] + x = graph[k]['x'] + y = graph[k]['y'] if miny == -1: miny = y miny = min(y, miny) @@ -150,31 +242,84 @@ class process_process(osv.osv): for k, v in nodes.items(): y = v['y'] v['y'] = min(y - miny + 10, y) - return dict(nodes=nodes, transitions=transitions) + + nodes = dict([str(n_key), n_val] for n_key, n_val in nodes.iteritems()) + transitions = dict([str(t_key), t_val] for t_key, t_val in transitions.iteritems()) + return dict(name=name, resource=resource, state=state, perm=perm, notes=notes, nodes=nodes, transitions=transitions) + + def copy(self, cr, uid, id, default=None, context=None): + """ Deep copy the entire process. + """ + + if not default: + default = {} + + pool = pooler.get_pool(cr.dbname) + process = pool.get('process.process').browse(cr, uid, id, context=context) + + nodes = {} + transitions = {} + + # first copy all nodes and and map the new nodes with original for later use in transitions + for node in process.node_ids: + for t in node.transition_in: + tr = transitions.setdefault(t.id, {}) + tr['target'] = node.id + for t in node.transition_out: + tr = transitions.setdefault(t.id, {}) + tr['source'] = node.id + nodes[node.id] = pool.get('process.node').copy(cr, uid, node.id, context=context) + + # then copy transitions with new nodes + for tid, tr in transitions.items(): + vals = { + 'source_node_id': nodes[tr['source']], + 'target_node_id': nodes[tr['target']] + } + tr = pool.get('process.transition').copy(cr, uid, tid, default=vals, context=context) + + # and finally copy the process itself with new nodes + default.update({ + 'active': True, + 'node_ids': [(6, 0, nodes.values())] + }) + return super(process_process, self).copy(cr, uid, id, default, context) process_process() class process_node(osv.osv): _name = 'process.node' - _description ='Process Nodes' + _description ='Process Node' _columns = { - 'name': fields.char('Name', size=30,required=True), + 'name': fields.char('Name', size=30,required=True, translate=True), 'process_id': fields.many2one('process.process', 'Process', required=True, ondelete='cascade'), 'kind': fields.selection([('state','State'), ('subflow','Subflow')], 'Kind of Node', required=True), 'menu_id': fields.many2one('ir.ui.menu', 'Related Menu'), - 'note': fields.text('Notes'), + 'note': fields.text('Notes', translate=True), 'model_id': fields.many2one('ir.model', 'Object', ondelete='set null'), 'model_states': fields.char('States Expression', size=128), + 'subflow_id': fields.many2one('process.process', 'Subflow', ondelete='set null'), 'flow_start': fields.boolean('Starting Flow'), 'transition_in': fields.one2many('process.transition', 'target_node_id', 'Starting Transitions'), 'transition_out': fields.one2many('process.transition', 'source_node_id', 'Ending Transitions'), - 'condition_ids': fields.one2many('process.condition', 'node_id', 'Conditions') + 'condition_ids': fields.one2many('process.condition', 'node_id', 'Conditions'), + 'help_url': fields.char('Help URL', size=255) } _defaults = { 'kind': lambda *args: 'state', 'model_states': lambda *args: False, 'flow_start': lambda *args: False, } + + def copy_data(self, cr, uid, id, default=None, context=None): + if not default: + default = {} + default.update({ + 'transition_in': [], + 'transition_out': [] + }) + return super(process_node, self).copy_data(cr, uid, id, default, context=context) + process_node() class process_node_condition(osv.osv): @@ -190,14 +335,15 @@ process_node_condition() class process_transition(osv.osv): _name = 'process.transition' - _description ='Process Transitions' + _description ='Process Transition' _columns = { - 'name': fields.char('Name', size=32, required=True), + 'name': fields.char('Name', size=32, required=True, translate=True), 'source_node_id': fields.many2one('process.node', 'Source Node', required=True, ondelete='cascade'), - 'target_node_id': fields.many2one('process.node', 'Target Node', required=True, ondelete='cascade'), + 'target_node_id': fields.many2one('process.node', 'Target Node', required=True, ondelete='cascade'), 'action_ids': fields.one2many('process.transition.action', 'transition_id', 'Buttons'), 'transition_ids': fields.many2many('workflow.transition', 'process_transition_ids', 'ptr_id', 'wtr_id', 'Workflow Transitions'), - 'note': fields.text('Description'), + 'group_ids': fields.many2many('res.groups', 'process_transition_group_rel', 'tid', 'rid', string='Required Groups'), + 'note': fields.text('Description', translate=True), } process_transition() @@ -205,7 +351,7 @@ class process_transition_action(osv.osv): _name = 'process.transition.action' _description ='Process Transitions Actions' _columns = { - 'name': fields.char('Name', size=32, required=True), + 'name': fields.char('Name', size=32, required=True, translate=True), 'state': fields.selection([('dummy','Dummy'), ('object','Object Method'), ('workflow','Workflow Trigger'), @@ -221,5 +367,17 @@ class process_transition_action(osv.osv): _defaults = { 'state': lambda *args: 'dummy', } + + def copy_data(self, cr, uid, id, default=None, context=None): + if not default: + default = {} + + state = self.pool.get('process.transition.action').browse(cr, uid, id, context=context).state + if state: + default['state'] = state + + return super(process_transition_action, self).copy_data(cr, uid, id, default, context) + process_transition_action() +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: