From 09295d229da8fe3c5bc7e011ec3f688a7989b740 Mon Sep 17 00:00:00 2001 From: tsm-odoo Date: Fri, 19 Apr 2024 10:41:39 +0200 Subject: [PATCH] [FIX] im_livechat: fix operator assignation Before this PR, trying to get an available operator could result in an error. When a live chat is created, an operator is selected based on various criteria such as language, country, number of ongoing chats and availability for calls. This selection process involves the `_get_less_active_operator` method, which receives the status of all operators and a list of operators to choose from. Prior to this fix, an operator not included in the list of operators to choose from could still be selected, resulting in errors when trying to locate them in the operator list. This PR resolves the issue by ensuring that operators are only selected from the provided list. opw-3874872 closes odoo/odoo#162357 Signed-off-by: Matthieu Stockbauer (tsm) --- .../im_livechat/models/im_livechat_channel.py | 35 ++++++++++++------- addons/im_livechat/tests/test_get_operator.py | 14 ++++++++ 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/addons/im_livechat/models/im_livechat_channel.py b/addons/im_livechat/models/im_livechat_channel.py index 9881f0c4172..8af1e6e62a1 100644 --- a/addons/im_livechat/models/im_livechat_channel.py +++ b/addons/im_livechat/models/im_livechat_channel.py @@ -187,23 +187,32 @@ class ImLivechatChannel(models.Model): operator is in a call. The list is ordered by the number of active chats (ascending) and whether the operator is in a call (descending). - :param operators: list of operators to choose from + :param operators: recordset of :class:`ResUsers` operators to choose from. + :return: the :class:`ResUsers` record for the chosen operator """ if not operators: return False - active_operator_ids = set(map(lambda s: s['livechat_operator_id'], operator_statuses)) - inactive_operators = [ - operator for operator in operators if operator.partner_id.id not in active_operator_ids + + # 1) only consider operators in the list to choose from + operator_statuses = [ + s for s in operator_statuses if s['livechat_operator_id'] in set(operators.partner_id.ids) ] - if inactive_operators: - return random.choice(inactive_operators) - count, in_call = itemgetter('count', 'in_call')(operator_statuses[0]) - i = 1 - while i < len(operator_statuses) and operator_statuses[i]['in_call'] == in_call and operator_statuses[i]['count'] == count: - i += 1 - less_active_operator_id = random.choice(operator_statuses[:i])['livechat_operator_id'] - # 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_id) + + # 2) try to select an inactive op, i.e. one w/ no active status (no recent chat) + active_op_partner_ids = {s['livechat_operator_id'] for s in operator_statuses} + candidates = operators.filtered(lambda o: o.partner_id.id not in active_op_partner_ids) + if candidates: + return random.choice(candidates) + + # 3) otherwise select least active ops, based on status ordering (count + in_call) + best_status = operator_statuses[0] + best_status_op_partner_ids = { + s['livechat_operator_id'] + for s in operator_statuses + if (s['count'], s['in_call']) == (best_status['count'], best_status['in_call']) + } + candidates = operators.filtered(lambda o: o.partner_id.id in best_status_op_partner_ids) + return random.choice(candidates) def _get_operator(self, previous_operator_id=None, lang=None, country_id=None): """ Return an operator for a livechat. Try to return the previous diff --git a/addons/im_livechat/tests/test_get_operator.py b/addons/im_livechat/tests/test_get_operator.py index 26da74003b3..95bc97049da 100644 --- a/addons/im_livechat/tests/test_get_operator.py +++ b/addons/im_livechat/tests/test_get_operator.py @@ -66,6 +66,20 @@ class TestGetOperator(HttpCase): self.assertEqual(fr_operator, livechat_channel._get_operator(lang="fr_FR")) self.assertEqual(en_operator, livechat_channel._get_operator(lang="en_US")) + def test_get_by_lang_both_operator_active(self): + fr_operator = self._create_operator("fr_FR") + en_operator = self._create_operator("en_US") + livechat_channel = self.env["im_livechat.channel"].create( + { + "name": "Livechat Channel", + "user_ids": [fr_operator.id, en_operator.id], + } + ) + self._create_chat(livechat_channel, fr_operator) + self._create_chat(livechat_channel, en_operator) + self._create_chat(livechat_channel, en_operator) + self.assertEqual(en_operator, livechat_channel._get_operator(lang="en_US")) + def test_get_by_lang_no_operator_matching_lang(self): fr_operator = self._create_operator("fr_FR") livechat_channel = self.env["im_livechat.channel"].create(