[FIX]l10n_fr_hr_holidays:fix bug in part-time employee duleaves duration calculation

This commit is contained in:
2026-06-25 18:55:22 +02:00
parent 2f73c1b3a7
commit ecba73e832
5 changed files with 352 additions and 26 deletions

View File

@@ -175,9 +175,14 @@ class HrLeave(models.Model):
#@overwrite
def _get_number_of_days_batch(self, date_from, date_to, employee_ids):
"""
Returns a dict with the number of legal leave days for each employee,
based on the company's calendar. In France, part-time employees accrue and take leave on company working days,
not only on their own working days. Handles half-day requests and rounds according to French rules.
Returns a dict with the number of legal leave days for each employee.
In France, part-time employees' leave is counted in company working days
(jours ouvrables) from the first day of absence to the return day
(exclusive). The absence period is determined by the employee's calendar,
but the day count uses the company's calendar. Public holidays and other
global leaves registered on the company calendar are excluded.
For half-day requests, the first day counts as 0.5 if the employee
works the other half of that day (returns same day), or 1 otherwise.
"""
employee = self.env['hr.employee'].browse(employee_ids)
# Force the company in the domain, as we are likely in a compute_sudo context
@@ -188,12 +193,55 @@ class HrLeave(models.Model):
calendar = self._get_calendar()
result = employee._get_work_days_data_batch(date_from, date_to, calendar=calendar, domain=domain)
for employee_id in result:
# For non-French context: a half-day leave always counts as 0.5 day
if self.request_unit_half and result[employee_id]['hours'] > 0 and not self._l10n_fr_leave_applies():
if self._l10n_fr_leave_applies():
company_calendar = calendar
employee_calendar = self.resource_calendar_id
first_day = date_from.date()
last_date = date_to.date()
# _get_fr_date_from_to returns date_to with different semantics:
# - AM half-day, employee doesn't work PM: date_to = return day
# (must be excluded from the count)
# - AM half-day, employee works PM: date_to = same day (leave day)
# - General case (full-day, PM half-day): date_to = last leave day
# (day before the return, must be included)
# Only the first case has date_to as the return day. It is
# detected by: AM half-day, date_to strictly after date_from.
if (self.request_unit_half and self.request_date_from_period == 'am'
and last_date > first_day):
last_date -= relativedelta(days=1)
# Get the set of dates where the company actually works between
# first_day and last_date, excluding public holidays (global leaves).
# Don't pass the company_id-filtered domain here: the leave is
# already matched by calendar_id in _leave_intervals_batch, and
# the company_id filter would exclude holidays created on the
# french company when self.env.company is the main company.
working_dates = company_calendar._get_working_dates(first_day, last_date)
if self.request_unit_half:
# Check if the employee works the other half of the first day
dayofweek = str(first_day.weekday())
other_period = 'afternoon' if self.request_date_from_period == 'am' else 'morning'
other_half = employee_calendar.attendance_ids.filtered(
lambda a: a.dayofweek == dayofweek and a.day_period == other_period
)
first_day_count = 0.5 if (other_half and first_day in working_dates) else (1.0 if first_day in working_dates else 0.0)
days_count = first_day_count
current_date = first_day + relativedelta(days=1)
else:
days_count = 0
current_date = first_day
while current_date <= last_date:
if current_date in working_dates:
days_count += 1
current_date += relativedelta(days=1)
result[employee_id]['days'] = float(days_count)
elif self.request_unit_half and result[employee_id]['hours'] > 0:
# Non-French context: a half-day leave always counts as 0.5 day
result[employee_id]['days'] = 0.5
# For French context: round the number of days to the nearest half-day (legal rule)
elif self.request_unit_half and result[employee_id]['hours'] > 0 and self._l10n_fr_leave_applies():
result[employee_id]['days'] = self._round_to_nearest_half(result[employee_id]['days'])
return result
def _round_to_nearest_half(self, x):