From eca1b7aeafbc58820c2d5ae3722296250073d029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Fran=C3=A7ois?= Date: Fri, 7 May 2021 14:43:37 +0000 Subject: [PATCH] [IMP] crm: ensure constant lead filling when assigning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PURPOSE All unassigned leads should be assigned to teams as soon as possible to ease lead analysis. Purpose of assign thresholds is to ensure sales people receive at least this amount of leads within 30 days, counting lost and won leads. Giving them leads regularly is also one goal of automatic assign. SPECIFICATIONS Counting every lead whatever its state may lead to an inconvenient situation. If a salesman always reaches its maximum every days he will always receive the number of lead he got 30 days ago. For example if the salesman goes in holidays for few days and set the max to 0 he receives no lead during his vacation. Then after a few days of assign he reaches its maximum and receive 0 leads for a few days. This leads to having windows of leads that repeat themselves every 30 days. * Solution: do not limit at maximum capacity anymore. Compensation is voided if limit is achieved. However asked assignment is done. Salesman could receive more than its max capacity but 30 days window ensure old leads are regularly going out of count. ``assignment_max`` is now more a mean target of leads to be assigned during a 30 days window than a real maximum capacity. Field is renamed accordingly. LINKS Task ID-2444908 (assign fixes) Task ID-2489951 (assign process improvements) COM PR odoo/odoo#70172 X-original-commit: 3d1841f26ce64f0106122e48750d2c653ea9790f Co-authored-by: Thibault François Co-authored-by: Thibault Delavallée --- addons/crm/i18n/crm.pot | 14 +++++++------- addons/crm/models/crm_team.py | 4 ++-- addons/crm/models/crm_team_member.py | 10 +++------- addons/crm/tests/test_crm_lead_assignment.py | 16 ++++++++-------- addons/crm/tests/test_performances.py | 2 +- 5 files changed, 21 insertions(+), 25 deletions(-) diff --git a/addons/crm/i18n/crm.pot b/addons/crm/i18n/crm.pot index 3330a2464dd..ed8b7e39ed4 100644 --- a/addons/crm/i18n/crm.pot +++ b/addons/crm/i18n/crm.pot @@ -529,6 +529,11 @@ msgstr "" msgid "Automated Probability" msgstr "" +#. module: crm +#: model:ir.model.fields,field_description:crm.field_crm_team_member__assignment_max +msgid "Average Leads Capacity (on 30 days)" +msgstr "" + #. module: crm #: model:ir.model.fields,field_description:crm.field_crm_lead__is_blacklisted msgid "Blacklist" @@ -1622,7 +1627,7 @@ msgstr "" #. module: crm #: model:ir.model.fields,field_description:crm.field_crm_team__assignment_max -msgid "Lead Capacity" +msgid "Lead Average Capacity" msgstr "" #. module: crm @@ -1915,11 +1920,6 @@ msgstr "" msgid "Marketing" msgstr "" -#. module: crm -#: model:ir.model.fields,field_description:crm.field_crm_team_member__assignment_max -msgid "Max Leads (last 30 days)" -msgstr "" - #. module: crm #: model:ir.model.fields,field_description:crm.field_crm_lead__medium_id #: model:ir.model.fields.selection,name:crm.selection__crm_lead__priority__1 @@ -2034,7 +2034,7 @@ msgstr "" #. module: crm #: model:ir.model.fields,help:crm.field_crm_team__assignment_max -msgid "Monthly leads for all salesmen belonging to the team" +msgid "Monthly average leads capacity for all salesmen belonging to the team" msgstr "" #. module: crm diff --git a/addons/crm/models/crm_team.py b/addons/crm/models/crm_team.py index 448814fd6e6..ac98ea89e43 100644 --- a/addons/crm/models/crm_team.py +++ b/addons/crm/models/crm_team.py @@ -30,8 +30,8 @@ class Team(models.Model): assignment_auto_enabled = fields.Boolean('Auto Assignment', compute='_compute_assignment_enabled') assignment_optout = fields.Boolean('Skip auto assignment') assignment_max = fields.Integer( - 'Lead Capacity', compute='_compute_assignment_max', - help='Monthly leads for all salesmen belonging to the team') + 'Lead Average Capacity', compute='_compute_assignment_max', + help='Monthly average leads capacity for all salesmen belonging to the team') assignment_domain = fields.Char( 'Assignment Domain', tracking=True, help='Additional filter domain when fetching unassigned leads to allocate to the team.') diff --git a/addons/crm/models/crm_team_member.py b/addons/crm/models/crm_team_member.py index 1c1995761e2..824d9eb99f8 100644 --- a/addons/crm/models/crm_team_member.py +++ b/addons/crm/models/crm_team_member.py @@ -22,7 +22,7 @@ class Team(models.Model): assignment_enabled = fields.Boolean(related="crm_team_id.assignment_enabled") assignment_domain = fields.Char('Assignment Domain', tracking=True) assignment_optout = fields.Boolean('Skip auto assignment') - assignment_max = fields.Integer('Max Leads (last 30 days)', default=30) + assignment_max = fields.Integer('Average Leads Capacity (on 30 days)', default=30) lead_month_count = fields.Integer( 'Leads (30 days)', compute='_compute_lead_month_count', help='Lead assigned to this member those last 30 days') @@ -202,15 +202,11 @@ class Team(models.Model): """ Compute assignment quota based on work_days. This quota includes a compensation to speedup getting to the lead average (``assignment_max``). As this field is a counter for "30 days" -> divide by requested work - days in order to have base assign number then add compensation. Limit - to max capacity of team member. + days in order to have base assign number then add compensation. :param float work_days: see ``CrmTeam.action_assign_leads()``; """ assign_ratio = work_days / 30.0 to_assign = self.assignment_max * assign_ratio compensation = max(0, self.assignment_max - (self.lead_month_count + to_assign)) * 0.2 - return min( - self.assignment_max - self.lead_month_count, - round(to_assign + compensation) - ) + return round(to_assign + compensation) diff --git a/addons/crm/tests/test_crm_lead_assignment.py b/addons/crm/tests/test_crm_lead_assignment.py index 5d9431047dc..c43bd0a3d60 100644 --- a/addons/crm/tests/test_crm_lead_assignment.py +++ b/addons/crm/tests/test_crm_lead_assignment.py @@ -167,7 +167,7 @@ class TestLeadAssign(TestLeadAssignCommon): self.members.invalidate_cache(fnames=['lead_month_count']) self.assertEqual(self.sales_team_1_m1.lead_month_count, 0) # archived do not get leads self.assertEqual(self.sales_team_1_m2.lead_month_count, 0) # opt-out through assignment_max = 0 - self.assertEqual(self.sales_team_1_m3.lead_month_count, 15) # 15 max on 4 days (2) + existing 14 but capped at 15 + self.assertEqual(self.sales_team_1_m3.lead_month_count, 16) # 15 max on 4 days (2) + existing 14 and not capped anymore @mute_logger('odoo.models.unlink') def test_assign_duplicates(self): @@ -363,12 +363,12 @@ class TestLeadAssign(TestLeadAssignCommon): self.assertEqual( self.sales_team_1_m1._get_assignment_quota(work_days=30), 45, - "Assignment quota: anyway 45 max available" + "Assignment quota: no compensation as exceeding monthly count" ) self.assertEqual( self.sales_team_1_m1._get_assignment_quota(work_days=60), - 45, - "Assignment quota: anyway 45 max available" + 90, + "Assignment quota: no compensation and no limit anymore (do as asked)" ) # create exiting leads for user_sales_leads (sales_team_1_m1) @@ -397,13 +397,13 @@ class TestLeadAssign(TestLeadAssignCommon): # quota should not exceed maximum self.assertEqual( self.sales_team_1_m1._get_assignment_quota(work_days=30), - 15, - "Assignment quota: anyway 15 max available (30 already assigned)" + 45, + "Assignment quota: no compensation and no limit anymore (do as asked even with 30 already assigned)" ) self.assertEqual( self.sales_team_1_m1._get_assignment_quota(work_days=60), - 15, - "Assignment quota: anyway 15 max available (30 already assigned)" + 90, + "Assignment quota: no compensation and no limit anymore (do as asked even with 30 already assigned)" ) def test_assign_specific_won_lost(self): diff --git a/addons/crm/tests/test_performances.py b/addons/crm/tests/test_performances.py index 155adf7d612..1f1324cbb41 100644 --- a/addons/crm/tests/test_performances.py +++ b/addons/crm/tests/test_performances.py @@ -171,7 +171,7 @@ class TestLeadAssignPerf(TestLeadAssignCommon): leads.flush() with self.with_user('user_sales_manager'): - with self.assertQueryCount(user_sales_manager=5384): # crm only: ?? + with self.assertQueryCount(user_sales_manager=5385): # crm only: ?? self.env['crm.team'].browse(sales_teams.ids)._action_assign_leads(work_days=30) # teams assign