diff --git a/mail_notify_employee_leave/README.rst b/mail_notify_employee_leave/README.rst new file mode 100644 index 000000000..08a53ceb8 --- /dev/null +++ b/mail_notify_employee_leave/README.rst @@ -0,0 +1,84 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +========================== +Mail Notify Employee Leave +========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:5f7b7a7481f72c60524273bb94fd697a975d3547b0b94c1f823902dfdaf99387 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmail-lightgray.png?logo=github + :target: https://github.com/OCA/mail/tree/17.0/mail_notify_employee_leave + :alt: OCA/mail +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/mail-17-0/mail-17-0-mail_notify_employee_leave + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/mail&target_branch=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module automatically notifies users when they mention or assign a +colleague who is out of office. The notification includes the expected +return date of the absent user and is sent only once per day per user. + +**Table of contents** + +.. contents:: + :local: + +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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* ForgeFlow + +Contributors +------------ + +- `ForgeFlow `__: + + - Guillermo Navas + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/mail `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/mail_notify_employee_leave/__init__.py b/mail_notify_employee_leave/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/mail_notify_employee_leave/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/mail_notify_employee_leave/__manifest__.py b/mail_notify_employee_leave/__manifest__.py new file mode 100644 index 000000000..4dd36844a --- /dev/null +++ b/mail_notify_employee_leave/__manifest__.py @@ -0,0 +1,14 @@ +# Copyright 2026 ForgeFlow S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Mail Notify Employee Leave", + "summary": """ + Notifies users when they mention or assign someone who is out of office. + """, + "version": "17.0.1.0.0", + "license": "AGPL-3", + "author": "ForgeFlow, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/mail", + "depends": ["mail", "hr_holidays"], +} diff --git a/mail_notify_employee_leave/i18n/es.po b/mail_notify_employee_leave/i18n/es.po new file mode 100644 index 000000000..208c39782 --- /dev/null +++ b/mail_notify_employee_leave/i18n/es.po @@ -0,0 +1,58 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_notify_employee_leave +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-03-05 10:44+0000\n" +"PO-Revision-Date: 2026-03-05 10:44+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_notify_employee_leave +#. odoo-python +#: code:addons/mail_notify_employee_leave/models/mail_thread.py:0 +#, python-format +msgid "%(name)s is out of office, expected back on %(date)s." +msgstr "%(name)s estará fuera de la oficina hasta el %(date)s." + +#. module: mail_notify_employee_leave +#: model:ir.model.fields,field_description:mail_notify_employee_leave.field_hr_employee__absence_notified_date +msgid "Date of Last Absence Notification" +msgstr "Fecha de la última notificación" + +#. module: mail_notify_employee_leave +#: model:ir.model.fields,help:mail_notify_employee_leave.field_hr_employee__absence_notified_date +msgid "Date of the last notification record" +msgstr "Fecha del último registro de notificaciones" + +#. module: mail_notify_employee_leave +#: model:ir.model,name:mail_notify_employee_leave.model_mail_thread +msgid "Email Thread" +msgstr "Hilo de correo electrónico" + +#. module: mail_notify_employee_leave +#: model:ir.model,name:mail_notify_employee_leave.model_hr_employee +msgid "Employee" +msgstr "Empleado" + +#. module: mail_notify_employee_leave +#: model:ir.model,name:mail_notify_employee_leave.model_hr_leave +msgid "Time Off" +msgstr "Ausencias" + +#. module: mail_notify_employee_leave +#: model:ir.model.fields,field_description:mail_notify_employee_leave.field_hr_employee__absence_notified_user_ids +msgid "Users Notified Today" +msgstr "Usuarios notificados hoy" + +#. module: mail_notify_employee_leave +#: model:ir.model.fields,help:mail_notify_employee_leave.field_hr_employee__absence_notified_user_ids +msgid "Users who have already been notified today about this absence" +msgstr "Usuarios que ya fueron notificados hoy sobre esta ausencia" diff --git a/mail_notify_employee_leave/models/__init__.py b/mail_notify_employee_leave/models/__init__.py new file mode 100644 index 000000000..c99dbc1e2 --- /dev/null +++ b/mail_notify_employee_leave/models/__init__.py @@ -0,0 +1,3 @@ +from . import mail_thread +from . import hr_leave +from . import hr_employee diff --git a/mail_notify_employee_leave/models/hr_employee.py b/mail_notify_employee_leave/models/hr_employee.py new file mode 100644 index 000000000..89363bd29 --- /dev/null +++ b/mail_notify_employee_leave/models/hr_employee.py @@ -0,0 +1,19 @@ +from odoo import fields, models + + +class HrEmployee(models.Model): + _inherit = "hr.employee" + + absence_notified_user_ids = fields.Many2many( + "res.users", + string="Users Notified Today", + readonly=True, + copy=False, + help="Users who have already been notified today about this absence", + ) + absence_notified_date = fields.Date( + string="Date of Last Absence Notification", + readonly=True, + copy=False, + help="Date of the last notification record", + ) diff --git a/mail_notify_employee_leave/models/hr_leave.py b/mail_notify_employee_leave/models/hr_leave.py new file mode 100644 index 000000000..0688f8543 --- /dev/null +++ b/mail_notify_employee_leave/models/hr_leave.py @@ -0,0 +1,17 @@ +from odoo import models + + +class HrLeave(models.Model): + _inherit = "hr.leave" + + def _validate_leave_request(self): + """ + Override _validate_leave_request to skip absence notifications when + approving leaves. The context flag 'skip_absence_notification' prevents + _notify_thread from sending messages, + """ + if not self.env.context.get("skip_absence_notification"): + return super( + HrLeave, self.with_context(skip_absence_notification=True) + )._validate_leave_request() + return super()._validate_leave_request() diff --git a/mail_notify_employee_leave/models/mail_thread.py b/mail_notify_employee_leave/models/mail_thread.py new file mode 100644 index 000000000..66fe614e8 --- /dev/null +++ b/mail_notify_employee_leave/models/mail_thread.py @@ -0,0 +1,61 @@ +from datetime import timedelta + +from odoo import _, fields, models + + +class MailThread(models.AbstractModel): + _inherit = "mail.thread" + + def _notify_thread(self, message, msg_vals=False, **kwargs): + """ + Alert the current user when a partner affected by the message is linked + is out of office. It ensures that each absent employee triggers only + one notification per user per day sending an internal message with the + employee's expected return date. + """ + recipients_data = super()._notify_thread(message, msg_vals=msg_vals, **kwargs) + + if self.env.context.get("skip_absence_notification"): + return recipients_data + + affected_partner_ids = msg_vals.get("partner_ids", []) if msg_vals else [] + + today = fields.Date.today() + + for partner_id in affected_partner_ids: + partner = self.env["res.partner"].browse(partner_id) + employee = self.env["hr.employee"].search( + [("user_id.partner_id", "=", partner.id)], limit=1 + ) + if employee and employee.is_absent: + if employee.absence_notified_date != today: + employee.absence_notified_user_ids = [(5, 0, 0)] + employee.absence_notified_date = today + + if self.env.user not in employee.absence_notified_user_ids: + leave_date = fields.Date.from_string(employee.leave_date_to) + back_date = self.add_working_day(leave_date) + body = _( + "%(name)s is out of office, expected back on %(date)s." + ) % { + "name": employee.name, + "date": fields.Date.to_string(back_date), + } + self.message_notify( + partner_ids=[self.env.user.partner_id.id], + body=body, + subject="User out of office", + subtype_xmlid="mail.mt_note", + notify_author=True, + ) + employee.absence_notified_user_ids = [(4, self.env.user.id)] + + return recipients_data + + def add_working_day(self, date_obj): + next_day = date_obj + timedelta(days=1) + if next_day.weekday() == 5: + next_day += timedelta(days=2) + elif next_day.weekday() == 6: + next_day += timedelta(days=1) + return next_day diff --git a/mail_notify_employee_leave/pyproject.toml b/mail_notify_employee_leave/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/mail_notify_employee_leave/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/mail_notify_employee_leave/readme/CONTRIBUTORS.md b/mail_notify_employee_leave/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..2f1465d02 --- /dev/null +++ b/mail_notify_employee_leave/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [ForgeFlow](https://www.forgeflow.com): + - Guillermo Navas \<\> diff --git a/mail_notify_employee_leave/readme/DESCRIPTION.md b/mail_notify_employee_leave/readme/DESCRIPTION.md new file mode 100644 index 000000000..5e9f4bcf0 --- /dev/null +++ b/mail_notify_employee_leave/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +This module automatically notifies users when they mention or assign +a colleague who is out of office. The notification includes the expected +return date of the absent user and is sent only once per day per user. \ No newline at end of file diff --git a/mail_notify_employee_leave/static/description/index.html b/mail_notify_employee_leave/static/description/index.html new file mode 100644 index 000000000..a36522900 --- /dev/null +++ b/mail_notify_employee_leave/static/description/index.html @@ -0,0 +1,434 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Mail Notify Employee Leave

