From f95555d0e51ea4e6fd1c07afa3b3f55c632c5af5 Mon Sep 17 00:00:00 2001 From: "Audric Onockx (auon)" Date: Thu, 6 Oct 2022 10:34:57 +0000 Subject: [PATCH] [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) --- addons/hr/models/resource.py | 23 -------------- addons/hr/tests/test_resource.py | 31 +------------------ .../hr_contract/models/resource_resource.py | 19 +++++++++++- addons/hr_contract/tests/test_resource.py | 2 +- 4 files changed, 20 insertions(+), 55 deletions(-) diff --git a/addons/hr/models/resource.py b/addons/hr/models/resource.py index d0db7c76b82..64b5f52db31 100644 --- a/addons/hr/models/resource.py +++ b/addons/hr/models/resource.py @@ -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 diff --git a/addons/hr/tests/test_resource.py b/addons/hr/tests/test_resource.py index 490fe343694..19074b3f75b 100644 --- a/addons/hr/tests/test_resource.py +++ b/addons/hr/tests/test_resource.py @@ -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.") diff --git a/addons/hr_contract/models/resource_resource.py b/addons/hr_contract/models/resource_resource.py index 5ac7e078eb2..953106496aa 100644 --- a/addons/hr_contract/models/resource_resource.py +++ b/addons/hr_contract/models/resource_resource.py @@ -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) diff --git a/addons/hr_contract/tests/test_resource.py b/addons/hr_contract/tests/test_resource.py index 0a6021c6051..9343f5d9b58 100644 --- a/addons/hr_contract/tests/test_resource.py +++ b/addons/hr_contract/tests/test_resource.py @@ -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)