From df9187ed28949f58bd132cbe4e8134389f71f0d6 Mon Sep 17 00:00:00 2001 From: Antoni Marroig Campomar Date: Tue, 23 Jul 2024 08:41:35 +0200 Subject: [PATCH] [MIG] hr_holidays_natural_period: Migration to 17.0 --- hr_holidays_natural_period/README.rst | 5 ++ hr_holidays_natural_period/__manifest__.py | 2 +- .../demo/hr_leave_type_data.xml | 2 +- hr_holidays_natural_period/models/__init__.py | 2 + .../models/hr_employee.py | 22 ++++++++ hr_holidays_natural_period/models/hr_leave.py | 29 +++++----- .../models/hr_leave_allocation.py | 13 +++++ .../models/hr_leave_type.py | 19 ------- .../models/resource_calendar.py | 46 ++++++++++++++- .../readme/CONTRIBUTORS.md | 5 ++ .../static/description/index.html | 8 +++ .../tests/test_hr_leave.py | 56 +++++++++---------- 12 files changed, 141 insertions(+), 68 deletions(-) create mode 100644 hr_holidays_natural_period/models/hr_employee.py create mode 100644 hr_holidays_natural_period/models/hr_leave_allocation.py diff --git a/hr_holidays_natural_period/README.rst b/hr_holidays_natural_period/README.rst index 34474664..a5e6135f 100644 --- a/hr_holidays_natural_period/README.rst +++ b/hr_holidays_natural_period/README.rst @@ -75,6 +75,11 @@ Contributors - Víctor Martínez - Pedro Baeza + - Carlos López + +- APSL-Nagarro + + - Antoni Marroig Maintainers ----------- diff --git a/hr_holidays_natural_period/__manifest__.py b/hr_holidays_natural_period/__manifest__.py index 08ff5304..face965a 100644 --- a/hr_holidays_natural_period/__manifest__.py +++ b/hr_holidays_natural_period/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Holidays natural period", "summary": "Apply natural days in holidays", - "version": "16.0.1.0.1", + "version": "17.0.1.0.0", "category": "Human Resources", "website": "https://github.com/OCA/hr-holidays", "author": "Tecnativa, Odoo Community Association (OCA)", diff --git a/hr_holidays_natural_period/demo/hr_leave_type_data.xml b/hr_holidays_natural_period/demo/hr_leave_type_data.xml index a10abd02..b0c82cb3 100644 --- a/hr_holidays_natural_period/demo/hr_leave_type_data.xml +++ b/hr_holidays_natural_period/demo/hr_leave_type_data.xml @@ -3,7 +3,7 @@ Test Time Off (natural day) natural_day - + yes diff --git a/hr_holidays_natural_period/models/__init__.py b/hr_holidays_natural_period/models/__init__.py index ae7ae6ad..3309c690 100644 --- a/hr_holidays_natural_period/models/__init__.py +++ b/hr_holidays_natural_period/models/__init__.py @@ -1,3 +1,5 @@ from . import hr_leave_type from . import hr_leave from . import resource_calendar +from . import hr_leave_allocation +from . import hr_employee diff --git a/hr_holidays_natural_period/models/hr_employee.py b/hr_holidays_natural_period/models/hr_employee.py new file mode 100644 index 00000000..12551b89 --- /dev/null +++ b/hr_holidays_natural_period/models/hr_employee.py @@ -0,0 +1,22 @@ +# Copyright 2024 APSL-Nagarro - Antoni Marroig Campomar +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class HrEmployee(models.Model): + _inherit = "hr.employee" + + def _get_consumed_leaves(self, leave_types, target_date=False, ignore_future=False): + """We need to set request_unit as 'day' to avoid the calculations being done + as hours. + """ + old_request_unit_data = {} + for item in leave_types.filtered(lambda x: x.request_unit == "natural_day"): + old_request_unit_data[item.id] = item.request_unit + item.sudo().request_unit = "day" + res = super()._get_consumed_leaves(leave_types, target_date, ignore_future) + for item in leave_types: + if item.id in old_request_unit_data: + item.sudo().request_unit = old_request_unit_data[item.id] + return res diff --git a/hr_holidays_natural_period/models/hr_leave.py b/hr_holidays_natural_period/models/hr_leave.py index 2d7fe792..6d441abf 100644 --- a/hr_holidays_natural_period/models/hr_leave.py +++ b/hr_holidays_natural_period/models/hr_leave.py @@ -1,26 +1,25 @@ # Copyright 2020-2024 Tecnativa - Víctor Martínez +# Copyright 2024 Tecnativa - Carlos Lopez # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import api, models +from odoo import models class HrLeave(models.Model): _inherit = "hr.leave" - def _get_number_of_days(self, date_from, date_to, employee_id): - instance = self.with_context( - natural_period=bool(self.holiday_status_id.request_unit == "natural_day") + def _get_duration(self, check_leave_type=True, resource_calendar=None): + # We need to set request_unit as 'day' + # to avoid the calculations being done as hours. + is_request_unit_natural_day = ( + self.holiday_status_id.request_unit == "natural_day" ) - return super(HrLeave, instance)._get_number_of_days( - date_from, date_to, employee_id + instance = self.with_context(natural_period=is_request_unit_natural_day) + if is_request_unit_natural_day: + self.holiday_status_id.sudo().request_unit = "day" + res = super(HrLeave, instance)._get_duration( + check_leave_type=check_leave_type, resource_calendar=resource_calendar ) - - @api.model_create_multi - def create(self, vals_list): - """Only in UX an incorrect value is set, recalculate. - https://github.com/OCA/hr-holidays/issues/105.""" - res = super().create(vals_list) - res.filtered( - lambda x: x.holiday_status_id.request_unit == "natural_day" - )._compute_number_of_days() + if is_request_unit_natural_day: + self.holiday_status_id.sudo().request_unit = "natural_day" return res diff --git a/hr_holidays_natural_period/models/hr_leave_allocation.py b/hr_holidays_natural_period/models/hr_leave_allocation.py new file mode 100644 index 00000000..11ace727 --- /dev/null +++ b/hr_holidays_natural_period/models/hr_leave_allocation.py @@ -0,0 +1,13 @@ +# Copyright 2024 APSL-Nagarro - Antoni Marroig Campomar +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class HrLeaveAllocation(models.Model): + _inherit = "hr.leave.allocation" + + # Added option to this field because it is now computed (was related before). + type_request_unit = fields.Selection( + selection_add=[("natural_day", "Natural day")], + ) diff --git a/hr_holidays_natural_period/models/hr_leave_type.py b/hr_holidays_natural_period/models/hr_leave_type.py index 7c8178b0..e6e77ed0 100644 --- a/hr_holidays_natural_period/models/hr_leave_type.py +++ b/hr_holidays_natural_period/models/hr_leave_type.py @@ -11,22 +11,3 @@ class HrLeaveType(models.Model): selection_add=[("natural_day", "Natural day")], ondelete={"natural_day": "set default"}, ) - - def _get_employees_days_per_allocation(self, employee_ids, date=None): - """We need to set request_unit as 'day' to avoid the calculations being done - as hours. - Related code: - hr_holidays/models/hr_leave_type.py#L326 - hr_holidays/models/hr_leave_type.py#L389 - """ - old_request_unit_data = {} - for item in self.filtered(lambda x: x.request_unit == "natural_day"): - old_request_unit_data[item.id] = item.request_unit - item.sudo().request_unit = "day" - res = super()._get_employees_days_per_allocation( - employee_ids=employee_ids, date=date - ) - for item in self: - if item.id in old_request_unit_data: - item.sudo().request_unit = old_request_unit_data[item.id] - return res diff --git a/hr_holidays_natural_period/models/resource_calendar.py b/hr_holidays_natural_period/models/resource_calendar.py index 9ae4784f..42d68c13 100644 --- a/hr_holidays_natural_period/models/resource_calendar.py +++ b/hr_holidays_natural_period/models/resource_calendar.py @@ -1,19 +1,54 @@ # Copyright 2020-2021 Tecnativa - Víctor Martínez +# Copyright 2024 Tecnativa - Carlos Lopez # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from collections import defaultdict from datetime import datetime, time from dateutil import rrule from pytz import timezone from odoo import models +from odoo.tools.float_utils import float_round -from odoo.addons.resource.models.resource import Intervals +from odoo.addons.resource.models.utils import Intervals class ResourceCalendar(models.Model): _inherit = "resource.calendar" + def _get_attendance_intervals_days_data(self, attendance_intervals): + # replace function to avoid division by zero + # it occurs when the calendar has no attendance intervals + # i.e no working days(weekends) + # meta is a recordset of resource.calendar.attendance + # but in the function _natural_period_intervals_batch + # we are passing an empty recordset + if not self.env.context.get("natural_period"): + return super()._get_attendance_intervals_days_data(attendance_intervals) + day_hours = defaultdict(float) + day_days = defaultdict(float) + for start, stop, meta in attendance_intervals: + interval_hours = (stop - start).total_seconds() / 3600 + if meta: + interval_days = ( + sum(meta.mapped("duration_days")) + * interval_hours + / sum(meta.mapped("duration_hours")) + ) + else: + interval_days = interval_hours / 24 # hours on a day + day_hours[start.date()] += interval_hours + day_days[start.date()] += interval_days + + return { + # Round the number of days to the closest 16th of a day. + "days": float_round( + sum(day_days[day] for day in day_days), precision_rounding=0.001 + ), + "hours": sum(day_hours.values()), + } + def _exist_interval_in_date(self, intervals, date): for interval in intervals: if interval[0].date() == date: @@ -41,10 +76,15 @@ def _natural_period_intervals_batch(self, start_dt, end_dt, intervals, resources return intervals def _attendance_intervals_batch( - self, start_dt, end_dt, resources=None, domain=None, tz=None + self, start_dt, end_dt, resources=None, domain=None, tz=None, lunch=False ): res = super()._attendance_intervals_batch( - start_dt=start_dt, end_dt=end_dt, resources=resources, domain=domain, tz=tz + start_dt=start_dt, + end_dt=end_dt, + resources=resources, + domain=domain, + tz=tz, + lunch=lunch, ) if self.env.context.get("natural_period"): return self._natural_period_intervals_batch( diff --git a/hr_holidays_natural_period/readme/CONTRIBUTORS.md b/hr_holidays_natural_period/readme/CONTRIBUTORS.md index ab9d6965..20fc22ff 100644 --- a/hr_holidays_natural_period/readme/CONTRIBUTORS.md +++ b/hr_holidays_natural_period/readme/CONTRIBUTORS.md @@ -2,3 +2,8 @@ > - Víctor Martínez > - Pedro Baeza + > - Carlos López + +- APSL-Nagarro \<\> + + > - Antoni Marroig \<\> diff --git a/hr_holidays_natural_period/static/description/index.html b/hr_holidays_natural_period/static/description/index.html index 62c841a9..61d0ed7d 100644 --- a/hr_holidays_natural_period/static/description/index.html +++ b/hr_holidays_natural_period/static/description/index.html @@ -423,6 +423,14 @@

