[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:
Audric Onockx (auon)
2022-10-08 09:42:49 +02:00
parent 692b4e93ec
commit f95555d0e5
4 changed files with 20 additions and 55 deletions
-23
View File
@@ -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
+1 -30
View File
@@ -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.")
+18 -1
View File
@@ -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)
+1 -1
View File
@@ -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)