diff --git a/calendar_public_holiday/README.rst b/calendar_public_holiday/README.rst new file mode 100644 index 00000000..392f4139 --- /dev/null +++ b/calendar_public_holiday/README.rst @@ -0,0 +1,91 @@ +======================== +Calendar Holidays Public +======================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:323fe101af3ba04c9322fb644c273896c1fb66484ed662a3ca0984279d457570 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/licence-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%2Fcalendar-lightgray.png?logo=github + :target: https://github.com/OCA/calendar/tree/18.0/calendar_public_holiday + :alt: OCA/calendar +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/calendar-18-0/calendar-18-0-calendar_public_holiday + :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/calendar&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module handles public holidays. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +For adding public holidays: + +1. Go to the menu *Calendar > Public Holidays > Public Holidays*. +2. Create your public holidays. + +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 +------- + +* Camptocamp + +Contributors +------------ + +- [Trobz](https://trobz.com): + + - Do Anh Duy <> + +Other credits +------------- + +The creation of this module was financially supported by Camptocamp + +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/calendar `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/calendar_public_holiday/__init__.py b/calendar_public_holiday/__init__.py new file mode 100644 index 00000000..aee8895e --- /dev/null +++ b/calendar_public_holiday/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/calendar_public_holiday/__manifest__.py b/calendar_public_holiday/__manifest__.py new file mode 100644 index 00000000..d6665759 --- /dev/null +++ b/calendar_public_holiday/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2024 Camptocamp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Calendar Holidays Public", + "summary": """ + Manage Public Holidays + """, + "version": "18.0.1.0.0", + "license": "AGPL-3", + "category": "HR/Calendar", + "author": "Camptocamp," "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/calendar", + "depends": ["calendar"], + "data": [ + "data/data.xml", + "security/ir.model.access.csv", + "views/calendar_public_holiday_view.xml", + "wizards/calendar_public_holiday_next_year_wizard.xml", + ], +} diff --git a/calendar_public_holiday/data/data.xml b/calendar_public_holiday/data/data.xml new file mode 100644 index 00000000..1c8be20d --- /dev/null +++ b/calendar_public_holiday/data/data.xml @@ -0,0 +1,8 @@ + + + + + Holidays + + diff --git a/calendar_public_holiday/models/__init__.py b/calendar_public_holiday/models/__init__.py new file mode 100644 index 00000000..4b85a9fa --- /dev/null +++ b/calendar_public_holiday/models/__init__.py @@ -0,0 +1,2 @@ +from . import calendar_public_holiday +from . import calendar_public_holiday_line diff --git a/calendar_public_holiday/models/calendar_public_holiday.py b/calendar_public_holiday/models/calendar_public_holiday.py new file mode 100644 index 00000000..56b008f2 --- /dev/null +++ b/calendar_public_holiday/models/calendar_public_holiday.py @@ -0,0 +1,124 @@ +# Copyright 2024 Camptocamp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import datetime + +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class ResourceCalendarPublicHoliday(models.Model): + _name = "calendar.public.holiday" + _description = "Calendar Public Holiday" + _rec_name = "year" + _order = "year" + + year = fields.Integer( + "Calendar Year", required=True, default=fields.Date.today().year + ) + line_ids = fields.One2many( + "calendar.public.holiday.line", + "public_holiday_id", + "Holiday Dates", + ) + country_id = fields.Many2one("res.country", "Country") + + @api.constrains("year", "country_id") + def _check_year(self): + for line in self: + line._check_year_one() + + def _check_year_one(self): + if self.search_count( + [ + ("year", "=", self.year), + ("country_id", "=", self.country_id.id), + ("id", "!=", self.id), + ] + ): + raise ValidationError( + self.env._( + "You can't create duplicate public holiday per year and/or" + " country" + ) + ) + return True + + @api.depends("country_id") + def _compute_display_name(self): + for line in self: + if line.country_id: + line.display_name = f"{line.year} ({line.country_id.name})" + else: + line.display_name = line.year + + def _get_domain_states_filter(self, pholidays, start_dt, end_dt, partner_id=None): + partner = self.env["res.partner"].browse(partner_id) + states_filter = [ + ("public_holiday_id", "in", pholidays.ids), + ("date", ">=", start_dt), + ("date", "<=", end_dt), + ] + if partner and partner.state_id: + states_filter.extend( + [ + "|", + ("state_ids", "in", partner.state_id.ids), + ("state_ids", "=", False), + ] + ) + else: + states_filter.append(("state_ids", "=", False)) + return states_filter + + @api.model + @api.returns("calendar.public.holiday.line") + def get_holidays_list(self, year=None, start_dt=None, end_dt=None, partner_id=None): + """Returns recordset of calendar.public.holiday.line + for the specified year and employee + :param year: year as string (optional if start_dt and end_dt defined) + :param start_dt: start_dt as date + :param end_dt: end_dt as date + :param partner_id: ID of the partner + :return: recordset of calendar.public.holiday.line + """ + partner = self.env["res.partner"].browse(partner_id) + if not start_dt and not end_dt: + start_dt = datetime.date(year, 1, 1) + end_dt = datetime.date(year, 12, 31) + years = list(range(start_dt.year, end_dt.year + 1)) + holidays_filter = [("year", "in", years)] + if partner: + if partner.country_id: + holidays_filter.append( + ("country_id", "in", (False, partner.country_id.id)) + ) + else: + holidays_filter.append(("country_id", "=", False)) + public_holidays = self.search(holidays_filter) + public_holiday_line = self.env["calendar.public.holiday.line"] + if not public_holidays: + return public_holiday_line + states_filter = self._get_domain_states_filter( + public_holidays, start_dt, end_dt, partner_id=partner.id + ) + return public_holiday_line.search(states_filter) + + @api.model + def is_public_holiday(self, selected_date, partner_id=None): + """ + Returns True if selected_date is a public holiday for the employee + :param selected_date: datetime object + :param partner_id: ID of the partner + :return: bool + """ + partner = self.env["res.partner"].browse(partner_id) + partner_id = partner.id if partner else None + holidays_lines = self.get_holidays_list( + year=selected_date.year, partner_id=partner_id + ) + if holidays_lines: + hol_date = holidays_lines.filtered(lambda r: r.date == selected_date) + if hol_date: + return True + return False diff --git a/calendar_public_holiday/models/calendar_public_holiday_line.py b/calendar_public_holiday/models/calendar_public_holiday_line.py new file mode 100644 index 00000000..6d88b02a --- /dev/null +++ b/calendar_public_holiday/models/calendar_public_holiday_line.py @@ -0,0 +1,123 @@ +# Copyright 2024 Camptocamp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import SUPERUSER_ID, api, fields, models +from odoo.exceptions import ValidationError + + +class CalendarHolidaysPublicLine(models.Model): + _name = "calendar.public.holiday.line" + _description = "Calendar Public Holiday Line" + _order = "date, name desc" + + name = fields.Char(required=True) + date = fields.Date(required=True) + public_holiday_id = fields.Many2one( + "calendar.public.holiday", + "Calendar Year", + required=True, + ondelete="cascade", + ) + variable_date = fields.Boolean("Date may change", default=True) + state_ids = fields.Many2many( + "res.country.state", + "public_holiday_state_rel", + "public_holiday_line_id", + "state_id", + "Related States", + ) + meeting_id = fields.Many2one( + "calendar.event", + string="Meeting", + copy=False, + ) + + @api.constrains("date", "state_ids") + def _check_date_state(self): + for line in self: + line._check_date_state_one() + + def _get_domain_check_date_state_one_state_ids(self): + return [ + ("date", "=", self.date), + ("public_holiday_id", "=", self.public_holiday_id.id), + ("state_ids", "!=", False), + ("id", "!=", self.id), + ] + + def _get_domain_check_date_state_one(self): + return [ + ("date", "=", self.date), + ("public_holiday_id", "=", self.public_holiday_id.id), + ("state_ids", "=", False), + ] + + def _check_date_state_one(self): + if self.date.year != self.public_holiday_id.year: + raise ValidationError( + self.env._( + "Dates of holidays should be the same year as the calendar" + " year they are being assigned to" + ) + ) + + if self.state_ids: + domain = self._get_domain_check_date_state_one_state_ids() + holidays = self.search(domain) + + for holiday in holidays: + if self.state_ids & holiday.state_ids: + raise ValidationError( + self.env._( + "You can't create duplicate public holiday per date" + f" {self.date} and one of the country states." + ) + ) + domain = self._get_domain_check_date_state_one() + if self.search_count(domain) > 1: + raise ValidationError( + self.env._( + f"You can't create duplicate public holiday per date {self.date}." + ) + ) + return True + + def _prepare_holidays_meeting_values(self): + self.ensure_one() + categ_id = self.env.ref("calendar_public_holiday.event_type_holiday", False) + meeting_values = { + "name": ( + f"{self.name} ({self.public_holiday_id.country_id.name})" + if self.public_holiday_id.country_id + else self.name + ), + "description": ", ".join(self.state_ids.mapped("name")), + "start": self.date, + "stop": self.date, + "allday": True, + "user_id": SUPERUSER_ID, + "privacy": "confidential", + "show_as": "busy", + } + if categ_id: + meeting_values.update({"categ_ids": [(6, 0, categ_id.ids)]}) + return meeting_values + + @api.constrains("date", "name", "public_holiday_id", "state_ids") + def _update_calendar_event(self): + for rec in self: + if rec.meeting_id: + rec.meeting_id.write(rec._prepare_holidays_meeting_values()) + + @api.model_create_multi + def create(self, vals_list): + res = super().create(vals_list) + for record in res: + record.meeting_id = self.env["calendar.event"].create( + record._prepare_holidays_meeting_values() + ) + return res + + def unlink(self): + self.mapped("meeting_id").unlink() + return super().unlink() diff --git a/calendar_public_holiday/pyproject.toml b/calendar_public_holiday/pyproject.toml new file mode 100644 index 00000000..4231d0cc --- /dev/null +++ b/calendar_public_holiday/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/calendar_public_holiday/readme/CONTRIBUTORS.md b/calendar_public_holiday/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..d9381e9b --- /dev/null +++ b/calendar_public_holiday/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- \[Trobz\](): + + - Do Anh Duy \<\<\>\> diff --git a/calendar_public_holiday/readme/CREDITS.md b/calendar_public_holiday/readme/CREDITS.md new file mode 100644 index 00000000..35e239a0 --- /dev/null +++ b/calendar_public_holiday/readme/CREDITS.md @@ -0,0 +1 @@ +The creation of this module was financially supported by Camptocamp diff --git a/calendar_public_holiday/readme/DESCRIPTION.md b/calendar_public_holiday/readme/DESCRIPTION.md new file mode 100644 index 00000000..2a7afcc9 --- /dev/null +++ b/calendar_public_holiday/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This module handles public holidays. diff --git a/calendar_public_holiday/readme/USAGE.md b/calendar_public_holiday/readme/USAGE.md new file mode 100644 index 00000000..185826a2 --- /dev/null +++ b/calendar_public_holiday/readme/USAGE.md @@ -0,0 +1,4 @@ +For adding public holidays: + +1. Go to the menu *Calendar \> Public Holidays \> Public Holidays*. +2. Create your public holidays. diff --git a/calendar_public_holiday/security/ir.model.access.csv b/calendar_public_holiday/security/ir.model.access.csv new file mode 100644 index 00000000..fa5aed10 --- /dev/null +++ b/calendar_public_holiday/security/ir.model.access.csv @@ -0,0 +1,6 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_calendar_public_holiday_user,access_calendar_public_holiday,model_calendar_public_holiday,base.group_user,1,0,0,0 +access_calendar_public_holiday_manager,access_calendar_public_holiday,model_calendar_public_holiday,base.group_partner_manager,1,1,1,1 +access_calendar_public_holiday_line_user,access_calendar_public_holiday_line,model_calendar_public_holiday_line,base.group_user,1,0,0,0 +access_calendar_public_holiday_line_manager,access_calendar_public_holiday_line,model_calendar_public_holiday_line,base.group_partner_manager,1,1,1,1 +access_calendar_public_holiday_manager_next_year,access_calendar_public_holiday_next_year,model_calendar_public_holiday_next_year,base.group_partner_manager,1,1,1,1 diff --git a/calendar_public_holiday/static/description/icon.png b/calendar_public_holiday/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/calendar_public_holiday/static/description/icon.png differ diff --git a/calendar_public_holiday/static/description/index.html b/calendar_public_holiday/static/description/index.html new file mode 100644 index 00000000..2b1d662e --- /dev/null +++ b/calendar_public_holiday/static/description/index.html @@ -0,0 +1,440 @@ + + + + + +Calendar Holidays Public + + + +
+

