diff --git a/addons/im_livechat/models/chatbot_script_step.py b/addons/im_livechat/models/chatbot_script_step.py
index a156e49cf3b..527a3a3df34 100644
--- a/addons/im_livechat/models/chatbot_script_step.py
+++ b/addons/im_livechat/models/chatbot_script_step.py
@@ -346,7 +346,8 @@ class ChatbotScriptStep(models.Model):
# then post a small custom 'Operator has joined' notification
discuss_channel._chatbot_post_message(
self.chatbot_script_id,
- Markup('
%s
') % _('%s has joined', human_operator.partner_id.name))
+ Markup('%s
')
+ % _('%s has joined', human_operator.livechat_username or human_operator.partner_id.name))
discuss_channel._broadcast(human_operator.partner_id.ids)
discuss_channel.channel_pin(pinned=True)
diff --git a/addons/im_livechat/models/discuss_channel_member.py b/addons/im_livechat/models/discuss_channel_member.py
index 334f3abc914..b962fc1fc51 100644
--- a/addons/im_livechat/models/discuss_channel_member.py
+++ b/addons/im_livechat/models/discuss_channel_member.py
@@ -27,6 +27,7 @@ class ChannelMember(models.Model):
'active': self.partner_id.active,
'id': self.partner_id.id,
'is_public': self.partner_id.is_public,
+ 'is_bot': self.partner_id.id in self.channel_id.livechat_channel_id.rule_ids.mapped('chatbot_script_id.operator_partner_id.id')
}
if self.partner_id.user_livechat_username:
data['user_livechat_username'] = self.partner_id.user_livechat_username
diff --git a/addons/im_livechat/static/src/core/channel_member_list_patch.js b/addons/im_livechat/static/src/core/channel_member_list_patch.js
new file mode 100644
index 00000000000..22101747f16
--- /dev/null
+++ b/addons/im_livechat/static/src/core/channel_member_list_patch.js
@@ -0,0 +1,10 @@
+/* @odoo-module */
+
+import { ChannelMemberList } from "@mail/discuss/core/common/channel_member_list";
+import { patch } from "@web/core/utils/patch";
+
+patch(ChannelMemberList.prototype, "im_livechat", {
+ canOpenChatWith(member) {
+ return this._super(member) && !member.persona.is_public;
+ },
+});
diff --git a/addons/im_livechat/static/src/core/channel_member_list_patch.xml b/addons/im_livechat/static/src/core/channel_member_list_patch.xml
new file mode 100644
index 00000000000..9b5ed3ef93c
--- /dev/null
+++ b/addons/im_livechat/static/src/core/channel_member_list_patch.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
$0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/im_livechat/static/src/core/channel_member_service_patch.js b/addons/im_livechat/static/src/core/channel_member_service_patch.js
new file mode 100644
index 00000000000..0f80b3da5fe
--- /dev/null
+++ b/addons/im_livechat/static/src/core/channel_member_service_patch.js
@@ -0,0 +1,19 @@
+/* @odoo-module */
+
+import { ChannelMemberService } from "@mail/core/common/channel_member_service";
+import { patch } from "@web/core/utils/patch";
+
+patch(ChannelMemberService, "im_livechat", {
+ getName(member) {
+ if (member.thread.type !== "livechat") {
+ return this._super();
+ }
+ if (member.persona.user_livechat_username) {
+ return member.persona.user_livechat_username;
+ }
+ if (member.persona.is_public) {
+ return member.thread.anonymous_name;
+ }
+ return this._super();
+ },
+});
diff --git a/addons/im_livechat/static/src/core/thread_model_patch.js b/addons/im_livechat/static/src/core/thread_model_patch.js
index 89ce9ea941d..c56d6fe4396 100644
--- a/addons/im_livechat/static/src/core/thread_model_patch.js
+++ b/addons/im_livechat/static/src/core/thread_model_patch.js
@@ -30,6 +30,10 @@ patch(Thread.prototype, "im_livechat", {
return this.type === "livechat" ? false : this._super();
},
+ get correspondents() {
+ return this._super().filter((correspondent) => !correspondent.is_bot);
+ },
+
get displayName() {
if (this.type !== "livechat" || !this.correspondent) {
return this._super();
diff --git a/addons/im_livechat/tests/__init__.py b/addons/im_livechat/tests/__init__.py
index 8caa7596302..805affd48b6 100644
--- a/addons/im_livechat/tests/__init__.py
+++ b/addons/im_livechat/tests/__init__.py
@@ -8,5 +8,6 @@ from . import test_digest
from . import test_get_discuss_channel
from . import test_im_livechat_report
from . import test_im_livechat_support_page
+from . import test_performance
from . import test_js
from . import test_message
diff --git a/addons/im_livechat/tests/test_get_discuss_channel.py b/addons/im_livechat/tests/test_get_discuss_channel.py
index d41dd0be531..f046ccf0353 100644
--- a/addons/im_livechat/tests/test_get_discuss_channel.py
+++ b/addons/im_livechat/tests/test_get_discuss_channel.py
@@ -36,11 +36,13 @@ class TestGetDiscussChannel(TestImLivechatCommon):
'active': True,
'country': [('clear',)],
'id': operator.partner_id.id,
+ 'is_bot': False,
'is_public': False,
'user_livechat_username': 'Michel Operator',
}, {
'active': False,
'id': public_user.partner_id.id,
+ 'is_bot': False,
'is_public': True,
'name': 'Public user',
}], key=lambda m: m['id']))
@@ -58,6 +60,7 @@ class TestGetDiscussChannel(TestImLivechatCommon):
'active': True,
'country': [('clear',)],
'id': operator.partner_id.id,
+ 'is_bot': False,
'is_public': False,
'user_livechat_username': 'Michel Operator',
},
@@ -75,6 +78,7 @@ class TestGetDiscussChannel(TestImLivechatCommon):
'name': 'Belgium',
},
'id': test_user.partner_id.id,
+ 'is_bot': False,
'is_public': False,
'name': 'Roger',
},
@@ -97,6 +101,7 @@ class TestGetDiscussChannel(TestImLivechatCommon):
'active': True,
'country': [('clear',)],
'id': operator.partner_id.id,
+ 'is_bot': False,
'is_public': False,
'user_livechat_username': 'Michel Operator',
},
diff --git a/addons/im_livechat/tests/test_performance.py b/addons/im_livechat/tests/test_performance.py
new file mode 100644
index 00000000000..59e79e1e41d
--- /dev/null
+++ b/addons/im_livechat/tests/test_performance.py
@@ -0,0 +1,20 @@
+from odoo.addons.test_discuss_full.tests.test_performance import TestDiscussFullPerformance
+
+old_method = TestDiscussFullPerformance._get_init_messaging_result
+
+
+def _get_init_messaging_result(self):
+ res = old_method(self)
+ for channel in res['channels']:
+ if not channel['channel']['channel_type'] == 'livechat':
+ continue
+ for channel_member in channel['channel']['channelMembers'][0][1]:
+ channel_member['persona']['partner']['is_bot'] = False
+ return res
+
+def _get_query_count(self):
+ return 79
+
+
+TestDiscussFullPerformance._get_init_messaging_result = _get_init_messaging_result
+TestDiscussFullPerformance._get_query_count = _get_query_count
diff --git a/addons/mail/static/src/core/common/channel_member_service.js b/addons/mail/static/src/core/common/channel_member_service.js
index f9b769e6974..1794ecef424 100644
--- a/addons/mail/static/src/core/common/channel_member_service.js
+++ b/addons/mail/static/src/core/common/channel_member_service.js
@@ -63,6 +63,14 @@ export class ChannelMemberService {
break;
}
}
+
+ /**
+ * @param {import("@mail/core/common/channel_member_model").ChannelMember} member
+ * @returns {string}
+ */
+ getName(member) {
+ return member.persona.nameOrDisplayName;
+ }
}
export const channelMemberService = {
diff --git a/addons/mail/static/src/core/common/thread_model.js b/addons/mail/static/src/core/common/thread_model.js
index f0192f89bd2..b7e2732204a 100644
--- a/addons/mail/static/src/core/common/thread_model.js
+++ b/addons/mail/static/src/core/common/thread_model.js
@@ -224,18 +224,23 @@ export class Thread {
return this.name;
}
- /** @type {import("@mail/core/common/persona_model").Persona|undefined} */
- get correspondent() {
- if (this.type === "channel") {
- return undefined;
- }
- const correspondents = this.channelMembers
+ /** @type {import("@mail/core/common/persona_model").Persona[]} */
+ get correspondents() {
+ return this.channelMembers
.map((member) => member.persona)
.filter((persona) => !!persona)
.filter(
({ id, type }) =>
id !== (type === "partner" ? this._store.user?.id : this._store.guest?.id)
);
+ }
+
+ /** @type {import("@mail/core/common/persona_model").Persona|undefined} */
+ get correspondent() {
+ if (this.type === "channel") {
+ return undefined;
+ }
+ const correspondents = this.correspondents;
if (correspondents.length === 1) {
// 2 members chat.
return correspondents[0];
diff --git a/addons/mail/static/src/discuss/core/common/channel_member_list.js b/addons/mail/static/src/discuss/core/common/channel_member_list.js
index e57f4143bae..94f65d82c5b 100644
--- a/addons/mail/static/src/discuss/core/common/channel_member_list.js
+++ b/addons/mail/static/src/discuss/core/common/channel_member_list.js
@@ -15,6 +15,7 @@ export class ChannelMemberList extends Component {
setup() {
this.messaging = useMessaging();
this.store = useStore();
+ this.channelMemberService = useService("discuss.channel.member");
this.threadService = useState(useService("mail.thread"));
onWillStart(() => this.threadService.fetchChannelMembers(this.props.thread));
onWillUpdateProps((nextProps) => {
diff --git a/addons/mail/static/src/discuss/core/common/channel_member_list.xml b/addons/mail/static/src/discuss/core/common/channel_member_list.xml
index 1dc218a19c7..8213a2bdbca 100644
--- a/addons/mail/static/src/discuss/core/common/channel_member_list.xml
+++ b/addons/mail/static/src/discuss/core/common/channel_member_list.xml
@@ -32,7 +32,7 @@
t-att-src="threadService.avatarUrl(member.persona, props.thread)"/>
-
+
diff --git a/addons/website_livechat/controllers/main.py b/addons/website_livechat/controllers/main.py
index 7129bd0ffab..fefebd161b6 100644
--- a/addons/website_livechat/controllers/main.py
+++ b/addons/website_livechat/controllers/main.py
@@ -65,7 +65,7 @@ class WebsiteLivechat(LivechatController):
""" Override to use visitor name instead of 'Visitor' whenever a visitor start a livechat session. """
visitor_sudo = request.env['website.visitor']._get_visitor_from_request()
if visitor_sudo:
- anonymous_name = visitor_sudo.with_context(lang=visitor_sudo.lang_id.code).display_name
+ anonymous_name = _('Visitor #%s') % visitor_sudo.id
return super(WebsiteLivechat, self).get_session(channel_id, anonymous_name, previous_operator_id=previous_operator_id, chatbot_script_id=chatbot_script_id, persisted=persisted, **kwargs)
def _livechat_templates_get(self):
diff --git a/addons/website_livechat/models/discuss_channel.py b/addons/website_livechat/models/discuss_channel.py
index d78e72c27d1..f050c9133fd 100644
--- a/addons/website_livechat/models/discuss_channel.py
+++ b/addons/website_livechat/models/discuss_channel.py
@@ -64,7 +64,7 @@ class DiscussChannel(models.Model):
message = _("""%s started a conversation with %s.
The chat request has been canceled.""") % (name, operator or _('an operator'))
else:
- message = _('%s left the conversation.', name)
+ message = _('Visitor %s left the conversation.', f"#{self.livechat_visitor_id.id}" if self.livechat_visitor_id else '')
return message
diff --git a/addons/website_livechat/static/src/core/persona_model_patch.js b/addons/website_livechat/static/src/core/persona_model_patch.js
index 15666e72b0e..c696424318a 100644
--- a/addons/website_livechat/static/src/core/persona_model_patch.js
+++ b/addons/website_livechat/static/src/core/persona_model_patch.js
@@ -1,14 +1,21 @@
/** @odoo-module */
import { Persona } from "@mail/core/common/persona_model";
+import { _t } from "@web/core/l10n/translation";
import { patch } from "@web/core/utils/patch";
+import { sprintf } from "@web/core/utils/strings";
patch(Persona.prototype, "website_livechat", {
get countryFlagUrl() {
- return this.country
- ? `/base/static/img/country_flags/${encodeURIComponent(
- this.country.code.toLowerCase()
- )}.png`
+ const country = this.partner?.country ?? this.country;
+ return country
+ ? `/base/static/img/country_flags/${encodeURIComponent(country.code.toLowerCase())}.png`
: undefined;
},
+ get nameOrDisplayName() {
+ if (this.type === "visitor" && !this.name) {
+ return sprintf(_t("Visitor #%s"), this.id);
+ }
+ return this._super();
+ },
});
diff --git a/addons/website_livechat/static/src/core_ui/thread_patch.xml b/addons/website_livechat/static/src/core_ui/thread_patch.xml
index 8bb512bd278..67276467759 100644
--- a/addons/website_livechat/static/src/core_ui/thread_patch.xml
+++ b/addons/website_livechat/static/src/core_ui/thread_patch.xml
@@ -11,7 +11,7 @@
-
![]()
+
diff --git a/addons/website_livechat/static/tests/thread_patch_tests.js b/addons/website_livechat/static/tests/thread_patch_tests.js
index 678dca51ac4..78a21055452 100644
--- a/addons/website_livechat/static/tests/thread_patch_tests.js
+++ b/addons/website_livechat/static/tests/thread_patch_tests.js
@@ -9,14 +9,16 @@ QUnit.test("Rendering of visitor banner", async (assert) => {
const countryId = pyEnv["res.country"].create({ code: "BE" });
const visitorId = pyEnv["website.visitor"].create({
country_id: countryId,
- display_name: "Visitor #11",
history: "Home → Contact",
is_connected: true,
lang_name: "English",
website_name: "General website",
});
+ pyEnv["website.visitor"].write([visitorId], {
+ display_name: `Visitor #${visitorId}`,
+ });
const channelId = pyEnv["discuss.channel"].create({
- anonymous_name: "Website Visitor 11",
+ anonymous_name: `Visitor #${visitorId}`,
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
@@ -34,7 +36,7 @@ QUnit.test("Rendering of visitor banner", async (assert) => {
);
assert.containsOnce($, ".o-website_livechat-VisitorBanner .o-mail-ImStatus");
assert.containsOnce($, ".o_country_flag[data-src='/base/static/img/country_flags/be.png']");
- assert.containsOnce($, "span:contains(Visitor #11)");
+ assert.containsOnce($, `.o-website_livechat-VisitorBanner span:contains(Visitor #${visitorId})`);
assert.containsOnce($, "span:contains(English)");
assert.containsOnce($, "span>:contains(General website)");
assert.containsOnce($, "span:contains(Home → Contact)");
diff --git a/addons/website_livechat/tests/test_livechat_basic_flow.py b/addons/website_livechat/tests/test_livechat_basic_flow.py
index 14935cb92ce..89355c89c83 100644
--- a/addons/website_livechat/tests/test_livechat_basic_flow.py
+++ b/addons/website_livechat/tests/test_livechat_basic_flow.py
@@ -49,7 +49,7 @@ class TestLivechatBasicFlowHttpCase(tests.HttpCase, TestLivechatCommon):
channel_1 = self.env['discuss.channel'].search([('livechat_visitor_id', '=', self.visitor.id), ('livechat_active', '=', True)], limit=1)
# Check Channel naming
- self.assertEqual(channel_1.name, "%s %s" % (self.visitor.display_name, self.operator.livechat_username))
+ self.assertEqual(channel_1.name, "%s %s" % (f'Visitor #{channel_1.livechat_visitor_id.id}', self.operator.livechat_username))
channel_1.unlink()
# Remove livechat_username
@@ -67,7 +67,7 @@ class TestLivechatBasicFlowHttpCase(tests.HttpCase, TestLivechatCommon):
channel_2 = self.env['discuss.channel'].search([('livechat_visitor_id', '=', self.visitor.id), ('livechat_active', '=', True)], limit=1)
# Check Channel naming
- self.assertEqual(channel_2.name, "%s %s" % (self.visitor.display_name, self.operator.name))
+ self.assertEqual(channel_2.name, "%s %s" % (f'Visitor #{channel_2.livechat_visitor_id.id}', self.operator.name))
def test_basic_flow_with_rating(self):
channel = self._common_basic_flow()
@@ -78,7 +78,7 @@ class TestLivechatBasicFlowHttpCase(tests.HttpCase, TestLivechatCommon):
self.assertEqual(len(channel.message_ids), 4)
self.assertEqual(channel.message_ids[0].author_id, self.env.ref('base.partner_root'), "Odoobot must be the sender of the 'left the conversation' message.")
- self.assertIn("%s left the conversation." % self.visitor.display_name, channel.message_ids[0].body)
+ self.assertIn(f"Visitor #{channel.livechat_visitor_id.id}", channel.message_ids[0].body)
self.assertEqual(channel.livechat_active, False, "The livechat session must be inactive as the visitor sent his feedback.")
def test_basic_flow_without_rating(self):
@@ -88,7 +88,7 @@ class TestLivechatBasicFlowHttpCase(tests.HttpCase, TestLivechatCommon):
channel._close_livechat_session()
self.assertEqual(len(channel.message_ids), 3)
self.assertEqual(channel.message_ids[0].author_id, self.env.ref('base.partner_root'), "Odoobot must be the author the message.")
- self.assertIn("%s left the conversation." % self.visitor.display_name, channel.message_ids[0].body)
+ self.assertIn(f"Visitor #{channel.livechat_visitor_id.id}", channel.message_ids[0].body)
self.assertEqual(channel.livechat_active, False, "The livechat session must be inactive since visitor left the conversation.")
def test_visitor_info_access_rights(self):
@@ -119,7 +119,7 @@ class TestLivechatBasicFlowHttpCase(tests.HttpCase, TestLivechatCommon):
# Check Channel and Visitor naming
self.assertEqual(self.visitor.display_name, "%s #%s" % (_("Website Visitor"), self.visitor.id))
- self.assertEqual(channel.name, "%s %s" % (self.visitor.display_name, self.operator.livechat_username))
+ self.assertEqual(channel.name, "%s %s" % (f'Visitor #{channel.livechat_visitor_id.id}', self.operator.livechat_username))
# Post Message from visitor
self._send_message(channel, self.visitor.display_name, "Message from Visitor")
diff --git a/addons/website_livechat/tests/test_livechat_request.py b/addons/website_livechat/tests/test_livechat_request.py
index 2f827fb57fd..47d7274733d 100644
--- a/addons/website_livechat/tests/test_livechat_request.py
+++ b/addons/website_livechat/tests/test_livechat_request.py
@@ -67,7 +67,7 @@ class TestLivechatRequestHttpCase(tests.HttpCase, TestLivechatCommon):
channel._close_livechat_session()
self.assertEqual(len(channel.message_ids), 3)
self.assertEqual(channel.message_ids[0].author_id, self.env.ref('base.partner_root'), "Odoobot must be the sender of the 'left the conversation' message.")
- self.assertIn("%s left the conversation." % self.visitor.display_name, channel.message_ids[0].body)
+ self.assertIn(f"Visitor #{channel.livechat_visitor_id.id}", channel.message_ids[0].body)
self.assertEqual(channel.livechat_active, False, "The livechat session must be inactive as the visitor sent his feedback.")
return channel