[FIX] project_timesheet_holiday: only create GTO of employee's company

Following fdb692c25b:

- when creating/unarchiving an employee, we should only create global
  time-off of the same company as the employee itself

- when computing the working hours relative to the GTO, if we had both
  calendar GTO and company GTO (not linked to a specific calendar), we
  were not correctly creating calendar GTO; we should merge those
  intervals intead of company GTO interval simply overwriting calendar
  GTO intervals

closes odoo/odoo#145596

Task-id: 3619083
X-original-commit: 8ec0c6c106352c19e5c7b5613be2f610fa6e0adb
Signed-off-by: Xavier Bol (xbo) <xbo@odoo.com>
This commit is contained in:
Xavier ALT
2023-12-11 08:29:20 +00:00
parent 591a73939a
commit 8114099ce7
3 changed files with 69 additions and 15 deletions
@@ -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]):
@@ -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
@@ -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):