[IMP] crm: ensure constant lead filling when assigning

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 <tfr@odoo.com>
Co-authored-by: Thibault Delavallée <tde@odoo.com>
This commit is contained in:
Thibault François
2021-05-12 14:23:55 +00:00
committed by Thibault Delavallée
co-authored by Thibault Delavallée
parent fe8c5b9b01
commit eca1b7aeaf
5 changed files with 21 additions and 25 deletions
+7 -7
View File
@@ -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
+2 -2
View File
@@ -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.')
+3 -7
View File
@@ -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)
+8 -8
View File
@@ -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):
+1 -1
View File
@@ -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