summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOdoo's Mergebot <xmo+roboto@odoo.com>2020-03-24 09:34:22 +0100
committerGitHub <noreply@github.com>2020-03-24 09:34:22 +0100
commitbaab53073701e522b26017e2e8cb3c4ff6c8bbe1 (patch)
treed0e706dd0cb70237c2d45c487905e63820b71b56
parenta5df7552842564b649ce2930ec302018ed18f534 (diff)
parent8304bc01ba9f150e884f56265a750a345507bb18 (diff)
[MERGE] crm(_*): move from onchange to editable stored fields
PURPOSE Try to move from onchange / default_get to stored editable computed fields. Behavior should be the same (computed or set by user), with support of create / write / onchange field update without additional code. USE EDITABLE STORED FIELDS Update classic fields updated in some cases by onchange and/or default methods by fields with store=True, readonly=False. It means their value comes either from manual user input, either from trigger based computation. Remove onchange and default_get when possible, leading to an unique computation method and clearing fields definition. Also clean some fields definition inconsistencies, notably required fields that should instead be correctly computed or default that have no real meaning. CLEAN CRM CONVERT WIZARDS CODE AND FIELDS USE In this merge we also 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. CLEAN CRM.TEAM MODEL Clean statistics fields definition on crm.team. Future tasks will improve the use of sales team in crm and first steps is to clean a bit models linked to sales team. LINKS Task ID 2088565 (crm: from onchange to compute) Community PR odoo/odoo#41955 Enterprise PR odoo/enterprise#7322 Upgrade PR odoo/upgrade#781 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
-rw-r--r--addons/crm/models/crm_lead.py555
-rw-r--r--addons/crm/models/crm_team.py107
-rw-r--r--addons/crm/models/res_config_settings.py23
-rw-r--r--addons/crm/models/res_users.py1
-rw-r--r--addons/crm/tests/common.py7
-rw-r--r--addons/crm/tests/test_crm_lead.py7
-rw-r--r--addons/crm/tests/test_crm_lead_convert.py119
-rw-r--r--addons/crm/tests/test_crm_lead_merge.py42
-rw-r--r--addons/crm/views/crm_lead_views.xml2
-rw-r--r--addons/crm/views/crm_team_views.xml14
-rw-r--r--addons/crm/wizard/crm_lead_to_opportunity.py239
-rw-r--r--addons/crm/wizard/crm_lead_to_opportunity_mass.py111
-rw-r--r--addons/crm/wizard/crm_lead_to_opportunity_mass_views.xml11
-rw-r--r--addons/crm/wizard/crm_lead_to_opportunity_views.xml2
-rw-r--r--addons/crm/wizard/crm_merge_opportunities.py23
-rw-r--r--addons/crm_livechat/models/mail_channel.py4
-rw-r--r--addons/sale_crm/wizard/crm_opportunity_to_quotation.py15
-rw-r--r--addons/website_crm/models/website_visitor.py6
-rw-r--r--addons/website_crm_partner_assign/models/crm_lead.py26
-rw-r--r--addons/website_crm_partner_assign/models/res_partner.py12
-rw-r--r--addons/website_crm_partner_assign/views/crm_lead_views.xml2
21 files changed, 673 insertions, 655 deletions
diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index d8fce1f43c8..c591a62a77e 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -45,8 +45,7 @@ CRM_LEAD_FIELDS_TO_MERGE = [
'date_action_last',
'email_from',
'email_cc',
- 'website',
- 'partner_name']
+ 'website']
class Lead(models.Model):
@@ -61,16 +60,8 @@ class Lead(models.Model):
'phone.validation.mixin']
_primary_email = 'email_from'
- def _default_team_id(self, user_id):
- domain = [('use_leads', '=', True)] if self._context.get('default_type') == "lead" or self.type == 'lead' else [('use_opportunities', '=', True)]
- return self.env['crm.team']._get_default_team_id(user_id=user_id, domain=domain)
-
- def _default_stage_id(self):
- team = self._default_team_id(user_id=self.env.uid)
- return self._stage_find(team_id=team.id, domain=[('fold', '=', False)]).id
-
# Description
- name = fields.Char('Opportunity', required=True, index=True)
+ name = fields.Char('Opportunity', index=True, required=True)
user_id = fields.Many2one('res.users', string='Salesperson', index=True, tracking=True, default=lambda self: self.env.user)
user_email = fields.Char('User Email', related='user_id.email', readonly=True)
user_login = fields.Char('User Login', related='user_id.login', readonly=True)
@@ -81,22 +72,23 @@ class Lead(models.Model):
type = fields.Selection([
('lead', 'Lead'), ('opportunity', 'Opportunity')],
index=True, required=True, tracking=15,
- default=lambda self: 'lead' if self.env['res.users'].has_group('crm.group_use_lead') else 'opportunity',
- help="Type is used to separate Leads and Opportunities")
+ default=lambda self: 'lead' if self.env['res.users'].has_group('crm.group_use_lead') else 'opportunity')
priority = fields.Selection(
crm_stage.AVAILABLE_PRIORITIES, string='Priority', index=True,
default=crm_stage.AVAILABLE_PRIORITIES[0][0])
team_id = fields.Many2one(
'crm.team', string='Sales Team', index=True, tracking=True,
- default=lambda self: self._default_team_id(self.env.uid))
+ compute='_compute_team_id', copy=True, readonly=False, store=True)
stage_id = fields.Many2one(
- 'crm.stage', string='Stage', ondelete='restrict',
- copy=False, group_expand='_read_group_stage_ids', index=True, tracking=True,
- domain="['|', ('team_id', '=', False), ('team_id', '=', team_id)]",
- default=lambda self: self._default_stage_id())
+ 'crm.stage', string='Stage', index=True, tracking=True,
+ compute='_compute_stage_id', readonly=False, store=True,
+ copy=False, group_expand='_read_group_stage_ids', ondelete='restrict',
+ domain="['|', ('team_id', '=', False), ('team_id', '=', team_id)]")
kanban_state = fields.Selection([
- ('grey', 'No next activity planned'), ('red', 'Next activity late'), ('green', 'Next activity is planned')],
- string='Kanban State', compute='_compute_kanban_state')
+ ('grey', 'No next activity planned'),
+ ('red', 'Next activity late'),
+ ('green', 'Next activity is planned')], string='Kanban State',
+ compute='_compute_kanban_state')
tag_ids = fields.Many2many(
'crm.tag', 'crm_tag_rel', 'lead_id', 'tag_id', string='Tags',
help="Classify and analyze your lead/opportunity categories like: Training, Service")
@@ -108,26 +100,37 @@ class Lead(models.Model):
# Dates
date_closed = fields.Datetime('Closed Date', readonly=True, copy=False)
date_action_last = fields.Datetime('Last Action', readonly=True)
- date_open = fields.Datetime('Assignment Date', readonly=True, default=fields.Datetime.now)
+ date_open = fields.Datetime(
+ 'Assignment Date', compute='_compute_date_open', readonly=True, store=True)
day_open = fields.Float('Days to Assign', compute='_compute_day_open', store=True)
day_close = fields.Float('Days to Close', compute='_compute_day_close', store=True)
- date_last_stage_update = fields.Datetime('Last Stage Update', index=True, default=fields.Datetime.now)
+ date_last_stage_update = fields.Datetime(
+ 'Last Stage Update', compute='_compute_date_last_stage_update', index=True, readonly=True, store=True)
date_conversion = fields.Datetime('Conversion Date', readonly=True)
date_deadline = fields.Date('Expected Closing', help="Estimate of the date on which the opportunity will be won.")
# Customer / contact
partner_id = fields.Many2one(
- 'res.partner', string='Customer', tracking=10, index=True,
+ 'res.partner', string='Customer', index=True, tracking=10,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",
help="Linked partner (optional). Usually created when converting the lead. You can find a partner by its Name, TIN, Email or Internal Reference.")
partner_address_email = fields.Char('Partner Contact Email', related='partner_id.email', readonly=True)
partner_is_blacklisted = fields.Boolean('Partner is blacklisted', related='partner_id.is_blacklisted', readonly=True)
- contact_name = fields.Char('Contact Name', tracking=30)
- partner_name = fields.Char("Company Name", tracking=20, index=True, help='The name of the future partner company that will be created while converting the lead into opportunity')
- function = fields.Char('Job Position')
- title = fields.Many2one('res.partner.title', string='Title')
- email_from = fields.Char('Email', index=True, tracking=40)
- phone = fields.Char('Phone', tracking=50)
- mobile = fields.Char('Mobile')
+ contact_name = fields.Char(
+ 'Contact Name', tracking=30,
+ compute='_compute_partner_id_values', readonly=False, store=True)
+ partner_name = fields.Char(
+ 'Company Name', tracking=20, index=True,
+ compute='_compute_partner_id_values', readonly=False, store=True,
+ help='The name of the future partner company that will be created while converting the lead into opportunity')
+ function = fields.Char('Job Position', compute='_compute_partner_id_values', readonly=False, store=True)
+ title = fields.Many2one('res.partner.title', string='Title',compute='_compute_partner_id_values', readonly=False, store=True)
+ email_from = fields.Char(
+ 'Email', tracking=40, index=True,
+ compute='_compute_partner_id_values', readonly=False, store=True)
+ phone = fields.Char(
+ 'Phone', tracking=50,
+ compute='_compute_partner_id_values', readonly=False, store=True)
+ mobile = fields.Char('Mobile', compute='_compute_partner_id_values', readonly=False, store=True)
phone_mobile_search = fields.Char('Phone/Mobile', store=False, search='_search_phone_mobile_search')
phone_state = fields.Selection([
('correct', 'Correct'),
@@ -135,18 +138,25 @@ class Lead(models.Model):
email_state = fields.Selection([
('correct', 'Correct'),
('incorrect', 'Incorrect')], string='Email Quality', compute="_compute_email_state", store=True)
- website = fields.Char('Website', index=True, help="Website of the contact")
+ website = fields.Char('Website', index=True, help="Website of the contact", compute="_compute_partner_id_values", store=True, readonly=False)
lang_id = fields.Many2one('res.lang', string='Language')
# Address fields
- street = fields.Char('Street')
- street2 = fields.Char('Street2')
- zip = fields.Char('Zip', change_default=True)
- city = fields.Char('City')
- state_id = fields.Many2one("res.country.state", string='State', domain="[('country_id', '=?', country_id)]")
- country_id = fields.Many2one('res.country', string='Country')
+ street = fields.Char('Street', compute='_compute_partner_id_values', readonly=False, store=True)
+ street2 = fields.Char('Street2', compute='_compute_partner_id_values', readonly=False, store=True)
+ zip = fields.Char('Zip', change_default=True, compute='_compute_partner_id_values', readonly=False, store=True)
+ city = fields.Char('City', compute='_compute_partner_id_values', readonly=False, store=True)
+ state_id = fields.Many2one(
+ "res.country.state", string='State',
+ compute='_compute_partner_id_values', readonly=False, store=True,
+ domain="[('country_id', '=?', country_id)]")
+ country_id = fields.Many2one(
+ 'res.country', string='Country',
+ compute='_compute_partner_id_values', readonly=False, store=True)
# Probability (Opportunity only)
- probability = fields.Float('Probability', group_operator="avg", copy=False)
- automated_probability = fields.Float('Automated Probability', readonly=True)
+ probability = fields.Float(
+ 'Probability', group_operator="avg", copy=False,
+ compute='_compute_probabilities', readonly=False, store=True)
+ automated_probability = fields.Float('Automated Probability', compute='_compute_probabilities', readonly=True, store=True)
is_automated_probability = fields.Boolean('Is automated probability?', compute="_compute_is_automated_probability")
# External records
meeting_count = fields.Integer('# Meetings', compute='_compute_meeting_count')
@@ -170,12 +180,38 @@ class Lead(models.Model):
kanban_state = 'red'
lead.kanban_state = kanban_state
- @api.depends('planned_revenue', 'probability')
- def _compute_expected_revenue(self):
+ @api.depends('user_id', 'type')
+ def _compute_team_id(self):
+ """ When changing the user, also set a team_id or restrict team id
+ to the ones user_id is member of. """
for lead in self:
- lead.expected_revenue = round((lead.planned_revenue or 0.0) * (lead.probability or 0) / 100.0, 2)
+ # setting user as void should not trigger a new team computation
+ if not lead.user_id:
+ continue
+ user = lead.user_id
+ if lead.team_id and user in lead.team_id.member_ids | lead.team_id.user_id:
+ continue
+ team_domain = [('use_leads', '=', True)] if lead.type == 'lead' else [('use_opportunities', '=', True)]
+ team = self.env['crm.team']._get_default_team_id(user_id=user.id, domain=team_domain)
+ lead.team_id = team.id
- @api.depends('date_open')
+ @api.depends('team_id', 'type')
+ def _compute_stage_id(self):
+ for lead in self:
+ if not lead.stage_id:
+ lead.stage_id = lead._stage_find(domain=[('fold', '=', False)]).id
+
+ @api.depends('user_id')
+ def _compute_date_open(self):
+ for lead in self:
+ lead.date_open = fields.Datetime.now() if lead.user_id else False
+
+ @api.depends('stage_id')
+ def _compute_date_last_stage_update(self):
+ for lead in self:
+ lead.date_last_stage_update = fields.Datetime.now()
+
+ @api.depends('create_date', 'date_open')
def _compute_day_open(self):
""" Compute difference between create date and open date """
leads = self.filtered(lambda l: l.date_open and l.create_date)
@@ -186,7 +222,7 @@ class Lead(models.Model):
date_open = fields.Datetime.from_string(lead.date_open)
lead.day_open = abs((date_open - date_create).days)
- @api.depends('date_closed')
+ @api.depends('create_date', 'date_closed')
def _compute_day_close(self):
""" Compute difference between current date and log date """
leads = self.filtered(lambda l: l.date_closed and l.create_date)
@@ -197,13 +233,11 @@ class Lead(models.Model):
date_close = fields.Datetime.from_string(lead.date_closed)
lead.day_close = abs((date_close - date_create).days)
- @api.depends('probability', 'automated_probability')
- def _compute_is_automated_probability(self):
- """ If probability and automated_probability are equal, the probability is considered as automatic.
- when the probability is in automatic mode, the field probability will be aligned
- when automated_probability is modified. """
+ @api.depends('partner_id')
+ def _compute_partner_id_values(self):
+ """ compute the new values when partner_id has changed """
for lead in self:
- lead.is_automated_probability = tools.float_compare(lead.probability, lead.automated_probability, 2) == 0
+ lead.update(lead._preare_values_from_partner(lead.partner_id))
@api.depends('phone', 'country_id.code')
def _compute_phone_state(self):
@@ -230,6 +264,33 @@ class Lead(models.Model):
break
lead.email_state = email_state
+ @api.depends('probability', 'automated_probability')
+ def _compute_is_automated_probability(self):
+ """ If probability and automated_probability are equal probability computation
+ is considered as automatic, aka probability is sync with automated_probability """
+ for lead in self:
+ # creation mode: consider it as being not automated
+ if not lead.id and not lead._origin.id:
+ lead.is_automated_probability = False
+ else:
+ lead.is_automated_probability = tools.float_compare(lead.probability, lead.automated_probability, 2) == 0
+
+ @api.depends(lambda self: ['tag_ids', 'stage_id', 'team_id'] + self._pls_get_safe_fields())
+ def _compute_probabilities(self):
+ for lead in self:
+ was_automated = False
+ lead_probabilities = lead._pls_get_naive_bayes_probabilities()
+ if lead.id in lead_probabilities:
+ was_automated = lead.active and lead.is_automated_probability
+ lead.automated_probability = lead_probabilities[lead.id]
+ if was_automated:
+ lead.probability = lead.automated_probability
+
+ @api.depends('planned_revenue', 'probability')
+ def _compute_expected_revenue(self):
+ for lead in self:
+ lead.expected_revenue = round((lead.planned_revenue or 0.0) * (lead.probability or 0) / 100.0, 2)
+
def _compute_meeting_count(self):
meeting_data = self.env['calendar.event'].read_group([('opportunity_id', 'in', self.ids)], ['opportunity_id'], ['opportunity_id'])
mapped_data = {m['opportunity_id'][0]: m['opportunity_id_count'] for m in meeting_data}
@@ -261,121 +322,6 @@ class Lead(models.Model):
return [(0, '=', 1)]
return [('id', 'in', [r[0] for r in res])]
- def _onchange_partner_id_values(self, partner_id):
- """ returns the new values when partner_id has changed """
- if partner_id:
- partner = self.env['res.partner'].browse(partner_id)
-
- partner_name = partner.parent_id.name
- if not partner_name and partner.is_company:
- partner_name = partner.name
-
- return {
- 'partner_name': partner_name,
- 'contact_name': partner.name if not partner.is_company else False,
- 'title': partner.title.id,
- 'street': partner.street,
- 'street2': partner.street2,
- 'city': partner.city,
- 'state_id': partner.state_id.id,
- 'country_id': partner.country_id.id,
- 'email_from': partner.email,
- 'phone': partner.phone,
- 'mobile': partner.mobile,
- 'zip': partner.zip,
- 'function': partner.function,
- 'website': partner.website,
- }
- return {}
-
- @api.onchange('partner_id')
- def _onchange_partner_id(self):
- values = self._onchange_partner_id_values(self.partner_id.id if self.partner_id else False)
- self.update(values)
-
- @api.model
- def _onchange_user_values(self, user_id):
- """ returns new values when user_id has changed """
- if not user_id:
- return {}
- if user_id and self._context.get('team_id'):
- team = self.env['crm.team'].browse(self._context['team_id'])
- if user_id in team.member_ids.ids or user_id == team.user_id.id:
- return {}
- team_id = self._default_team_id(user_id)
- return {'team_id': team_id}
-
- @api.onchange('user_id')
- def _onchange_user_id(self):
- """ When changing the user, also set a team_id or restrict team id to the ones user_id is member of. """
- if self.user_id.sale_team_id:
- values = self._onchange_user_values(self.user_id.id)
- self.update(values)
-
- def _onchange_compute_probability(self, optional_field_name=None):
- """Recompute probability on onchange methods of :
- 'stage_id', 'team_id', 'tag_ids'
- 'country_id', 'state_id', 'phone_state', 'email_state', 'source_id' """
- if optional_field_name and optional_field_name not in self._pls_get_safe_fields():
- return
- lead_probabilities = self._pls_get_naive_bayes_probabilities()
- if self.id in lead_probabilities:
- self.automated_probability = lead_probabilities[self.id]
- if self._origin.is_automated_probability:
- self.probability = self.automated_probability
-
- @api.onchange('stage_id')
- def _onchange_stage_id(self):
- self._onchange_compute_probability()
-
- @api.onchange('source_id')
- def _onchange_source_id(self):
- self._onchange_compute_probability(optional_field_name='source_id')
-
- @api.onchange('automated_probability')
- def _onchange_automated_probability(self):
- """ If was in auto mode before the change, align both fields so that it will stay in auto mode. """
- if self._origin.is_automated_probability:
- self.probability = self.automated_probability
-
- @api.onchange('team_id')
- def _onchange_team_id(self):
- self._onchange_compute_probability()
-
- @api.onchange('tag_ids')
- def _onchange_tag_ids(self):
- self._onchange_compute_probability()
-
- @api.onchange('phone_state')
- def _onchange_phone_state(self):
- self._onchange_compute_probability(optional_field_name='phone_state')
-
- @api.onchange('email_state')
- def _onchange_email_state(self):
- self._onchange_compute_probability(optional_field_name='email_state')
-
- @api.constrains('user_id')
- def _valid_team(self):
- for lead in self:
- if lead.user_id:
- values = lead.with_context(team_id=lead.team_id.id)._onchange_user_values(lead.user_id.id)
- if values:
- lead.update(values)
-
- @api.onchange('state_id')
- def _onchange_state(self):
- self._onchange_compute_probability(optional_field_name='state_id')
- if self.state_id:
- self.country_id = self.state_id.country_id.id
-
- @api.onchange('country_id')
- def _onchange_country_id(self):
- self._onchange_compute_probability(optional_field_name='country_id')
-
- @api.onchange('lang_id')
- def _onchange_lang_id(self):
- self._onchange_compute_probability(optional_field_name='lang_id')
-
@api.onchange('phone', 'country_id', 'company_id')
def _onchange_phone_validation(self):
if self.phone:
@@ -386,6 +332,30 @@ class Lead(models.Model):
if self.mobile:
self.mobile = self.phone_format(self.mobile)
+ def _preare_values_from_partner(self, partner):
+ """ Get a dictionary with values coming from customer information to
+ copy on a lead. Email_from and phone fields get the current lead
+ values to avoid being reset if customer has no value for them. """
+ partner_name = partner.parent_id.name
+ if not partner_name and partner.is_company:
+ partner_name = partner.name
+ return {
+ 'partner_name': partner_name,
+ 'contact_name': partner.name if not partner.is_company else False,
+ 'title': partner.title.id,
+ 'street': partner.street,
+ 'street2': partner.street2,
+ 'city': partner.city,
+ 'state_id': partner.state_id.id,
+ 'country_id': partner.country_id.id,
+ 'email_from': partner.email or self.email_from,
+ 'phone': partner.phone or self.phone,
+ 'mobile': partner.mobile,
+ 'zip': partner.zip,
+ 'function': partner.function,
+ 'website': partner.website,
+ }
+
# ------------------------------------------------------------
# ORM
# ------------------------------------------------------------
@@ -400,44 +370,23 @@ class Lead(models.Model):
@api.model
def create(self, vals):
- # set up context used to find the lead's Sales Team which is needed
- # to correctly set the default stage_id
- context = dict(self._context or {})
- if vals.get('type') and not self._context.get('default_type'):
- context['default_type'] = vals.get('type')
- if vals.get('team_id') and not self._context.get('default_team_id'):
- context['default_team_id'] = vals.get('team_id')
-
- if vals.get('user_id') and 'date_open' not in vals:
- vals['date_open'] = fields.Datetime.now()
-
- partner_id = vals.get('partner_id') or context.get('default_partner_id')
- onchange_values = self._onchange_partner_id_values(partner_id)
- onchange_values.update(vals) # we don't want to overwrite any existing key
- vals = onchange_values
-
- result = super(Lead, self.with_context(context)).create(vals)
+ lead = super(Lead, self).create(vals)
# Compute new probability for each lead separately
- result._update_probability()
- return result
+ lead._update_probability()
+ return lead
def write(self, vals):
# stage change:
if 'stage_id' in vals:
- vals['date_last_stage_update'] = fields.Datetime.now()
stage_id = self.env['crm.stage'].browse(vals['stage_id'])
if stage_id.is_won:
vals.update({'probability': 100})
- # Only write the 'date_open' if no salesperson was assigned.
- if vals.get('user_id') and 'date_open' not in vals and not self.mapped('user_id'):
- vals['date_open'] = fields.Datetime.now()
+
# stage change with new stage: update probability and date_closed
if vals.get('probability', 0) >= 100 or not vals.get('active', True):
vals['date_closed'] = fields.Datetime.now()
elif 'probability' in vals:
vals['date_closed'] = False
- if vals.get('user_id') and 'date_open' not in vals:
- vals['date_open'] = fields.Datetime.now()
write_result = super(Lead, self).write(vals)
# Compute new automated_probability (and, eventually, probability) for each lead separately
@@ -452,7 +401,7 @@ class Lead(models.Model):
if lead.id in lead_probabilities:
lead_proba = lead_probabilities[lead.id]
proba_vals = {'automated_probability': lead_proba}
- if tools.float_compare(lead.probability, lead.automated_probability, 2) == 0:
+ if lead.active and lead.is_automated_probability:
proba_vals['probability'] = lead_proba
super(Lead, lead).write(proba_vals)
return
@@ -870,51 +819,18 @@ 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
:param team_id : identifier of the Sales Team to determine the stage
"""
new_team_id = team_id if team_id else self.team_id.id
- upd_values = {}
- if customer:
- upd_values.update(self._onchange_partner_id_values(customer and customer.id))
- upd_values['email_from'] = upd_values['email_from'] if upd_values.get('email_from') else self.email_from
- upd_values['phone'] = upd_values['phone'] if upd_values.get('phone') else self.phone
- upd_values.update({
+ upd_values = {
'partner_id': customer.id if customer else False,
'type': 'opportunity',
'date_open': fields.Datetime.now(),
'date_conversion': fields.Datetime.now(),
- })
+ }
if not self.stage_id:
stage = self._stage_find(team_id=new_team_id)
upd_values['stage_id'] = stage.id
@@ -931,11 +847,64 @@ 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 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))
+ domain = ['|'] * (len(domain) - 1) + domain
+
+ if not domain:
+ return self.env['crm.lead']
+
+ 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
@@ -967,107 +936,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 self.email_from: # search through the existing partners based on the lead's email
- return Partner.search([('email', '=', self.email_from)], limit=1).id
-
- 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
-
- if search_criteria:
- return Partner.search([('name', 'ilike', '%' + search_criteria + '%')], limit=1).id
-
- return False
+ if not partner and self.email_from:
+ partner = self.env['res.partner'].search([('email', '=', self.email_from)], limit=1)
- def handle_partner_assignation(self, action='create', partner_id=False):
- """ Handle partner assignation during a lead conversion.
+ 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
- If action is 'create', create new partner with contact and assign lead to
- new partner_id. Otherwise assign lead to the specified partner_id
+ return partner
- TDE FIXME: docstring does not match code... code seems a bit random.
+ 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.
- :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
-
- def allocate_salesman(self, user_ids=None, team_id=False):
+ 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 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.
-
- :param list ids: leads/opportunities ids to process
- :param list user_ids: salesmen to assign
- :param int team_id: salesteam to assign
- :return bool
+ 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 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
@@ -1200,8 +1121,6 @@ class Lead(models.Model):
'email_from': msg_dict.get('from'),
'partner_id': msg_dict.get('author_id', False),
}
- if msg_dict.get('author_id'):
- defaults.update(self._onchange_partner_id_values(msg_dict.get('author_id')))
if msg_dict.get('priority') in dict(crm_stage.AVAILABLE_PRIORITIES):
defaults['priority'] = msg_dict.get('priority')
defaults.update(custom_values)
diff --git a/addons/crm/models/crm_team.py b/addons/crm/models/crm_team.py
index f6a2b1c83b3..a02b7f34433 100644
--- a/addons/crm/models/crm_team.py
+++ b/addons/crm/models/crm_team.py
@@ -1,13 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from dateutil.relativedelta import relativedelta
+import ast
+import datetime
from odoo import api, fields, models, _
from odoo.tools.safe_eval import safe_eval
-from odoo.exceptions import ValidationError
-import ast
class Team(models.Model):
_name = 'crm.team'
@@ -17,30 +16,26 @@ class Team(models.Model):
use_leads = fields.Boolean('Leads', help="Check this box to filter and qualify incoming requests as leads before converting them into opportunities and assigning them to a salesperson.")
use_opportunities = fields.Boolean('Pipeline', default=True, help="Check this box to manage a presales process with opportunities.")
alias_id = fields.Many2one('mail.alias', string='Alias', ondelete="restrict", required=True, help="The email address associated with this channel. New emails received will automatically create new leads assigned to the channel.")
-
- unassigned_leads_count = fields.Integer(
- compute='_compute_unassigned_leads_count',
- string='Unassigned Leads')
+ # statistics about leads / opportunities / both
+ lead_unassigned_count = fields.Integer(
+ string='# Unassigned Leads', compute='_compute_lead_unassigned_count')
+ lead_all_assigned_month_count = fields.Integer(
+ string='# Leads/Opps assigned this month', compute='_compute_lead_all_assigned_month_count',
+ help="Number of leads and opportunities assigned this last month.")
opportunities_count = fields.Integer(
- compute='_compute_opportunities',
- string='Number of open opportunities')
- overdue_opportunities_count = fields.Integer(
- compute='_compute_overdue_opportunities',
- string='Number of overdue opportunities')
+ string='# Opportunities', compute='_compute_opportunities_data')
opportunities_amount = fields.Integer(
- compute='_compute_opportunities',
- string='Opportunities Revenues')
- overdue_opportunities_amount = fields.Integer(
- compute='_compute_overdue_opportunities',
- string='Overdue Opportunities Revenues')
-
- # Since we are in a _inherits case, this is not an override
- # but a plain definition of a field
- # So we need to reset the property related of that field
- alias_user_id = fields.Many2one('res.users', related='alias_id.alias_user_id', inherited=True, domain=lambda self: [
- ('groups_id', 'in', self.env.ref('sales_team.group_sale_salesman_all_leads').id)])
-
- def _compute_unassigned_leads_count(self):
+ string='Opportunities Revenues', compute='_compute_opportunities_data')
+ opportunities_overdue_count = fields.Integer(
+ string='# Overdue Opportunities', compute='_compute_opportunities_overdue_data')
+ opportunities_overdue_amount = fields.Integer(
+ string='Overdue Opportunities Revenues', compute='_compute_opportunities_overdue_data',)
+ # alias: improve fields coming from _inherits, use inherited to avoid replacing them
+ alias_user_id = fields.Many2one(
+ 'res.users', related='alias_id.alias_user_id', inherited=True,
+ domain=lambda self: [('groups_id', 'in', self.env.ref('sales_team.group_sale_salesman_all_leads').id)])
+
+ def _compute_lead_unassigned_count(self):
leads_data = self.env['crm.lead'].read_group([
('team_id', 'in', self.ids),
('type', '=', 'lead'),
@@ -48,9 +43,20 @@ class Team(models.Model):
], ['team_id'], ['team_id'])
counts = {datum['team_id'][0]: datum['team_id_count'] for datum in leads_data}
for team in self:
- team.unassigned_leads_count = counts.get(team.id, 0)
+ team.lead_unassigned_count = counts.get(team.id, 0)
- def _compute_opportunities(self):
+ def _compute_lead_all_assigned_month_count(self):
+ limit_date = datetime.datetime.now() - datetime.timedelta(days=30)
+ leads_data = self.env['crm.lead'].read_group([
+ ('team_id', 'in', self.ids),
+ ('date_open', '>=', fields.Datetime.to_string(limit_date)),
+ ('user_id', '!=', False),
+ ], ['team_id'], ['team_id'])
+ counts = {datum['team_id'][0]: datum['team_id_count'] for datum in leads_data}
+ for team in self:
+ team.lead_all_assigned_month_count = counts.get(team.id, 0)
+
+ def _compute_opportunities_data(self):
opportunity_data = self.env['crm.lead'].search([
('team_id', 'in', self.ids),
('probability', '<', 100),
@@ -67,7 +73,7 @@ class Team(models.Model):
team.opportunities_count = counts.get(team.id, 0)
team.opportunities_amount = amounts.get(team.id, 0)
- def _compute_overdue_opportunities(self):
+ def _compute_opportunities_overdue_data(self):
opportunity_data = self.env['crm.lead'].read_group([
('team_id', 'in', self.ids),
('probability', '<', 100),
@@ -77,14 +83,46 @@ class Team(models.Model):
counts = {datum['team_id'][0]: datum['team_id_count'] for datum in opportunity_data}
amounts = {datum['team_id'][0]: (datum['planned_revenue']) for datum in opportunity_data}
for team in self:
- team.overdue_opportunities_count = counts.get(team.id, 0)
- team.overdue_opportunities_amount = amounts.get(team.id, 0)
+ team.opportunities_overdue_count = counts.get(team.id, 0)
+ team.opportunities_overdue_amount = amounts.get(team.id, 0)
@api.onchange('use_leads', 'use_opportunities')
def _onchange_use_leads_opportunities(self):
if not self.use_leads and not self.use_opportunities:
self.alias_name = False
+ # ------------------------------------------------------------
+ # ORM
+ # ------------------------------------------------------------
+
+ @api.model
+ def create(self, vals):
+ alias_values = self._synchronize_alias(vals)
+ if alias_values:
+ vals.update(alias_values)
+ return super(Team, self).create(vals)
+
+ def write(self, vals):
+ result = super(Team, self).write(vals)
+ if 'use_leads' in vals or 'alias_defaults' in vals:
+ for team in self:
+ team.alias_id.write(team.get_alias_values())
+ if 'use_leads' in vals or 'use_opportunities' in vals:
+ alias_values = self._synchronize_alias(vals)
+ if alias_values:
+ self.write(alias_values)
+ return result
+
+ def _synchronize_alias(self, values):
+ use_leads = self.use_leads if self else values.get('use_leads', False)
+ use_opportunities = self.use_opportunities if self else values.get('use_opportunities', True)
+ if not use_leads and not use_opportunities:
+ return {'alias_name': False}
+ return {}
+ # ------------------------------------------------------------
+ # MESSAGING
+ # ------------------------------------------------------------
+
def get_alias_model_name(self, vals):
return 'crm.lead'
@@ -96,12 +134,9 @@ class Team(models.Model):
defaults['team_id'] = self.id
return values
- def write(self, vals):
- result = super(Team, self).write(vals)
- if 'use_leads' in vals or 'alias_defaults' in vals:
- for team in self:
- team.alias_id.write(team.get_alias_values())
- return result
+ # ------------------------------------------------------------
+ # ACTIONS
+ # ------------------------------------------------------------
#TODO JEM : refactor this stuff with xml action, proper customization,
@api.model
diff --git a/addons/crm/models/res_config_settings.py b/addons/crm/models/res_config_settings.py
index 3861c1c411f..258caf44d56 100644
--- a/addons/crm/models/res_config_settings.py
+++ b/addons/crm/models/res_config_settings.py
@@ -2,14 +2,17 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
-from datetime import date
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
- crm_alias_prefix = fields.Char('Default Alias Name for Leads')
- generate_lead_from_alias = fields.Boolean('Manual Assignment of Emails', config_parameter='crm.generate_lead_from_alias')
+ crm_alias_prefix = fields.Char(
+ 'Default Alias Name for Leads',
+ compute="_compute_crm_alias_prefix" , readonly=False, store=True)
+ generate_lead_from_alias = fields.Boolean(
+ 'Manual Assignment of Emails', config_parameter='crm.generate_lead_from_alias',
+ compute="_compute_generate_lead_from_alias", readonly=False, store=True)
group_use_lead = fields.Boolean(string="Leads", implied_group='crm.group_use_lead')
module_crm_iap_lead = fields.Boolean("Generate new leads based on their country, industries, size, etc.")
module_crm_iap_lead_website = fields.Boolean("Create Leads/Opportunities from your website's traffic")
@@ -71,15 +74,17 @@ class ResConfigSettings(models.TransientModel):
if setting.predictive_lead_scoring_start_date:
setting.predictive_lead_scoring_start_date_str = fields.Date.to_string(setting.predictive_lead_scoring_start_date)
- @api.onchange('group_use_lead')
- def _onchange_group_use_lead(self):
+ @api.depends('group_use_lead')
+ def _compute_generate_lead_from_alias(self):
""" Reset alias / leads configuration if leads are not used """
if not self.group_use_lead:
- self.generate_lead_from_alias = False
+ for setting in self:
+ setting.generate_lead_from_alias = False
- @api.onchange('generate_lead_from_alias')
- def _onchange_generate_lead_from_alias(self):
- self.crm_alias_prefix = (self.crm_alias_prefix or 'info') if self.generate_lead_from_alias else False
+ @api.depends('generate_lead_from_alias')
+ def _compute_crm_alias_prefix(self):
+ for setting in self:
+ setting.crm_alias_prefix = (setting.crm_alias_prefix or 'info') if setting.generate_lead_from_alias else False
@api.model
def get_values(self):
diff --git a/addons/crm/models/res_users.py b/addons/crm/models/res_users.py
index 2c94f1c05ab..951287d5958 100644
--- a/addons/crm/models/res_users.py
+++ b/addons/crm/models/res_users.py
@@ -5,7 +5,6 @@ from odoo import fields, models
class Users(models.Model):
-
_inherit = 'res.users'
target_sales_won = fields.Integer('Won in Opportunities Target')
diff --git a/addons/crm/tests/common.py b/addons/crm/tests/common.py
index 2fbfcc0affa..c1171e4c44d 100644
--- a/addons/crm/tests/common.py
+++ b/addons/crm/tests/common.py
@@ -116,6 +116,10 @@ class TestCrmCommon(SavepointCase, MailCase):
'contact_name': 'Amy Wong',
'email_from': 'amy.wong@test.example.com',
})
+ # update lead_1: stage_id is not computed anymore by default for leads
+ cls.lead_1.write({
+ 'stage_id': cls.stage_team1_1.id,
+ })
# create an history for new team
cls.lead_team_1_won = cls.env['crm.lead'].create({
@@ -303,6 +307,7 @@ class TestLeadConvertMassCommon(TestLeadConvertCommon):
'stage_id': False,
'partner_id': cls.contact_1.id,
})
+ cls.lead_w_partner.write({'stage_id': False})
cls.lead_w_partner_company = cls.env['crm.lead'].create({
'name': 'New1',
'type': 'lead',
@@ -338,6 +343,4 @@ class TestLeadConvertMassCommon(TestLeadConvertCommon):
'stage_id': cls.stage_team1_2.id,
'active': False,
})
- for l in (cls.lead_w_partner | cls.lead_w_partner_company | cls.lead_w_contact | cls.lead_w_email | cls.lead_w_email_lost):
- l._onchange_user_id()
(cls.lead_w_partner | cls.lead_w_partner_company | cls.lead_w_contact | cls.lead_w_email | cls.lead_w_email_lost).flush()
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 7e589598c41..75801e4a39f 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):
@@ -15,6 +19,14 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon):
date = Datetime.from_string('2020-01-20 16:00:00')
cls.crm_lead_dt_mock.now.return_value = date
+ def test_initial_data(self):
+ """ Ensure initial data to avoid spaghetti test update afterwards """
+ self.assertFalse(self.lead_1.date_conversion)
+ self.assertEqual(self.lead_1.date_open, Datetime.from_string('2020-01-15 11:30:00'))
+ self.assertEqual(self.lead_1.user_id, self.user_sales_leads)
+ self.assertEqual(self.lead_1.team_id, self.sales_team_1)
+ self.assertEqual(self.lead_1.stage_id, self.stage_team1_1)
+
@users('user_sales_manager')
def test_lead_convert_base(self):
""" Test base method ``convert_opportunity`` or crm.lead model """
@@ -91,24 +103,21 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon):
other_lead = self.lead_1.copy()
other_lead.write({'partner_id': self.contact_1.id})
- # TDE FIXME: default_lead_id should be correctly supported by wizard -> currently completely broken
- # convert = self.env['crm.lead2opportunity.partner'].with_context({
- # 'default_lead_id': other_lead.id,
- # }).create({})
- # self.assertEqual(convert.lead_id, other_lead)
- # self.assertEqual(convert.partner_id, self.contact_1)
- # self.assertEqual(convert.action, 'exist')
+ convert = self.env['crm.lead2opportunity.partner'].with_context({
+ 'default_lead_id': other_lead.id,
+ }).create({})
+ self.assertEqual(convert.lead_id, other_lead)
+ self.assertEqual(convert.partner_id, self.contact_1)
+ self.assertEqual(convert.action, 'exist')
convert = self.env['crm.lead2opportunity.partner'].with_context({
'default_lead_id': other_lead.id,
'active_model': 'crm.lead',
'active_id': self.lead_1.id,
}).create({})
- # TDE FIXME: active_id should be a fallback, not the only one way to work with a standard wizard
- self.assertEqual(convert.lead_id, self.lead_1)
- # self.assertEqual(convert.lead_id, other_lead)
- # self.assertEqual(convert.partner_id, self.contact_1)
- # self.assertEqual(convert.action, 'exist')
+ self.assertEqual(convert.lead_id, other_lead)
+ self.assertEqual(convert.partner_id, self.contact_1)
+ self.assertEqual(convert.action, 'exist')
@users('user_sales_manager')
def test_lead_convert_corner_cases_matching(self):
@@ -133,13 +142,6 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon):
@users('user_sales_manager')
def test_lead_convert_internals(self):
""" Test internals of convert wizard """
- # ensure initial data to avoid spaghetti test update afterwards
- self.assertFalse(self.lead_1.date_conversion)
- self.assertEqual(self.lead_1.date_open, Datetime.from_string('2020-01-15 11:30:00'))
- self.assertEqual(self.lead_1.user_id, self.user_sales_leads)
- self.assertEqual(self.lead_1.team_id, self.sales_team_1)
- self.assertEqual(self.lead_1.stage_id, self.stage_team1_1)
-
convert = self.env['crm.lead2opportunity.partner'].with_context({
'active_model': 'crm.lead',
'active_id': self.lead_1.id,
@@ -155,7 +157,6 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon):
self.assertEqual(convert.action, 'create')
convert.write({'user_id': self.user_sales_salesman.id})
- convert._onchange_user()
self.assertEqual(convert.user_id, self.user_sales_salesman)
self.assertEqual(convert.team_id, self.sales_team_convert)
@@ -226,7 +227,7 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon):
}).create({})
# test internals of convert wizard
- self.assertEqual(convert.opportunity_ids, self.lead_1 | leads)
+ self.assertEqual(convert.duplicated_lead_ids, self.lead_1 | leads)
self.assertEqual(convert.user_id, self.lead_1.user_id)
self.assertEqual(convert.team_id, self.lead_1.team_id)
self.assertFalse(convert.partner_id)
@@ -234,7 +235,6 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon):
self.assertEqual(convert.action, 'create')
convert.write({'user_id': self.user_sales_salesman.id})
- convert._onchange_user()
self.assertEqual(convert.user_id, self.user_sales_salesman)
self.assertEqual(convert.team_id, self.sales_team_convert)
@@ -243,7 +243,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)
@@ -257,9 +257,8 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon):
'active_ids': self.lead_1.ids,
}).create({})
self.assertEqual(convert.partner_id, self.customer)
- # TDE FIXME: should not give priority to partner email if it is void
- # self.assertEqual(convert.opportunity_ids, self.lead_1 | lead_email_from | lead_email_normalized | lead_partner)
- self.assertEqual(convert.opportunity_ids, self.env['crm.lead'])
+ # self.assertEqual(convert.duplicated_lead_ids, self.lead_1 | self.lead_email_from | self.lead_email_normalized | self.lead_partner | self.opp_lost)
+ self.assertEqual(convert.duplicated_lead_ids, self.lead_1 | self.lead_email_from | self.lead_partner | self.opp_lost)
# Check: partner fallbacks
self.lead_1.write({
@@ -273,13 +272,11 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon):
'active_ids': self.lead_1.ids,
}).create({})
self.assertEqual(convert.partner_id, self.customer)
- # TDE FIXME: CHECKME: 2917b38f28d5c2d6c53c706e613da7b8e2ad7b52
- # self.assertEqual(convert.opportunity_ids, self.lead_1 | lead_partner)
- self.assertEqual(convert.opportunity_ids, self.env['crm.lead'])
+ self.assertEqual(convert.duplicated_lead_ids, self.lead_1 | self.lead_partner)
@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({
@@ -294,9 +291,8 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon):
}).create({})
self.assertEqual(convert.partner_id, self.customer)
# TDE FIXME: should check for email_normalized -> lead_email_normalized not correctly found
- # self.assertEqual(convert.opportunity_ids, self.lead_1 | lead_email_from | lead_email_normalized | lead_partner | opp_lost)
- # TDE FIXME: add a active test context on opportunity_ids fields
- self.assertEqual(convert.with_context(active_test=False).opportunity_ids, self.lead_1 | self.lead_email_from | self.lead_partner | self.opp_lost)
+ # self.assertEqual(convert.duplicated_lead_ids, self.lead_1 | lead_email_from | lead_email_normalized | lead_partner | opp_lost)
+ self.assertEqual(convert.duplicated_lead_ids, self.lead_1 | self.lead_email_from | self.lead_partner | self.opp_lost)
convert.action_apply()
self.assertEqual(
@@ -308,6 +304,34 @@ class TestLeadConvert(crm_common.TestLeadConvertCommon):
@tagged('lead_manage')
class TestLeadConvertBatch(crm_common.TestLeadConvertMassCommon):
+ def test_initial_data(self):
+ """ Ensure initial data to avoid spaghetti test update afterwards """
+ self.assertFalse(self.lead_1.date_conversion)
+ self.assertEqual(self.lead_1.date_open, Datetime.from_string('2020-01-15 11:30:00'))
+ self.assertEqual(self.lead_1.user_id, self.user_sales_leads)
+ self.assertEqual(self.lead_1.team_id, self.sales_team_1)
+ self.assertEqual(self.lead_1.stage_id, self.stage_team1_1)
+
+ self.assertEqual(self.lead_w_partner.stage_id, self.env['crm.stage'])
+ self.assertEqual(self.lead_w_partner.user_id, self.user_sales_manager)
+ self.assertEqual(self.lead_w_partner.team_id, self.sales_team_1)
+
+ self.assertEqual(self.lead_w_partner_company.stage_id, self.stage_team1_1)
+ self.assertEqual(self.lead_w_partner_company.user_id, self.user_sales_manager)
+ self.assertEqual(self.lead_w_partner_company.team_id, self.sales_team_1)
+
+ self.assertEqual(self.lead_w_contact.stage_id, self.stage_gen_1)
+ self.assertEqual(self.lead_w_contact.user_id, self.user_sales_salesman)
+ self.assertEqual(self.lead_w_contact.team_id, self.sales_team_convert)
+
+ self.assertEqual(self.lead_w_email.stage_id, self.stage_gen_1)
+ self.assertEqual(self.lead_w_email.user_id, self.user_sales_salesman)
+ self.assertEqual(self.lead_w_email.team_id, self.sales_team_convert)
+
+ self.assertEqual(self.lead_w_email_lost.stage_id, self.stage_team1_2)
+ self.assertEqual(self.lead_w_email_lost.user_id, self.user_sales_leads)
+ self.assertEqual(self.lead_w_email_lost.team_id, self.sales_team_1)
+
@users('user_sales_manager')
def test_lead_convert_batch_internals(self):
""" Test internals of convert wizard, working in batch mode """
@@ -315,17 +339,8 @@ class TestLeadConvertBatch(crm_common.TestLeadConvertMassCommon):
self.crm_lead_dt_mock.now.return_value = date
lead_w_partner = self.lead_w_partner
- self.assertEqual(lead_w_partner.user_id, self.user_sales_manager)
- self.assertEqual(lead_w_partner.team_id, self.sales_team_1)
- self.assertEqual(lead_w_partner.stage_id, self.env['crm.stage'])
lead_w_contact = self.lead_w_contact
- self.assertEqual(lead_w_contact.user_id, self.user_sales_salesman)
- self.assertEqual(lead_w_contact.team_id, self.sales_team_convert)
- self.assertEqual(lead_w_contact.stage_id, self.stage_gen_1)
lead_w_email_lost = self.lead_w_email_lost
- self.assertEqual(lead_w_email_lost.user_id, self.user_sales_leads)
- self.assertEqual(lead_w_email_lost.team_id, self.sales_team_1)
- self.assertEqual(lead_w_email_lost.stage_id, self.stage_team1_2)
lead_w_email_lost.action_set_lost()
self.assertEqual(lead_w_email_lost.active, False)
@@ -397,10 +412,8 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon):
}).create({
'deduplicate': False,
'user_id': self.user_sales_salesman.id,
- 'force_assignation': False,
+ 'force_assignment': False,
})
- mass_convert.onchange_action()
- mass_convert._onchange_user()
# default values
self.assertEqual(mass_convert.name, 'convert')
@@ -411,7 +424,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:
@@ -427,7 +440,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
@@ -439,7 +452,7 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon):
# self.assertEqual(self.lead_w_email_lost.partner_id, self.env['res.partner'])
def test_mass_convert_deduplicate(self):
- """ Test opportunity_ids fields having another behavior in mass convert
+ """ Test duplicated_lead_ids fields having another behavior in mass convert
because why not. Its use is: among leads under convert, store those with
duplicates if deduplicate is set to True. """
lead_1_dups = self._create_duplicates(self.lead_1, create_opp=False)
@@ -456,12 +469,12 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon):
}).create({
'deduplicate': True,
})
- mass_convert._onchange_deduplicate()
self.assertEqual(mass_convert.action, 'each_exist_or_create')
self.assertEqual(mass_convert.name, 'convert')
- self.assertEqual(mass_convert.opportunity_ids, self.lead_1 | self.lead_w_partner)
+ self.assertEqual(mass_convert.lead_tomerge_ids, self.leads)
+ self.assertEqual(mass_convert.duplicated_lead_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(),
@@ -484,11 +497,11 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon):
}).create({
'deduplicate': False,
'user_ids': self.assign_users.ids,
- 'force_assignation': True,
+ 'force_assignment': True,
})
# 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/tests/test_crm_lead_merge.py b/addons/crm/tests/test_crm_lead_merge.py
index 22d103f4f1f..1bcf1c2a640 100644
--- a/addons/crm/tests/test_crm_lead_merge.py
+++ b/addons/crm/tests/test_crm_lead_merge.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.crm.tests.common import TestLeadConvertMassCommon
+from odoo.fields import Datetime
from odoo.tests.common import tagged, users
@@ -17,8 +18,9 @@ class TestLeadMerge(TestLeadConvertMassCommon):
cls.leads = cls.lead_1 + cls.lead_w_partner + cls.lead_w_contact + cls.lead_w_email + cls.lead_w_partner_company + cls.lead_w_email_lost
# reset some assigned users to test salesmen assign
(cls.lead_w_partner | cls.lead_w_email_lost).write({
- 'user_id': False
+ 'user_id': False,
})
+ cls.lead_w_partner.write({'stage_id': False})
cls.lead_w_contact.write({'description': 'lead_w_contact'})
cls.lead_w_email.write({'description': 'lead_w_email'})
@@ -27,6 +29,34 @@ class TestLeadMerge(TestLeadConvertMassCommon):
cls.assign_users = cls.user_sales_manager + cls.user_sales_leads_convert + cls.user_sales_salesman
+ def test_initial_data(self):
+ """ Ensure initial data to avoid spaghetti test update afterwards """
+ self.assertFalse(self.lead_1.date_conversion)
+ self.assertEqual(self.lead_1.date_open, Datetime.from_string('2020-01-15 11:30:00'))
+ self.assertEqual(self.lead_1.user_id, self.user_sales_leads)
+ self.assertEqual(self.lead_1.team_id, self.sales_team_1)
+ self.assertEqual(self.lead_1.stage_id, self.stage_team1_1)
+
+ self.assertEqual(self.lead_w_partner.stage_id, self.env['crm.stage'])
+ self.assertEqual(self.lead_w_partner.user_id, self.env['res.users'])
+ self.assertEqual(self.lead_w_partner.team_id, self.sales_team_1)
+
+ self.assertEqual(self.lead_w_partner_company.stage_id, self.stage_team1_1)
+ self.assertEqual(self.lead_w_partner_company.user_id, self.user_sales_manager)
+ self.assertEqual(self.lead_w_partner_company.team_id, self.sales_team_1)
+
+ self.assertEqual(self.lead_w_contact.stage_id, self.stage_gen_1)
+ self.assertEqual(self.lead_w_contact.user_id, self.user_sales_salesman)
+ self.assertEqual(self.lead_w_contact.team_id, self.sales_team_convert)
+
+ self.assertEqual(self.lead_w_email.stage_id, self.stage_gen_1)
+ self.assertEqual(self.lead_w_email.user_id, self.user_sales_salesman)
+ self.assertEqual(self.lead_w_email.team_id, self.sales_team_convert)
+
+ self.assertEqual(self.lead_w_email_lost.stage_id, self.stage_team1_2)
+ self.assertEqual(self.lead_w_email_lost.user_id, self.env['res.users'])
+ self.assertEqual(self.lead_w_email_lost.team_id, self.sales_team_1)
+
@users('user_sales_manager')
def test_lead_merge_internals(self):
""" Test internals of merge wizard. In this test leads are ordered as
@@ -37,9 +67,6 @@ class TestLeadMerge(TestLeadConvertMassCommon):
lead_w_partner --lead---seq=False
"""
# ensure initial data
- self.assertEqual(self.lead_1.user_id, self.user_sales_leads)
- self.assertEqual(self.lead_1.team_id, self.sales_team_1)
- self.assertEqual(self.lead_1.stage_id, self.stage_team1_1)
self.lead_w_partner_company.action_set_won() # won opps should be excluded
merge = self.env['crm.merge.opportunity'].with_context({
@@ -49,8 +76,6 @@ class TestLeadMerge(TestLeadConvertMassCommon):
}).create({
'user_id': self.user_sales_leads_convert.id,
})
- # check user correctly triggered its sales team
- merge._onchange_user()
self.assertEqual(merge.team_id, self.sales_team_convert)
# TDE FIXME: not sure the browse in default get of wizard intended to exlude lost, as it browse ids
@@ -96,7 +121,6 @@ class TestLeadMerge(TestLeadConvertMassCommon):
'user_id': False,
})
# TDE FIXME: see aa44700dccdc2618e0b8bc94252789264104047c -> no user, no team -> strange
- merge._onchange_user()
merge.write({'team_id': self.sales_team_convert.id})
# TDE FIXME: not sure the browse in default get of wizard intended to exlude lost, as it browse ids
@@ -113,7 +137,7 @@ class TestLeadMerge(TestLeadConvertMassCommon):
# merged opportunity has same salesman (not updated in wizard)
self.assertEqual(merge_opportunity.user_id, self.user_sales_leads)
# TDE FIXME: as same uer_id is enforced, team is updated through onchange and therefore stage
- # self.assertEqual(merge_opportunity.team_id, self.sales_team_convert)
- self.assertEqual(merge_opportunity.team_id, self.sales_team_1)
+ self.assertEqual(merge_opportunity.team_id, self.sales_team_convert)
+ # self.assertEqual(merge_opportunity.team_id, self.sales_team_1)
# TDE FIXME: BUT team_id is computed after checking stage, based on wizard's team_id
self.assertEqual(merge_opportunity.stage_id, self.stage_team_convert_1)
diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml
index 4b177ca273a..89459f4d434 100644
--- a/addons/crm/views/crm_lead_views.xml
+++ b/addons/crm/views/crm_lead_views.xml
@@ -172,7 +172,7 @@
</group>
<group>
<field name="user_id" domain="[('share', '=', False)]"
- context="{'team_id': team_id}"/>
+ context="{'default_sales_team_id': team_id}"/>
<field name="team_id" widget="selection"
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]"/>
<field name="type" invisible="1"/>
diff --git a/addons/crm/views/crm_team_views.xml b/addons/crm/views/crm_team_views.xml
index 038b69028b6..90301bc7385 100644
--- a/addons/crm/views/crm_team_views.xml
+++ b/addons/crm/views/crm_team_views.xml
@@ -191,11 +191,11 @@
</xpath>
<xpath expr="//t[@name='first_options']" position="after">
- <div class="row" t-if="record.unassigned_leads_count.raw_value">
+ <div class="row" t-if="record.lead_unassigned_count.raw_value">
<div class="col-8">
<a name="%(crm_case_form_view_salesteams_lead)d" type="action" context="{'search_default_unassigned_leads': 1}">
- <field name="unassigned_leads_count"/>
- <t t-if="record.unassigned_leads_count.raw_value == 1">Unassigned Lead</t>
+ <field name="lead_unassigned_count"/>
+ <t t-if="record.lead_unassigned_count.raw_value == 1">Unassigned Lead</t>
<t t-else="">Unassigned Leads</t>
</a>
</div>
@@ -212,16 +212,16 @@
<field name="opportunities_amount" widget="monetary"/>
</div>
</div>
- <div class="row" t-if="record.overdue_opportunities_count.raw_value">
+ <div class="row" t-if="record.opportunities_overdue_count.raw_value">
<div class="col-8">
<a name="%(crm_lead_action_team_overdue_opportunity)d" type="action">
- <field name="overdue_opportunities_count"/>
- <t t-if="record.overdue_opportunities_count.raw_value == 1">Overdue Opportunity</t>
+ <field name="opportunities_overdue_count"/>
+ <t t-if="record.opportunities_overdue_count.raw_value == 1">Overdue Opportunity</t>
<t t-else="">Overdue Opportunities</t>
</a>
</div>
<div class="col-4 text-right text-truncate">
- <field name="overdue_opportunities_amount" widget="monetary"/>
+ <field name="opportunities_overdue_amount" widget="monetary"/>
</div>
</div>
</xpath>
diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py
index e4e101298ec..2d7b8b77489 100644
--- a/addons/crm/wizard/crm_lead_to_opportunity.py
+++ b/addons/crm/wizard/crm_lead_to_opportunity.py
@@ -12,148 +12,159 @@ class Lead2OpportunityPartner(models.TransientModel):
@api.model
def default_get(self, fields):
- """ Default get for name, opportunity_ids.
- If there is an exisitng partner link to the lead, find all existing
- opportunities links with this partner to merge all information together
- """
+
+ """ Allow support of active_id / active_model instead of jut default_lead_id
+ to ease window action definitions, and be backward compatible. """
result = super(Lead2OpportunityPartner, self).default_get(fields)
- if self._context.get('active_id'):
- tomerge = {int(self._context['active_id'])}
-
- lead = self.env['crm.lead'].browse(self._context['active_id'])
- result['lead_id'] = lead.id
-
- partner_id = 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)
-
- if 'action' in fields and not result.get('action'):
- result['action'] = 'exist' if partner_id else 'create'
- if 'partner_id' in fields:
- 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:
- result['opportunity_ids'] = list(tomerge)
- if lead.user_id:
- 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:
- result['action'] = 'nothing'
+ if not result.get('lead_id') and self.env.context.get('active_id'):
+ result['lead_id'] = self.env.context.get('active_id')
return result
name = fields.Selection([
('convert', 'Convert to opportunity'),
('merge', 'Merge with existing opportunities')
- ], 'Conversion Action', required=True)
+ ], 'Conversion Action', compute='_compute_name', readonly=False, store=True)
action = fields.Selection([
('create', 'Create a new customer'),
('exist', 'Link to an existing customer'),
('nothing', 'Do not link to a customer')
- ], string='Related Customer', required=True)
- lead_id = fields.Many2one('crm.lead', "Associated Lead")
- opportunity_ids = fields.Many2many('crm.lead', string='Opportunities')
- partner_id = fields.Many2one('res.partner', 'Customer')
- user_id = fields.Many2one('res.users', 'Salesperson')
- team_id = fields.Many2one('crm.team', 'Sales Team')
-
- @api.onchange('action')
- def onchange_action(self):
- if self.action == 'exist':
- self.partner_id = self.lead_id._find_matching_partner()
- else:
- self.partner_id = False
+ ], string='Related Customer', compute='_compute_action', readonly=False, store=True)
+ lead_id = fields.Many2one('crm.lead', 'Associated Lead', required=True)
+ duplicated_lead_ids = fields.Many2many(
+ 'crm.lead', string='Opportunities', context={'active_test': False},
+ compute='_compute_duplicated_lead_ids', readonly=False, store=True)
+ partner_id = fields.Many2one(
+ 'res.partner', 'Customer',
+ compute='_compute_partner_id', readonly=False, store=True)
+ user_id = fields.Many2one(
+ 'res.users', 'Salesperson',
+ compute='_compute_user_id', readonly=False, store=True)
+ team_id = fields.Many2one(
+ 'crm.team', 'Sales Team',
+ compute='_compute_team_id', readonly=False, store=True)
+ force_assignment = fields.Boolean(
+ 'Force assignment', default=True,
+ help='If checked, forces salesman to be updated on updated opportunities even if already set.')
+
+ @api.depends('duplicated_lead_ids')
+ def _compute_name(self):
+ for convert in self:
+ convert.name = 'merge' if convert.duplicated_lead_ids and len(convert.duplicated_lead_ids) >= 2 else 'convert'
+
+ @api.depends('lead_id')
+ def _compute_action(self):
+ for convert in self:
+ if not convert.lead_id:
+ convert.action = 'nothing'
+ else:
+ partner = convert.lead_id._find_matching_partner()
+ if partner:
+ convert.action = 'exist'
+ elif convert.lead_id.contact_name:
+ convert.action = 'create'
+ else:
+ convert.action = 'nothing'
+
+ @api.depends('lead_id', 'partner_id')
+ def _compute_duplicated_lead_ids(self):
+ for convert in self:
+ if not convert.lead_id:
+ convert.duplicated_lead_ids = False
+ continue
+ convert.duplicated_lead_ids = self.env['crm.lead']._get_lead_duplicates(
+ convert.partner_id,
+ convert.lead_id.partner_id.email if convert.lead_id.partner_id.email else convert.lead_id.email_from,
+ include_lost=True).ids
+
+ @api.depends('action')
+ def _compute_partner_id(self):
+ for convert in self:
+ if convert.action == 'exist':
+ convert.partner_id = convert.lead_id._find_matching_partner()
+ else:
+ convert.partner_id = False
+
+ @api.depends('lead_id')
+ def _compute_user_id(self):
+ for convert in self:
+ convert.user_id = convert.lead_id.user_id if convert.lead_id.user_id else False
- @api.onchange('user_id')
- def _onchange_user(self):
+ @api.depends('user_id')
+ def _compute_team_id(self):
""" When changing the user, also set a team_id or restrict team id
- to the ones user_id is member of.
- """
- if self.user_id:
- if self.team_id:
- user_in_team = self.env['crm.team'].search_count([('id', '=', self.team_id.id), '|', ('user_id', '=', self.user_id.id), ('member_ids', '=', self.user_id.id)])
- else:
- user_in_team = False
- if not user_in_team:
- 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)
+ to the ones user_id is member of. """
+ for convert in self:
+ # setting user as void should not trigger a new team computation
+ if not convert.user_id:
+ continue
+ 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)
+ convert.team_id = team.id
- # 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):
- self.ensure_one()
+ def action_apply(self):
+ if self.name == 'merge':
+ result_opportunity = self._action_merge()
+ else:
+ result_opportunity = self._action_convert()
- res = False
+ return result_opportunity.redirect_lead_opportunity_view()
- 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)
+ def _action_merge(self):
+ to_merge = self.duplicated_lead_ids
+ result_opportunity = to_merge.merge_opportunity(auto_unlink=False)
+ if not result_opportunity.active:
+ result_opportunity.write({'active': True, 'activity_type_id': False, 'lost_reason': False})
- 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)
+ if result_opportunity.type == "lead":
+ self._convert_and_allocate(result_opportunity, [self.user_id.id], team_id=self.team_id.id)
+ else:
+ if not result_opportunity.user_id or self.force_assignment:
+ result_opportunity.write({
+ 'user_id': self.user_id.id,
+ 'team_id': self.team_id.id,
+ })
+ (to_merge - result_opportunity).unlink()
+ 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()
+
+ for lead in leads:
+ if lead.active and self.action != 'nothing':
+ self._convert_handle_partner(
+ lead, self.action, self.partner_id.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'):
+ if not self.force_assignment:
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')))
-
- 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)
+ leads_to_allocate.handle_salesmen_assignment(user_ids, team_id=team_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..f33abf83ea9 100644
--- a/addons/crm/wizard/crm_lead_to_opportunity_mass.py
+++ b/addons/crm/wizard/crm_lead_to_opportunity_mass.py
@@ -5,7 +5,6 @@ from odoo import api, fields, models
class Lead2OpportunityMassConvert(models.TransientModel):
-
_name = 'crm.lead2opportunity.partner.mass'
_description = 'Convert Lead to Opportunity (in mass)'
_inherit = 'crm.lead2opportunity.partner'
@@ -13,70 +12,87 @@ class Lead2OpportunityMassConvert(models.TransientModel):
@api.model
def default_get(self, fields):
res = super(Lead2OpportunityMassConvert, self).default_get(fields)
- if 'partner_id' in fields: # avoid forcing the partner of the first lead as default
- res['partner_id'] = False
- if 'action' in fields:
- res['action'] = 'each_exist_or_create'
- if 'name' in fields:
- res['name'] = 'convert'
- if 'opportunity_ids' in fields:
- res['opportunity_ids'] = False
+ if 'lead_tomerge_ids' in fields and not res.get('lead_tomerge_ids'):
+ res['lead_tomerge_ids'] = self.env.context.get('active_ids', [])
return res
+ lead_id = fields.Many2one(required=False)
+ lead_tomerge_ids = fields.Many2many(
+ 'crm.lead', 'crm_convert_lead_mass_lead_rel',
+ string='Active Leads', context={'active_test': False})
user_ids = fields.Many2many('res.users', string='Salesmen')
- team_id = fields.Many2one('crm.team', 'Sales Team', index=True)
deduplicate = fields.Boolean('Apply deduplication', default=True, help='Merge with existing leads/opportunities of each partner')
action = fields.Selection(selection_add=[
('each_exist_or_create', 'Use existing partner or create'),
- ], string='Related Customer', required=True)
- force_assignation = fields.Boolean('Force assignment', help='If unchecked, this will leave the salesman of duplicated opportunities')
+ ], string='Related Customer')
+ force_assignment = fields.Boolean(default=False)
+
+ @api.depends('duplicated_lead_ids')
+ def _compute_name(self):
+ for convert in self:
+ convert.name = 'convert'
- @api.onchange('action')
- def _onchange_action(self):
- if self.action != 'exist':
- self.partner_id = False
+ @api.depends('lead_tomerge_ids')
+ def _compute_action(self):
+ for convert in self:
+ convert.action = 'each_exist_or_create'
- @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]
- 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)
- if len(duplicated_leads) > 1:
- partners_duplicated_leads.setdefault((partner_id, email), []).extend(duplicated_leads)
+ @api.depends('lead_tomerge_ids')
+ def _compute_partner_id(self):
+ for convert in self:
+ convert.partner_id = False
- leads_with_duplicates = []
- for lead in active_leads:
- lead_tuple = (lead.partner_id.id, lead.partner_id.email if lead.partner_id else lead.email_from)
- if len(partners_duplicated_leads.get(lead_tuple, [])) > 1:
- leads_with_duplicates.append(lead.id)
+ @api.depends('user_ids')
+ def _compute_team_id(self):
+ """ When changing the user, also set a team_id or restrict team id
+ to the ones user_id is member of. """
+ for convert in self:
+ # setting user as void should not trigger a new team computation
+ if not convert.user_id and not convert.user_ids and convert.team_id:
+ continue
+ 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)
+ convert.team_id = team.id
- self.opportunity_ids = self.env['crm.lead'].browse(leads_with_duplicates)
+ @api.depends('lead_tomerge_ids')
+ def _compute_duplicated_lead_ids(self):
+ for convert in self:
+ duplicated = self.env['crm.lead']
+ for lead in convert.lead_tomerge_ids:
+ duplicated_leads = self.env['crm.lead']._get_lead_duplicates(
+ partner=lead.partner_id,
+ email=lead.partner_id and lead.partner_id.email or lead.email_from,
+ include_lost=False)
+ if len(duplicated_leads) > 1:
+ duplicated += lead
+ convert.duplicated_lead_ids = duplicated.ids
- 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()
remaining_lead_ids = set()
- lead_selected = self._context.get('active_ids', [])
- 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)
+ for lead in self.lead_tomerge_ids:
+ if lead not in merged_lead_ids:
+ 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)
@@ -85,5 +101,10 @@ class Lead2OpportunityMassConvert(models.TransientModel):
active_ids = (active_ids - merged_lead_ids) | remaining_lead_ids
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()
+ return self.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..d83c27b045a 100644
--- a/addons/crm/wizard/crm_lead_to_opportunity_mass_views.xml
+++ b/addons/crm/wizard/crm_lead_to_opportunity_mass_views.xml
@@ -5,6 +5,7 @@
<field name="model">crm.lead2opportunity.partner.mass</field>
<field name="arch" type="xml">
<form string="Convert to Opportunity">
+ <field name="lead_tomerge_ids" invisible="1"/>
<separator string="Conversion Options"/>
<group>
<field name="name" class="oe_inline" widget="radio"/>
@@ -13,11 +14,11 @@
<group string="Assign these opportunities to">
<field name="team_id" kanban_view_ref="%(sales_team.crm_team_view_kanban)s"/>
<field name="user_ids" widget="many2many_tags" domain="[('share', '=', False)]"/>
- <field name="force_assignation"/>
+ <field name="force_assignment"/>
</group>
- <label for="opportunity_ids" string="Leads with existing duplicates (for information)" help="Leads that you selected that have duplicates. If the list is empty, it means that no duplicates were found" attrs="{'invisible': [('deduplicate', '=', False)]}"/>
+ <label for="duplicated_lead_ids" string="Leads with existing duplicates (for information)" help="Leads that you selected that have duplicates. If the list is empty, it means that no duplicates were found" attrs="{'invisible': [('deduplicate', '=', False)]}"/>
<group attrs="{'invisible': [('deduplicate', '=', False)]}">
- <field name="opportunity_ids" colspan="4" nolabel="1" readonly="1">
+ <field name="duplicated_lead_ids" colspan="4" nolabel="1" readonly="1">
<tree create="false" delete="false">
<field name="create_date" widget="date"/>
<field name="name"/>
@@ -42,7 +43,7 @@
</group>
</group>
<footer>
- <button name="mass_convert" string="Convert to Opportunities" type="object" class="btn-primary"/>
+ <button string="Convert to Opportunities" name="action_mass_convert" type="object" class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
@@ -55,7 +56,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"
/>
</odoo>
diff --git a/addons/crm/wizard/crm_lead_to_opportunity_views.xml b/addons/crm/wizard/crm_lead_to_opportunity_views.xml
index 6871a97baca..4534e4352cd 100644
--- a/addons/crm/wizard/crm_lead_to_opportunity_views.xml
+++ b/addons/crm/wizard/crm_lead_to_opportunity_views.xml
@@ -13,7 +13,7 @@
<field name="team_id" widget="selection"/>
</group>
<group string="Opportunities" attrs="{'invisible': [('name', '!=', 'merge')]}">
- <field name="opportunity_ids" nolabel="1">
+ <field name="duplicated_lead_ids" nolabel="1">
<tree>
<field name="create_date" widget="date"/>
<field name="name"/>
diff --git a/addons/crm/wizard/crm_merge_opportunities.py b/addons/crm/wizard/crm_merge_opportunities.py
index 3693df8c0e4..7307bd37554 100644
--- a/addons/crm/wizard/crm_merge_opportunities.py
+++ b/addons/crm/wizard/crm_merge_opportunities.py
@@ -34,22 +34,23 @@ class MergeOpportunity(models.TransientModel):
opportunity_ids = fields.Many2many('crm.lead', 'merge_opportunity_rel', 'merge_id', 'opportunity_id', string='Leads/Opportunities')
user_id = fields.Many2one('res.users', 'Salesperson', index=True)
- team_id = fields.Many2one('crm.team', 'Sales Team', index=True)
+ team_id = fields.Many2one(
+ 'crm.team', 'Sales Team', index=True,
+ compute='_compute_team_id', readonly=False, store=True)
def action_merge(self):
self.ensure_one()
merge_opportunity = self.opportunity_ids.merge_opportunity(self.user_id.id, self.team_id.id)
return merge_opportunity.redirect_lead_opportunity_view()
- @api.onchange('user_id')
- def _onchange_user(self):
+ @api.depends('user_id')
+ def _compute_team_id(self):
""" When changing the user, also set a team_id or restrict team id
to the ones user_id is member of. """
- team_id = False
- if self.user_id:
- user_in_team = False
- if self.team_id:
- user_in_team = self.env['crm.team'].search_count([('id', '=', self.team_id.id), '|', ('user_id', '=', self.user_id.id), ('member_ids', '=', self.user_id.id)])
- if not user_in_team:
- team_id = self.env['crm.team'].search(['|', ('user_id', '=', self.user_id.id), ('member_ids', '=', self.user_id.id)], limit=1)
- self.team_id = team_id
+ for wizard in self:
+ if wizard.user_id:
+ user_in_team = False
+ if wizard.team_id:
+ user_in_team = wizard.env['crm.team'].search_count([('id', '=', wizard.team_id.id), '|', ('user_id', '=', wizard.user_id.id), ('member_ids', '=', wizard.user_id.id)])
+ if not user_in_team:
+ wizard.team_id = wizard.env['crm.team'].search(['|', ('user_id', '=', wizard.user_id.id), ('member_ids', '=', wizard.user_id.id)], limit=1)
diff --git a/addons/crm_livechat/models/mail_channel.py b/addons/crm_livechat/models/mail_channel.py
index 6df38d2317c..f608bd1a0dd 100644
--- a/addons/crm_livechat/models/mail_channel.py
+++ b/addons/crm_livechat/models/mail_channel.py
@@ -31,7 +31,7 @@ class MailChannel(models.Model):
for message in self.channel_message_ids.sorted('id')
)
utm_source = self.env.ref('crm_livechat.utm_source_livechat', raise_if_not_found=False)
- lead = self.env['crm.lead'].create({
+ return self.env['crm.lead'].create({
'name': html2plaintext(key[5:]),
'partner_id': channel_partners.partner_id.id,
'user_id': None,
@@ -40,5 +40,3 @@ class MailChannel(models.Model):
'referred': partner.name,
'source_id': utm_source and utm_source.id,
})
- lead._onchange_partner_id()
- return lead
diff --git a/addons/sale_crm/wizard/crm_opportunity_to_quotation.py b/addons/sale_crm/wizard/crm_opportunity_to_quotation.py
index 6ffa8860153..aef9a259ff8 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,5 @@ 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._onchange_partner_id()
+ self.lead_id.handle_partner_assignment(force_partner_id=self.partner_id.id, create_missing=(self.action == 'create'))
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..817169bb56e 100644
--- a/addons/website_crm_partner_assign/models/crm_lead.py
+++ b/addons/website_crm_partner_assign/models/crm_lead.py
@@ -9,6 +9,7 @@ from odoo.exceptions import AccessDenied, AccessError
class CrmLead(models.Model):
_inherit = "crm.lead"
+
partner_latitude = fields.Float('Geo Latitude', digits=(16, 5))
partner_longitude = fields.Float('Geo Longitude', digits=(16, 5))
partner_assigned_id = fields.Many2one('res.partner', 'Assigned Partner', tracking=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", help="Partner this case has been forwarded/assigned to.", index=True)
@@ -18,22 +19,22 @@ class CrmLead(models.Model):
'lead_id',
'partner_id',
string='Partner not interested')
- date_assign = fields.Date('Partner Assignment Date', help="Last date this case was forwarded/assigned to a partner")
+ date_partner_assign = fields.Date(
+ 'Partner Assignment Date', compute='_compute_date_partner_assign',
+ copy=True, readonly=False, store=True,
+ help="Last date this case was forwarded/assigned to a partner")
def _merge_data(self, fields):
fields += ['partner_latitude', 'partner_longitude', 'partner_assigned_id', 'date_assign']
return super(CrmLead, self)._merge_data(fields)
@api.onchange("partner_assigned_id")
- def onchange_assign_id(self):
- """This function updates the "assignation date" automatically, when manually assign a partner in the geo assign tab
- """
- partner_assigned = self.partner_assigned_id
- if not partner_assigned:
- self.date_assign = False
- else:
- self.date_assign = fields.Date.context_today(self)
- self.user_id = partner_assigned.user_id
+ def _compute_date_partner_assign(self):
+ for lead in self:
+ if not lead.partner_assigned_id:
+ lead.date_partner_assign = False
+ else:
+ lead.date_assign = fields.Date.context_today(lead)
def assign_salesman_of_assigned_partner(self):
salesmans_leads = {}
@@ -64,9 +65,8 @@ 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)
- values = {'date_assign': fields.Date.context_today(lead), 'partner_assigned_id': partner_id}
- lead.write(values)
+ lead.handle_salesmen_assignment(partner.user_id.ids, team_id=partner.team_id.id)
+ lead.write({'partner_assigned_id': partner_id})
return res
def assign_geo_localize(self, latitude=False, longitude=False):
diff --git a/addons/website_crm_partner_assign/models/res_partner.py b/addons/website_crm_partner_assign/models/res_partner.py
index 93c45db11b4..bc4a0202d95 100644
--- a/addons/website_crm_partner_assign/models/res_partner.py
+++ b/addons/website_crm_partner_assign/models/res_partner.py
@@ -37,7 +37,9 @@ class ResPartnerActivation(models.Model):
class ResPartner(models.Model):
_inherit = "res.partner"
- partner_weight = fields.Integer('Level Weight', default=0, tracking=True,
+ partner_weight = fields.Integer(
+ 'Level Weight', compute='_compute_partner_weight',
+ copy=True, readonly=False, store=True, tracking=True,
help="This should be a numerical value greater than 0 which will decide the contention for this partner to take this lead/opportunity.")
grade_id = fields.Many2one('res.partner.grade', 'Partner Level', tracking=True)
grade_sequence = fields.Integer(related='grade_id.sequence', readonly=True, store=True)
@@ -60,7 +62,7 @@ class ResPartner(models.Model):
for partner in self:
partner.implemented_count = len(partner.implemented_partner_ids.filtered('website_published'))
- @api.onchange('grade_id')
- def _onchange_grade_id(self):
- grade = self.grade_id
- self.partner_weight = grade.partner_weight if grade else 0
+ @api.depends('grade_id.partner_weight')
+ def _compute_partner_weight(self):
+ for partner in self:
+ partner.partner_weight = partner.grade_id.partner_weight if partner.grade_id else 0
diff --git a/addons/website_crm_partner_assign/views/crm_lead_views.xml b/addons/website_crm_partner_assign/views/crm_lead_views.xml
index 229290f92e7..161519522f2 100644
--- a/addons/website_crm_partner_assign/views/crm_lead_views.xml
+++ b/addons/website_crm_partner_assign/views/crm_lead_views.xml
@@ -39,7 +39,7 @@
<field name="arch" type="xml">
<field name="user_id" position="before">
<field name="partner_assigned_id" optional="show"/>
- <field name="date_assign" invisible="1"/>
+ <field name="date_partner_assign" invisible="1"/>
</field>
</field>
</record>