From d025251c7e4ee4c26462e61b5d63e5c5d58689f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 3 Nov 2015 11:28:55 +0100 Subject: [PATCH] [IMP] crm: stage management update 42 Stages are now shared by default between sales team. As the sales flow should be quite the same whatever the sales team, stages are now shared and not sales team dependant. The possibility of customizing the flow by adding stages specific to a sales team still exist. Stages have a team_id field that can be set in the stage form, in configuration. However this is not the default behavior. A stage is either common to all sales team, either specific to only one sales team. This is why there is no team_ids field anymore, only team_id. Moreover leads do not use stage anymore. A lead is either alive or archived. The type field on stages is then removed, as all stages are now used in opportunities only. A small hack has been introduced in crm_stage default_get. Indeed when going from the pipeline, creating a stage with a sales team in context should not create a stage for the current sales team only. Therefore unless specified in the context, default_team_id is discarded in the default get context. --- addons/crm/__openerp__.py | 1 - addons/crm/crm_lead.py | 50 +++---------- addons/crm/crm_lead_demo.xml | 30 ++++---- addons/crm/crm_lead_view.xml | 41 +---------- addons/crm/crm_stage.py | 19 +++-- addons/crm/crm_stage_views.xml | 35 ++++++++-- addons/crm/data/crm_stage_data.xml | 15 +--- addons/crm/data/crm_stage_demo.xml | 70 ------------------- addons/crm/report/crm_opportunity_report.py | 2 +- .../report/crm_opportunity_report_view.xml | 4 +- addons/crm/sales_team.py | 45 ------------ addons/crm/sales_team_view.xml | 6 -- addons/crm/test/crm_lead_find_stage.yml | 13 ++-- .../crm_partner_assign_data.xml | 2 - 14 files changed, 76 insertions(+), 257 deletions(-) delete mode 100644 addons/crm/data/crm_stage_demo.xml diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py index 04feb481db2..89e649289b2 100644 --- a/addons/crm/__openerp__.py +++ b/addons/crm/__openerp__.py @@ -80,7 +80,6 @@ Dashboard for CRM will include: 'crm_mail_template_data.xml' ], 'demo': [ - 'data/crm_stage_demo.xml', 'crm_demo.xml', 'crm_lead_demo.xml', 'data/crm_action_demo.xml', diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 8f046fc8cdb..ef52be93934 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -72,14 +72,6 @@ class crm_lead(format_address, osv.osv): team_id = self.pool['crm.team']._get_default_team_id(cr, SUPERUSER_ID, context=context, user_id=uid) return self.stage_find(cr, uid, [], team_id, [('fold', '=', False)], context=context) - def _resolve_type_from_context(self, cr, uid, context=None): - """ Returns the type (lead or opportunity) from the type context - key. Returns None if it cannot be resolved. - """ - if context is None: - context = {} - return context.get('default_type') - def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None): access_rights_uid = access_rights_uid or uid stage_obj = self.pool.get('crm.stage') @@ -91,17 +83,11 @@ class crm_lead(format_address, osv.osv): # - ('id', 'in', 'ids'): add columns that should be present # - OR ('fold', '=', False): add default columns that are not folded # - OR ('team_ids', '=', team_id), ('fold', '=', False) if team_id: add team columns that are not folded - search_domain = [] team_id = context and context.get('default_team_id') or False if team_id: - search_domain += ['|', ('team_ids', '=', team_id)] - search_domain += [('id', 'in', ids)] + search_domain = ['|', ('id', 'in', ids), '|', ('team_id', '=', False), ('team_id', '=', team_id)] else: - search_domain += [('id', 'in', ids)] - # retrieve type from the context (if set: choose 'type' or 'both') - type = self._resolve_type_from_context(cr, uid, context=context) - if type: - search_domain += ['|', ('type', '=', type), ('type', '=', 'both')] + search_domain = ['|', ('id', 'in', ids), ('team_id', '=', False)] # perform search stage_ids = stage_obj._search(cr, uid, search_domain, order=order, access_rights_uid=access_rights_uid, context=context) result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context) @@ -206,7 +192,7 @@ class crm_lead(format_address, osv.osv): 'priority': fields.selection(crm_stage.AVAILABLE_PRIORITIES, 'Rating', select=True), 'date_closed': fields.datetime('Closed', readonly=True, copy=False), 'stage_id': fields.many2one('crm.stage', 'Stage', track_visibility='onchange', select=True, - domain="['&', ('team_ids', '=', team_id), '|', ('type', '=', type), ('type', '=', 'both')]"), + domain="['|', ('team_id', '=', False), ('team_id', '=', team_id)]"), 'user_id': fields.many2one('res.users', 'Salesperson', select=True, track_visibility='onchange'), 'referred': fields.char('Referred By'), 'date_open': fields.datetime('Assigned', readonly=True), @@ -320,32 +306,20 @@ class crm_lead(format_address, osv.osv): cases = self.browse(cr, uid, cases, context=context) if context is None: context = {} - # check whether we should try to add a condition on type - avoid_add_type_term = any([term for term in domain if len(term) == 3 if term[0] == 'type']) # collect all team_ids team_ids = set() - types = ['both'] - if not cases and context.get('default_type'): - ctx_type = context.get('default_type') - types += [ctx_type] if team_id: team_ids.add(team_id) for lead in cases: if lead.team_id: team_ids.add(lead.team_id.id) - if lead.type not in types: - types.append(lead.type) # OR all team_ids - search_domain = [] if team_ids: - search_domain += [('|')] * (len(team_ids) - 1) - for team_id in team_ids: - search_domain.append(('team_ids', '=', team_id)) - # AND with cases types - if not avoid_add_type_term: - search_domain.append(('type', 'in', types)) + search_domain = ['|', ('team_id', '=', False), ('team_id', 'in', list(team_ids))] + else: + search_domain = [('team_id', '=', False)] # AND with the domain in parameter - search_domain += list(domain) + search_domain = ['&'] + list(domain) + search_domain # perform search, return the first found stage_ids = self.pool.get('crm.stage').search(cr, uid, search_domain, order=order, limit=1, context=context) if stage_ids: @@ -628,7 +602,7 @@ class crm_lead(format_address, osv.osv): # Check if the stage is in the stages of the sales team. If not, assign the stage with the lowest sequence if merged_data.get('team_id'): - team_stage_ids = self.pool.get('crm.stage').search(cr, uid, [('team_ids', 'in', merged_data['team_id']), ('type', 'in', [merged_data.get('type'), 'both'])], order='sequence', context=context) + team_stage_ids = self.pool.get('crm.stage').search(cr, uid, [('team_id', '=', merged_data['team_id'])], order='sequence', context=context) if merged_data.get('stage_id') not in team_stage_ids: merged_data['stage_id'] = team_stage_ids and team_stage_ids[0] or False # Write merged data into first opportunity @@ -640,10 +614,6 @@ class crm_lead(format_address, osv.osv): return highest.id def _convert_opportunity_data(self, cr, uid, lead, customer, team_id=False, context=None): - crm_stage = self.pool.get('crm.stage') - contact_id = False - if customer: - contact_id = self.pool.get('res.partner').address_get(cr, uid, [customer.id])['contact'] if not team_id: team_id = lead.team_id and lead.team_id.id or False val = { @@ -657,8 +627,8 @@ class crm_lead(format_address, osv.osv): 'phone': customer and customer.phone or lead.phone, 'date_conversion': fields.datetime.now(), } - if not lead.stage_id or lead.stage_id.type=='lead': - val['stage_id'] = self.stage_find(cr, uid, [lead], team_id, [('type', 'in', ('opportunity', 'both'))], context=context) + if not lead.stage_id: + val['stage_id'] = self.stage_find(cr, uid, [lead], team_id, [], context=context) return val def convert_opportunity(self, cr, uid, ids, partner_id, user_ids=False, team_id=False, context=None): diff --git a/addons/crm/crm_lead_demo.xml b/addons/crm/crm_lead_demo.xml index 2bfcba26553..f0abb56096e 100644 --- a/addons/crm/crm_lead_demo.xml +++ b/addons/crm/crm_lead_demo.xml @@ -127,7 +127,7 @@ - + @@ -148,7 +148,7 @@ - + @@ -170,7 +170,7 @@ 2 - + @@ -208,7 +208,7 @@ Contact: +1 813 494 5005

]]>
2 - + @@ -303,7 +303,7 @@ ESM Expert
]]>
2 - + @@ -337,7 +337,7 @@ Andrew

]]>
2 - + @@ -415,7 +415,7 @@ Andrew

]]>
Send Catalogue by Email - + @@ -555,7 +555,7 @@ Andrew

]]>
Send price list regarding our interventions - + @@ -590,7 +590,7 @@ Andrew

]]>
Call to define real needs about training - + @@ -702,7 +702,7 @@ Andrew

]]>
- + @@ -720,7 +720,7 @@ Andrew

]]>
- + @@ -740,7 +740,7 @@ Andrew

]]>
- + @@ -766,7 +766,7 @@ Andrew

]]>
Conf call with technical service - + @@ -791,7 +791,7 @@ Andrew

]]>
Send Catalogue by Email - + @@ -817,7 +817,7 @@ Andrew

]]>
Send Catalogue by Email - + diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index 416c14e632c..367d8fc9484 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -1,41 +1,6 @@ - - - - Stage - Search - crm.stage - - - - - - - - - - - - - Stages - crm.stage - form - - -

