diff --git a/addons/crm/tests/__init__.py b/addons/crm/tests/__init__.py index bbc44d6dff9..647f4cd0edf 100644 --- a/addons/crm/tests/__init__.py +++ b/addons/crm/tests/__init__.py @@ -10,3 +10,4 @@ from . import test_crm_lead_merge from . import test_crm_activity from . import test_crm_ui from . import test_crm_pls +from . import test_performances diff --git a/addons/crm/tests/common.py b/addons/crm/tests/common.py index 252b06d87ae..1295806a8fd 100644 --- a/addons/crm/tests/common.py +++ b/addons/crm/tests/common.py @@ -421,7 +421,8 @@ class TestLeadConvertCommon(TestCrmCommon): if member.crm_team_id.assignment_domain: self.assertEqual( member_leads.filtered_domain(literal_eval(member.crm_team_id.assignment_domain)), - member_leads + member_leads, + 'Assign domain not matching: %s' % member.crm_team_id.assignment_domain ) class TestLeadConvertMassCommon(TestLeadConvertCommon): diff --git a/addons/crm/tests/test_crm_lead_assignment.py b/addons/crm/tests/test_crm_lead_assignment.py index 750e3df705d..67b04fbb5a9 100644 --- a/addons/crm/tests/test_crm_lead_assignment.py +++ b/addons/crm/tests/test_crm_lead_assignment.py @@ -8,15 +8,15 @@ from unittest.mock import patch from odoo import fields from odoo.addons.crm.tests.common import TestLeadConvertCommon from odoo.tests.common import tagged +from odoo.tools import mute_logger @tagged('lead_assign') -class TestLeadAssign(TestLeadConvertCommon): - """ Test lead assignment feature added in saas-13.5. """ +class TestLeadAssignCommon(TestLeadConvertCommon): @classmethod def setUpClass(cls): - super(TestLeadAssign, cls).setUpClass() + super(TestLeadAssignCommon, cls).setUpClass() cls._switch_to_multi_membership() cls._switch_to_auto_assign() @@ -52,6 +52,11 @@ class TestLeadAssign(TestLeadConvertCommon): self.assertEqual(self.sales_team_convert_m1.lead_month_count, 0) self.assertEqual(self.sales_team_convert_m2.lead_month_count, 0) + +@tagged('lead_assign') +class TestLeadAssign(TestLeadAssignCommon): + """ Test lead assignment feature added in saas-14.2 """ + def test_assign_configuration(self): now_patch = datetime(2020, 11, 2, 10, 0, 0) @@ -98,7 +103,10 @@ class TestLeadAssign(TestLeadConvertCommon): self.assertFalse(self.assign_cron.active) self.assertEqual(self.assign_cron.nextcall, datetime(2020, 11, 1, 10, 0, 0)) - def test_crm_team_assign_duplicates(self): + @mute_logger('odoo.models.unlink') + def test_assign_duplicates(self): + """ Test assign process with duplicates on partner. Allow to ensure notably + that de duplication is effectively performed. """ leads = self._create_leads_batch( lead_type='lead', user_ids=[False], @@ -115,8 +123,7 @@ class TestLeadAssign(TestLeadConvertCommon): lead.probability = (idx + 1) * 10 * ((int(lead.priority) + 1) / 2) with self.with_user('user_sales_manager'): - with self.assertQueryCount(user_sales_manager=467): # crm only: 460 - self.env['crm.team'].browse(self.sales_teams.ids)._action_assign_leads(work_days=2) + self.env['crm.team'].browse(self.sales_teams.ids)._action_assign_leads(work_days=2) # teams assign leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order @@ -148,7 +155,8 @@ class TestLeadAssign(TestLeadConvertCommon): ]) self.assertEqual(len(new_assigned_leads_wpartner), 2) - def test_crm_team_assign_no_duplicates(self): + @mute_logger('odoo.models.unlink') + def test_assign_no_duplicates(self): leads = self._create_leads_batch( lead_type='lead', user_ids=[False], @@ -165,8 +173,7 @@ class TestLeadAssign(TestLeadConvertCommon): lead.probability = (idx + 1) * 10 * ((int(lead.priority) + 1) / 2) with self.with_user('user_sales_manager'): - with self.assertQueryCount(user_sales_manager=206): # crm only: 201 - self.env['crm.team'].browse(self.sales_teams.ids)._action_assign_leads(work_days=2) + self.env['crm.team'].browse(self.sales_teams.ids)._action_assign_leads(work_days=2) # teams assign leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order @@ -183,10 +190,11 @@ class TestLeadAssign(TestLeadConvertCommon): self.assertMemberAssign(self.sales_team_convert_m1, 2) # 30 max on 15 self.assertMemberAssign(self.sales_team_convert_m2, 4) # 60 max on 15 - def test_crm_team_assign_populated(self): + @mute_logger('odoo.models.unlink') + def test_assign_populated(self): """ Test assignment on a more high volume oriented test set in order to - have more insights on query counts. """ - # create leads enough to have interesting counters + test more real life use cases. """ + # create leads enough to assign one month of work _lead_count, _email_dup_count, _partner_count = 500, 50, 150 leads = self._create_leads_batch( lead_type='lead', @@ -240,8 +248,7 @@ class TestLeadAssign(TestLeadConvertCommon): lead.probability = (idx + 1) * 10 * ((int(lead.priority) + 1) / 2) with self.with_user('user_sales_manager'): - with self.assertQueryCount(user_sales_manager=6289): # crm only: 6165 - self.env['crm.team'].browse(sales_teams.ids)._action_assign_leads(work_days=30) + self.env['crm.team'].browse(sales_teams.ids)._action_assign_leads(work_days=30) self.members.invalidate_cache(fnames=['lead_month_count']) self.assertMemberAssign(self.sales_team_1_m1, 45) # 45 max on one month diff --git a/addons/crm/tests/test_crm_lead_convert_mass.py b/addons/crm/tests/test_crm_lead_convert_mass.py index f22679f8ef0..9e379ac3d8c 100644 --- a/addons/crm/tests/test_crm_lead_convert_mass.py +++ b/addons/crm/tests/test_crm_lead_convert_mass.py @@ -24,7 +24,7 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon): with self.assertQueryCount(user_sales_manager=0): test_leads = self.env['crm.lead'].browse(test_leads.ids) - with self.assertQueryCount(user_sales_manager=254): # crm only: 252 + with self.assertQueryCount(user_sales_manager=255): # crm only: 252 test_leads._handle_salesmen_assignment(user_ids=user_ids, team_id=False) self.assertEqual(test_leads.team_id, self.sales_team_convert | self.sales_team_1) @@ -42,7 +42,7 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon): with self.assertQueryCount(user_sales_manager=0): test_leads = self.env['crm.lead'].browse(test_leads.ids) - with self.assertQueryCount(user_sales_manager=220): # crm only: 218 + with self.assertQueryCount(user_sales_manager=223): # crm only: 218 test_leads._handle_salesmen_assignment(user_ids=user_ids, team_id=team_id) self.assertEqual(test_leads.team_id, self.sales_team_convert) diff --git a/addons/crm/tests/test_performances.py b/addons/crm/tests/test_performances.py new file mode 100644 index 00000000000..b02c37f6028 --- /dev/null +++ b/addons/crm/tests/test_performances.py @@ -0,0 +1,188 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import random + +from odoo.addons.crm.tests.test_crm_lead_assignment import TestLeadAssignCommon +from odoo.tests.common import tagged +from odoo.tools import mute_logger + + +@tagged('lead_assign') +class TestLeadAssignPerf(TestLeadAssignCommon): + """ Test performances of lead assignment feature added in saas-14.2 + + Assign process is a random process: randomizing teams leads to searching, + assigning and de-duplicating leads in various order. As a lot of search + are implied during assign process query counters may vary from run to run. + "Heavy" performance test included here ranged from 6K to 6.3K queries. Either + we set high counters maximum which makes those tests less useful. Either we + avoid random if possible which is what we decided to do by setting the seed + of random in tests. + """ + + @classmethod + def setUpClass(cls): + super(TestLeadAssignPerf, cls).setUpClass() + random.seed(2042) + + @mute_logger('odoo.models.unlink', 'odoo.addons.crm.models.crm_team', 'odoo.addons.crm.models.crm_team_member') + def test_assign_perf_duplicates(self): + """ Test assign process with duplicates on partner. Allow to ensure notably + that de duplication is effectively performed. """ + leads = self._create_leads_batch( + lead_type='lead', + user_ids=[False], + partner_ids=[self.contact_1.id, self.contact_2.id, False, False, False], + count=50 + ) + self.assertInitialData() + + # assign probability to leads (bypass auto probability as purpose is not to test pls) + leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order + for idx in range(0, 5): + sliced_leads = leads[idx:len(leads):5] + for lead in sliced_leads: + lead.probability = (idx + 1) * 10 * ((int(lead.priority) + 1) / 2) + + with self.with_user('user_sales_manager'): + with self.assertQueryCount(user_sales_manager=470): # crm only: 465 + self.env['crm.team'].browse(self.sales_teams.ids)._action_assign_leads(work_days=2) + + # teams assign + leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order + leads_st1 = leads.filtered_domain([('team_id', '=', self.sales_team_1.id)]) + leads_stc = leads.filtered_domain([('team_id', '=', self.sales_team_convert.id)]) + self.assertEqual(len(leads_st1), 10) # 2 * 2 * 75 / 30.0 + self.assertEqual(len(leads_stc), 12) # 2 * 2 * 90 / 30.0 + + # salespersons assign + self.members.invalidate_cache(fnames=['lead_month_count']) + self.assertMemberAssign(self.sales_team_1_m1, 3) # 45 max on 2 days + self.assertMemberAssign(self.sales_team_1_m2, 1) # 15 max on 2 days + self.assertMemberAssign(self.sales_team_1_m3, 1) # 15 max on 2 days + self.assertMemberAssign(self.sales_team_convert_m1, 2) # 30 max on 15 + self.assertMemberAssign(self.sales_team_convert_m2, 4) # 60 max on 15 + + # run a second round to finish leads + with self.with_user('user_sales_manager'): + with self.assertQueryCount(user_sales_manager=131): # crm only: 123 + self.env['crm.team'].browse(self.sales_teams.ids)._action_assign_leads(work_days=2) + + # teams assign: everything should be done due to duplicates + leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order + self.assertTrue(len(leads.filtered_domain([('team_id', '=', False)])) == 0) + + # deduplicate should have removed all duplicated linked to contact_1 and contact_2 + new_assigned_leads_wpartner = self.env['crm.lead'].search([ + ('partner_id', 'in', (self.contact_1 | self.contact_2).ids), + ('id', 'in', leads.ids) + ]) + self.assertEqual(len(new_assigned_leads_wpartner), 2) + + @mute_logger('odoo.models.unlink', 'odoo.addons.crm.models.crm_team', 'odoo.addons.crm.models.crm_team_member') + def test_assign_perf_no_duplicates(self): + leads = self._create_leads_batch( + lead_type='lead', + user_ids=[False], + partner_ids=[False], + count=50 + ) + self.assertInitialData() + + # assign probability to leads (bypass auto probability as purpose is not to test pls) + leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order + for idx in range(0, 5): + sliced_leads = leads[idx:len(leads):5] + for lead in sliced_leads: + lead.probability = (idx + 1) * 10 * ((int(lead.priority) + 1) / 2) + + with self.with_user('user_sales_manager'): + with self.assertQueryCount(user_sales_manager=210): # crm only: 206 + self.env['crm.team'].browse(self.sales_teams.ids)._action_assign_leads(work_days=2) + + # teams assign + leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order + leads_st1 = leads.filtered_domain([('team_id', '=', self.sales_team_1.id)]) + leads_stc = leads.filtered_domain([('team_id', '=', self.sales_team_convert.id)]) + self.assertEqual(len(leads_st1), 10) # 2 * 2 * 75 / 30.0 + self.assertEqual(len(leads_stc), 12) # 2 * 2 * 90 / 30.0 + + # salespersons assign + self.members.invalidate_cache(fnames=['lead_month_count']) + self.assertMemberAssign(self.sales_team_1_m1, 3) # 45 max on 2 days + self.assertMemberAssign(self.sales_team_1_m2, 1) # 15 max on 2 days + self.assertMemberAssign(self.sales_team_1_m3, 1) # 15 max on 2 days + self.assertMemberAssign(self.sales_team_convert_m1, 2) # 30 max on 15 + self.assertMemberAssign(self.sales_team_convert_m2, 4) # 60 max on 15 + + @mute_logger('odoo.models.unlink', 'odoo.addons.crm.models.crm_team', 'odoo.addons.crm.models.crm_team_member') + def test_assign_perf_populated(self): + """ Test assignment on a more high volume oriented test set in order to + have more insights on query counts. """ + # create leads enough to have interesting counters + _lead_count, _email_dup_count, _partner_count = 500, 50, 150 + leads = self._create_leads_batch( + lead_type='lead', + user_ids=[False], + partner_count=_partner_count, + country_ids=[self.env.ref('base.be').id, self.env.ref('base.fr').id, False], + count=_lead_count, + email_dup_count=_email_dup_count) + self.assertInitialData() + # assign for one month, aka a lot + self.env.ref('crm.ir_cron_crm_lead_assign').write({'interval_type': 'days', 'interval_number': 30}) + self.env['ir.config_parameter'].set_param('crm.assignment.bundle', '20') + # create a third team + sales_team_3 = self.env['crm.team'].create({ + 'name': 'Sales Team 3', + 'sequence': 15, + 'alias_name': False, + 'use_leads': True, + 'use_opportunities': True, + 'company_id': False, + 'user_id': False, + 'assignment_domain': [('country_id', '!=', False)], + }) + sales_team_3_m1 = self.env['crm.team.member'].create({ + 'user_id': self.user_sales_manager.id, + 'crm_team_id': sales_team_3.id, + 'assignment_max': 60, + 'assignment_domain': False, + }) + sales_team_3_m2 = self.env['crm.team.member'].create({ + 'user_id': self.user_sales_leads.id, + 'crm_team_id': sales_team_3.id, + 'assignment_max': 60, + 'assignment_domain': False, + }) + sales_team_3_m3 = self.env['crm.team.member'].create({ + 'user_id': self.user_sales_salesman.id, + 'crm_team_id': sales_team_3.id, + 'assignment_max': 15, + 'assignment_domain': [('probability', '>=', 10)], + }) + sales_teams = self.sales_teams | sales_team_3 + self.assertEqual(sum(team.assignment_max for team in sales_teams), 300) + self.assertEqual(len(leads), 550) + + # assign probability to leads (bypass auto probability as purpose is not to test pls) + leads = self.env['crm.lead'].search([('id', 'in', leads.ids)]) # ensure order + for idx in range(0, 5): + sliced_leads = leads[idx:len(leads):5] + for lead in sliced_leads: + lead.probability = (idx + 1) * 10 * ((int(lead.priority) + 1) / 2) + + with self.with_user('user_sales_manager'): + with self.assertQueryCount(user_sales_manager=6246): # crm only: 6237 + self.env['crm.team'].browse(sales_teams.ids)._action_assign_leads(work_days=30) + + self.members.invalidate_cache(fnames=['lead_month_count']) + self.assertMemberAssign(self.sales_team_1_m1, 45) # 45 max on one month + self.assertMemberAssign(self.sales_team_1_m2, 15) # 15 max on one month + self.assertMemberAssign(self.sales_team_1_m3, 15) # 15 max on one month + self.assertMemberAssign(self.sales_team_convert_m1, 30) # 30 max on one month + self.assertMemberAssign(self.sales_team_convert_m2, 60) # 60 max on one month + self.assertMemberAssign(sales_team_3_m1, 60) # 60 max on one month + self.assertMemberAssign(sales_team_3_m2, 60) # 60 max on one month + self.assertMemberAssign(sales_team_3_m3, 15) # 15 max on one month