[FIX] hr_work_entry_holidays: ensure the consistency of leaves

Steps to reproduce:
-------------------
- install `hr_work_entry_holidays` module
- create an employee and add two contracts:
    - from 2023-01-01 to 2023-06-30 with a full time (5/5) resource
      which is expired
    - from 2023-07-01 to 2023-12-31 with a partial time (4/5) resource
      which is running
      (doesn't work on Wednesday)
- create an allocation with 10 days
- with the employee, create two leaves:
    - 3 days during the first semester with one Wednesday
    - 3 days during the second semester with one Wednesday

Issue:
------
Leave duration is based on the current contract.
If we change the type of leave by modifying its days/hours unit,
we will get inconsistencies between days and hours for leave taken
in a period belonging to another contract.

Cause:
------
Expired contracts are not taken into account
when calculating the number of days and hours.

Solution:
---------
Add `close` state to contract search.

opw-3419380

closes odoo/odoo#132144

X-original-commit: 249f8c836f606c0dfc5f3b6e64ad9b319b5a4602
Signed-off-by: Sofie Gvaladze (sgv) <sgv@odoo.com>
Signed-off-by: Thomas Lefebvre (thle) <thle@odoo.com>
This commit is contained in:
Thomas Lefebvre (thle)
2023-08-21 07:19:51 +02:00
parent 92d9b6d779
commit f24f2d6384
2 changed files with 105 additions and 3 deletions
@@ -237,7 +237,7 @@ Contracts:
days = super()._get_number_of_days(date_from, date_to, employee)
if employee:
# Use sudo otherwise base users can't compute number of days
contracts = employee.sudo()._get_contracts(date_from, date_to, states=['open'])
contracts = employee.sudo()._get_contracts(date_from, date_to, states=['open', 'close'])
contracts |= employee.sudo()._get_incoming_contracts(date_from, date_to)
calendar = contracts[:1].resource_calendar_id if contracts else None # Note: if len(contracts)>1, the leave creation will crash because of unicity constaint
# We force the company in the domain as we are more than likely in a compute_sudo
@@ -252,7 +252,7 @@ Contracts:
def _get_calendar(self):
self.ensure_one()
if self.date_from and self.date_to:
contracts = self.employee_id.sudo()._get_contracts(self.date_from, self.date_to, states=['open'])
contracts = self.employee_id.sudo()._get_contracts(self.date_from, self.date_to, states=['open', 'close'])
contracts |= self.employee_id.sudo()._get_incoming_contracts(self.date_from, self.date_to)
contract_calendar = contracts[:1].resource_calendar_id if contracts else None
return contract_calendar or self.employee_id.resource_calendar_id or self.env.company.resource_calendar_id
@@ -1,7 +1,7 @@
# # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, date
from datetime import datetime, date, time
from odoo.exceptions import ValidationError
from odoo.tests import tagged
from odoo.addons.hr_work_entry_holidays.tests.common import TestWorkEntryHolidaysBase
@@ -170,3 +170,105 @@ class TestWorkEntryHolidaysMultiContract(TestWorkEntryHolidaysBase):
second_leave = leaves.filtered(lambda l: l.date_from.day == 16 and l.date_to.day == 30)
self.assertEqual(second_leave.state, 'validate')
self.assertEqual(second_leave.number_of_days, 11)
def test_contract_traceability_calculate_nbr_leave(self):
"""
The goal is to test the traceability of contracts in the past,
i.e. to check that expired contracts are taken into account
to ensure the consistency of leaves (number of days/hours) in the past.
"""
calendar_full, calendar_partial = self.env['resource.calendar'].create([
{
'name': 'Full time (5/5)',
},
{
'name': 'Partial time (4/5)',
'attendance_ids': [
(0, 0, {'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Monday Evening', 'dayofweek': '0', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
(0, 0, {'name': 'Tuesday Morning', 'dayofweek': '1', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Tuesday Evening', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
# Does not work on Wednesdays
(0, 0, {'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Thursday Evening', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}),
(0, 0, {'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
(0, 0, {'name': 'Friday Evening', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'})
]
},
])
employee = self.env['hr.employee'].create({
'name': 'Employee',
'resource_calendar_id': calendar_partial.id,
})
self.env['hr.contract'].create([
{
'name': 'Full time (5/5)',
'employee_id': employee.id,
'date_start': datetime.strptime('2023-01-01', '%Y-%m-%d').date(),
'date_end': datetime.strptime('2023-06-30', '%Y-%m-%d').date(),
'resource_calendar_id': calendar_full.id,
'wage': 1000.0,
'state': 'close', # Old contract
'date_generated_from': datetime.strptime('2023-01-01', '%Y-%m-%d'),
'date_generated_to': datetime.strptime('2023-01-01', '%Y-%m-%d'),
},
{
'name': 'Partial time (4/5)',
'employee_id': employee.id,
'date_start': datetime.strptime('2023-07-01', '%Y-%m-%d').date(),
'date_end': datetime.strptime('2023-12-31', '%Y-%m-%d').date(),
'resource_calendar_id': calendar_partial.id,
'wage': 1000.0,
'state': 'open', # Current contract
'date_generated_from': datetime.strptime('2023-07-01', '%Y-%m-%d'),
'date_generated_to': datetime.strptime('2023-07-01', '%Y-%m-%d'),
},
])
leave_type = self.env['hr.leave.type'].create({
'name': 'Leave Type',
'time_type': 'leave',
'requires_allocation': 'yes',
'leave_validation_type': 'hr',
'request_unit': 'day',
})
self.env['hr.leave.allocation'].create({
'name': 'Allocation',
'employee_id': employee.id,
'holiday_status_id': leave_type.id,
'number_of_days': 10,
'state': 'confirm',
'date_from': datetime.strptime('2023-01-01', '%Y-%m-%d').date(),
'date_to': datetime.strptime('2023-12-31', '%Y-%m-%d').date(),
}).action_validate()
leave_during_full_time, leave_during_partial_time = self.env['hr.leave'].create([
{
'employee_id': employee.id,
'holiday_status_id': leave_type.id,
'number_of_days': 3,
'date_from': datetime.combine(date(2023, 1, 3), time.min), # Tuesday
'date_to': datetime.combine(date(2023, 1, 5), time.max), # Thursday
},
{
'employee_id': employee.id,
'holiday_status_id': leave_type.id,
'number_of_days': 2,
'date_from': datetime.combine(date(2023, 12, 5), time.min), # Tuesday
'date_to': datetime.combine(date(2023, 12, 7), time.max), # Thursday
},
])
self.assertEqual(leave_during_full_time.number_of_days_display, 3)
self.assertEqual(leave_during_partial_time.number_of_days_display, 2)
self.assertEqual(leave_during_full_time.number_of_hours_display, 24)
self.assertEqual(leave_during_partial_time.number_of_hours_display, 16)
# Simulate the unit change days/hours of the time off type
(leave_during_full_time + leave_during_partial_time)._compute_number_of_days()
self.assertEqual(leave_during_full_time.number_of_days_display, 3)
self.assertEqual(leave_during_partial_time.number_of_days_display, 2)
(leave_during_full_time + leave_during_partial_time)._compute_number_of_hours_display()
self.assertEqual(leave_during_full_time.number_of_hours_display, 24)
self.assertEqual(leave_during_partial_time.number_of_hours_display, 16)
# Check after leave approval
(leave_during_full_time + leave_during_partial_time).action_approve()
(leave_during_full_time + leave_during_partial_time)._compute_number_of_hours_display()
self.assertEqual(leave_during_full_time.number_of_hours_display, 24)
self.assertEqual(leave_during_partial_time.number_of_hours_display, 16)