From d8b6b35cb3c75b5c503b5b147fb8dd0e36e4ecfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 14 Jan 2020 13:30:18 +0000 Subject: [PATCH] [REF] crm(_*): improve code of methods used in lead/opp convert/merge wizards PURPOSE As CRM is going to be updated (onchange -> compute, code improvements, better management of sales teams) cleaning and improving code is a necessary first step. SPECIFICATIONS Purpose of this commit is to rewrite code located and/or used in convert or merge wizards. Indeed it holds unnecessary variables, dictionaries manipulation that hides the real business flow, badly named variables, unclear methods, ... Logic of those wizards seem quite simple but is hidden behind a wall of bloated code. Notably for wizards * try to set explicit arguments in methods; * less use of context values, replaced by arguments when possible; * try to make better use of lead2opportunity wizard in its mass wizard that inherit from it, notably by correctly calling super; Notably for tools * ``get_duplicated_leads`` was just a shortcut to ``_get_duplicated_leads_by _emails``; * ``_get_duplicated_leads_by_emails`` is renamed to ``_get_lead_duplicates`` because email is not the only way of finding duplicates; * try to make better use of record sets instead of propagating ids, notably for partner. For example input of ``_get_lead_duplicates`` or output of ``_find_matching_partner``; * ``_create_lead_partner`` rename to ``_create_customer``, and its sub method ``_create_lead_partner_data`` to ``_prepare_customer_values``; * ``handle_partner_assignation`` was difficult as its use is not documented and its docstring was not updated. Parameter have been renamed to be more explicit about what it does. Method is renamed to ``handle_partner_ assignment`` as assignation has other meaning in English; * ``allocate_salesman`` is renamed to ``handle_salesmen_assignment`` to match the partner assignement and code is slightly cleaned; No functional change should occur with this commit. Tests have been gradually added to try to ensure behavior does not change. LINKS Task ID 2088565 (crm: from onchange to compute) --- addons/crm/models/crm_lead.py | 206 ++++++++---------- addons/crm/tests/test_crm_lead.py | 7 +- addons/crm/tests/test_crm_lead_convert.py | 16 +- addons/crm/wizard/crm_lead_to_opportunity.py | 110 ++++------ .../wizard/crm_lead_to_opportunity_mass.py | 32 ++- .../crm_lead_to_opportunity_mass_views.xml | 4 +- .../wizard/crm_opportunity_to_quotation.py | 14 +- addons/website_crm/models/website_visitor.py | 6 +- .../models/crm_lead.py | 2 +- 9 files changed, 177 insertions(+), 220 deletions(-) diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index d8fce1f43c8..189c651fbe2 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -870,34 +870,6 @@ class Lead(models.Model): return self.sorted(key=opps_key, reverse=reverse) - def get_duplicated_leads(self, partner_id, include_lost=False): - """ Search for opportunities that have the same partner and that arent done or cancelled - :param partner_id : partner to search - """ - self.ensure_one() - email = self.partner_id.email or self.email_from - return self._get_duplicated_leads_by_emails(partner_id, email, include_lost=include_lost) - - @api.model - def _get_duplicated_leads_by_emails(self, partner_id, email, include_lost=False): - """ Search for opportunities that have the same partner and that arent done or cancelled """ - if not email: - return self.env['crm.lead'] - partner_match_domain = [] - for email in set(email_split(email) + [email]): - partner_match_domain.append(('email_from', '=ilike', email)) - if partner_id: - partner_match_domain.append(('partner_id', '=', partner_id)) - partner_match_domain = ['|'] * (len(partner_match_domain) - 1) + partner_match_domain - if not partner_match_domain: - return self.env['crm.lead'] - domain = partner_match_domain - if not include_lost: - domain += ['&', ('active', '=', True), ('probability', '<', 100)] - else: - domain += ['|', '&', ('type', '=', 'lead'), ('active', '=', True), ('type', '=', 'opportunity')] - return self.search(domain) - def _convert_opportunity_data(self, customer, team_id=False): """ Extract the data from a lead to create the opportunity :param customer : res.partner record @@ -931,11 +903,61 @@ class Lead(models.Model): lead.write(vals) if user_ids or team_id: - self.allocate_salesman(user_ids, team_id) + self.handle_salesmen_assignment(user_ids, team_id) return True - def _create_lead_partner_data(self, partner_name, is_company=False, parent_id=False): + def _get_lead_duplicates(self, partner=None, email=None, include_lost=False): + """ Search for leads that seem duplicated based on partner / email. + + :param partner : optional customer when searching duplicated + :param email: email (possibly formatted) to search + :param boolean include_lost: if True, search for archived leads or + active opportunities. If False, search for active and not won leads + and opportunities; + """ + if not email: + return self.env['crm.lead'] + partner_match_domain = [] + for email in set(email_split(email) + [email]): + partner_match_domain.append(('email_from', '=ilike', email)) + if partner: + partner_match_domain.append(('partner_id', '=', partner.id)) + partner_match_domain = ['|'] * (len(partner_match_domain) - 1) + partner_match_domain + if not partner_match_domain: + return self.env['crm.lead'] + domain = partner_match_domain + if not include_lost: + domain += ['&', ('active', '=', True), ('probability', '<', 100)] + else: + domain += ['|', '&', ('type', '=', 'lead'), ('active', '=', True), ('type', '=', 'opportunity')] + return self.search(domain) + + def _create_customer(self): + """ Create a partner from lead data and link it to the lead. + + :return: newly-created partner browse record + """ + Partner = self.env['res.partner'] + contact_name = self.contact_name + if not contact_name: + contact_name = Partner._parse_partner_name(self.email_from)[0] if self.email_from else False + + if self.partner_name: + partner_company = Partner.create(self._prepare_customer_values(self.partner_name, is_company=True)) + elif self.partner_id: + partner_company = self.partner_id + else: + partner_company = None + + if contact_name: + return Partner.create(self._prepare_customer_values(contact_name, is_company=False, parent_id=partner_company.id if partner_company else False)) + + if partner_company: + return partner_company + return Partner.create(self._prepare_customer_values(self.name, is_company=False)) + + def _prepare_customer_values(self, partner_name, is_company=False, parent_id=False): """ Extract data from lead to create a partner. :param name : furtur name of the partner @@ -967,107 +989,59 @@ class Lead(models.Model): 'type': 'contact' } - def _create_lead_partner(self): - """ Create a partner from lead data - :returns res.partner record - """ - Partner = self.env['res.partner'] - contact_name = self.contact_name - if not contact_name: - contact_name = Partner._parse_partner_name(self.email_from)[0] if self.email_from else False - - if self.partner_name: - partner_company = Partner.create(self._create_lead_partner_data(self.partner_name, is_company=True)) - elif self.partner_id: - partner_company = self.partner_id - else: - partner_company = None - - if contact_name: - return Partner.create(self._create_lead_partner_data(contact_name, is_company=False, parent_id=partner_company.id if partner_company else False)) - - if partner_company: - return partner_company - return Partner.create(self._create_lead_partner_data(self.name, is_company=False)) - def _find_matching_partner(self): - """ Try to find a matching partner with available information on the Lead. - Like the customer's name, email, phone number, etc. - :return int partner_id if any, False otherwise + """ Try to find a matching partner with available information on the + lead, using notably customer's name, email, phone, ... + + :return: partner browse record """ self.ensure_one() + partner = self.partner_id - # find the best matching partner - Partner = self.env['res.partner'] - if self.partner_id: # a partner is set already - return self.partner_id.id + if not partner and self.email_from: + partner = self.env['res.partner'].search([('email', '=', self.email_from)], limit=1) - if self.email_from: # search through the existing partners based on the lead's email - return Partner.search([('email', '=', self.email_from)], limit=1).id + if not partner: + # search through the existing partners based on the lead's partner or contact name + # to be aligned with _create_customer, search on lead's name as last possibility + for customer_potential_name in [self[field_name] for field_name in ['partner_name', 'contact_name', 'name'] if self[field_name]]: + partner = self.env['res.partner'].search([('name', 'ilike', '%' + customer_potential_name + '%')], limit=1) + if partner: + break - search_criteria = False - if self.partner_name: # search through the existing partners based on the lead's partner or contact name - search_criteria = self.partner_name - elif self.contact_name: - search_criteria = self.contact_name - elif self.name: # to be aligned with _create_lead_partner, search on lead's name as last possibility - search_criteria = self.name + return partner - if search_criteria: - return Partner.search([('name', 'ilike', '%' + search_criteria + '%')], limit=1).id + def handle_partner_assignment(self, force_partner_id=False, create_missing=True): + """ Update customer (partner_id) of leads. Purpose is to set the same + partner on most leads; either through a newly created partner either + through a given partner_id. - return False - - def handle_partner_assignation(self, action='create', partner_id=False): - """ Handle partner assignation during a lead conversion. - - If action is 'create', create new partner with contact and assign lead to - new partner_id. Otherwise assign lead to the specified partner_id - - TDE FIXME: docstring does not match code... code seems a bit random. - - :param string action: what has to be done regarding partners (create it, assign an existing one, or nothing) - :param int partner_id: partner to assign if any - :return: dict(lead_id: partner_id) + :param int force_partner_id: if set, update all leads to that customer; + :param create_missing: for leads without customer, create a new one + based on lead information; """ - partner_ids = {} for lead in self: - if partner_id: - lead.partner_id = partner_id - if lead.partner_id: - partner_ids[lead.id] = lead.partner_id.id - continue - if action == 'create': - partner = lead._create_lead_partner() - partner_id = partner.id - partner.team_id = lead.team_id - partner_ids[lead.id] = partner_id - return partner_ids + if force_partner_id: + lead.partner_id = force_partner_id + if not lead.partner_id and create_missing: + partner = lead._create_customer() + lead.partner_id = partner.id - def allocate_salesman(self, user_ids=None, team_id=False): + def handle_salesmen_assignment(self, user_ids=None, team_id=False): """ Assign salesmen and salesteam to a batch of leads. If there are more - leads than salesmen, these salesmen will be assigned in round-robin. - E.g.: 4 salesmen (S1, S2, S3, S4) for 6 leads (L1, L2, ... L6). They - will be assigned as followed: L1 - S1, L2 - S2, L3 - S3, L4 - S4, - L5 - S1, L6 - S2. + leads than salesmen, these salesmen will be assigned in round-robin. E.g. + 4 salesmen (S1, S2, S3, S4) for 6 leads (L1, L2, ... L6) will assigned as + following: L1 - S1, L2 - S2, L3 - S3, L4 - S4, L5 - S1, L6 - S2. - :param list ids: leads/opportunities ids to process - :param list user_ids: salesmen to assign - :param int team_id: salesteam to assign - :return bool + :param list user_ids: salesmen to assign + :param int team_id: salesteam to assign """ - index = 0 - for lead in self: - value = {} - if team_id: - value['team_id'] = team_id + update_vals = {'team_id': team_id} if team_id else {} + for index, lead in enumerate(self): if user_ids: - value['user_id'] = user_ids[index] - # Cycle through user_ids - index = (index + 1) % len(user_ids) - if value: - lead.write(value) - return True + update_vals['user_id'] = user_ids[index % len(user_ids)] + if update_vals: + lead.write(update_vals) # ------------------------------------------------------------ # TOOLS diff --git a/addons/crm/tests/test_crm_lead.py b/addons/crm/tests/test_crm_lead.py index d8c2e7d74f9..3ec5fde38f4 100644 --- a/addons/crm/tests/test_crm_lead.py +++ b/addons/crm/tests/test_crm_lead.py @@ -78,7 +78,6 @@ class TestCRMLead(TestCrmCommon): subtype_xmlid='mail.mt_comment') self.assertEqual(message.author_id, self.user_sales_manager.partner_id) - new_partner_id = new_lead.handle_partner_assignation(action='create')[new_lead.id] - new_partner = self.env['res.partner'].with_user(self.user_sales_manager).browse(new_partner_id) - self.assertEqual(new_partner.email, 'unknown.sender@test.example.com') - self.assertEqual(new_partner.team_id, self.sales_team_1) + new_lead.handle_partner_assignment(create_missing=True) + self.assertEqual(new_lead.partner_id.email, 'unknown.sender@test.example.com') + self.assertEqual(new_lead.partner_id.team_id, self.sales_team_1) diff --git a/addons/crm/tests/test_crm_lead_convert.py b/addons/crm/tests/test_crm_lead_convert.py index e05bc8fb548..d12cb4e5ab2 100644 --- a/addons/crm/tests/test_crm_lead_convert.py +++ b/addons/crm/tests/test_crm_lead_convert.py @@ -8,6 +8,10 @@ from odoo.tests.common import tagged, users @tagged('lead_manage') class TestLeadConvert(crm_common.TestLeadConvertCommon): + """ + TODO: created partner (handle assignation) has team of lead + TODO: create partner has user_id coming from wizard + """ @classmethod def setUpClass(cls): @@ -244,7 +248,7 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon): @users('user_sales_manager') def test_lead_merge_duplicates(self): - """ Test Lead._get_duplicated_leads_by_emails() """ + """ Test Lead._get_lead_duplicates() """ # Check: partner / email fallbacks self._create_duplicates(self.lead_1) @@ -280,7 +284,7 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon): @users('user_sales_manager') def test_lead_merge_duplicates_flow(self): - """ Test Lead._get_duplicated_leads_by_emails() + merge with active_test """ + """ Test Lead._get_lead_duplicates() + merge with active_test """ # Check: email formatting self.lead_1.write({ @@ -431,7 +435,7 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon): self.assertEqual(mass_convert.user_id, self.user_sales_salesman) self.assertEqual(mass_convert.team_id, self.sales_team_convert) - mass_convert.mass_convert() + mass_convert.action_mass_convert() for lead in self.lead_1 | self.lead_w_partner: self.assertEqual(lead.type, 'opportunity') if lead == self.lead_w_partner: @@ -447,7 +451,7 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon): mass_convert.write({ 'user_ids': self.user_sales_salesman.ids, }) - mass_convert.mass_convert() + mass_convert.action_mass_convert() self.assertEqual(self.lead_w_partner.user_id, self.user_sales_salesman) self.assertEqual(self.lead_1.user_id, self.user_sales_leads) # existing value not forced @@ -481,7 +485,7 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon): self.assertEqual(mass_convert.name, 'convert') self.assertEqual(mass_convert.opportunity_ids, self.lead_1 | self.lead_w_partner) - mass_convert.mass_convert() + mass_convert.action_mass_convert() self.assertEqual( (lead_1_dups | lead_w_partner_dups).exists(), @@ -508,7 +512,7 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon): }) # TDE FIXME: what happens if we mix people from different sales team ? currently nothing, to check - mass_convert.mass_convert() + mass_convert.action_mass_convert() for idx, lead in enumerate(self.leads - self.lead_w_email_lost): self.assertEqual(lead.type, 'opportunity') diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py index e4e101298ec..22385dabe85 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity.py +++ b/addons/crm/wizard/crm_lead_to_opportunity.py @@ -23,15 +23,15 @@ class Lead2OpportunityPartner(models.TransientModel): lead = self.env['crm.lead'].browse(self._context['active_id']) result['lead_id'] = lead.id - partner_id = lead._find_matching_partner() + partner = lead._find_matching_partner() email = lead.partner_id.email if lead.partner_id else lead.email_from - tomerge.update(self.env['crm.lead']._get_duplicated_leads_by_emails(partner_id, email, include_lost=True).ids) + tomerge.update(self.env['crm.lead']._get_lead_duplicates(partner, email, include_lost=True).ids) if 'action' in fields and not result.get('action'): - result['action'] = 'exist' if partner_id else 'create' + result['action'] = 'exist' if partner else 'create' if 'partner_id' in fields: - result['partner_id'] = partner_id + result['partner_id'] = partner.id if 'name' in fields: result['name'] = 'merge' if len(tomerge) >= 2 else 'convert' if 'opportunity_ids' in fields and len(tomerge) >= 2: @@ -40,7 +40,7 @@ class Lead2OpportunityPartner(models.TransientModel): result['user_id'] = lead.user_id.id if lead.team_id: result['team_id'] = lead.team_id.id - if not partner_id and not lead.contact_name: + if not partner and not lead.contact_name: result['action'] = 'nothing' return result @@ -63,7 +63,7 @@ class Lead2OpportunityPartner(models.TransientModel): @api.onchange('action') def onchange_action(self): if self.action == 'exist': - self.partner_id = self.lead_id._find_matching_partner() + self.partner_id = self.lead_id._find_matching_partner().id else: self.partner_id = False @@ -81,79 +81,63 @@ class Lead2OpportunityPartner(models.TransientModel): values = self.env['crm.lead']._onchange_user_values(self.user_id.id if self.user_id else False) self.team_id = values.get('team_id', False) - # NOTE JEM : is it the good place to test this ? @api.model def view_init(self, fields): + # JEM TDE FIXME: clean that brol """ Check some preconditions before the wizard executes. """ for lead in self.env['crm.lead'].browse(self._context.get('active_ids', [])): if lead.probability == 100: raise UserError(_("Closed/Dead leads cannot be converted into opportunities.")) return False - def _convert_opportunity(self, vals): + def action_apply(self): + if self.name == 'merge': + result_opportunity = self._action_merge() + else: + result_opportunity = self._action_convert() + + return result_opportunity.redirect_lead_opportunity_view() + + def _action_merge(self): + result_opportunity = self.with_context(active_test=False).opportunity_ids.merge_opportunity() + if not result_opportunity.active: + result_opportunity.write({'active': True, 'activity_type_id': False, 'lost_reason': False}) + + if result_opportunity.type == "lead": + self._convert_and_allocate(result_opportunity, [self.user_id.id], team_id=self.team_id.id) + elif not self._context.get('no_force_assignation') or not result_opportunity.user_id: + result_opportunity.write({ + 'user_id': self.user_id.id, + 'team_id': self.team_id.id, + }) + return result_opportunity + + def _action_convert(self): + """ """ + result_opportunities = self.env['crm.lead'].browse(self._context.get('active_ids', [])) + self._convert_and_allocate(result_opportunities, [self.user_id.id], team_id=self.team_id.id) + return result_opportunities[0] + + def _convert_and_allocate(self, leads, user_ids, team_id=False): self.ensure_one() - res = False - - leads = self.env['crm.lead'].browse(vals.get('lead_ids')) for lead in leads: - self_def_user = self.with_context(default_user_id=self.user_id.id) + if lead.active and self.action != 'nothing': + self._convert_handle_partner( + lead, self.action, self.partner_id.id or lead.partner_id.id) - partner_id = False - if self.action != 'nothing': - partner_id = self_def_user._create_partner( - lead.id, self.action, vals.get('partner_id') or lead.partner_id.id) - - res = lead.convert_opportunity(partner_id, [], False) - user_ids = vals.get('user_ids') + lead.convert_opportunity(lead.partner_id.id, [], False) leads_to_allocate = leads if self._context.get('no_force_assignation'): leads_to_allocate = leads_to_allocate.filtered(lambda lead: not lead.user_id) if user_ids: - leads_to_allocate.allocate_salesman(user_ids, team_id=(vals.get('team_id'))) + leads_to_allocate.handle_salesmen_assignment(user_ids, team_id=team_id) - return res - - def action_apply(self): - """ Convert lead to opportunity or merge lead and opportunity and open - the freshly created opportunity view. - """ - self.ensure_one() - values = { - 'team_id': self.team_id.id, - } - - if self.partner_id: - values['partner_id'] = self.partner_id.id - - if self.name == 'merge': - leads = self.with_context(active_test=False).opportunity_ids.merge_opportunity() - if not leads.active: - leads.write({'active': True, 'activity_type_id': False, 'lost_reason': False}) - if leads.type == "lead": - values.update({'lead_ids': leads.ids, 'user_ids': [self.user_id.id]}) - self.with_context(active_ids=leads.ids)._convert_opportunity(values) - elif not self._context.get('no_force_assignation') or not leads.user_id: - values['user_id'] = self.user_id.id - leads.write(values) - else: - leads = self.env['crm.lead'].browse(self._context.get('active_ids', [])) - values.update({'lead_ids': leads.ids, 'user_ids': [self.user_id.id]}) - self._convert_opportunity(values) - - return leads[0].redirect_lead_opportunity_view() - - def _create_partner(self, lead_id, action, partner_id): - """ Create partner based on action. - :return dict: dictionary organized as followed: {lead_id: partner_assigned_id} - """ - #TODO this method in only called by Lead2OpportunityPartner - #wizard and would probably diserve to be refactored or at least - #moved to a better place - if action == 'each_exist_or_create': - partner_id = self.env['crm.lead'].browse(lead_id)._find_matching_partner() - action = 'create' - result = self.env['crm.lead'].browse(lead_id).handle_partner_assignation(action, partner_id) - return result.get(lead_id) + def _convert_handle_partner(self, lead, action, partner_id): + # used to propagate user_id (salesman) on created partners during conversion + lead.with_context(default_user_id=self.user_id.id).handle_partner_assignment( + force_partner_id=partner_id, + create_missing=(action == 'create') + ) diff --git a/addons/crm/wizard/crm_lead_to_opportunity_mass.py b/addons/crm/wizard/crm_lead_to_opportunity_mass.py index af5d8226443..a10ef43f31a 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity_mass.py +++ b/addons/crm/wizard/crm_lead_to_opportunity_mass.py @@ -39,12 +39,12 @@ class Lead2OpportunityMassConvert(models.TransientModel): @api.onchange('deduplicate') def _onchange_deduplicate(self): active_leads = self.env['crm.lead'].browse(self._context['active_ids']) - partner_ids = [(lead.partner_id.id, lead.partner_id and lead.partner_id.email or lead.email_from) for lead in active_leads] + partner_ids = [(lead.partner_id, lead.partner_id and lead.partner_id.email or lead.email_from) for lead in active_leads] partners_duplicated_leads = {} - for partner_id, email in partner_ids: - duplicated_leads = self.env['crm.lead']._get_duplicated_leads_by_emails(partner_id, email, include_lost=False) + for partner, email in partner_ids: + duplicated_leads = self.env['crm.lead']._get_lead_duplicates(partner=partner, email=email, include_lost=False) if len(duplicated_leads) > 1: - partners_duplicated_leads.setdefault((partner_id, email), []).extend(duplicated_leads) + partners_duplicated_leads.setdefault((partner.id, email), []).extend(duplicated_leads) leads_with_duplicates = [] for lead in active_leads: @@ -54,20 +54,18 @@ class Lead2OpportunityMassConvert(models.TransientModel): self.opportunity_ids = self.env['crm.lead'].browse(leads_with_duplicates) - def _convert_opportunity(self, vals): + def _convert_and_allocate(self, leads, user_ids, team_id=False): """ When "massively" (more than one at a time) converting leads to - opportunities, check the salesteam_id and salesmen_ids and update - the values before calling super. + opportunities, check the salesteam_id and salesmen_ids and update + the values before calling super. """ self.ensure_one() - salesteam_id = self.team_id.id if self.team_id else False salesmen_ids = [] if self.user_ids: salesmen_ids = self.user_ids.ids - vals.update({'user_ids': salesmen_ids, 'team_id': salesteam_id}) - return super(Lead2OpportunityMassConvert, self)._convert_opportunity(vals) + return super(Lead2OpportunityMassConvert, self)._convert_and_allocate(leads, salesmen_ids, team_id=team_id) - def mass_convert(self): + def action_mass_convert(self): self.ensure_one() if self.name == 'convert' and self.deduplicate: merged_lead_ids = set() @@ -76,7 +74,11 @@ class Lead2OpportunityMassConvert(models.TransientModel): for lead_id in lead_selected: if lead_id not in merged_lead_ids: lead = self.env['crm.lead'].browse(lead_id) - duplicated_leads = self.env['crm.lead']._get_duplicated_leads_by_emails(lead.partner_id.id, lead.partner_id.email if lead.partner_id else lead.email_from, include_lost=False) + duplicated_leads = self.env['crm.lead']._get_lead_duplicates( + partner=lead.partner_id, + email=lead.partner_id.email or lead.email_from, + include_lost=False + ) if len(duplicated_leads) > 1: lead = duplicated_leads.merge_opportunity() merged_lead_ids.update(duplicated_leads.ids) @@ -87,3 +89,9 @@ class Lead2OpportunityMassConvert(models.TransientModel): self = self.with_context(active_ids=list(active_ids)) # only update active_ids when there are set no_force_assignation = self._context.get('no_force_assignation', not self.force_assignation) return self.with_context(no_force_assignation=no_force_assignation).action_apply() + + def _convert_handle_partner(self, lead, action, partner_id): + if self.action == 'each_exist_or_create': + partner_id = lead._find_matching_partner().id + action = 'create' + return super(Lead2OpportunityMassConvert, self)._convert_handle_partner(lead, action, partner_id) diff --git a/addons/crm/wizard/crm_lead_to_opportunity_mass_views.xml b/addons/crm/wizard/crm_lead_to_opportunity_mass_views.xml index e670e7b0053..f5d3b4a4f1b 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity_mass_views.xml +++ b/addons/crm/wizard/crm_lead_to_opportunity_mass_views.xml @@ -42,7 +42,7 @@ @@ -55,7 +55,7 @@ res_model="crm.lead2opportunity.partner.mass" binding_model="crm.lead" binding_views="list" view_mode="form" target="new" - context="{'mass_convert' : True}" + context="{}" view_id="view_crm_lead2opportunity_partner_mass" /> diff --git a/addons/sale_crm/wizard/crm_opportunity_to_quotation.py b/addons/sale_crm/wizard/crm_opportunity_to_quotation.py index 6ffa8860153..aa465066f8d 100644 --- a/addons/sale_crm/wizard/crm_opportunity_to_quotation.py +++ b/addons/sale_crm/wizard/crm_opportunity_to_quotation.py @@ -24,7 +24,7 @@ class Opportunity2Quotation(models.TransientModel): lead = self.env['crm.lead'].browse(self._context['active_id']) if lead: result['lead_id'] = lead.id - partner_id = result.get('partner_id') or lead._find_matching_partner() + partner_id = result.get('partner_id') or lead._find_matching_partner().id if 'action' in fields and not result.get('action'): result['action'] = 'exist' if partner_id else 'create' if 'partner_id' in fields and not result.get('partner_id'): @@ -46,16 +46,6 @@ class Opportunity2Quotation(models.TransientModel): """ self.ensure_one() if self.action != 'nothing': - self.lead_id.write({ - 'partner_id': self.partner_id.id if self.action == 'exist' else self._create_partner() - }) + self.lead_id.handle_partner_assignment(force_partner_id=self.partner_id.id, create_missing=(self.action == 'create')) self.lead_id._onchange_partner_id() return self.lead_id.action_new_quotation() - - def _create_partner(self): - """ Create partner based on action. - :return int: created res.partner id - """ - self.ensure_one() - result = self.lead_id.handle_partner_assignation(action='create') - return result.get(self.lead_id.id) diff --git a/addons/website_crm/models/website_visitor.py b/addons/website_crm/models/website_visitor.py index a6e8c669363..9b0b4c52c34 100644 --- a/addons/website_crm/models/website_visitor.py +++ b/addons/website_crm/models/website_visitor.py @@ -51,10 +51,8 @@ class WebsiteVisitor(models.Model): partners = sorted_leads.mapped('partner_id') if not partners: main_lead = self.lead_ids[0] - partner_id = main_lead.handle_partner_assignation(action='create')[main_lead.id] - if not main_lead.partner_id: - main_lead.partner_id = partner_id - self.partner_id = partner_id + main_lead.handle_partner_assignment(create_missing=True) + self.partner_id = main_lead.partner_id.id return True return check diff --git a/addons/website_crm_partner_assign/models/crm_lead.py b/addons/website_crm_partner_assign/models/crm_lead.py index 3c55bf5f86a..5e640e0beed 100644 --- a/addons/website_crm_partner_assign/models/crm_lead.py +++ b/addons/website_crm_partner_assign/models/crm_lead.py @@ -64,7 +64,7 @@ class CrmLead(models.Model): lead.assign_geo_localize(lead.partner_latitude, lead.partner_longitude) partner = self.env['res.partner'].browse(partner_id) if partner.user_id: - lead.allocate_salesman(partner.user_id.ids, team_id=partner.team_id.id) + lead.handle_salesmen_assignment(partner.user_id.ids, team_id=partner.team_id.id) values = {'date_assign': fields.Date.context_today(lead), 'partner_assigned_id': partner_id} lead.write(values) return res