[MOV] crm: reorganize convert / merge tool methods

PURPOSE

Prepare sales team membership and lead assignment improvements by
reorganizing and cleaning some code bits.

SPECIFICATIONS

Separate business methods from tool methods. It eases their discovering and
their re-use through various other methods and models. Moreover it helps
reading business oriented methods without being disturbed by tools.

LINKS

Task ID-2428882
Prepares Task ID-2086889
COM PR odoo/odoo#64196
ENT PT odoo/enterprise#15610
This commit is contained in:
Thibault Delavallée
2021-01-07 11:15:47 +00:00
parent 3e287843f0
commit 4819df905e
+165 -155
View File
@@ -856,6 +856,43 @@ class Lead(models.Model):
})
return True
# ------------------------------------------------------------
# VIEWS
# ------------------------------------------------------------
def redirect_lead_opportunity_view(self):
self.ensure_one()
return {
'name': _('Lead or Opportunity'),
'view_mode': 'form',
'res_model': 'crm.lead',
'domain': [('type', '=', self.type)],
'res_id': self.id,
'view_id': False,
'type': 'ir.actions.act_window',
'context': {'default_type': self.type}
}
@api.model
def get_empty_list_help(self, help):
help_title, sub_title = "", ""
if self._context.get('default_type') == 'lead':
help_title = _('Create a new lead')
else:
help_title = _('Create an opportunity to start playing with your pipeline.')
alias_record = self.env['mail.alias'].search([
('alias_name', '!=', False),
('alias_name', '!=', ''),
('alias_model_id.model', '=', 'crm.lead'),
('alias_parent_model_id.model', '=', 'crm.team'),
('alias_force_thread_id', '=', False)
], limit=1)
if alias_record and alias_record.alias_domain and alias_record.alias_name:
email = '%s@%s' % (alias_record.alias_name, alias_record.alias_domain)
email_link = "<b><a href='mailto:%s'>%s</a></b>" % (email, email)
sub_title = _('Use the top left <i>Create</i> button, or send an email to %s to test the email gateway.') % (email_link)
return '<p class="o_view_nocontent_smiling_face">%s</p><p class="oe_view_nocontent_alias">%s</p>' % (help_title, sub_title)
# ------------------------------------------------------------
# BUSINESS
# ------------------------------------------------------------
@@ -872,7 +909,7 @@ class Lead(models.Model):
return self.message_post(body=message)
# ------------------------------------------------------------
# MERGE LEADS / OPPS
# MERGE AND CONVERT LEADS / OPPORTUNITIES
# ------------------------------------------------------------
def _merge_get_result_type(self):
@@ -1083,16 +1120,6 @@ class Lead(models.Model):
return opportunities_head
def _sort_by_confidence_level(self, reverse=False):
""" Sorting the leads/opps according to the confidence level of its stage, which relates to the probability of winning it
The confidence level increases with the stage sequence
An Opportunity always has higher confidence level than a lead
"""
def opps_key(opportunity):
return opportunity.type == 'opportunity', opportunity.stage_id.sequence, -opportunity._origin.id
return self.sorted(key=opps_key, reverse=reverse)
def _convert_opportunity_data(self, customer, team_id=False):
""" Extract the data from a lead to create the opportunity
:param customer : res.partner record
@@ -1126,118 +1153,6 @@ class Lead(models.Model):
return True
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 includes archived opportunities
(still only active leads are considered). If False, search for active
and not won leads and opportunities;
"""
if not email and not partner:
return self.env['crm.lead']
domain = []
for normalized_email in [tools.email_normalize(email) for email in tools.email_split(email)]:
domain.append(('email_normalized', '=', normalized_email))
if partner:
domain.append(('partner_id', '=', partner.id))
if not domain:
return self.env['crm.lead']
domain = ['|'] * (len(domain) - 1) + domain
if include_lost:
domain += ['|', ('type', '=', 'opportunity'), ('active', '=', True)]
else:
domain += ['&', ('active', '=', True), '|', ('probability', '=', False), ('probability', '<', 100)]
return self.with_context(active_test=False).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
:param is_company : True if the partner is a company
:param parent_id : id of the parent partner (False if no parent)
:return: dictionary of values to give at res_partner.create()
"""
email_split = tools.email_split(self.email_from)
res = {
'name': partner_name,
'user_id': self.env.context.get('default_user_id') or self.user_id.id,
'comment': self.description,
'team_id': self.team_id.id,
'parent_id': parent_id,
'phone': self.phone,
'mobile': self.mobile,
'email': email_split[0] if email_split else False,
'title': self.title.id,
'function': self.function,
'street': self.street,
'street2': self.street2,
'zip': self.zip,
'city': self.city,
'country_id': self.country_id.id,
'state_id': self.state_id.id,
'website': self.website,
'is_company': is_company,
'type': 'contact'
}
if self.lang_id:
res['lang'] = self.lang_id.code
return res
def _find_matching_partner(self, email_only=False):
""" Try to find a matching partner with available information on the
lead, using notably customer's name, email, ...
:param email_only: Only find a matching based on the email. To use
for automatic process where ilike based on name can be too dangerous
:return: partner browse record
"""
self.ensure_one()
partner = self.partner_id
if not partner and self.email_from:
partner = self.env['res.partner'].search([('email', '=', self.email_from)], limit=1)
if not partner and not email_only:
# 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
return partner
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
@@ -1279,41 +1194,136 @@ class Lead(models.Model):
self.env['crm.lead'].browse(subset_ids).write(update_vals)
# ------------------------------------------------------------
# TOOLS
# ------------------------------------------------------------
# MERGE / CONVERT TOOLS
# ---------------------------------------------------------
def redirect_lead_opportunity_view(self):
self.ensure_one()
return {
'name': _('Lead or Opportunity'),
'view_mode': 'form',
'res_model': 'crm.lead',
'domain': [('type', '=', self.type)],
'res_id': self.id,
'view_id': False,
'type': 'ir.actions.act_window',
'context': {'default_type': self.type}
}
# CLASSIFICATION TOOLS
# --------------------------------------------------
@api.model
def get_empty_list_help(self, help):
help_title, sub_title = "", ""
if self._context.get('default_type') == 'lead':
help_title = _('Create a new lead')
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 includes archived opportunities
(still only active leads are considered). If False, search for active
and not won leads and opportunities;
"""
if not email and not partner:
return self.env['crm.lead']
domain = []
for normalized_email in [tools.email_normalize(email) for email in tools.email_split(email)]:
domain.append(('email_normalized', '=', normalized_email))
if partner:
domain.append(('partner_id', '=', partner.id))
if not domain:
return self.env['crm.lead']
domain = ['|'] * (len(domain) - 1) + domain
if include_lost:
domain += ['|', ('type', '=', 'opportunity'), ('active', '=', True)]
else:
help_title = _('Create an opportunity to start playing with your pipeline.')
alias_record = self.env['mail.alias'].search([
('alias_name', '!=', False),
('alias_name', '!=', ''),
('alias_model_id.model', '=', 'crm.lead'),
('alias_parent_model_id.model', '=', 'crm.team'),
('alias_force_thread_id', '=', False)
], limit=1)
if alias_record and alias_record.alias_domain and alias_record.alias_name:
email = '%s@%s' % (alias_record.alias_name, alias_record.alias_domain)
email_link = "<b><a href='mailto:%s'>%s</a></b>" % (email, email)
sub_title = _('Use the top left <i>Create</i> button, or send an email to %s to test the email gateway.') % (email_link)
return '<p class="o_view_nocontent_smiling_face">%s</p><p class="oe_view_nocontent_alias">%s</p>' % (help_title, sub_title)
domain += ['&', ('active', '=', True), '|', ('probability', '=', False), ('probability', '<', 100)]
return self.with_context(active_test=False).search(domain)
def _sort_by_confidence_level(self, reverse=False):
""" Sorting the leads/opps according to the confidence level of its stage, which relates to the probability of winning it
The confidence level increases with the stage sequence
An Opportunity always has higher confidence level than a lead
"""
def opps_key(opportunity):
return opportunity.type == 'opportunity', opportunity.stage_id.sequence, -opportunity._origin.id
return self.sorted(key=opps_key, reverse=reverse)
# CUSTOMER TOOLS
# --------------------------------------------------
def _find_matching_partner(self, email_only=False):
""" Try to find a matching partner with available information on the
lead, using notably customer's name, email, ...
:param email_only: Only find a matching based on the email. To use
for automatic process where ilike based on name can be too dangerous
:return: partner browse record
"""
self.ensure_one()
partner = self.partner_id
if not partner and self.email_from:
partner = self.env['res.partner'].search([('email', '=', self.email_from)], limit=1)
if not partner and not email_only:
# 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
return partner
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
:param is_company : True if the partner is a company
:param parent_id : id of the parent partner (False if no parent)
:return: dictionary of values to give at res_partner.create()
"""
email_parts = tools.email_split(self.email_from)
res = {
'name': partner_name,
'user_id': self.env.context.get('default_user_id') or self.user_id.id,
'comment': self.description,
'team_id': self.team_id.id,
'parent_id': parent_id,
'phone': self.phone,
'mobile': self.mobile,
'email': email_parts[0] if email_parts else False,
'title': self.title.id,
'function': self.function,
'street': self.street,
'street2': self.street2,
'zip': self.zip,
'city': self.city,
'country_id': self.country_id.id,
'state_id': self.state_id.id,
'website': self.website,
'is_company': is_company,
'type': 'contact'
}
if self.lang_id:
res['lang'] = self.lang_id.code
return res
# ------------------------------------------------------------
# MAILING