From fac25094c74cec4bd154acc85e69102ac7997a3b Mon Sep 17 00:00:00 2001 From: Juan Ignacio Carreras Date: Wed, 15 Apr 2026 19:15:03 +0000 Subject: [PATCH] [IMP]mail_announcement: Add mail announcement module --- mail_announcement/__init__.py | 1 + mail_announcement/__manifest__.py | 41 ++++ mail_announcement/i18n/es.po | 137 +++++++++++++ mail_announcement/models/__init__.py | 2 + mail_announcement/models/mail_announcement.py | 144 +++++++++++++ .../models/mail_announcement_receipt.py | 35 ++++ .../security/ir.model.access.csv | 5 + .../security/mail_announcement_security.xml | 14 ++ .../views/mail_announcement_views.xml | 193 ++++++++++++++++++ 9 files changed, 572 insertions(+) create mode 100644 mail_announcement/__init__.py create mode 100644 mail_announcement/__manifest__.py create mode 100644 mail_announcement/i18n/es.po create mode 100644 mail_announcement/models/__init__.py create mode 100644 mail_announcement/models/mail_announcement.py create mode 100644 mail_announcement/models/mail_announcement_receipt.py create mode 100644 mail_announcement/security/ir.model.access.csv create mode 100644 mail_announcement/security/mail_announcement_security.xml create mode 100644 mail_announcement/views/mail_announcement_views.xml diff --git a/mail_announcement/__init__.py b/mail_announcement/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/mail_announcement/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/mail_announcement/__manifest__.py b/mail_announcement/__manifest__.py new file mode 100644 index 00000000..5b43cbf8 --- /dev/null +++ b/mail_announcement/__manifest__.py @@ -0,0 +1,41 @@ +############################################################################## +# +# Copyright (C) 2024 ADHOC SA (http://www.adhoc.com.ar) +# All Rights Reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +{ + "name": "Mail Announcement", + "version": "19.0.1.0.0", + "category": "Discuss", + "sequence": 14, + "summary": "Important Communications with Mandatory Read Receipt", + "author": "ADHOC SA", + "website": "www.adhoc.com.ar", + "license": "AGPL-3", + "depends": [ + "mail", + ], + "data": [ + "security/mail_announcement_security.xml", + "security/ir.model.access.csv", + "views/mail_announcement_views.xml", + ], + "demo": [], + "installable": True, + "auto_install": False, + "application": True, +} diff --git a/mail_announcement/i18n/es.po b/mail_announcement/i18n/es.po new file mode 100644 index 00000000..bd6533f8 --- /dev/null +++ b/mail_announcement/i18n/es.po @@ -0,0 +1,137 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_announcement +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-01-01 00:00+0000\n" +"PO-Revision-Date: 2024-01-01 00:00+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: mail_announcement +#: model:ir.model,name:mail_announcement.model_mail_announcement +msgid "Important Announcement" +msgstr "Comunicado Importante" + +#. module: mail_announcement +#: model:ir.model,name:mail_announcement.model_mail_announcement_receipt +msgid "Announcement Read Receipt" +msgstr "Acuse de Recibo" + +#. module: mail_announcement +#: model:ir.model.fields,field_description:mail_announcement.field_mail_announcement__name +msgid "Subject" +msgstr "Asunto" + +#. module: mail_announcement +#: model:ir.model.fields,field_description:mail_announcement.field_mail_announcement__body +msgid "Content" +msgstr "Contenido" + +#. module: mail_announcement +#: model:ir.model.fields,field_description:mail_announcement.field_mail_announcement__author_id +msgid "Author" +msgstr "Autor" + +#. module: mail_announcement +#: model:ir.model.fields,field_description:mail_announcement.field_mail_announcement__channel_id +msgid "Channel/Origin" +msgstr "Canal/Origen" + +#. module: mail_announcement +#: model:ir.model.fields,field_description:mail_announcement.field_mail_announcement__date +msgid "Date" +msgstr "Fecha" + +#. module: mail_announcement +#: model:ir.model.fields.selection,name:mail_announcement.selection__mail_announcement__state__draft +msgid "Draft" +msgstr "Borrador" + +#. module: mail_announcement +#: model:ir.model.fields.selection,name:mail_announcement.selection__mail_announcement__state__sent +msgid "Sent" +msgstr "Enviado" + +#. module: mail_announcement +#: model:ir.model.fields,field_description:mail_announcement.field_mail_announcement__recipient_ids +msgid "Recipients" +msgstr "Destinatarios" + +#. module: mail_announcement +#: model:ir.model.fields,field_description:mail_announcement.field_mail_announcement__receipt_count +msgid "Total Recipients" +msgstr "Total Destinatarios" + +#. module: mail_announcement +#: model:ir.model.fields,field_description:mail_announcement.field_mail_announcement__pending_receipt_count +msgid "Pending" +msgstr "Pendientes" + +#. module: mail_announcement +#: model:ir.model.fields,field_description:mail_announcement.field_mail_announcement__is_pending_for_me +msgid "Pending for Me" +msgstr "Pendiente para Mí" + +#. module: mail_announcement +#: model:ir.model.fields,field_description:mail_announcement.field_mail_announcement__status +msgid "Status" +msgstr "Estado" + +#. module: mail_announcement +#: model:ir.model.fields,field_description:mail_announcement.field_mail_announcement_receipt__announcement_id +msgid "Announcement" +msgstr "Comunicado" + +#. module: mail_announcement +#: model:ir.model.fields,field_description:mail_announcement.field_mail_announcement_receipt__user_id +msgid "User" +msgstr "Usuario" + +#. module: mail_announcement +#: model:ir.model.fields,field_description:mail_announcement.field_mail_announcement_receipt__is_read +msgid "Read" +msgstr "Leído" + +#. module: mail_announcement +#: model:ir.model.fields,field_description:mail_announcement.field_mail_announcement_receipt__read_date +msgid "Read Date" +msgstr "Fecha de Lectura" + +#. module: mail_announcement +#: model_terms:ir.ui.view,arch_db:mail_announcement.view_mail_announcement_form +msgid "Enviar Comunicado" +msgstr "Enviar Comunicado" + +#. module: mail_announcement +#: model_terms:ir.ui.view,arch_db:mail_announcement.view_mail_announcement_form +msgid "✔ Acusar Recibo" +msgstr "✔ Acusar Recibo" + +#. module: mail_announcement +#: model:ir.actions.act_window,name:mail_announcement.action_mail_announcement_pending +msgid "Comunicados Pendientes" +msgstr "Comunicados Pendientes" + +#. module: mail_announcement +#: model:ir.actions.act_window,name:mail_announcement.action_mail_announcement_all +msgid "Todos los Comunicados" +msgstr "Todos los Comunicados" + +#. module: mail_announcement +#: model:ir.ui.menu,name:mail_announcement.menu_mail_announcement_root +msgid "Comunicados" +msgstr "Comunicados" + +#. module: mail_announcement +#: constraint:mail_announcement_receipt:0 +msgid "Each user can only have one read receipt per announcement." +msgstr "Cada usuario solo puede tener un acuse de recibo por comunicado." diff --git a/mail_announcement/models/__init__.py b/mail_announcement/models/__init__.py new file mode 100644 index 00000000..7c85ac39 --- /dev/null +++ b/mail_announcement/models/__init__.py @@ -0,0 +1,2 @@ +from . import mail_announcement +from . import mail_announcement_receipt diff --git a/mail_announcement/models/mail_announcement.py b/mail_announcement/models/mail_announcement.py new file mode 100644 index 00000000..7b433983 --- /dev/null +++ b/mail_announcement/models/mail_announcement.py @@ -0,0 +1,144 @@ +from odoo import api, fields, models + + +class MailAnnouncement(models.Model): + _name = "mail.announcement" + _description = "Important Announcement" + _order = "date desc" + + name = fields.Char( + string="Subject", + required=True, + ) + body = fields.Html( + string="Content", + required=True, + sanitize=True, + ) + author_id = fields.Many2one( + comodel_name="res.users", + string="Author", + default=lambda self: self.env.user, + required=True, + ondelete="restrict", + ) + channel_id = fields.Many2one( + comodel_name="discuss.channel", + string="Channel/Origin", + ondelete="set null", + ) + date = fields.Datetime( + string="Date", + default=fields.Datetime.now, + required=True, + ) + state = fields.Selection( + selection=[("draft", "Draft"), ("sent", "Sent")], + string="Status", + default="draft", + required=True, + readonly=True, + copy=False, + ) + recipient_ids = fields.Many2many( + comodel_name="res.users", + relation="mail_announcement_res_users_rel", + column1="announcement_id", + column2="user_id", + string="Recipients", + ) + receipt_ids = fields.One2many( + comodel_name="mail.announcement.receipt", + inverse_name="announcement_id", + string="Read Receipts", + ) + receipt_count = fields.Integer( + compute="_compute_receipt_stats", + string="Total Recipients", + ) + pending_receipt_count = fields.Integer( + compute="_compute_receipt_stats", + string="Pending", + ) + is_pending_for_me = fields.Boolean( + compute="_compute_is_pending_for_me", + search="_search_is_pending_for_me", + string="Pending for Me", + ) + + @api.depends("receipt_ids.is_read") + def _compute_receipt_stats(self): + for rec in self: + rec.receipt_count = len(rec.receipt_ids) + rec.pending_receipt_count = len(rec.receipt_ids.filtered(lambda r: not r.is_read)) + + @api.depends("receipt_ids.is_read", "receipt_ids.user_id") + def _compute_is_pending_for_me(self): + user = self.env.user + for rec in self: + my_receipt = rec.receipt_ids.filtered(lambda r: r.user_id == user) + rec.is_pending_for_me = bool(my_receipt) and not my_receipt[:1].is_read + + def _search_is_pending_for_me(self, operator, value): + user = self.env.user + pending_ids = ( + self.env["mail.announcement.receipt"] + .search([("user_id", "=", user.id), ("is_read", "=", False)]) + .announcement_id.ids + ) + if (operator == "=" and value) or (operator == "!=" and not value): + return [("id", "in", pending_ids)] + return [("id", "not in", pending_ids)] + + @api.onchange("channel_id") + def _onchange_channel_id(self): + if self.channel_id: + partner_ids = self.channel_id.channel_member_ids.partner_id.ids + channel_users = self.env["res.users"].search([("partner_id", "in", partner_ids), ("share", "=", False)]) + self.recipient_ids = channel_users + else: + self.recipient_ids = False + + def _get_recipients(self): + """Return the full set of users that must receive this announcement.""" + self.ensure_one() + recipients = self.recipient_ids + if self.channel_id: + partner_ids = self.channel_id.channel_member_ids.partner_id.ids + channel_users = self.env["res.users"].search([("partner_id", "in", partner_ids), ("share", "=", False)]) + recipients = recipients | channel_users + return recipients + + def action_send(self): + self.ensure_one() + if self.state == "sent": + return + recipients = self._get_recipients() + existing_user_ids = self.receipt_ids.user_id.ids + vals_list = [ + { + "announcement_id": self.id, + "user_id": user.id, + "is_read": False, + } + for user in recipients + if user.id not in existing_user_ids + ] + if vals_list: + self.env["mail.announcement.receipt"].sudo().create(vals_list) + self.write({"state": "sent"}) + + def action_mark_as_read(self): + for rec in self: + receipt = rec.receipt_ids.filtered(lambda r: r.user_id == self.env.user) + if receipt: + receipt.write({"is_read": True, "read_date": fields.Datetime.now()}) + else: + self.env["mail.announcement.receipt"].create( + { + "announcement_id": rec.id, + "user_id": self.env.user.id, + "is_read": True, + "read_date": fields.Datetime.now(), + } + ) diff --git a/mail_announcement/models/mail_announcement_receipt.py b/mail_announcement/models/mail_announcement_receipt.py new file mode 100644 index 00000000..0d7eae48 --- /dev/null +++ b/mail_announcement/models/mail_announcement_receipt.py @@ -0,0 +1,35 @@ +from odoo import fields, models + + +class MailAnnouncementReceipt(models.Model): + _name = "mail.announcement.receipt" + _description = "Announcement Read Receipt" + _order = "read_date desc, id desc" + + announcement_id = fields.Many2one( + comodel_name="mail.announcement", + string="Announcement", + required=True, + ondelete="cascade", + index=True, + ) + user_id = fields.Many2one( + comodel_name="res.users", + string="User", + required=True, + ondelete="cascade", + index=True, + ) + is_read = fields.Boolean( + string="Read", + default=False, + ) + read_date = fields.Datetime( + string="Read Date", + readonly=True, + ) + + _unique_user_announcement = models.Constraint( + "UNIQUE(announcement_id, user_id)", + "Each user can only have one read receipt per announcement.", + ) diff --git a/mail_announcement/security/ir.model.access.csv b/mail_announcement/security/ir.model.access.csv new file mode 100644 index 00000000..68bd9c79 --- /dev/null +++ b/mail_announcement/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_mail_announcement_user,mail.announcement.user,model_mail_announcement,base.group_user,1,1,1,0 +access_mail_announcement_system,mail.announcement.system,model_mail_announcement,base.group_system,1,1,1,1 +access_mail_announcement_receipt_user,mail.announcement.receipt.user,model_mail_announcement_receipt,base.group_user,1,1,1,0 +access_mail_announcement_receipt_system,mail.announcement.receipt.system,model_mail_announcement_receipt,base.group_system,1,1,1,1 diff --git a/mail_announcement/security/mail_announcement_security.xml b/mail_announcement/security/mail_announcement_security.xml new file mode 100644 index 00000000..aa6715db --- /dev/null +++ b/mail_announcement/security/mail_announcement_security.xml @@ -0,0 +1,14 @@ + + + + + Announcement Receipt: own records only (write/delete) + + [('user_id', '=', user.id)] + + + + + + + diff --git a/mail_announcement/views/mail_announcement_views.xml b/mail_announcement/views/mail_announcement_views.xml new file mode 100644 index 00000000..00984d54 --- /dev/null +++ b/mail_announcement/views/mail_announcement_views.xml @@ -0,0 +1,193 @@ + + + + + + + mail.announcement.list + mail.announcement + + + + + + + + + + +