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