[IMP] im_livechat: improve random assignation of conversations

Task #1919871

Purpose
=======
The method 'get_mail_channel' in the 'im_livechat.channel' model used a simple random.choice in
the available users to select the operator.

It was improved to select the operator that has the lowest number of active livechats. If multiple
operators share the same number (lowest) of active livechats, it selects randomly between those.

A livechat is considered 'active' if it has at least one message within the last hour.

Spec
=======
The assignation method should always give the new conversation to the operator who
has the less active conversation.

If two operators have the same amout of active conversation, it should chose one of
them randomly.
This commit is contained in:
Aurélien Warnon
2019-04-01 07:03:11 +00:00
committed by jem-odoo
parent c04b9c019e
commit 9b867cd12d
3 changed files with 107 additions and 5 deletions
@@ -142,12 +142,9 @@ class ImLivechatChannel(models.Model):
:rtype : dict
"""
self.ensure_one()
# get the avalable user of the channel
operators = self._get_available_users()
if len(operators) == 0:
operator = self._get_random_operator()
if not operator:
return False
# choose the res.users operator and get its partner id
operator = random.choice(operators)
operator_partner_id = operator.partner_id.id
# partner to add to the mail.channel
channel_partner_to_add = [(4, operator_partner_id)]
@@ -168,6 +165,47 @@ class ImLivechatChannel(models.Model):
mail_channel._broadcast([operator_partner_id])
return mail_channel.sudo().channel_info()[0]
def _get_random_operator(self):
""" Return a random operator from the available users of the channel that have the lowest number of active livechats.
A livechat is considered 'active' if it has at least one message within the 30 minutes.
(Some annoying conversions have to be made on the fly because this model holds 'res.users' as available operators
and the mail_channel model stores the partner_id of the randomly selected operator)
:return : user
:rtype : res.users
"""
operators = self._get_available_users()
if len(operators) == 0:
return False
self.env.cr.execute("""SELECT COUNT(DISTINCT c.id), c.livechat_operator_id
FROM mail_channel c
LEFT OUTER JOIN mail_message_mail_channel_rel r ON c.id = r.mail_channel_id
LEFT OUTER JOIN mail_message m ON r.mail_message_id = m.id
WHERE m.create_date > ((now() at time zone 'UTC') - interval '30 minutes')
AND c.channel_type = 'livechat'
AND c.livechat_operator_id in %s
GROUP BY c.livechat_operator_id
ORDER BY COUNT(DISTINCT c.id) asc""", (tuple(operators.mapped('partner_id').ids),))
active_channels = self.env.cr.dictfetchall()
# If inactive operator(s), return one of them
active_channel_operator_ids = [active_channel['livechat_operator_id'] for active_channel in active_channels]
inactive_operators = [operator for operator in operators if operator.partner_id.id not in active_channel_operator_ids]
if inactive_operators:
return random.choice(inactive_operators)
# If no inactive operator, active_channels is not empty as len(operators) > 0 (see above).
# Get the less active operator using the active_channels first element's count (since they are sorted 'ascending')
lowest_number_of_conversations = active_channels[0]['count']
less_active_operator = random.choice([
active_channel['livechat_operator_id'] for active_channel in active_channels
if active_channel['count'] == lowest_number_of_conversations])
# convert the selected 'partner_id' to its corresponding res.users
return next(operator for operator in operators if operator.partner_id.id == less_active_operator)
def _get_channel_infos(self):
self.ensure_one()
+4
View File
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_get_mail_channel
@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests.common import TransactionCase
class TestGetMailChannel(TransactionCase):
def setUp(self):
super(TestGetMailChannel, self).setUp()
self.operators = self.env['res.users'].create([{
'name': 'Michel',
'login': 'michel'
}, {
'name': 'Paul',
'login': 'paul'
}, {
'name': 'Pierre',
'login': 'pierre'
}, {
'name': 'Jean',
'login': 'jean'
}, {
'name': 'Georges',
'login': 'georges'
}])
self.livechat_channel = self.env['im_livechat.channel'].create({
'name': 'The channel',
'user_ids': (6, 0, self.operators.ids)
})
operators = self.operators
def get_available_users(self):
return operators
self.patch(type(self.env['im_livechat.channel']), '_get_available_users', get_available_users)
def test_get_mail_channel(self):
"""For a livechat with 5 available operators, we open 5 channels 5 times (25 channels total).
For every 5 channels opening, we check that all operators were assigned.
"""
for i in range(5):
mail_channels = self._get_mail_channels()
channel_operators = [channel_info['operator_pid'] for channel_info in mail_channels]
channel_operator_ids = [channel_operator[0] for channel_operator in channel_operators]
self.assertTrue(all(partner_id in channel_operator_ids for partner_id in self.operators.mapped('partner_id').ids))
def _get_mail_channels(self):
mail_channels = []
for i in range(5):
mail_channel = self.livechat_channel._get_mail_channel('Anonymous')
mail_channels.append(mail_channel)
# send a message to mark this channel as 'active'
self.env['mail.channel'].browse(mail_channel['id']).write({
'channel_message_ids': [(0, 0, {'body': 'cc'})]
})
return mail_channels