From acf0c22adf3212799f18f416f1321acedfdb719c Mon Sep 17 00:00:00 2001 From: Alexandre Fayolle Date: Thu, 16 Jun 2022 09:05:57 +0200 Subject: [PATCH 1/2] [FIX] hr_holidays_public: wrong computation in case resources in different countries Fix an issue in hr_holidays_public where resource.calendar::_attendance_intervals_batch_exclude_public_holidays would return a wrong result when called with multiple resources working in different countries. The original implementation uses a context key employee_id to find the employee, which only allows this method to be called with a single employee. The fix searches for the employees related to the resources passed in arguments, and default to the context key if there are no provided resources. Also rework the implementation to use a python set rather than a list for faster in test. [FIX] hr_holidays_public: provide defaults to process holidays --- hr_holidays_public/__manifest__.py | 2 +- .../models/resource_calendar.py | 46 +++++++++++++++---- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/hr_holidays_public/__manifest__.py b/hr_holidays_public/__manifest__.py index b3ed8a7c..8d1d279f 100644 --- a/hr_holidays_public/__manifest__.py +++ b/hr_holidays_public/__manifest__.py @@ -14,7 +14,7 @@ "Odoo Community Association (OCA),", "summary": "Manage Public Holidays", "website": "https://github.com/OCA/hr-holidays", - "depends": ["hr_holidays"], + "depends": ["hr_holidays", "hr_attendance"], "data": [ "data/data.xml", "security/ir.model.access.csv", diff --git a/hr_holidays_public/models/resource_calendar.py b/hr_holidays_public/models/resource_calendar.py index 28526afd..565d3181 100644 --- a/hr_holidays_public/models/resource_calendar.py +++ b/hr_holidays_public/models/resource_calendar.py @@ -15,20 +15,46 @@ class ResourceCalendar(models.Model): def _attendance_intervals_batch_exclude_public_holidays( self, start_dt, end_dt, intervals, resources, tz ): - list_by_dates = ( - self.env["hr.holidays.public"] - .get_holidays_list( - start_dt=start_dt.date(), - end_dt=end_dt.date(), - employee_id=self.env.context.get("employee_id", False), + holidays_by_country = { + False: set( + self.env["hr.holidays.public"] + .get_holidays_list( + start_dt=start_dt.date(), + end_dt=end_dt.date(), + employee_id=self.env.context.get("employee_id", False), + ) + .mapped("date") ) - .mapped("date") - ) + } + if resources: + employees = self.env["hr.employee.public"].search( + [("resource_id", "in", resources.ids)] + ) + resource_country = {} + for employee in employees: + country = employee.address_id.country_id + resource_country[employee.resource_id.id] = country.id + if country.id not in holidays_by_country: + holidays_by_country[country.id] = set( + self.env["hr.holidays.public"] + .get_holidays_list( + start_dt=start_dt.date(), + end_dt=end_dt.date(), + employee_id=employee.id, + ) + .mapped("date") + ) + # even if employees and resource_country are empty + # we still process holidays, so provide defaults for resource in resources: - interval_resource = intervals[resource.id] + interval_resource = intervals.get( + resource.id, Intervals(self.env["hr.attendance"]) + ) attendances = [] + country = resource_country.get(resource.id, self.env["res.country"]) + holidays = holidays_by_country.get(country, set()) for attendance in interval_resource._items: - if attendance[0].date() not in list_by_dates: + if attendance[0].date() not in holidays: attendances.append(attendance) intervals[resource.id] = Intervals(attendances) return intervals From d23b264740d68b15699d698bb577a54091f853cf Mon Sep 17 00:00:00 2001 From: Alexandre Fayolle Date: Fri, 10 Mar 2023 10:53:15 +0100 Subject: [PATCH 2/2] fixup! [FIX] hr_holidays_public: wrong computation in case resources in different countries --- hr_holidays_public/__manifest__.py | 2 +- .../models/resource_calendar.py | 5 +-- .../tests/test_holidays_public.py | 41 ++++++++++++++++++- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/hr_holidays_public/__manifest__.py b/hr_holidays_public/__manifest__.py index 8d1d279f..b3ed8a7c 100644 --- a/hr_holidays_public/__manifest__.py +++ b/hr_holidays_public/__manifest__.py @@ -14,7 +14,7 @@ "Odoo Community Association (OCA),", "summary": "Manage Public Holidays", "website": "https://github.com/OCA/hr-holidays", - "depends": ["hr_holidays", "hr_attendance"], + "depends": ["hr_holidays"], "data": [ "data/data.xml", "security/ir.model.access.csv", diff --git a/hr_holidays_public/models/resource_calendar.py b/hr_holidays_public/models/resource_calendar.py index 565d3181..c52d7196 100644 --- a/hr_holidays_public/models/resource_calendar.py +++ b/hr_holidays_public/models/resource_calendar.py @@ -23,6 +23,7 @@ def _attendance_intervals_batch_exclude_public_holidays( end_dt=end_dt.date(), employee_id=self.env.context.get("employee_id", False), ) + .filtered(lambda rec: not rec.year_id.country_id) .mapped("date") ) } @@ -47,9 +48,7 @@ def _attendance_intervals_batch_exclude_public_holidays( # even if employees and resource_country are empty # we still process holidays, so provide defaults for resource in resources: - interval_resource = intervals.get( - resource.id, Intervals(self.env["hr.attendance"]) - ) + interval_resource = intervals.get(resource.id, Intervals()) attendances = [] country = resource_country.get(resource.id, self.env["res.country"]) holidays = holidays_by_country.get(country, set()) diff --git a/hr_holidays_public/tests/test_holidays_public.py b/hr_holidays_public/tests/test_holidays_public.py index 9d975e43..ac9d68d8 100644 --- a/hr_holidays_public/tests/test_holidays_public.py +++ b/hr_holidays_public/tests/test_holidays_public.py @@ -2,7 +2,9 @@ # Copyright 2018 Brainbean Apps (https://brainbeanapps.com) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from datetime import date +from datetime import date, datetime + +import pytz from odoo.exceptions import UserError, ValidationError from odoo.tests.common import TransactionCase @@ -314,3 +316,40 @@ def test_get_unusual_days_not_return_public_holidays_fallback_to_company_state( country_id=demo_user_empl_addr.country_id.id, state_ids=[(6, 0, [self.env.ref("base.state_us_3").id])], ) + + def test_calendar_attendance_interval_exclude_public_holidays(self): + # SK employee has holiday 3, so off on 1994-11-14 + employee_sk = self.employee_model.create( + { + "name": "Employee Sk", + "address_id": self.env["res.partner"] + .create( + {"name": "Employee Sk", "country_id": self.env.ref("base.sk").id} + ) + .id, + } + ) + resource_sk = employee_sk.resource_id + # SL employee has holiday2 so off on 1994-10-14 + employee_sl = self.employee + resource_sl = employee_sl.resource_id + calendar = employee_sl.resource_id.calendar_id.with_context( + exclude_public_holidays=True + ) + start_dt = datetime(1994, 10, 1, tzinfo=pytz.utc) + end_dt = datetime(1994, 11, 30, tzinfo=pytz.utc) + intervals_sk_sl = calendar._attendance_intervals_batch( + start_dt=start_dt, end_dt=end_dt, resources=resource_sk + resource_sl + ) + intervals_sk = calendar._attendance_intervals_batch( + start_dt=start_dt, end_dt=end_dt, resources=resource_sk + ) + intervals_sl = calendar._attendance_intervals_batch( + start_dt=start_dt, end_dt=end_dt, resources=resource_sl + ) + self.assertEqual( + intervals_sk[resource_sk.id]._items, intervals_sk_sl[resource_sk.id]._items + ) + self.assertEqual( + intervals_sl[resource_sl.id]._items, intervals_sk_sl[resource_sl.id]._items + )