- Click to set a new stage in your lead/opportunity pipeline. -

- Stages will allow salespersons to easily track how a specific - lead or opportunity is positioned in the sales cycle. -

-
-
- - - @@ -106,10 +71,6 @@
@@ -400,7 +361,7 @@ attrs="{'invisible': [('active', '=', False)]}"/> diff --git a/addons/crm/crm_stage.py b/addons/crm/crm_stage.py index c83461a9c0f..c686b62f3b2 100644 --- a/addons/crm/crm_stage.py +++ b/addons/crm/crm_stage.py @@ -22,32 +22,31 @@ class crm_stage(osv.Model): _rec_name = 'name' _order = "sequence" - def _default_team_ids(self, cr, uid, context=None): - return context.get('default_team_id') and [(6, 0, [context['default_team_id']])] or False - _columns = { 'name': fields.char('Stage Name', required=True, translate=True), 'sequence': fields.integer('Sequence', help="Used to order stages. Lower is better."), 'probability': fields.float('Probability (%)', required=True, help="This percentage depicts the default/average probability of the Case for this stage to be a success"), 'on_change': fields.boolean('Change Probability Automatically', help="Setting this stage will change the probability automatically on the opportunity."), 'requirements': fields.text('Requirements', help="Enter here the internal requirements for this stage (ex: Offer sent to customer). It will appear as a tooltip over the stage's name."), - 'team_ids': fields.many2many('crm.team', 'crm_team_stage_rel', 'stage_id', 'team_id', string='Teams', - help="Link between stages and sales teams. When set, this limitate the current stage to the selected sales teams."), + 'team_id': fields.many2one('crm.team', 'Team', + ondelete='set null', + help='Specific team that uses this stage. Other teams will not ne able to see or use this stage.'), 'legend_priority': fields.text( 'Priority Management Explanation', translate=True, help='Explanation text to help users using the star and priority mechanism on stages or issues that are in this stage.'), 'fold': fields.boolean('Folded in Pipeline', help='This stage is folded in the kanban view when ' 'there are no records in that stage to display.'), - 'type': fields.selection([('lead', 'Lead'), ('opportunity', 'Opportunity'), ('both', 'Both')], - string='Type', required=True, - help="This field is used to distinguish stages related to Leads from stages related to Opportunities, or to specify stages available for both types."), } _defaults = { 'sequence': 1, 'probability': 10.0, - 'team_ids': _default_team_ids, 'fold': False, - 'type': 'both', } + + def default_get(self, cr, uid, fields, context=None): + if context and context.get('default_team_id') and not context.get('crm_team_mono', False): + context = dict(context) + context.pop('default_team_id') + return super(crm_stage, self).default_get(cr, uid, fields, context=context) diff --git a/addons/crm/crm_stage_views.xml b/addons/crm/crm_stage_views.xml index ad1199d6b27..95d0535a0f1 100644 --- a/addons/crm/crm_stage_views.xml +++ b/addons/crm/crm_stage_views.xml @@ -1,5 +1,19 @@ + + + Stage - Search + crm.stage + + + + + + + + + + crm.stage.tree crm.stage @@ -8,7 +22,7 @@ - + @@ -22,7 +36,7 @@ - + @@ -32,16 +46,25 @@ - - - + Stages crm.stage form - + + +