+ +

Beta License: AGPL-3 OCA/mail Translate me on Weblate Try me on Runboat

+

This module automatically notifies users when they mention or assign a +colleague who is out of office. The notification includes the expected +return date of the absent user and is sent only once per day per user.

+

Table of contents

+ +
+

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 to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • ForgeFlow
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/mail project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+
+ + diff --git a/mail_notify_employee_leave/tests/__init__.py b/mail_notify_employee_leave/tests/__init__.py new file mode 100644 index 000000000..ddabba994 --- /dev/null +++ b/mail_notify_employee_leave/tests/__init__.py @@ -0,0 +1 @@ +from . import test_mail_notify_employee_leave diff --git a/mail_notify_employee_leave/tests/test_mail_notify_employee_leave.py b/mail_notify_employee_leave/tests/test_mail_notify_employee_leave.py new file mode 100644 index 000000000..98eeabbe9 --- /dev/null +++ b/mail_notify_employee_leave/tests/test_mail_notify_employee_leave.py @@ -0,0 +1,78 @@ +# Copyright 2026 ForgeFlow S.L. +# (http://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields +from odoo.tests.common import TransactionCase + + +class TestNotifyEmployeeLeave(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.user = cls.env["res.users"].create( + { + "name": "Test User", + "login": "test_user", + "email": "test@example.com", + "notification_type": "inbox", + } + ) + + cls.absent_user = cls.env["res.users"].create( + { + "name": "Absent User", + "login": "absent_user", + "email": "absent@example.com", + "notification_type": "inbox", + } + ) + + cls.partner = cls.absent_user.partner_id + + cls.employee = cls.env["hr.employee"].create( + { + "name": "Absent Employee", + "user_id": cls.absent_user.id, + } + ) + + cls.record = cls.env["res.partner"].create({"name": "Test Record"}) + + def test_no_notify_when_user_not_absent(self): + self.employee.is_absent = False + + self.record.with_user(self.user).message_post( + body="Test message", partner_ids=[self.partner.id] + ) + + self.assertNotIn(self.user, self.employee.absence_notified_user_ids) + + def test_notify_when_user_absent(self): + self.employee.is_absent = True + self.employee.leave_date_to = fields.Date.today() + + self.record.with_user(self.user).message_post( + body="Test message", partner_ids=[self.partner.id] + ) + + self.assertIn(self.user, self.employee.absence_notified_user_ids) + self.assertEqual(self.employee.absence_notified_date, fields.Date.today()) + + def test_only_one_notification_per_day(self): + self.employee.is_absent = True + self.employee.leave_date_to = fields.Date.today() + + self.record.with_user(self.user).message_post( + body="Test message 1", partner_ids=[self.partner.id] + ) + + self.record.with_user(self.user).message_post( + body="Test message 2", partner_ids=[self.partner.id] + ) + + notified_users = self.employee.absence_notified_user_ids + self.assertEqual(len(notified_users), 1) + self.assertIn(self.user, notified_users) + self.assertEqual(self.employee.absence_notified_date, fields.Date.today())