Calendar Holidays Public

+ + +

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

+

This module handles public holidays.

+

Table of contents

+ +
+

Usage

+

For adding public holidays:

+
    +
  1. Go to the menu Calendar > Public Holidays > Public Holidays.
  2. +
  3. Create your public holidays.
  4. +
+
+
+

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

+
    +
  • Camptocamp
  • +
+
+
+

Contributors

+ +
+
+

Other credits

+

The creation of this module was financially supported by Camptocamp

+
+
+

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/calendar project on GitHub.

+

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

+
+
+
+ + diff --git a/calendar_public_holiday/tests/__init__.py b/calendar_public_holiday/tests/__init__.py new file mode 100644 index 00000000..c0012457 --- /dev/null +++ b/calendar_public_holiday/tests/__init__.py @@ -0,0 +1 @@ +from . import test_calendar_public_holiday diff --git a/calendar_public_holiday/tests/test_calendar_public_holiday.py b/calendar_public_holiday/tests/test_calendar_public_holiday.py new file mode 100644 index 00000000..7c372a4a --- /dev/null +++ b/calendar_public_holiday/tests/test_calendar_public_holiday.py @@ -0,0 +1,245 @@ +# Copyright 2024 Camptocamp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import date + +from odoo.exceptions import UserError, ValidationError + +from odoo.addons.base.tests.common import BaseCommon + + +class TestCalendarPublicHoliday(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.holiday_model = cls.env["calendar.public.holiday"] + cls.holiday_line_model = cls.env["calendar.public.holiday.line"] + cls.calendar_event = cls.env["calendar.event"] + cls.wizard_next_year = cls.env["calendar.public.holiday.next.year"] + + # Remove possibly existing public holidays that would interfer. + cls.holiday_line_model.search([]).unlink() + cls.holiday_model.search([]).unlink() + cls.calendar_event.search([]).unlink() + + cls.country_1 = cls.env["res.country"].create( + { + "name": "Country 1", + "code": "XX", + } + ) + cls.country_2 = cls.env["res.country"].create( + { + "name": "Country 2", + "code": "YY", + } + ) + cls.country_3 = cls.env["res.country"].create( + { + "name": "Country 3", + "code": "ZZ", + } + ) + cls.res_partner = cls.env["res.partner"].create( + {"name": "Partner 1", "country_id": cls.country_1.id} + ) + cls.holiday_1 = cls.holiday_model.create( + { + "year": 2024, + "country_id": cls.country_1.id, + "line_ids": [ + ( + 0, + 0, + { + "name": "Christmas Day for Country 1", + "date": "2024-12-25", + }, + ) + ], + } + ) + cls.holiday_2 = cls.holiday_model.create( + { + "year": 2024, + "country_id": cls.country_2.id, + "line_ids": [ + ( + 0, + 0, + { + "name": "Christmas Day for Country 2", + "date": "2024-12-25", + }, + ) + ], + } + ) + cls.holiday_3 = cls.holiday_model.create({"year": 2025}) + ls_dates = ["2025-01-02", "2025-01-05", "2025-01-07"] + for i in range(len(ls_dates)): + cls.holiday_line_model.create( + { + "name": f"Public Holiday Line {i + 1}", + "date": ls_dates[i], + "public_holiday_id": cls.holiday_3.id, + } + ) + + def test_display_name(self): + holiday_1_display_name = self.holiday_1.display_name + expect_display_name = ( + f"{self.holiday_1.year} ({self.holiday_1.country_id.name})" + ) + self.assertEqual(holiday_1_display_name, expect_display_name) + + # without country + holiday_3_display_name = self.holiday_3.display_name + expect_display_name = f"{self.holiday_3.year}" + self.assertEqual(holiday_3_display_name, expect_display_name) + + def test_duplicate_year_country_fail(self): + # ensures that duplicate year cannot be created for the same country + with self.assertRaises(ValidationError): + # same year with country = False + self.holiday_model.create({"year": 2025}) + with self.assertRaises(ValidationError): + # same country with holiday_1 + self.holiday_model.create({"year": 2024, "country_id": self.country_1.id}) + + def test_duplicate_date_state_fail(self): + # ensures that duplicate date cannot be created for the same country + # state or with state null + holiday_4 = self.holiday_model.create( + {"year": 2024, "country_id": self.country_3.id} + ) + holiday_4_line = self.holiday_line_model.create( + { + "name": "holiday x", + "date": "2024-12-25", + "public_holiday_id": holiday_4.id, + } + ) + with self.assertRaises(ValidationError): + self.holiday_line_model.create( + { + "name": "holiday x", + "date": "2024-12-25", + "public_holiday_id": holiday_4.id, + } + ) + holiday_4_line.state_ids = [(6, 0, [self.country_3.id])] + with self.assertRaises(ValidationError): + self.holiday_line_model.create( + { + "name": "holiday x", + "date": "2024-12-25", + "public_holiday_id": holiday_4.id, + "state_ids": [(6, 0, [self.country_3.id])], + } + ) + + def test_holiday_in_country(self): + # ensures that correct holidays are identified for a country + self.assertTrue( + self.holiday_model.is_public_holiday( + date(2024, 12, 25), partner_id=self.res_partner.id + ) + ) + self.assertFalse( + self.holiday_model.is_public_holiday( + date(2024, 12, 23), partner_id=self.res_partner.id + ) + ) + + def test_holiday_line_same_year_with_parent(self): + # ensures that line year and holiday year are the same + with self.assertRaises(ValidationError): + self.holiday_model.create( + { + "year": 2026, + "line_ids": [ + ( + 0, + 0, + { + "name": "Line with not the same year", + "date": "2027-12-25", + }, + ) + ], + } + ) + + def test_list_holidays_in_list_country_specific(self): + # ensures that correct holidays are identified for a country + lines = self.holiday_model.get_holidays_list( + 2024, partner_id=self.res_partner.id + ) + res = lines.filtered(lambda r: r.date == date(2024, 12, 25)) + self.assertEqual(len(res), 1) + self.assertEqual(len(lines), 1) + + def test_list_holidays_in_list(self): + # ensures that correct holidays are identified for a country + lines = self.holiday_model.get_holidays_list(2025) + res = lines.filtered(lambda r: r.date == date(2025, 1, 2)) + self.assertEqual(len(res), 1) + self.assertEqual(len(lines), 3) + + def test_create_year_2026_public_holidays(self): + # holiday_1 and holiday_2 have the same line in 2024 but different country + ph_start_ids = self.holiday_model.search([("year", "=", 2024)]) + vals = {"public_holiday_ids": ph_start_ids, "year": 2026} + wizard = self.wizard_next_year.new(values=vals) + wizard.create_public_holidays() + lines = self.holiday_model.get_holidays_list(2026) + self.assertEqual(len(lines), 2) + res = lines.filtered( + lambda r: r.public_holiday_id.country_id.id == self.country_1.id + ) + self.assertEqual(len(res), 1) + + def test_create_year_2027_public_holidays(self): + # holiday_3 have 3 line in year 2025 + ph_start_ids = self.holiday_model.search([("year", "=", 2025)]) + wizard = self.wizard_next_year.new( + values={ + "public_holiday_ids": ph_start_ids, + "year": 2027, + } + ) + wizard.create_public_holidays() + lines = self.holiday_model.get_holidays_list(2027) + self.assertEqual(len(lines), 3) + + def test_february_29th(self): + # Ensures that users get a UserError (not a nasty Exception) when + # trying to create public holidays from year including 29th of + # February + holiday_tw_2024 = self.holiday_model.create( + {"year": 2024, "country_id": self.country_3.id} + ) + self.holiday_line_model.create( + { + "name": "Peace Memorial Holiday", + "date": "2024-02-29", + "public_holiday_id": holiday_tw_2024.id, + } + ) + vals = {"public_holiday_ids": holiday_tw_2024} + wz_create_ph = self.wizard_next_year.new(values=vals) + + with self.assertRaises(UserError): + wz_create_ph.create_public_holidays() + + def test_calendar_event_created(self): + holiday_1_line = self.holiday_1.line_ids[0] + meeting_id = holiday_1_line.meeting_id + self.assertTrue(meeting_id) + holiday_1_line.unlink() + self.assertFalse(meeting_id.exists()) + all_lines = self.holiday_line_model.search([]) + categ_id = self.env.ref("calendar_public_holiday.event_type_holiday", False) + all_meetings = self.calendar_event.search([("categ_ids", "in", categ_id.id)]) + self.assertEqual(len(all_lines), len(all_meetings)) diff --git a/calendar_public_holiday/views/calendar_public_holiday_view.xml b/calendar_public_holiday/views/calendar_public_holiday_view.xml new file mode 100644 index 00000000..7d5a0724 --- /dev/null +++ b/calendar_public_holiday/views/calendar_public_holiday_view.xml @@ -0,0 +1,70 @@ + + + + + calendar.public.holiday.list + calendar.public.holiday + + + + + + + + + + calendar.public.holiday.form + calendar.public.holiday + +
+ + + + + + + + + + + + + + + + + + + +
+
+ + + Public Holidays + calendar.public.holiday + list,form + + + + + +
diff --git a/calendar_public_holiday/wizards/__init__.py b/calendar_public_holiday/wizards/__init__.py new file mode 100644 index 00000000..63f9b214 --- /dev/null +++ b/calendar_public_holiday/wizards/__init__.py @@ -0,0 +1 @@ +from . import calendar_public_holiday_next_year_wizard diff --git a/calendar_public_holiday/wizards/calendar_public_holiday_next_year_wizard.py b/calendar_public_holiday/wizards/calendar_public_holiday_next_year_wizard.py new file mode 100644 index 00000000..8dca59f6 --- /dev/null +++ b/calendar_public_holiday/wizards/calendar_public_holiday_next_year_wizard.py @@ -0,0 +1,84 @@ +# Copyright 2024 Camptocamp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import fields, models +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + + +class CalendarPublicHolidayNextYear(models.TransientModel): + _name = "calendar.public.holiday.next.year" + _description = "Create Public Holiday From Existing Ones" + + public_holiday_ids = fields.Many2many( + comodel_name="calendar.public.holiday", + string="Templates", + help="Select the public holidays to use as template. " + "If not set, latest public holidays of each country will be used. " + "Only the last templates of each country for each year will " + "be taken into account (If you select templates from 2012 and 2015, " + "only the templates from 2015 will be taken into account.", + ) + year = fields.Integer( + help="Year for which you want to create the public holidays. " + "By default, the year following the template." + ) + + def create_public_holidays(self): + self.ensure_one() + last_ph_dict = {} + ph_env = self.env["calendar.public.holiday"] + pholidays = self.public_holiday_ids or ph_env.search([]) + if not pholidays: + raise UserError( + self.env._( + "No Public Holidays found as template. " + "Please create the first Public Holidays manually." + ) + ) + + for ph in pholidays: + last_ph_country = last_ph_dict.get(ph.country_id, False) + if last_ph_country: + if last_ph_country.year < ph.year: + last_ph_dict[ph.country_id] = ph + else: + last_ph_dict[ph.country_id] = ph + + new_ph_ids = [] + for last_ph in last_ph_dict.values(): + new_year = self.year or last_ph.year + 1 + new_ph_vals = {"year": new_year} + new_ph = last_ph.copy(new_ph_vals) + new_ph_ids.append(new_ph.id) + for last_ph_line in last_ph.line_ids: + feb_29 = last_ph_line.date.month == 2 and last_ph_line.date.day == 29 + if feb_29: + # Handling this rare case would mean quite a lot of + # complexity because previous or next day might also be a + # public holiday. + raise UserError( + self.env._( + "You cannot use as template the public holidays " + "of a year that " + "includes public holidays on 29th of February " + "(2016, 2020...), please select a template from " + "another year." + ) + ) + new_date = last_ph_line.date.replace(year=new_year) + new_ph_line_vals = {"date": new_date, "public_holiday_id": new_ph.id} + last_ph_line.copy(new_ph_line_vals) + + domain = [["id", "in", new_ph_ids]] + action = { + "type": "ir.actions.act_window", + "name": self.env._("New public holidays"), + "view_mode": "list,form", + "res_model": ph_env._name, + "domain": domain, + } + return action diff --git a/calendar_public_holiday/wizards/calendar_public_holiday_next_year_wizard.xml b/calendar_public_holiday/wizards/calendar_public_holiday_next_year_wizard.xml new file mode 100644 index 00000000..e7a80e11 --- /dev/null +++ b/calendar_public_holiday/wizards/calendar_public_holiday_next_year_wizard.xml @@ -0,0 +1,71 @@ + + + + + Create Next Year Public Holidays + calendar.public.holiday.next.year + +
+ +
+ Use this wizard to create public holidays based on the + existing ones.
+ Only the last templates of each country + will be taken into account (If you select templates + from 2012 and 2015 of the same country; ' + only the templates from 2015 will be taken into + account). +
+ + +
+ By default, the most recent public holidays + for each country are used as template to create + public holidays for the year following the templates. +

+ Normally, you should not need to input anything in + optional fields and only need to click on the button + "Create". +
+
+ +
+ The below optional fields are here only to handle + special situations like "2011 was a special year with + an additional public holiday for the 150th + anniversary of the Italian unification, so you want to + replicate the 2010 Italian holidays to 2012." +
+ + + + +
+
+
+
+
+
+
+
+ + Create Next Year Public Holidays + calendar.public.holiday.next.year + form + new + + +