diff --git a/mail_channel_notify_tag/README.rst b/mail_channel_notify_tag/README.rst new file mode 100644 index 00000000000..a7a7b54d551 --- /dev/null +++ b/mail_channel_notify_tag/README.rst @@ -0,0 +1,60 @@ +.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg + :alt: License: LGPL-3 + +Mail Channel Notify Tag +======================= + +This module re-enables sending messages to channels and their members +when the channels are mentioned in the messages of the records. + +Message in the record: + + .. image:: ../mail_channel_notify_tag/static/description/record.png + :alt: Message in the record + :width: 1200px + +Message in the channel: + + .. image:: ../mail_channel_notify_tag/static/description/channel.png + :alt: Message in the channel + :width: 1200px + +Email: + + .. image:: ../mail_channel_notify_tag/static/description/email.png + :alt: Email + :width: 1200px + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed `feedback +`_. + +Contributors +------------ + +* Luis González +* Rolando Duarte + +Maintainer +---------- + +.. image:: https://www.vauxoo.com/logo.png + :alt: Vauxoo + :target: https://vauxoo.com + +This module is maintained by Vauxoo. + +A latinamerican company that provides training, coaching, +development and implementation of enterprise management +systems and bases its entire operation strategy in the use +of Open Source Software and its main product is odoo. + +To contribute to this module, please visit https://www.vauxoo.com. diff --git a/mail_channel_notify_tag/__init__.py b/mail_channel_notify_tag/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/mail_channel_notify_tag/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/mail_channel_notify_tag/__manifest__.py b/mail_channel_notify_tag/__manifest__.py new file mode 100644 index 00000000000..8944cc89e0f --- /dev/null +++ b/mail_channel_notify_tag/__manifest__.py @@ -0,0 +1,26 @@ +{ + "name": "Mail Channel Notify Tag", + "summary": "Module to notify the channels when they are mentioned in the chatter", + "author": "Vauxoo", + "website": "https://www.vauxoo.com", + "license": "LGPL-3", + "category": "Productivity/Discuss", + "version": "19.0.1.0.0", + "depends": [ + "mail", + ], + "data": [ + "views/mail_templates.xml", + ], + "assets": { + "web.assets_backend": [ + "mail_channel_notify_tag/static/src/core/store_service_patch.js", + ], + "mail.assets_discuss_public": [ + "mail_channel_notify_tag/static/src/core/store_service_patch.js", + ], + }, + "installable": True, + "auto_install": False, + "application": False, +} diff --git a/mail_channel_notify_tag/i18n/es.po b/mail_channel_notify_tag/i18n/es.po new file mode 100644 index 00000000000..f8fbce73991 --- /dev/null +++ b/mail_channel_notify_tag/i18n/es.po @@ -0,0 +1,43 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_channel_notify_tag +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-04-29 04:37+0000\n" +"PO-Revision-Date: 2026-04-29 04:37+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: mail_channel_notify_tag +#: model:ir.model.fields,field_description:mail_channel_notify_tag.field_mail_followers__display_name +#: model:ir.model.fields,field_description:mail_channel_notify_tag.field_mail_thread__display_name +msgid "Display Name" +msgstr "Nombre para mostrar" + +#. module: mail_channel_notify_tag +#: model:ir.model,name:mail_channel_notify_tag.model_mail_followers +msgid "Document Followers" +msgstr "Seguidores del documento" + +#. module: mail_channel_notify_tag +#: model:ir.model,name:mail_channel_notify_tag.model_mail_thread +msgid "Email Thread" +msgstr "Hilo de correo electrónico" + +#. module: mail_channel_notify_tag +#: model:ir.model.fields,field_description:mail_channel_notify_tag.field_mail_followers__id +#: model:ir.model.fields,field_description:mail_channel_notify_tag.field_mail_thread__id +msgid "ID" +msgstr "" + +#. module: mail_channel_notify_tag +#: model_terms:ir.ui.view,arch_db:mail_channel_notify_tag.mail_channel_message_repost +msgid "on" +msgstr "en" diff --git a/mail_channel_notify_tag/models/__init__.py b/mail_channel_notify_tag/models/__init__.py new file mode 100644 index 00000000000..7180a1928dc --- /dev/null +++ b/mail_channel_notify_tag/models/__init__.py @@ -0,0 +1,2 @@ +from . import mail_followers +from . import mail_thread diff --git a/mail_channel_notify_tag/models/mail_followers.py b/mail_channel_notify_tag/models/mail_followers.py new file mode 100644 index 00000000000..d0913c800a5 --- /dev/null +++ b/mail_channel_notify_tag/models/mail_followers.py @@ -0,0 +1,14 @@ +from odoo import models + + +class MailFollowers(models.Model): + _inherit = "mail.followers" + + def _get_recipient_data(self, records, message_type, subtype_id, pids=None): + """Add channel members as email recipients when channels are mentioned.""" + channel_ids = self.env.context.get("mentioned_channel_ids") + if not channel_ids: + return super()._get_recipient_data(records, message_type, subtype_id, pids) + pids = set(pids) if pids else set() + pids |= set(self.env["discuss.channel"].sudo().browse(channel_ids).channel_partner_ids.ids) + return super()._get_recipient_data(records, message_type, subtype_id, pids) diff --git a/mail_channel_notify_tag/models/mail_thread.py b/mail_channel_notify_tag/models/mail_thread.py new file mode 100644 index 00000000000..1d2618af51b --- /dev/null +++ b/mail_channel_notify_tag/models/mail_thread.py @@ -0,0 +1,40 @@ +from odoo import models + + +class MailThread(models.AbstractModel): + _inherit = "mail.thread" + + def _get_allowed_message_params(self): + """Allow channel_ids to pass through the controller whitelist.""" + return super()._get_allowed_message_params() | {"channel_ids"} + + def message_post(self, *, body="", **kwargs): + """Extract channel_ids before they reach the parent validation.""" + channel_ids = kwargs.pop("channel_ids", []) + self = self.with_context(mentioned_channel_ids=channel_ids) + return super().message_post(body=body, **kwargs) + + def _message_post_after_hook(self, message, msg_values): + """Repost message to mentioned channels after the message is created.""" + result = super()._message_post_after_hook(message, msg_values) + channel_ids = self.env.context.get("mentioned_channel_ids") + if channel_ids: + self._notify_mentioned_channels(message, channel_ids) + return result + + def _notify_mentioned_channels(self, message, channel_ids): + """Repost the message to mentioned channels for Discuss visibility. + + Email notifications to channel members are handled separately by + mail_followers._get_recipient_data on the source record. + """ + channels = self.env["discuss.channel"].sudo().browse(channel_ids).exists() + if not channels: + return + template = self.env.ref("mail_channel_notify_tag.mail_channel_message_repost") + channels.message_post_with_source( + template, + render_values={"message": message, "source": self}, + message_type="comment", + subtype_xmlid="mail.mt_comment", + ) diff --git a/mail_channel_notify_tag/static/description/channel.png b/mail_channel_notify_tag/static/description/channel.png new file mode 100644 index 00000000000..ffd32410965 Binary files /dev/null and b/mail_channel_notify_tag/static/description/channel.png differ diff --git a/mail_channel_notify_tag/static/description/email.png b/mail_channel_notify_tag/static/description/email.png new file mode 100644 index 00000000000..13a45a91f82 Binary files /dev/null and b/mail_channel_notify_tag/static/description/email.png differ diff --git a/mail_channel_notify_tag/static/description/record.png b/mail_channel_notify_tag/static/description/record.png new file mode 100644 index 00000000000..7e567fcd16a Binary files /dev/null and b/mail_channel_notify_tag/static/description/record.png differ diff --git a/mail_channel_notify_tag/static/src/core/store_service_patch.js b/mail_channel_notify_tag/static/src/core/store_service_patch.js new file mode 100644 index 00000000000..6876caa117f --- /dev/null +++ b/mail_channel_notify_tag/static/src/core/store_service_patch.js @@ -0,0 +1,13 @@ +import {Store} from "@mail/core/common/store_service"; +import {patch} from "@web/core/utils/patch"; + +patch(Store.prototype, { + async getMessagePostParams({postData}) { + const params = await super.getMessagePostParams(...arguments); + const channelIds = (postData.mentionedChannels || []).map((channel) => channel.id); + if (channelIds.length) { + params.post_data.channel_ids = channelIds; + } + return params; + }, +}); diff --git a/mail_channel_notify_tag/tests/__init__.py b/mail_channel_notify_tag/tests/__init__.py new file mode 100644 index 00000000000..d10f37dba7c --- /dev/null +++ b/mail_channel_notify_tag/tests/__init__.py @@ -0,0 +1 @@ +from . import test_mail_thread diff --git a/mail_channel_notify_tag/tests/test_mail_thread.py b/mail_channel_notify_tag/tests/test_mail_thread.py new file mode 100644 index 00000000000..2274c7e9c07 --- /dev/null +++ b/mail_channel_notify_tag/tests/test_mail_thread.py @@ -0,0 +1,55 @@ +from odoo.tests import TransactionCase, tagged + + +@tagged("mail_thread") +class TestMailThread(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.member_user = ( + cls.env["res.users"] + .with_context(mail_create_nolog=True) + .create( + { + "name": "Channel Member", + "login": "channel_member_test", + "email": "member@test.com", + "notification_type": "email", + } + ) + ) + cls.member_partner = cls.member_user.partner_id + cls.channel = cls.env["discuss.channel"].create( + { + "name": "Test Notify Channel", + "channel_type": "channel", + } + ) + cls.channel.add_members(partner_ids=cls.member_partner.ids) + cls.partner = cls.env.user.partner_id + + def test_01_message_post_without_channel_ids(self): + """Messages without channel_ids should not appear in the channel.""" + initial_count = len(self.channel.message_ids) + self.partner.message_post(subject="No Channel", body="No repost expected") + self.assertEqual(len(self.channel.message_ids), initial_count) + + def test_02_message_post_with_channel_ids(self): + """Messages with channel_ids should be reposted to the mentioned channel.""" + self.partner.message_post( + subject="Test Channel IDs", + body="Test Message", + channel_ids=self.channel.ids, + ) + msg = self.channel.message_ids[0] + self.assertIn("Test Message", msg.body) + + def test_03_email_notification_queued(self): + """Channel members with email preference should get a queued mail.mail.""" + self.partner.message_post( + subject="Email Test", + body="Email Notification", + channel_ids=self.channel.ids, + ) + mail = self.env["mail.mail"].search([("recipient_ids", "in", self.member_partner.ids)]) + self.assertTrue(mail, "Email notification should be queued for channel member") diff --git a/mail_channel_notify_tag/views/mail_templates.xml b/mail_channel_notify_tag/views/mail_templates.xml new file mode 100644 index 00000000000..6aa1ac7a7f8 --- /dev/null +++ b/mail_channel_notify_tag/views/mail_templates.xml @@ -0,0 +1,15 @@ + + + + + +