From cd72fd9a3f0519fe8a36c1771f52bd8b2fd8a032 Mon Sep 17 00:00:00 2001 From: David Beguin Date: Fri, 13 Sep 2019 09:51:43 +0000 Subject: [PATCH 1/8] [FIX] crm : fix and add data for pls settings and use null value instead of empty for phone and email state fields This commit applies multiples things: - Fix PLS settings that were not loaded correctly (was not using technical field names) - Add data for PLS settings to check all optional fields and set the start date. - use null value instead of empty for phone and email state fields to lessen the DB storage - set pls start date required Task ID: 2044539 PR #37413 --- addons/crm/data/crm_lead_prediction_data.xml | 8 ++++++++ addons/crm/models/crm_lead.py | 18 ++++++++---------- addons/crm/models/res_config_settings.py | 7 ++++--- addons/crm/views/res_config_settings_views.xml | 2 +- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/addons/crm/data/crm_lead_prediction_data.xml b/addons/crm/data/crm_lead_prediction_data.xml index f390b6e6138..42a2e8e34d9 100644 --- a/addons/crm/data/crm_lead_prediction_data.xml +++ b/addons/crm/data/crm_lead_prediction_data.xml @@ -20,6 +20,14 @@ + + crm.pls_fields + + + + crm.pls_start_date + + diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index abe9e285c82..3a2b7acdde4 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -110,12 +110,10 @@ class Lead(models.Model): is_automated_probability = fields.Boolean('Is automated probability?', compute="_compute_is_automated_probability") phone_state = fields.Selection([ ('correct', 'Correct'), - ('incorrect', 'Incorrect'), - ('empty', 'Empty')], string='Phone Quality', default="empty", compute="_compute_phone_state", store=True) + ('incorrect', 'Incorrect')], string='Phone Quality', compute="_compute_phone_state", store=True) email_state = fields.Selection([ ('correct', 'Correct'), - ('incorrect', 'Incorrect'), - ('empty', 'Empty')], string='Email Quality', default="empty", compute="_compute_email_state", store=True) + ('incorrect', 'Incorrect')], string='Email Quality', compute="_compute_email_state", store=True) # Only used for type opportunity planned_revenue = fields.Monetary('Expected Revenue', currency_field='company_currency', tracking=True) @@ -222,7 +220,7 @@ class Lead(models.Model): @api.depends('phone', 'country_id.code') def _compute_phone_state(self): for lead in self: - phone_status = 'empty' + phone_status = False if lead.phone: country_code = lead.country_id.code if lead.country_id and lead.country_id.code else None try: @@ -235,7 +233,7 @@ class Lead(models.Model): @api.depends('email_from') def _compute_email_state(self): for lead in self: - email_state = 'empty' + email_state = False if lead.email_from: email_state = 'incorrect' for email in email_split(lead.email_from): @@ -1464,7 +1462,7 @@ class Lead(models.Model): that are defined on the model. """ pls_fields_config = self.env['ir.config_parameter'].sudo().get_param('crm.pls_fields') pls_fields = pls_fields_config.split(',') if pls_fields_config else [] - pls_safe_fields = [field for field in pls_fields if field in self._fields] + pls_safe_fields = [field for field in pls_fields if field in self._fields.keys()] return pls_safe_fields # Rebuild Frequency Table Tools @@ -1525,7 +1523,7 @@ class Lead(models.Model): lost = result['count'] if result['probability'] == 0 else 0 for field in fields: value = result[field] - if value: + if value or field in ('email_state', 'phone_state'): if field == 'stage_id': if won: # increment all stages if won stages_to_increment = [stage['id'] for stage in stage_ids] @@ -1621,7 +1619,7 @@ class Lead(models.Model): if field == 'team_id': # ignore team_id as stored separately in leads_values_dict[lead_id][team_id] continue value = lead[field] - if value: + if value or field in ('email_state', 'phone_state'): lead_values.append((field, value)) leads_values_dict[lead['id']] = {'values': lead_values, 'team_id': lead['team_id']} @@ -1636,7 +1634,7 @@ class Lead(models.Model): if field == 'team_id': # ignore team_id as stored separately in leads_values_dict[lead_id][team_id] continue value = lead[field].id if isinstance(lead[field], models.BaseModel) else lead[field] - if value: + if value or field in ('email_state', 'phone_state'): lead_values.append((field, value)) for tag in lead.tag_ids: lead_values.append(('tag_id', tag.id)) diff --git a/addons/crm/models/res_config_settings.py b/addons/crm/models/res_config_settings.py index deb4fc322a9..07b91adef98 100644 --- a/addons/crm/models/res_config_settings.py +++ b/addons/crm/models/res_config_settings.py @@ -16,7 +16,7 @@ class ResConfigSettings(models.TransientModel): module_crm_iap_lead_enrich = fields.Boolean("Enrich your leads automatically with company data based on their email address.") lead_mining_in_pipeline = fields.Boolean("Create a lead mining request directly from the opportunity pipeline.", config_parameter='crm.lead_mining_in_pipeline') predictive_lead_scoring_start_date = fields.Date(string='Lead Scoring Starting Date', compute="_compute_pls_start_date", inverse="_inverse_pls_start_date_str") - predictive_lead_scoring_start_date_str = fields.Char(string='Lead Scoring Starting Date in String', default=date.today().strftime('%Y-%m-%d'), config_parameter='crm.pls_start_date') + predictive_lead_scoring_start_date_str = fields.Char(string='Lead Scoring Starting Date in String', config_parameter='crm.pls_start_date') predictive_lead_scoring_fields = fields.Many2many('crm.lead.scoring.frequency.field', string='Lead Scoring Frequency Fields', compute="_compute_pls_fields", inverse="_inverse_pls_fields_str") predictive_lead_scoring_fields_str = fields.Char(string='Lead Scoring Frequency Fields in String', config_parameter='crm.pls_fields') @@ -39,7 +39,8 @@ class ResConfigSettings(models.TransientModel): for setting in self: if setting.predictive_lead_scoring_fields_str: names = setting.predictive_lead_scoring_fields_str.split(',') - setting.predictive_lead_scoring_fields = self.env['crm.lead.scoring.frequency.field'].search([('name', 'in', names)]) + fields = self.env['ir.model.fields'].search([('name', 'in', names), ('model', '=', 'crm.lead')]) + setting.predictive_lead_scoring_fields = self.env['crm.lead.scoring.frequency.field'].search([('field_id', 'in', fields.ids)]) else: setting.predictive_lead_scoring_fields = None @@ -48,7 +49,7 @@ class ResConfigSettings(models.TransientModel): we store the fields with a comma separated string into a Char config field """ for setting in self: if setting.predictive_lead_scoring_fields: - setting.predictive_lead_scoring_fields_str = ','.join(setting.predictive_lead_scoring_fields.mapped('name')) + setting.predictive_lead_scoring_fields_str = ','.join(setting.predictive_lead_scoring_fields.mapped('field_id.name')) else: setting.predictive_lead_scoring_fields_str = '' diff --git a/addons/crm/views/res_config_settings_views.xml b/addons/crm/views/res_config_settings_views.xml index baa8dc3d925..4431b46aae6 100644 --- a/addons/crm/views/res_config_settings_views.xml +++ b/addons/crm/views/res_config_settings_views.xml @@ -66,7 +66,7 @@
Consider leads created as of the - +
From 25a299dfc1c371d19b1700337ea378c16bf3f16b Mon Sep 17 00:00:00 2001 From: David Beguin Date: Tue, 17 Sep 2019 11:22:53 +0000 Subject: [PATCH 2/8] [FIX] tools, crm : remove Flanker for email validation Since it appears that email validation feature from Flanker v0.9.0 is completelly broken (the lib explodes when calling address.validate_address('some@address.xxx')), Flanker is then removed and email validation will be done only using the email_normalize method. Task ID: 2044539 PR #37413 --- addons/crm/models/crm_lead.py | 2 +- odoo/tools/mail.py | 35 ----------------------------------- 2 files changed, 1 insertion(+), 36 deletions(-) diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index 3a2b7acdde4..e57d4a771f7 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -237,7 +237,7 @@ class Lead(models.Model): if lead.email_from: email_state = 'incorrect' for email in email_split(lead.email_from): - if tools.email_validate(email): + if tools.email_normalize(email): email_state = 'correct' break lead.email_state = email_state diff --git a/odoo/tools/mail.py b/odoo/tools/mail.py index 50ccd6b0375..f9a5cfa67cf 100644 --- a/odoo/tools/mail.py +++ b/odoo/tools/mail.py @@ -20,31 +20,6 @@ from odoo.tools import misc _logger = logging.getLogger(__name__) -# Optional Flanker dependency for email validation. -_flanker_warning = False -try: - from flanker.addresslib import address - if hasattr(address, 'six'): - # Python 3 supported - def checkmail(mail): - return bool(address.validate_address(mail)) - else: - def checkmail(mail): - global _flanker_warning - if not _flanker_warning: - _logger.info("Flanker version 0.9 or greater required for Python 3 compatibility") - _flanker_warning = True - return True - -except ImportError: - _logger.info('The flanker Python module is not installed, so email validation with flanker is unavailable') - def checkmail(mail): - global _flanker_warning - if not _flanker_warning: - _logger.info('The flanker Python module is not installed, so email validation with flanker is unavailable') - _flanker_warning = True - return True - #---------------------------------------------------------- # HTML Sanitizer #---------------------------------------------------------- @@ -531,16 +506,6 @@ def email_normalize(text): return False return emails[0].lower() -def email_validate(email): - """ - Check is the email is valid using email normalization + optional Flanker module. - If Flanker is installed, it will check if email is valid (checking DNS and other stuff). - If Flanker is not installed, this method only normalizes the email - :param text: string containing the email to validate - :return: normalized email if mail is valid or False - """ - return email_normalize(email) if checkmail(email) else False - def email_escape_char(email_address): """ Escape problematic characters in the given email address string""" return email_address.replace('\\', '\\\\').replace('%', '\\%').replace('_', '\\_') From da87a63d63f0d3f7ae2edebb361bc25fbff21504 Mon Sep 17 00:00:00 2001 From: David Beguin Date: Mon, 23 Sep 2019 12:07:22 +0000 Subject: [PATCH 3/8] [FIX-IMP] crm : remove probability default value and modify default pls start date This commit - removes probability default value in order to activate Bayes predictive lead scoring by default - set the default pls start date to today - 8 days to include all demo data in PLS computation Task ID: 2044539 PR #37413 --- addons/crm/data/crm_lead_prediction_data.xml | 2 +- addons/crm/models/crm_lead.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/addons/crm/data/crm_lead_prediction_data.xml b/addons/crm/data/crm_lead_prediction_data.xml index 42a2e8e34d9..a437956d0fc 100644 --- a/addons/crm/data/crm_lead_prediction_data.xml +++ b/addons/crm/data/crm_lead_prediction_data.xml @@ -26,7 +26,7 @@
crm.pls_start_date - + diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index e57d4a771f7..bdb39032b89 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -105,7 +105,7 @@ class Lead(models.Model): date_conversion = fields.Datetime('Conversion Date', readonly=True) # Probability - Only used for type opportunity - probability = fields.Float('Probability', group_operator="avg", default=10.0) + probability = fields.Float('Probability', group_operator="avg") automated_probability = fields.Float('Automated Probability', readonly=True) is_automated_probability = fields.Boolean('Is automated probability?', compute="_compute_is_automated_probability") phone_state = fields.Selection([ @@ -1426,9 +1426,11 @@ class Lead(models.Model): # Recompute all the leads. Won : probability = 100 | Lost : probability = 0 or inactive # Here, inactive won't be returned anyway + # Get also all the lead without probability --> These are the new leads. Activate auto probability on them. pls_start_date = self._pls_get_safe_start_date() if pls_start_date: - pending_lead_domain = [('probability', '<', 100), ('probability', '>', 0), ('create_date', '>', pls_start_date)] + pending_lead_domain = ['&', ('create_date', '>', pls_start_date), + '|', ('probability', '=', False), '&', ('probability', '<', 100), ('probability', '>', 0)] leads_to_update = self.env['crm.lead'].search(pending_lead_domain) lead_probabilities = leads_to_update._pls_get_naive_bayes_probabilities(batch_mode=True) @@ -1598,7 +1600,7 @@ class Lead(models.Model): self.flush(['probability']) query = """SELECT id, %s FROM crm_lead l - WHERE probability > 0 AND probability < 100 AND active = True AND id in %%s order by team_id asc""" + WHERE ((probability > 0 AND probability < 100) OR probability is null) AND active = True AND id in %%s order by team_id asc""" query = sql.SQL(query % str_fields).format(*args) self._cr.execute(query, [tuple(self.ids)]) @@ -1608,7 +1610,7 @@ class Lead(models.Model): FROM crm_lead l LEFT JOIN crm_lead_tag_rel rel ON l.id = rel.lead_id LEFT JOIN crm_lead_tag t ON rel.tag_id = t.id - WHERE l.probability > 0 AND l.probability < 100 AND l.active = True AND l.id in %s order by l.team_id asc""" + WHERE ((l.probability > 0 AND l.probability < 100) OR l.probability is null) AND l.active = True AND l.id in %s order by l.team_id asc""" self._cr.execute(query, [tuple(self.ids)]) tag_results = self._cr.dictfetchall() From c9317a3f47ada89e34101b4045d84544a4fc4c0b Mon Sep 17 00:00:00 2001 From: David Beguin Date: Mon, 23 Sep 2019 14:10:38 +0000 Subject: [PATCH 4/8] [FIX] crm : compute PLS probability only if stage_id is set As PLS is based on stage flow, probability will return 0 if stage_id is not set. Task ID: 2044539 PR #37413 --- addons/crm/models/crm_lead.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index bdb39032b89..ab72b177edc 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -1372,6 +1372,12 @@ class Lead(models.Model): save_team_id = None p_won, p_lost = 1, 1 for lead_id, lead_values in leads_values_dict.items(): + # if stage_id is null, return 0 and bypass computation + lead_fields = [value[0] for value in lead_values.get('values', [])] + if not 'stage_id' in lead_fields: + lead_probabilities[lead_id] = 0 + continue + lead_team_id = lead_values['team_id'] if lead_values['team_id'] else 0 # team_id = None -> Convert to 0 lead_team_id = lead_team_id if lead_team_id in result else -1 # team_id not in frequency Table -> convert to -1 if lead_team_id != save_team_id: @@ -1429,7 +1435,7 @@ class Lead(models.Model): # Get also all the lead without probability --> These are the new leads. Activate auto probability on them. pls_start_date = self._pls_get_safe_start_date() if pls_start_date: - pending_lead_domain = ['&', ('create_date', '>', pls_start_date), + pending_lead_domain = ['&', '&', ('stage_id', '!=', False), ('create_date', '>', pls_start_date), '|', ('probability', '=', False), '&', ('probability', '<', 100), ('probability', '>', 0)] leads_to_update = self.env['crm.lead'].search(pending_lead_domain) lead_probabilities = leads_to_update._pls_get_naive_bayes_probabilities(batch_mode=True) @@ -1652,7 +1658,7 @@ class Lead(models.Model): :param first_stage_id: stage with smallest sequence :return: won count, lost count and total count for all records in frequencies """ - if str(first_stage_id.id) not in team_results['stage_id']: + if str(first_stage_id.id) not in team_results.get('stage_id', []): return 0, 0, 0 stage_result = team_results['stage_id'][str(first_stage_id.id)] return stage_result['won'], stage_result['lost'], stage_result['won'] + stage_result['lost'] From b8dda50aaa4ba605f6d536d83e6475fcb9b65298 Mon Sep 17 00:00:00 2001 From: David Beguin Date: Wed, 25 Sep 2019 09:28:53 +0000 Subject: [PATCH 5/8] [IMP] crm : set automated_probability = 100 when stage won If the stage is set to won, automated probability should = 100% Without forcing it, it should be the case as all lead at stage won have been won. (thanks Captain Obvious!) But if a user mark as lost a lead that is at won stage, it will reduce the probability of the won stage. Also, if the cron didn't run recently, it could be that the autmomated probability will give the same result than in previous stage (if no lead have been won at all). Task ID: 2044539 PR #37413 --- addons/crm/models/crm_lead.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index ab72b177edc..5503d5e9235 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -1318,6 +1318,7 @@ class Lead(models.Model): # get stages first_stage_id = self.env['crm.stage'].search([], order='sequence', limit=1) + won_stage_ids = self.env['crm.stage'].search([('is_won', '=', True)]).ids # Get all leads values, no matter the team_id leads_values_dict = self._pls_get_lead_pls_values(batch_mode=batch_mode) @@ -1326,8 +1327,11 @@ class Lead(models.Model): # Get unique couples to search in frequency table leads_values = set() - for values in leads_values_dict.values(): + won_leads = set() + for lead_id, values in leads_values_dict.items(): for couple in values['values']: + if couple[0] == 'stage_id' and couple[1] in won_stage_ids: + won_leads.add(lead_id) leads_values.add(couple) # get all variable related records from frequency table, no matter the team_id @@ -1377,6 +1381,10 @@ class Lead(models.Model): if not 'stage_id' in lead_fields: lead_probabilities[lead_id] = 0 continue + # if lead stage is won, return 100 + elif lead_id in won_leads: + lead_probabilities[lead_id] = 100 + continue lead_team_id = lead_values['team_id'] if lead_values['team_id'] else 0 # team_id = None -> Convert to 0 lead_team_id = lead_team_id if lead_team_id in result else -1 # team_id not in frequency Table -> convert to -1 From b8401b09f8b5505440e88028386f7ad9902cb0cb Mon Sep 17 00:00:00 2001 From: David Beguin Date: Wed, 25 Sep 2019 11:13:44 +0000 Subject: [PATCH 6/8] [FIX] crm_iap_lead_enrich : fix lost semantic in lead iap_enrich process Since the default value of probability is not set anymore, the probability of a new lead is equivalent to 0. In Lead enrichement, we filter out won and lost leads. But probability = 0 is not a good ways anymore to filter out lost leads. Lost reprensents archived leads. Task ID: 2044539 PR #37413 --- addons/crm_iap_lead_enrich/models/crm_lead.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/crm_iap_lead_enrich/models/crm_lead.py b/addons/crm_iap_lead_enrich/models/crm_lead.py index 48df18f3c77..dd6e4b9ee1d 100644 --- a/addons/crm_iap_lead_enrich/models/crm_lead.py +++ b/addons/crm_iap_lead_enrich/models/crm_lead.py @@ -19,10 +19,11 @@ class Lead(models.Model): @api.model def _iap_enrich_leads_cron(self): timeDelta = fields.datetime.now() - datetime.timedelta(hours=1) + # Get all leads not lost nor won (lost: active = False) leads = self.search([ ('iap_enrich_done', '=', False), ('reveal_id', '=', False), - ('probability', 'not in', (0, 100)), + ('probability', '<', 100), ('create_date', '>', timeDelta) ]) leads._iap_enrich(from_cron=True) @@ -30,7 +31,8 @@ class Lead(models.Model): def _iap_enrich(self, from_cron=False): lead_emails = {} for lead in self: - if lead.probability in (0, 100) or lead.iap_enrich_done: + # If lead is lost, active == False, but is anyway removed from the search in the cron. + if lead.probability == 100 or lead.iap_enrich_done: continue normalized_email = tools.email_normalize(lead.partner_address_email) or tools.email_normalize(lead.email_from) if normalized_email: From a07324af172a1808749adb151ed37157b949c40b Mon Sep 17 00:00:00 2001 From: David Beguin Date: Wed, 25 Sep 2019 12:01:22 +0000 Subject: [PATCH 7/8] [IMP] crm : add PLS rebuild threshold for onboarding purpose When there are only few leads, marking a lead as won or lost changes a lot the won/lost repartition. For onboarding purpose, the cron is launched if the threshold PLS config param is not reached. When the number of lead in the database reaches the PLS threshold, the config parameter is set to 0 to avoid the useless check of the number of lead in the dabase in the future Task ID: 2044539 PR #37413 --- addons/crm/data/crm_lead_prediction_data.xml | 4 ++++ addons/crm/models/crm_lead.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/addons/crm/data/crm_lead_prediction_data.xml b/addons/crm/data/crm_lead_prediction_data.xml index a437956d0fc..1a1cbc3675f 100644 --- a/addons/crm/data/crm_lead_prediction_data.xml +++ b/addons/crm/data/crm_lead_prediction_data.xml @@ -28,6 +28,10 @@ crm.pls_start_date + + crm.pls_rebuild_threshold + 100 + diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index 5503d5e9235..568aff28e73 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -481,9 +481,24 @@ class Lead(models.Model): # Actions Methods # ---------------------------------------- + def _rebuild_pls_frequency_table_threshold(self): + """ Called by action_set_lost and action_set_won. + Will run the cron to update the frequency table only if the number of lead is above + a specified value (from config_parameter) for onboarding purpose. + Once the threshold is reached, the config_param is set to 0 to avoid re-run the cron + and, mainly, to avoid making useless search_count in the future.""" + pls_threshold = int(self.env['ir.config_parameter'].sudo().get_param('crm.pls_rebuild_threshold')) + if pls_threshold: + lead_count = self.env['crm.lead'].sudo().search_count([]) + if lead_count < pls_threshold: + self.sudo()._cron_update_automated_probabilities() + else: + self.env['ir.config_parameter'].sudo().set_param('crm.pls_rebuild_threshold', 0) + def action_set_lost(self, **additional_values): """ Lost semantic: probability = 0 or active = False """ result = self.write({'active': False, 'probability': 0, **additional_values}) + self._rebuild_pls_frequency_table_threshold() return result def action_set_won(self): @@ -491,6 +506,7 @@ class Lead(models.Model): for lead in self: stage_id = lead._stage_find(domain=[('is_won', '=', True)]) lead.write({'stage_id': stage_id.id, 'probability': 100}) + self._rebuild_pls_frequency_table_threshold() return True def action_set_automated_probability(self): From 9f6540c41e05a1dae3738d8bf92b000334281300 Mon Sep 17 00:00:00 2001 From: David Beguin Date: Wed, 25 Sep 2019 12:15:03 +0000 Subject: [PATCH 8/8] [FIX] crm : update both auto and manual probability in PLS cron when runnning the PLS cron, only the automated probability was updated with the new values. But dissociating probability and automated probability (for the leads that were in auto mode), breaks the autom mode and set implicitelly the mode to manual. (as probability and automated_probability are not equal anymore). This commit update also the probability only in the case both fields were equal before the cron process. Task ID: 2044539 PR #37413 --- addons/crm/models/crm_lead.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index 568aff28e73..c2a2bb40759 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -1465,8 +1465,15 @@ class Lead(models.Model): lead_probabilities = leads_to_update._pls_get_naive_bayes_probabilities(batch_mode=True) # Update in execute batch to avoid server roundtrips + page_size to 10000 to avoid memory errors - sql = "update crm_lead set automated_probability = %s where id = %s" - batch_params = [(lead_probabilities[lead.id], lead.id) for lead in leads_to_update if lead.id in lead_probabilities] + # Update both probability and automated_probability if they were equal, else, update only automated_probability + sql = """UPDATE crm_lead + SET automated_probability = %s, + probability = CASE WHEN (ROUND(probability::numeric, 2) = ROUND(automated_probability::numeric, 2) or probability is null) + THEN (%s) + ELSE (probability) + END + WHERE id = %s""" + batch_params = [(lead_probabilities[lead.id], lead_probabilities[lead.id], lead.id) for lead in leads_to_update if lead.id in lead_probabilities] extras.execute_batch(self._cr, sql, batch_params, page_size=10000) _logger.info("Predictive Lead Scoring : all automated probability updated (count: %d)" % (len(leads_to_update)))