From fdc02d7dcb5a4ea53a4fe61bd8970cd7b8057997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 1 Dec 2020 12:34:27 +0000 Subject: [PATCH] [REF] sales_team, various: improve default team computation GLOBAl PURPOSE Ability to have salesmen belonging to several sales team is a core requirement of CRM. It is therefore moved from website_crm_score to crm along with cleaning and behavior improvement. Automatic lead assignment is also moved and cleaned. SPECIFICATIONS Compute default team id for sales related documents. Note that this method is not called by default_get as it takes some additional parameters and is meant to be called by other default methods. Heuristic (when multiple match: take first sequence ordered) 1- any of my teams (member OR responsible) matching domain 2- any of my teams (member OR responsible) 3- default from context 4- any team matching my company and domain 5- any team matching my company Note: ResPartner.team_id field is explicitly not taken into account. We think this field causes a lot of noises compared to its added value. Think notably: team not in responsible teams, team company not matching responsible or lead company, asked domain not matching, ... We also improve various places where sale team is computed on sales documents. Purpose is to try to use same heuristic as much as possible in various team_id fields. LINKS Task ID-2086889 (main task) Task ID-2357969 (scoring migration task) Community PR odoo/odoo#48422 Enterprise PR odoo/enterprise#499 Upgrade PR odoo/upgrade#996 --- .../crm/tests/test_crm_lead_convert_mass.py | 2 +- addons/crm/wizard/crm_lead_to_opportunity.py | 3 +- .../wizard/crm_lead_to_opportunity_mass.py | 3 +- .../models/crm_iap_lead_mining_request.py | 16 ++++-- addons/sale/models/sale.py | 2 +- addons/sales_team/models/crm_team.py | 54 ++++++++++++++++--- 6 files changed, 64 insertions(+), 16 deletions(-) diff --git a/addons/crm/tests/test_crm_lead_convert_mass.py b/addons/crm/tests/test_crm_lead_convert_mass.py index a58a60d269d..82126f9a821 100644 --- a/addons/crm/tests/test_crm_lead_convert_mass.py +++ b/addons/crm/tests/test_crm_lead_convert_mass.py @@ -166,7 +166,7 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon): test_leads = self._create_leads_batch(count=50, user_ids=[False]) user_ids = self.assign_users.ids - with self.assertQueryCount(user_sales_manager=1363): # crm only: 1352 + with self.assertQueryCount(user_sales_manager=1364): # crm only: 1353 mass_convert = self.env['crm.lead2opportunity.partner.mass'].with_context({ 'active_model': 'crm.lead', 'active_ids': test_leads.ids, diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py index fbc8923cbc2..2a4eef8a447 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity.py +++ b/addons/crm/wizard/crm_lead_to_opportunity.py @@ -101,8 +101,7 @@ class Lead2OpportunityPartner(models.TransientModel): user = convert.user_id if convert.team_id and user in convert.team_id.member_ids | convert.team_id.user_id: continue - team_domain = [] - team = self.env['crm.team']._get_default_team_id(user_id=user.id, domain=team_domain) + team = self.env['crm.team']._get_default_team_id(user_id=user.id, domain=None) convert.team_id = team.id @api.model diff --git a/addons/crm/wizard/crm_lead_to_opportunity_mass.py b/addons/crm/wizard/crm_lead_to_opportunity_mass.py index 9deb29293e3..eb4b8c41815 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity_mass.py +++ b/addons/crm/wizard/crm_lead_to_opportunity_mass.py @@ -50,8 +50,7 @@ class Lead2OpportunityMassConvert(models.TransientModel): user = convert.user_id or convert.user_ids and convert.user_ids[0] or self.env.user if convert.team_id and user in convert.team_id.member_ids | convert.team_id.user_id: continue - team_domain = [] - team = self.env['crm.team']._get_default_team_id(user_id=user.id, domain=team_domain) + team = self.env['crm.team']._get_default_team_id(user_id=user.id, domain=None) convert.team_id = team.id @api.depends('lead_tomerge_ids') diff --git a/addons/crm_iap_lead/models/crm_iap_lead_mining_request.py b/addons/crm_iap_lead/models/crm_iap_lead_mining_request.py index ede89baaf54..e5a739ab53a 100644 --- a/addons/crm_iap_lead/models/crm_iap_lead_mining_request.py +++ b/addons/crm_iap_lead/models/crm_iap_lead_mining_request.py @@ -104,10 +104,20 @@ class CRMLeadMiningRequest(models.Model): for request in self: request.lead_count = mapped_data.get(request.id, 0) - @api.depends('user_id') + @api.depends('user_id', 'lead_type') def _compute_team_id(self): - for record in self: - record.team_id = record.user_id.sale_team_id + """ When changing the user, also set a team_id or restrict team id + to the ones user_id is member of. """ + for mining in self: + # setting user as void should not trigger a new team computation + if not mining.user_id: + continue + user = mining.user_id + if mining.team_id and user in mining.team_id.member_ids | mining.team_id.user_id: + continue + team_domain = [('use_leads', '=', True)] if mining.lead_type == 'lead' else [('use_opportunities', '=', True)] + team = self.env['crm.team']._get_default_team_id(user_id=user.id, domain=team_domain) + mining.team_id = team.id @api.onchange('lead_number') def _onchange_lead_number(self): diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 81ec265ef4e..4c36265956f 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -407,7 +407,7 @@ class SaleOrder(models.Model): if self.user_id: self.team_id = self.env['crm.team'].with_context( default_team_id=self.team_id.id - )._get_default_team_id(user_id=self.user_id.id) + )._get_default_team_id(user_id=self.user_id.id, domain=None) @api.onchange('partner_id') def onchange_partner_id_warning(self): diff --git a/addons/sales_team/models/crm_team.py b/addons/sales_team/models/crm_team.py index 70c13d612fe..b8b57409870 100644 --- a/addons/sales_team/models/crm_team.py +++ b/addons/sales_team/models/crm_team.py @@ -20,17 +20,57 @@ class CrmTeam(models.Model): _check_company_auto = True def _get_default_team_id(self, user_id=None, domain=None): - if user_id is None: - user_id = self.env.user.id + """ Compute default team id for sales related documents. Note that this + method is not called by default_get as it takes some additional + parameters and is meant to be called by other default methods. - team = self.env['crm.team'].search([ - ('company_id', 'in', [False, self.env.company.id]), - '|', ('user_id', '=', user_id), ('member_ids', 'in', [user_id]), - ], limit=1) + Heuristic (when multiple match: take first sequence ordered) + + 1- any of my teams (member OR responsible) matching domain + 2- any of my teams (member OR responsible) + 3- default from context + 4- any team matching my company and domain + 5- any team matching my company + + Note: ResPartner.team_id field is explicitly not taken into account. We + think this field causes a lot of noises compared to its added value. + Think notably: team not in responsible teams, team company not matching + responsible or lead company, asked domain not matching, ... + + :param user_id: salesperson to target, fallback on env.uid; + :domain: optional domain to filter teams (like use_lead = True); + """ + if user_id is None: + user = self.env.user + else: + user = self.env['res.users'].sudo().browse(user_id) + valid_cids = [False] + user.company_ids.ids + + # 1- find in user memberships - note that if current user in C1 searches + # for team belonging to a user in C1/C2 -> only results for C1 will be returned + team = self.env['crm.team'] + teams = self.env['crm.team'].search([ + ('company_id', 'in', valid_cids), + '|', ('user_id', '=', user.id), ('member_ids', 'in', [user.id]), + ]) + if teams and domain: + team = teams.filtered_domain(domain)[:1] + # 2- any of my teams + if not team: + team = teams[:1] + + # 3- default: context if not team and 'default_team_id' in self.env.context: team = self.env['crm.team'].browse(self.env.context.get('default_team_id')) + + # 4- default: first one matching domain, then first one if not team: - team = self.env['crm.team'].search(domain or [], limit=1) + teams = self.env['crm.team'].search([('company_id', 'in', valid_cids)]) + if teams and domain: + team = teams.filtered_domain(domain)[:1] + if not team: + team = teams[:1] + return team def _get_default_favorite_user_ids(self):