[FIX] hr,hr_contract,resource: allocate hours before resource creation
Steps : - On a new DB (no demo data). Say created at 15:00. - Go to planning. - Add a shift for today. - Set the dates from 08:00 to 17:00. Issue : The 'Allocated Time' = 02:00. Cause : We used to count from the resource creation min to the departure max. So, in this case, not from 08:00, but from 15:00. While this might seem logical, it confuses users in onboarding. Fix : Calculate the whole time, regardless of the resource lifespan. Notes : - Similar issues solved with this commit : > Once the shift validated, the avatar progress bar uses the same allocated time. > Same in Project Task gantt view. - When contract is installed, if the resource does not have a contract, the Allocated Time = 0. Now, it is calculated from the resource calendar. task-2983993 closes odoo/odoo#102732 X-original-commit: 6a61d1c4c56075ade36f6f32e7a61f1f6c771a6f Related: odoo/enterprise#32547 Signed-off-by: Laurent Stukkens (ltu) <ltu@odoo.com>
This commit is contained in:
@@ -12,26 +12,3 @@ class ResourceResource(models.Model):
|
||||
|
||||
user_id = fields.Many2one(copy=False)
|
||||
employee_id = fields.One2many('hr.employee', 'resource_id', domain="[('company_id', '=', company_id)]")
|
||||
|
||||
def _get_calendars_validity_within_period(self, start, end, default_company=None):
|
||||
"""
|
||||
Returns a dict of dict with resource's id as first key and resource's calendar as secondary key
|
||||
The value is the validity interval of the calendar for the given resource.
|
||||
|
||||
The validity interval of the employee resource calendar is the lifetime of the employee, from creation to departure.
|
||||
"""
|
||||
assert start.tzinfo and end.tzinfo
|
||||
calendars_within_period_per_resource = super()._get_calendars_validity_within_period(start, end, default_company=default_company)
|
||||
for resource in self:
|
||||
if not resource.employee_id:
|
||||
continue
|
||||
create_date = max(start, utc.localize(resource.employee_id.create_date))
|
||||
if resource.employee_id.departure_date and resource.employee_id.departure_date <= end.date():
|
||||
departure_datetime = timezone(resource.tz).localize(datetime.combine(resource.employee_id.departure_date, datetime.max.time()))
|
||||
departure_datetime = min(departure_datetime, end)
|
||||
else:
|
||||
departure_datetime = end
|
||||
interval = Intervals([(create_date, departure_datetime, self.env['resource.calendar.attendance'])])
|
||||
for calendar in calendars_within_period_per_resource[resource.id]:
|
||||
calendars_within_period_per_resource[resource.id][calendar] &= interval
|
||||
return calendars_within_period_per_resource
|
||||
|
||||
@@ -48,39 +48,10 @@ class TestResource(TestHrCommon):
|
||||
utc.localize(datetime(2021, 1, 31, 17, 0, 0)),
|
||||
)
|
||||
interval = Intervals([(
|
||||
utc.localize(datetime(2021, 1, 1, 10, 0, 0)),
|
||||
utc.localize(datetime(2020, 12, 1, 8, 0, 0)),
|
||||
utc.localize(datetime(2021, 1, 31, 17, 0, 0)),
|
||||
self.env['resource.calendar.attendance']
|
||||
)])
|
||||
niv_entry = calendars[self.employee_niv.resource_id.id]
|
||||
self.assertFalse(niv_entry[self.calendar_40h] - interval, "Interval should cover all calendar's validity")
|
||||
self.assertFalse(interval - niv_entry[self.calendar_40h], "Calendar validity should cover all interval")
|
||||
|
||||
def test_calendars_validity_within_period_before_creation(self):
|
||||
calendars = self.employee_niv.resource_id._get_calendars_validity_within_period(
|
||||
utc.localize(datetime(2020, 12, 1, 8, 0, 0)),
|
||||
utc.localize(datetime(2020, 12, 31, 17, 0, 0)),
|
||||
)
|
||||
niv_entry = calendars[self.employee_niv.resource_id.id]
|
||||
self.assertFalse(niv_entry[self.calendar_40h], "Interval should be empty")
|
||||
|
||||
def test_calendars_validity_within_period_before_departure(self):
|
||||
calendars = self.employee_niv.resource_id._get_calendars_validity_within_period(
|
||||
utc.localize(datetime(2022, 5, 1, 8, 0, 0)),
|
||||
utc.localize(datetime(2022, 6, 30, 17, 0, 0)),
|
||||
)
|
||||
interval = Intervals([(
|
||||
utc.localize(datetime(2022, 5, 1, 8, 0, 0)),
|
||||
timezone(self.employee_niv.tz).localize(datetime(2022, 6, 1, 23, 59, 59, 999999)),
|
||||
self.env['resource.calendar.attendance']
|
||||
)])
|
||||
niv_entry = calendars[self.employee_niv.resource_id.id]
|
||||
self.assertFalse(niv_entry[self.calendar_40h] - interval, "Interval should cover all calendar's validity")
|
||||
self.assertFalse(interval - niv_entry[self.calendar_40h], "Calendar validity should cover all interval")
|
||||
|
||||
def test_get_valid_work_intervals(self):
|
||||
start = utc.localize(datetime(2022, 5, 29, 0, 0, 0))
|
||||
end = utc.localize(datetime(2022, 6, 4, 23, 59, 59))
|
||||
work_intervals, _ = self.employee_niv.resource_id._get_valid_work_intervals(start, end)
|
||||
sum_work_intervals = sum_intervals(work_intervals[self.employee_niv.resource_id.id])
|
||||
self.assertEqual(24, sum_work_intervals, "Sum of the work intervals for the resource niv should be 24h as he left his work after wednesday.")
|
||||
|
||||
@@ -15,7 +15,24 @@ class ResourceResource(models.Model):
|
||||
if not self:
|
||||
return super()._get_calendars_validity_within_period(start, end, default_company=default_company)
|
||||
calendars_within_period_per_resource = defaultdict(lambda: defaultdict(Intervals)) # keys are [resource id:integer][calendar:self.env['resource.calendar']]
|
||||
resource_without_contract = self.filtered(lambda r: not r.employee_id or r.employee_id.employee_type not in ['employee', 'student'])
|
||||
# Employees that have ever had an active contract
|
||||
employee_ids_with_active_contracts = {
|
||||
contract['employee_id'][0] for contract in
|
||||
self.env['hr.contract']._read_group(
|
||||
domain=[
|
||||
('employee_id', 'in', self.employee_id.ids),
|
||||
'|', ('state', '=', 'open'),
|
||||
'|', ('state', '=', 'close'),
|
||||
'&', ('state', '=', 'draft'), ('kanban_state', '=', 'done')
|
||||
],
|
||||
fields=['employee_id'], groupby=['employee_id']
|
||||
)
|
||||
}
|
||||
resource_without_contract = self.filtered(
|
||||
lambda r: not r.employee_id\
|
||||
or not r.employee_id.id in employee_ids_with_active_contracts\
|
||||
or r.employee_id.employee_type not in ['employee', 'student']
|
||||
)
|
||||
if resource_without_contract:
|
||||
calendars_within_period_per_resource.update(
|
||||
super(ResourceResource, resource_without_contract)._get_calendars_validity_within_period(start, end, default_company=default_company)
|
||||
|
||||
@@ -96,7 +96,7 @@ class TestResource(TestContractCommon):
|
||||
|
||||
start = utc.localize(datetime(2021, 9, 1, 0, 0, 0))
|
||||
end = utc.localize(datetime(2021, 11, 30, 23, 59, 59))
|
||||
with self.assertQueryCount(14):
|
||||
with self.assertQueryCount(15):
|
||||
work_intervals, _ = (employees_test | self.employee).resource_id._get_valid_work_intervals(start, end)
|
||||
|
||||
self.assertEqual(len(work_intervals), 51)
|
||||
|
||||
Reference in New Issue
Block a user