diff --git a/addons/project_timesheet_holidays/models/hr_employee.py b/addons/project_timesheet_holidays/models/hr_employee.py index 988294428e4..4c9216f37b5 100644 --- a/addons/project_timesheet_holidays/models/hr_employee.py +++ b/addons/project_timesheet_holidays/models/hr_employee.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, fields, models +from collections import defaultdict class Employee(models.Model): @@ -24,7 +25,8 @@ class Employee(models.Model): if 'active' in vals: if vals.get('active'): # Create future holiday timesheets - self._create_future_public_holidays_timesheets(self) + inactive_emp = self.filtered(lambda e: not e.active) + inactive_emp._create_future_public_holidays_timesheets(self) else: # Delete future holiday timesheets self._delete_future_public_holidays_timesheets() @@ -42,12 +44,17 @@ class Employee(models.Model): def _create_future_public_holidays_timesheets(self, employees): lines_vals = [] today = fields.Datetime.today() - global_leaves_wo_calendar = self.env['resource.calendar.leaves'].search([('calendar_id', '=', False), ('date_from', '>=', today)]) + global_leaves_wo_calendar = defaultdict(lambda: self.env["resource.calendar.leaves"]) + global_leaves_wo_calendar.update(dict(self.env['resource.calendar.leaves']._read_group( + [('calendar_id', '=', False), ('date_from', '>=', today)], + groupby=['company_id'], + aggregates=['id:recordset'], + ))) for employee in employees: if not employee.active: continue # First we look for the global time off that are already planned after today - global_leaves = employee.resource_calendar_id.global_leave_ids.filtered(lambda l: l.date_from >= today) + global_leaves_wo_calendar + global_leaves = employee.resource_calendar_id.global_leave_ids.filtered(lambda l: l.date_from >= today) + global_leaves_wo_calendar[employee.company_id] work_hours_data = global_leaves._work_time_per_day() for global_time_off in global_leaves: for index, (day_date, work_hours_count) in enumerate(work_hours_data[employee.resource_calendar_id.id][global_time_off.id]): diff --git a/addons/project_timesheet_holidays/models/resource_calendar_leaves.py b/addons/project_timesheet_holidays/models/resource_calendar_leaves.py index 1a2d8834f49..43ca8af55d6 100644 --- a/addons/project_timesheet_holidays/models/resource_calendar_leaves.py +++ b/addons/project_timesheet_holidays/models/resource_calendar_leaves.py @@ -28,7 +28,7 @@ class ResourceCalendarLeaves(models.Model): (b) the resources which have a leave, (c) the oldest and (d) the latest leave dates - ) for leaves in self. + ) for leaves in self (first for calendar's leaves, then for company's global leaves) 2) Search the attendances based on the characteristics retrieved for each calendar. The attendances found are the ones between the date_from of the oldest leave and the date_to of the most recent leave. @@ -41,8 +41,11 @@ class ResourceCalendarLeaves(models.Model): } """ resource_calendars = resource_calendars or self._get_resource_calendars() + # to easily find the calendar with its id. + calendars_dict = {calendar.id: calendar for calendar in resource_calendars} + leaves_read_group = self.env['resource.calendar.leaves']._read_group( - [('id', 'in', self.ids)], + [('id', 'in', self.ids), ('calendar_id', '!=', False)], ['calendar_id'], ['id:recordset', 'resource_id:recordset', 'date_from:min', 'date_to:max'], ) @@ -56,13 +59,33 @@ class ResourceCalendarLeaves(models.Model): 'resources': resources, 'leaves': leaves, } - if not calendar: - for calendar_id in resource_calendars.ids: + cal_attendance_intervals_dict[calendar.id] = calendar_data + + comp_leaves_read_group = self.env['resource.calendar.leaves']._read_group( + [('id', 'in', self.ids), ('calendar_id', '=', False)], + ['company_id'], + ['id:recordset', 'resource_id:recordset', 'date_from:min', 'date_to:max'], + ) + for company, leaves, resources, date_from_min, date_to_max in comp_leaves_read_group: + for calendar_id in resource_calendars.ids: + if calendars_dict[calendar_id].company_id != company: + continue # only consider global leaves of the same company as the calendar + calendar_data = cal_attendance_intervals_dict.get(calendar_id) + if calendar_data is None: + calendar_data = { + 'date_from': utc.localize(date_from_min), + 'date_to': utc.localize(date_to_max), + 'resources': resources, + 'leaves': leaves, + } cal_attendance_intervals_dict[calendar_id] = calendar_data - else: - cal_attendance_intervals_dict[calendar.id] = calendar_data - # to easily find the calendar with its id. - calendars_dict = {calendar.id: calendar for calendar in resource_calendars} + else: + calendar_data.update( + date_from=min(utc.localize(date_from_min), calendar_data['date_from']), + date_to=max(utc.localize(date_to_max), calendar_data['date_to']), + resources=resources | calendar_data['resources'], + leaves=leaves | calendar_data['leaves'], + ) # dict of keys: calendar_id # and values: a dict of keys: leave.id diff --git a/addons/project_timesheet_holidays/tests/test_timesheet_global_time_off.py b/addons/project_timesheet_holidays/tests/test_timesheet_global_time_off.py index 300f820c07e..b6a68725d01 100644 --- a/addons/project_timesheet_holidays/tests/test_timesheet_global_time_off.py +++ b/addons/project_timesheet_holidays/tests/test_timesheet_global_time_off.py @@ -68,6 +68,11 @@ class TestTimesheetGlobalTimeOff(common.TransactionCase): }, ]) + # Create a 2nd company + self.test_company_2 = self.env['res.company'].create({ + 'name': 'My Test Company 2', + }) + def _get_timesheets_by_employee(self, leave_task): timesheets_by_read_dict = self.env['account.analytic.line']._read_group([('task_id', '=', leave_task.id)], ['employee_id'], ['__count']) return { @@ -121,19 +126,38 @@ class TestTimesheetGlobalTimeOff(common.TransactionCase): 'date_to': leave_end_datetime, }) - # 5 Timesheets should have been created for full_time_employee + # Create a global time-off in the same company (not specific a to calendar) + # this should be added to calendar's leaves + self.env['resource.calendar.leaves'].with_company(self.test_company).create({ + 'name': 'Global leave', + 'calendar_id': False, + 'date_from': (leave_start_datetime + timedelta(weeks=1)).replace(hour=0, minute=0, second=0), + 'date_to': (leave_start_datetime + timedelta(weeks=1, days=1)).replace(hour=23, minute=59, second=59), + }) + + # Create a global time-off in another company which should not be + # taken into account when creating/unarchiving employee + self.env['resource.calendar.leaves'].with_company(self.test_company_2).create({ + 'name': 'Global leave in another company', + 'calendar_id': False, + # Monday in two weeks + 'date_from': (leave_start_datetime + timedelta(weeks=2)).replace(hour=0, minute=0, second=0), + 'date_to': (leave_start_datetime + timedelta(weeks=2)).replace(hour=23, minute=59, second=59), + }) + + # 7 Timesheets should have been created for full_time_employee timesheets_full_time_employee = self.env['account.analytic.line'].search([('employee_id', '=', self.full_time_employee.id)]) - self.assertEqual(len(timesheets_full_time_employee), 5) + self.assertEqual(len(timesheets_full_time_employee), 7) # All timesheets should have been deleted for full_time_employee when he is archived self.full_time_employee.active = False timesheets_full_time_employee = self.env['account.analytic.line'].search([('employee_id', '=', self.full_time_employee.id)]) self.assertEqual(len(timesheets_full_time_employee), 0) - # 5 Timesheets should have been created for full_time_employee when he is unarchived + # 7 Timesheets should have been created for full_time_employee when he is unarchived self.full_time_employee.active = True timesheets_full_time_employee = self.env['account.analytic.line'].search([('employee_id', '=', self.full_time_employee.id)]) - self.assertEqual(len(timesheets_full_time_employee), 5) + self.assertEqual(len(timesheets_full_time_employee), 7) # This tests that no timesheet are created for days when the employee is not supposed to work def test_no_timesheet_on_off_days(self):