Contributors

  • Víctor Martínez
  • Pedro Baeza
  • +
  • Carlos López
  • +
+ + +
  • APSL-Nagarro <https://www.apsl.tech>

    +
    +
  • diff --git a/hr_holidays_natural_period/tests/test_hr_leave.py b/hr_holidays_natural_period/tests/test_hr_leave.py index cd6be330..e86ced64 100644 --- a/hr_holidays_natural_period/tests/test_hr_leave.py +++ b/hr_holidays_natural_period/tests/test_hr_leave.py @@ -3,25 +3,17 @@ from freezegun import freeze_time from odoo import fields -from odoo.tests import Form, common, new_test_user +from odoo.tests import Form, new_test_user from odoo.tests.common import users +from odoo.addons.base.tests.common import BaseCommon + @freeze_time("2023-01-01", tick=True) -class TestHrLeave(common.TransactionCase): +class TestHrLeave(BaseCommon): @classmethod def setUpClass(cls): super().setUpClass() - cls.env = cls.env( - context=dict( - cls.env.context, - mail_create_nolog=True, - mail_create_nosubscribe=True, - mail_notrack=True, - no_reset_password=True, - ) - ) - cls.HrLeave = cls.env["hr.leave"] cls.leave_type = cls.env.ref( "hr_holidays_natural_period.hr_leave_type_natural_day_test" ) @@ -43,7 +35,7 @@ def setUpClass(cls): partner = cls.env["res.partner"].create( { "name": "Test employee", - "type": "private", + "type": "other", "country_id": cls.env.ref("base.es").id, } ) @@ -51,7 +43,7 @@ def setUpClass(cls): cls.employee = cls.env["hr.employee"].create( { "name": "Test employee", - "address_home_id": partner.id, + "address_id": partner.id, "resource_calendar_id": calendar.id, "user_id": cls.user.id, } @@ -64,7 +56,7 @@ def _create_leave_allocation(self, leave_type, days): default_date_to="%s-12-31" % (fields.Date.today().year), ) ) - leave_allocation_form.name = "TEST" + leave_allocation_form.holiday_status_id = leave_type leave_allocation_form.number_of_days_display = days return leave_allocation_form.save() @@ -79,14 +71,17 @@ def _create_hr_leave(self, leave_type, date_from, date_to): @users("test-user") def test_hr_leave_natural_day(self): leave_allocation = self._create_leave_allocation(self.leave_type, 5) - leave_allocation.action_confirm() leave_allocation.sudo().action_validate() - res_leave_type = self.env["hr.leave.type"].get_days_all_request()[0][1] - self.assertEqual(res_leave_type["remaining_leaves"], "5") - self.assertEqual(res_leave_type["virtual_remaining_leaves"], "5") - self.assertEqual(res_leave_type["max_leaves"], "5") - self.assertEqual(res_leave_type["leaves_taken"], "0") - self.assertEqual(res_leave_type["virtual_leaves_taken"], "0") + res_leave_type = ( + self.env["hr.leave.type"] + .with_company(self.env.company) + .get_allocation_data_request()[0][1] + ) + self.assertEqual(res_leave_type["remaining_leaves"], 5) + self.assertEqual(res_leave_type["virtual_remaining_leaves"], 5) + self.assertEqual(res_leave_type["max_leaves"], 5) + self.assertEqual(res_leave_type["leaves_taken"], 0) + self.assertEqual(res_leave_type["virtual_leaves_taken"], 0) self.assertEqual(res_leave_type["request_unit"], "natural_day") leave = self._create_hr_leave(self.leave_type, "2023-01-02", "2023-01-05") self.assertEqual(leave.number_of_days, 4.0) @@ -95,14 +90,17 @@ def test_hr_leave_natural_day(self): @users("test-user") def test_hr_leave_day(self): leave_allocation = self._create_leave_allocation(self.leave_type_day, 5) - leave_allocation.action_confirm() leave_allocation.sudo().action_validate() - res_leave_type = self.env["hr.leave.type"].get_days_all_request()[0][1] - self.assertEqual(res_leave_type["remaining_leaves"], "5") - self.assertEqual(res_leave_type["virtual_remaining_leaves"], "5") - self.assertEqual(res_leave_type["max_leaves"], "5") - self.assertEqual(res_leave_type["leaves_taken"], "0") - self.assertEqual(res_leave_type["virtual_leaves_taken"], "0") + res_leave_type = ( + self.env["hr.leave.type"] + .with_company(self.env.company) + .get_allocation_data_request()[0][1] + ) + self.assertEqual(res_leave_type["remaining_leaves"], 5) + self.assertEqual(res_leave_type["virtual_remaining_leaves"], 5) + self.assertEqual(res_leave_type["max_leaves"], 5) + self.assertEqual(res_leave_type["leaves_taken"], 0) + self.assertEqual(res_leave_type["virtual_leaves_taken"], 0) self.assertEqual(res_leave_type["request_unit"], "day") leave = self._create_hr_leave(self.leave_type_day, "2023-01-08", "2023-01-15") self.assertEqual(leave.number_of_days, 5)