+ Click to set a new stage in your opportunity pipeline. +

+ Stages allow salespersons to easily track how a specific opportunity + is positioned in the sales cycle. +

+
+ + +
diff --git a/addons/crm/data/crm_stage_data.xml b/addons/crm/data/crm_stage_data.xml index 6c0c2500da2..f63c2ee08be 100644 --- a/addons/crm/data/crm_stage_data.xml +++ b/addons/crm/data/crm_stage_data.xml @@ -5,33 +5,24 @@ 10 1 1 - both Qualified 30 1 - 1 - opportunity + 2 Proposition 70 1 - 1 - opportunity + 3 - + Won 100 1 70 - opportunity - - - - - \ No newline at end of file diff --git a/addons/crm/data/crm_stage_demo.xml b/addons/crm/data/crm_stage_demo.xml deleted file mode 100644 index c233d9849e6..00000000000 --- a/addons/crm/data/crm_stage_demo.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - Qualification - 20 - 1 - 40 - opportunity - - - Proposition - 40 - 1 - 50 - opportunity - - - Negotiation - 60 - 1 - 60 - opportunity - - - - New - 10 - 1 - 1 - both - - - Qualification - 20 - 1 - 40 - opportunity - - - Negotiation - 60 - 1 - 60 - opportunity - - - Won - - 100 - 1 - 70 - opportunity - - - - - - - - - - - diff --git a/addons/crm/report/crm_opportunity_report.py b/addons/crm/report/crm_opportunity_report.py index 12a20929752..c0bde171243 100644 --- a/addons/crm/report/crm_opportunity_report.py +++ b/addons/crm/report/crm_opportunity_report.py @@ -35,7 +35,7 @@ class crm_opportunity_report(osv.Model): 'probability': fields.float('Probability',digits=(16,2),readonly=True, group_operator="avg"), 'total_revenue': fields.float('Total Revenue',digits=(16,2),readonly=True), 'expected_revenue': fields.float('Expected Revenue', digits=(16,2),readonly=True), - 'stage_id': fields.many2one ('crm.stage', 'Stage', readonly=True, domain="[('team_ids', '=', team_id)]"), + 'stage_id': fields.many2one ('crm.stage', 'Stage', readonly=True, domain="['|', ('team_id', '=', False), ('team_id', '=', team_id)]"), 'stage_name': fields.char('Stage Name', readonly=True), 'partner_id': fields.many2one('res.partner', 'Partner' , readonly=True), 'company_id': fields.many2one('res.company', 'Company', readonly=True), diff --git a/addons/crm/report/crm_opportunity_report_view.xml b/addons/crm/report/crm_opportunity_report_view.xml index 7567d981cea..19a018b9970 100644 --- a/addons/crm/report/crm_opportunity_report_view.xml +++ b/addons/crm/report/crm_opportunity_report_view.xml @@ -114,13 +114,13 @@ - + - + diff --git a/addons/crm/sales_team.py b/addons/crm/sales_team.py index 1ae14e831e8..3a6fbb4778c 100644 --- a/addons/crm/sales_team.py +++ b/addons/crm/sales_team.py @@ -9,52 +9,8 @@ class crm_team(osv.Model): _inherit = 'crm.team' _inherits = {'mail.alias': 'alias_id'} - def _get_default_stage_ids(self, cr, uid, context=None): - return [ - (0, 0, { - 'name': _('Incoming'), - 'sequence': 1, - 'probability': 10.0, - 'on_change': True, - 'fold': False, - 'type': 'both', - }), - (0, 0, { - 'name': _('Qualified'), - 'sequence': 2, - 'probability': 30.0, - 'on_change': True, - 'fold': False, - 'type': 'opportunity', - }), - (0, 0, { - 'name': _('Proposal'), - 'sequence': 3, - 'probability': 70.0, - 'on_change': True, - 'fold': False, - 'type': 'opportunity', - }), - (0, 0, { - 'name': _('Negotiation'), - 'sequence': 4, - 'probability': 85.0, - 'on_change': True, - 'fold': False, - 'type': 'opportunity', - }), - (0, 0, { - 'name': _('Won'), - 'sequence': 50, - 'probability': 100.0, - 'on_change': True, - 'fold': True, - 'type': 'both', - })] - _columns = { 'resource_calendar_id': fields.many2one('resource.calendar', "Working Time", help="Used to compute open days"), - 'stage_ids': fields.many2many('crm.stage', 'crm_team_stage_rel', 'team_id', 'stage_id', 'Stages'), 'use_leads': fields.boolean('Leads', help="The first contact you get with a potential customer is a lead you qualify before converting it into a real business opportunity. Check this box to manage leads in this sales team."), 'use_opportunities': fields.boolean('Opportunities', help="Check this box to manage opportunities in this sales team."), @@ -67,7 +23,6 @@ class crm_team(osv.Model): 'crm.lead', self._columns['alias_id'], 'name', alias_prefix='Lead+', alias_defaults={}, context=context) _defaults = { - 'stage_ids': _get_default_stage_ids, 'use_opportunities': True, } diff --git a/addons/crm/sales_team_view.xml b/addons/crm/sales_team_view.xml index 78f3aacb752..afef3ec8f18 100644 --- a/addons/crm/sales_team_view.xml +++ b/addons/crm/sales_team_view.xml @@ -96,12 +96,6 @@
- - - - - -
- lead To Recycle - lead Lead Mass Mail