From 9b867cd12dbc8cca5da1e3d6426b5cadf8a0ec33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Warnon?= Date: Thu, 3 Jan 2019 10:52:12 +0000 Subject: [PATCH] [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. --- .../im_livechat/models/im_livechat_channel.py | 48 +++++++++++++-- addons/im_livechat/tests/__init__.py | 4 ++ .../tests/test_get_mail_channel.py | 60 +++++++++++++++++++ 3 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 addons/im_livechat/tests/__init__.py create mode 100644 addons/im_livechat/tests/test_get_mail_channel.py diff --git a/addons/im_livechat/models/im_livechat_channel.py b/addons/im_livechat/models/im_livechat_channel.py index acba917036c..4a682c912f1 100644 --- a/addons/im_livechat/models/im_livechat_channel.py +++ b/addons/im_livechat/models/im_livechat_channel.py @@ -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() diff --git a/addons/im_livechat/tests/__init__.py b/addons/im_livechat/tests/__init__.py new file mode 100644 index 00000000000..a22a67032d1 --- /dev/null +++ b/addons/im_livechat/tests/__init__.py @@ -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 diff --git a/addons/im_livechat/tests/test_get_mail_channel.py b/addons/im_livechat/tests/test_get_mail_channel.py new file mode 100644 index 00000000000..ac3d5807333 --- /dev/null +++ b/addons/im_livechat/tests/test_get_mail_channel.py @@ -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