diff --git a/addons/crm/models/crm_lead_scoring_frequency.py b/addons/crm/models/crm_lead_scoring_frequency.py index 390da13a95e..b54fe922898 100644 --- a/addons/crm/models/crm_lead_scoring_frequency.py +++ b/addons/crm/models/crm_lead_scoring_frequency.py @@ -12,7 +12,6 @@ class LeadScoringFrequency(models.Model): lost_count = fields.Float('Lost Count', digits=(16, 1)) # Float because we add 0.1 to avoid zero Frequency issue team_id = fields.Many2one('crm.team', 'Sales Team', ondelete="cascade") - class FrequencyField(models.Model): _name = 'crm.lead.scoring.frequency.field' _description = 'Fields that can be used for predictive lead scoring computation' diff --git a/addons/crm/models/crm_team.py b/addons/crm/models/crm_team.py index ac98ea89e43..238b70aed2a 100644 --- a/addons/crm/models/crm_team.py +++ b/addons/crm/models/crm_team.py @@ -10,6 +10,7 @@ from ast import literal_eval from odoo import api, exceptions, fields, models, _ from odoo.osv import expression +from odoo.tools import float_compare, float_round from odoo.tools.safe_eval import safe_eval _logger = logging.getLogger(__name__) @@ -138,6 +139,42 @@ class Team(models.Model): }) return result + def unlink(self): + """ When unlinking, concatenate ``crm.lead.scoring.frequency`` linked to + the team into "no team" statistics. """ + frequencies = self.env['crm.lead.scoring.frequency'].search([('team_id', 'in', self.ids)]) + if frequencies: + existing_noteam = self.env['crm.lead.scoring.frequency'].sudo().search([ + ('team_id', '=', False), + ('variable', 'in', frequencies.mapped('variable')) + ]) + for frequency in frequencies: + # skip void-like values + if float_compare(frequency.won_count, 0.1, 2) != 1 and float_compare(frequency.lost_count, 0.1, 2) != 1: + continue + + match = existing_noteam.filtered(lambda frequ_nt: frequ_nt.variable == frequency.variable and frequ_nt.value == frequency.value) + if match: + # remove extra .1 that may exist in db as those are artifacts of initializing + # frequency table. Final value of 0 will be set to 0.1. + exist_won_count = float_round(match.won_count, precision_digits=0, rounding_method='HALF-UP') + exist_lost_count = float_round(match.lost_count, precision_digits=0, rounding_method='HALF-UP') + add_won_count = float_round(frequency.won_count, precision_digits=0, rounding_method='HALF-UP') + add_lost_count = float_round(frequency.lost_count, precision_digits=0, rounding_method='HALF-UP') + new_won_count = exist_won_count + add_won_count + new_lost_count = exist_lost_count + add_lost_count + match.won_count = new_won_count if float_compare(new_won_count, 0.1, 2) == 1 else 0.1 + match.lost_count = new_lost_count if float_compare(new_lost_count, 0.1, 2) == 1 else 0.1 + else: + existing_noteam += self.env['crm.lead.scoring.frequency'].sudo().create({ + 'lost_count': frequency.lost_count if float_compare(frequency.lost_count, 0.1, 2) == 1 else 0.1, + 'team_id': False, + 'value': frequency.value, + 'variable': frequency.variable, + 'won_count': frequency.won_count if float_compare(frequency.won_count, 0.1, 2) == 1 else 0.1, + }) + return super(Team, self).unlink() + # ------------------------------------------------------------ # MESSAGING # ------------------------------------------------------------ diff --git a/addons/crm/tests/test_crm_pls.py b/addons/crm/tests/test_crm_pls.py index b05e6d2c816..cda14e08da0 100644 --- a/addons/crm/tests/test_crm_pls.py +++ b/addons/crm/tests/test_crm_pls.py @@ -3,14 +3,35 @@ from datetime import timedelta -from odoo.tests.common import TransactionCase -from odoo.tests import Form from odoo import tools +from odoo.addons.mail.tests.common import mail_new_test_user from odoo.fields import Date +from odoo.tests import Form, tagged, users +from odoo.tests.common import TransactionCase +@tagged('crm_lead_pls') class TestCRMPLS(TransactionCase): + @classmethod + def setUpClass(cls): + """ Keep a limited setup to ensure tests are not impacted by other + records created in CRM common. """ + super(TestCRMPLS, cls).setUpClass() + + cls.company_main = cls.env.user.company_id + cls.user_sales_manager = mail_new_test_user( + cls.env, login='user_sales_manager', + name='Martin PLS Sales Manager', email='crm_manager@test.example.com', + company_id=cls.company_main.id, + notification_type='inbox', + groups='sales_team.group_sale_manager,base.group_partner_manager', + ) + + cls.pls_team = cls.env['crm.team'].create({ + 'name': 'PLS Team', + }) + def _get_lead_values(self, team_id, name_suffix, country_id, state_id, email_state, phone_state, source_id, stage_id): return { 'name': 'lead_' + name_suffix, @@ -478,3 +499,63 @@ class TestCRMPLS(TransactionCase): res_config_new = resConfig.new() self.assertEqual(Date.to_string(res_config_new.predictive_lead_scoring_start_date), str_date_8_days_ago, "If config param is not a valid date, date in settings should be set to 8 days before today") + + @users('user_sales_manager') + def test_team_unlink(self): + """ Test that frequencies are sent to "no team" when unlinking a team + in order to avoid loosing too much informations. """ + pls_team = self.env["crm.team"].browse(self.pls_team.ids) + + # clean existing data + self.env["crm.lead.scoring.frequency"].sudo().search([('team_id', '=', False)]).unlink() + + # existing no-team data + no_team = [ + ('stage_id', '1', 20, 10), + ('stage_id', '2', 0.1, 0.1), + ('stage_id', '3', 10, 0), + ('country_id', '1', 10, 0.1), + ] + self.env["crm.lead.scoring.frequency"].sudo().create([ + {'variable': variable, 'value': value, + 'won_count': won_count, 'lost_count': lost_count, + 'team_id': False, + } for variable, value, won_count, lost_count in no_team + ]) + + # add some frequencies to team to unlink + team = [ + ('stage_id', '1', 20, 10), # existing noteam + ('country_id', '1', 0.1, 10), # existing noteam + ('country_id', '2', 0.1, 0), # new but void + ('country_id', '3', 30, 30), # new + ] + existing_plsteam = self.env["crm.lead.scoring.frequency"].sudo().create([ + {'variable': variable, 'value': value, + 'won_count': won_count, 'lost_count': lost_count, + 'team_id': pls_team.id, + } for variable, value, won_count, lost_count in team + ]) + + pls_team.unlink() + + final_noteam = [ + ('stage_id', '1', 40, 20), + ('stage_id', '2', 0.1, 0.1), + ('stage_id', '3', 10, 0), + ('country_id', '1', 10, 10), + ('country_id', '3', 30, 30), + + ] + self.assertEqual( + existing_plsteam.exists(), self.env["crm.lead.scoring.frequency"], + 'Frequencies of unlinked teams should be unlinked (cascade)') + existing_noteam = self.env["crm.lead.scoring.frequency"].sudo().search([ + ('team_id', '=', False), + ('variable', 'in', ['stage_id', 'country_id']), + ]) + for frequency in existing_noteam: + stat = next(item for item in final_noteam if item[0] == frequency.variable and item[1] == frequency.value) + self.assertEqual(frequency.won_count, stat[2]) + self.assertEqual(frequency.lost_count, stat[3]) + self.assertEqual(len(existing_noteam), len(final_noteam))