From f255aa7b36d98aeb63273e6da49d1720d8a15f41 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 14 Jul 2015 15:36:18 +0200 Subject: [PATCH] [IMP] crm: stage management Stages are now sales team dependant, and not shared anymore. This comes with a sales-team way of working : - sales team is the entrance point, click on the big Pipeline button to see the opportunities pipeline of this specific sales team - no more opportunities menu entry, as you should come from a sales team - leads are still sales-team agnostic The stage management has been updated : - Won is still a stage (probability=100, onchange=1). - Lost is not a stage anymore. Lost is equivalent to archiving. Archiving uses the active field. Setting a lead or an opportunity as lost set them as unactive. Downside of the no more share policy: multi sales team opportunities management can quickly become complicated. Indeed you may have stages with the same name, not being the same stage, therefore not displayed in the same column. Grouping on the stage name leads to other issues (sequencing incoherence, impossible to drag n drop), so this is not an option. --- addons/crm/__openerp__.py | 1 + addons/crm/crm_demo.xml | 6 -- addons/crm/crm_lead.py | 62 +++++++---------- addons/crm/crm_lead_menu.xml | 6 +- addons/crm/crm_lead_view.xml | 62 +++++++++++++---- addons/crm/crm_stage.py | 8 ++- addons/crm/crm_stage_views.xml | 13 +--- addons/crm/data/crm_stage_data.xml | 23 +++++++ addons/crm/data/crm_stage_demo.xml | 66 ++++++++++--------- addons/crm/sales_team.py | 21 ++++++ addons/crm/test/crm_lead_find_stage.yml | 15 +---- addons/crm/test/lead2opportunity2win.yml | 2 - .../report/crm_lead_report.py | 6 +- 13 files changed, 173 insertions(+), 118 deletions(-) create mode 100644 addons/crm/data/crm_stage_data.xml diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py index 6104ce3697c..2a982678332 100644 --- a/addons/crm/__openerp__.py +++ b/addons/crm/__openerp__.py @@ -46,6 +46,7 @@ Dashboard for CRM will include: 'data': [ 'data/crm_action_data.xml', 'crm_data.xml', + 'data/crm_stage_data.xml', 'crm_lead_data.xml', 'crm_tip_data.xml', diff --git a/addons/crm/crm_demo.xml b/addons/crm/crm_demo.xml index be18b00a75c..4daf241fd49 100644 --- a/addons/crm/crm_demo.xml +++ b/addons/crm/crm_demo.xml @@ -1,7 +1,6 @@ - @@ -9,10 +8,5 @@ - - - Indirect Sales - - diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index dab3577e4fa..289d262b5f9 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -249,6 +249,7 @@ class crm_lead(format_address, osv.osv): 'team_id': lambda s, cr, uid, c: s.pool['crm.team']._get_default_team_id(cr, uid, context=c), 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c), 'priority': lambda *a: crm_stage.AVAILABLE_PRIORITIES[0][0], + 'probability': 10, 'color': 0, 'date_last_stage_update': fields.datetime.now, } @@ -260,13 +261,10 @@ class crm_lead(format_address, osv.osv): def onchange_stage_id(self, cr, uid, ids, stage_id, context=None): if not stage_id: return {'value': {}} - stage = self.pool.get('crm.stage').browse(cr, uid, stage_id, context=context) + stage = self.pool['crm.stage'].browse(cr, uid, stage_id, context=context) if not stage.on_change: return {'value': {}} - vals = {'probability': stage.probability} - if stage.on_change and (stage.probability >= 100 or (stage.probability == 0 and stage.sequence > 1)): - vals['date_closed'] = fields.datetime.now() - return {'value': vals} + return {'value': {'probability': stage.probability}} def on_change_partner_id(self, cr, uid, ids, partner_id, context=None): values = {} @@ -345,28 +343,20 @@ class crm_lead(format_address, osv.osv): return stage_ids[0] return False - def case_mark_lost(self, cr, uid, ids, context=None): - """ Mark the case as lost: state=cancel and probability=0 - """ - stages_leads = {} - for lead in self.browse(cr, uid, ids, context=context): - stage_id = self.stage_find(cr, uid, [lead], lead.team_id.id or False, [('probability', '=', 0.0), ('on_change', '=', True), ('sequence', '>', 1)], context=context) - if stage_id: - if stages_leads.get(stage_id): - stages_leads[stage_id].append(lead.id) - else: - stages_leads[stage_id] = [lead.id] - else: - raise UserError(_('To relieve your sales pipe and group all Lost opportunities, configure one of your sales stage as follow:\n' - 'probability = 0 %, select "Change Probability Automatically".\n' - 'Create a specific stage or edit an existing one by editing columns of your opportunity pipe.')) - for stage_id, lead_ids in stages_leads.items(): - self.write(cr, uid, lead_ids, {'stage_id': stage_id}, context=context) - return True + def action_set_lost(self, cr, uid, ids, context=None): + """ Lost semantic: probability = 0, active = False """ + return self.write(cr, uid, ids, {'probability': 0, 'active': False}, context=context) + # Backward compatibility + case_mark_lost = action_set_lost - def case_mark_won(self, cr, uid, ids, context=None): - """ Mark the case as won: state=done and probability=100 - """ + def action_set_active(self, cr, uid, ids, context=None): + return self.write(cr, uid, ids, {'active': True}, context=context) + + def action_set_unactive(self, cr, uid, ids, context=None): + return self.write(cr, uid, ids, {'active': False}, context=context) + + def action_set_won(self, cr, uid, ids, context=None): + """ Won semantic: probability = 100 (active untouched) """ stages_leads = {} for lead in self.browse(cr, uid, ids, context=context): stage_id = self.stage_find(cr, uid, [lead], lead.team_id.id or False, [('probability', '=', 100.0), ('on_change', '=', True)], context=context) @@ -375,13 +365,11 @@ class crm_lead(format_address, osv.osv): stages_leads[stage_id].append(lead.id) else: stages_leads[stage_id] = [lead.id] - else: - raise UserError(_('To relieve your sales pipe and group all Won opportunities, configure one of your sales stage as follow:\n' - 'probability = 100 % and select "Change Probability Automatically".\n' - 'Create a specific stage or edit an existing one by editing columns of your opportunity pipe.')) for stage_id, lead_ids in stages_leads.items(): self.write(cr, uid, lead_ids, {'stage_id': stage_id}, context=context) - return True + return self.write(cr, uid, ids, {'probability': 100}, context=context) + # Backward compatibility + case_mark_won = action_set_won def log_next_activity_1(self, cr, uid, ids, context=None): return self.log_next_activity_done(cr, uid, ids, next_activity_name='activity_1_id', context=context) @@ -615,7 +603,6 @@ class crm_lead(format_address, osv.osv): """ Search for opportunities that have the same partner and that arent done or cancelled """ - final_stage_domain = ['|', '|', ('stage_id.on_change', '=', False), ('stage_id.probability', 'not in', [0, 100]), ('stage_id.sequence', '<=', 1)] partner_match_domain = [] for email in set(email_split(email) + [email]): partner_match_domain.append(('email_from', '=ilike', email)) @@ -626,7 +613,7 @@ class crm_lead(format_address, osv.osv): return [] domain = partner_match_domain if not include_lost: - domain += final_stage_domain + domain += ['&', ('active', '=', True), ('probability', '<', 100)] return self.search(cr, uid, domain, context=context) def merge_dependences(self, cr, uid, highest, opportunities, context=None): @@ -723,8 +710,7 @@ class crm_lead(format_address, osv.osv): partner = self.pool.get('res.partner') customer = partner.browse(cr, uid, partner_id, context=context) for lead in self.browse(cr, uid, ids, context=context): - # TDE: was if lead.state in ('done', 'cancel'): - if (lead.probability == 100 or lead.probability == 0) and lead.stage_id.on_change and lead.stage_id.sequence > 1: + if not lead.active or lead.probability == 100: continue vals = self._convert_opportunity_data(cr, uid, lead, customer, team_id, context=context) self.write(cr, uid, [lead.id], vals, context=context) @@ -907,9 +893,11 @@ class crm_lead(format_address, osv.osv): if vals.get('user_id') and 'date_open' not in vals: vals['date_open'] = fields.datetime.now() # stage change with new stage: update probability and date_closed - if vals.get('stage_id') and not vals.get('probability'): + if vals.get('stage_id') and 'probability' not in vals: onchange_stage_values = self.onchange_stage_id(cr, uid, ids, vals.get('stage_id'), context=context)['value'] vals.update(onchange_stage_values) + if vals.get('probability') >= 100 or not vals.get('active', True): + vals['date_closed'] = fields.datetime.now() return super(crm_lead, self).write(cr, uid, ids, vals, context=context) def copy(self, cr, uid, id, default=None, context=None): @@ -944,7 +932,7 @@ class crm_lead(format_address, osv.osv): record = self.browse(cr, uid, ids[0], context=context) if 'stage_id' in init_values and record.probability == 100 and record.stage_id and record.stage_id.on_change: return 'crm.mt_lead_won' - elif 'stage_id' in init_values and record.probability == 0 and record.stage_id and record.stage_id.on_change and record.stage_id.sequence > 1: + elif 'active' in init_values and record.probability == 0 and not record.active: return 'crm.mt_lead_lost' elif 'stage_id' in init_values and record.probability == 0 and record.stage_id and record.stage_id.sequence <= 1: return 'crm.mt_lead_create' diff --git a/addons/crm/crm_lead_menu.xml b/addons/crm/crm_lead_menu.xml index 9011f2af12e..f6ec9e09b88 100644 --- a/addons/crm/crm_lead_menu.xml +++ b/addons/crm/crm_lead_menu.xml @@ -5,12 +5,13 @@ Leads crm.lead - tree,form + tree,kanban,graph,pivot,calendar,form ['|', ('type','=','lead'), ('type','=',False)] { 'default_type':'lead', + 'search_default_type': 'lead', 'stage_type':'lead', 'needaction_menu_ref': 'crm.menu_crm_opportunities', 'search_default_to_process':1, @@ -77,9 +78,6 @@ - diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index 43c311feadc..e4eaecb2794 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -106,6 +106,18 @@
@@ -397,6 +432,10 @@