diff --git a/addons/hr/models/hr_employee.py b/addons/hr/models/hr_employee.py index 0c76f4ccd19..078b95f8096 100644 --- a/addons/hr/models/hr_employee.py +++ b/addons/hr/models/hr_employee.py @@ -4,11 +4,14 @@ import base64 from random import choice from string import digits +import itertools from werkzeug import url_encode +import pytz from odoo import api, fields, models, tools, _ from odoo.exceptions import ValidationError, AccessError from odoo.modules.module import get_module_resource +from odoo.addons.resource.models.resource_mixin import timezone_datetime class HrEmployeePrivate(models.Model): @@ -148,7 +151,7 @@ class HrEmployeePrivate(models.Model): search on an hr.employee returns a hr.employee recordset, even if you don't have access to this model, as the result of _search (the ids of the public employees) is to be browsed on the hr.employee model. This can be trusted as the ids of the public - employees exactly match the ids of the related hr.employee. + employees exactly match the ids of the related hr.employee. """ if self.check_access_rights('read', raise_exception=False): return super(HrEmployeePrivate, self)._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid) @@ -306,6 +309,10 @@ class HrEmployeePrivate(models.Model): except AccessError: employee.is_address_home_a_company = False + # --------------------------------------------------------- + # Business Methods + # --------------------------------------------------------- + @api.model def get_import_templates(self): return [{ @@ -326,6 +333,55 @@ class HrEmployeePrivate(models.Model): self = self.with_user(real_user) return self + def _get_work_interval(self, start, end): + """ Return interval's start datetime for interval closest to start. And interval's end datetime for interval closest to end. + If none is found return None + Note: this method is used in enterprise (forecast and planning) + + :start: datetime + :end: datetime + :return: (datetime|None, datetime|None) + """ + start_datetime = timezone_datetime(start) + end_datetime = timezone_datetime(end) + employee_mapping = {} + for employee in self: + work_intervals = sorted( + employee.resource_calendar_id._work_intervals(start_datetime, end_datetime, employee.resource_id), + key=lambda x: x[0] + ) + if work_intervals: + employee_mapping[employee.id] = (work_intervals[0][0].astimezone(pytz.utc), work_intervals[-1][1].astimezone(pytz.utc)) + else: + employee_mapping[employee.id] = (None, None) + return employee_mapping + + def _get_unavailable_intervals(self, start, end): + """ Compute the intervals during which employee is unavailable with hour granularity between start and end + Note: this method is used in enterprise (forecast and planning) + + """ + start_datetime = timezone_datetime(start) + end_datetime = timezone_datetime(end) + employee_mapping = {} + for employee in self: + calendar = employee.resource_calendar_id + resource = employee.resource_id + employee_work_intervals = calendar._work_intervals(start_datetime, end_datetime, resource) + employee_work_intervals = [(start, stop) for start, stop, meta in employee_work_intervals] + # start + flatten(intervals) + end + employee_work_intervals = [start_datetime] + list(itertools.chain.from_iterable(employee_work_intervals)) + [end_datetime] + # put it back to UTC + employee_work_intervals = list(map(lambda dt: dt.astimezone(pytz.utc), employee_work_intervals)) + # pick groups of two + employee_work_intervals = list(zip(employee_work_intervals[0::2], employee_work_intervals[1::2])) + employee_mapping[employee.id] = employee_work_intervals + return employee_mapping + + # --------------------------------------------------------- + # Messaging + # --------------------------------------------------------- + def _message_log(self, **kwargs): return super(HrEmployeePrivate, self._post_author())._message_log(**kwargs)