def _get_contract_work_entries_values(self, date_start, date_stop): start_dt = pytz.utc.localize(date_start) if not date_start.tzinfo else date_start end_dt = pytz.utc.localize(date_stop) if not date_stop.tzinfo else date_stop contract_vals = [] bypassing_work_entry_type_codes = self._get_bypassing_work_entry_type_codes() attendances_by_resource = self._get_attendance_intervals(start_dt, end_dt) resource_calendar_leaves = self.env['resource.calendar.leaves'].search(self._get_leave_domain(start_dt, end_dt)) # {resource: resource_calendar_leaves} leaves_by_resource = defaultdict(lambda: self.env['resource.calendar.leaves']) for leave in resource_calendar_leaves: leaves_by_resource[leave.resource_id.id] |= leave tz_dates = {} for contract in self: employee = contract.employee_id calendar = contract.resource_calendar_id resource = employee.resource_id tz = pytz.timezone(calendar.tz) attendances = attendances_by_resource[resource.id] # Other calendars: In case the employee has declared time off in another calendar # Example: Take a time off, then a credit time. # YTI TODO: This mimics the behavior of _leave_intervals_batch, while waiting to be cleaned # in master. resources_list = [self.env['resource.resource'], resource] result = defaultdict(lambda: []) for leave in itertools.chain(leaves_by_resource[False], leaves_by_resource[resource.id]): for resource in resources_list: # Global time off is not for this calendar, can happen with multiple calendars in self if resource and leave.calendar_id and leave.calendar_id != calendar and not leave.resource_id: continue tz = tz if tz else pytz.timezone((resource or contract).tz) if (tz, start_dt) in tz_dates: start = tz_dates[(tz, start_dt)] else: start = start_dt.astimezone(tz) tz_dates[(tz, start_dt)] = start if (tz, end_dt) in tz_dates: end = tz_dates[(tz, end_dt)] else: end = end_dt.astimezone(tz) tz_dates[(tz, end_dt)] = end dt0 = string_to_datetime(leave.date_from).astimezone(tz) dt1 = string_to_datetime(leave.date_to).astimezone(tz) result[resource.id].append((max(start, dt0), min(end, dt1), leave)) mapped_leaves = {r.id: Intervals(result[r.id]) for r in resources_list} leaves = mapped_leaves[resource.id] real_attendances = attendances - leaves if contract.has_static_work_entries() or not leaves: # Empty leaves means empty real_leaves real_leaves = attendances - real_attendances else: # In the case of attendance based contracts use regular attendances to generate leave intervals static_attendances = calendar._attendance_intervals_batch( start_dt, end_dt, resources=resource, tz=tz)[resource.id] real_leaves = static_attendances & leaves if not contract.has_static_work_entries(): # An attendance based contract might have an invalid planning, by definition it may not happen with # static work entries. # Creating overlapping slots for example might lead to a single work entry. # In that case we still create both work entries to indicate a problem (conflicting W E). split_attendances = [] for attendance in real_attendances: if attendance[2] and len(attendance[2]) > 1: split_attendances += [(attendance[0], attendance[1], a) for a in attendance[2]] else: split_attendances += [attendance] real_attendances = split_attendances # A leave period can be linked to several resource.calendar.leave split_leaves = [] for leave_interval in leaves: if leave_interval[2] and len(leave_interval[2]) > 1: split_leaves += [(leave_interval[0], leave_interval[1], l) for l in leave_interval[2]] else: split_leaves += [(leave_interval[0], leave_interval[1], leave_interval[2])] leaves = split_leaves # Attendances default_work_entry_type = contract._get_default_work_entry_type() for interval in real_attendances: work_entry_type = 'work_entry_type_id' in interval[2] and interval[2].work_entry_type_id[:1]\ or default_work_entry_type # All benefits generated here are using datetimes converted from the employee's timezone contract_vals += [dict([ ('name', "%s: %s" % (work_entry_type.name, employee.name)), ('date_start', interval[0].astimezone(pytz.utc).replace(tzinfo=None)), ('date_stop', interval[1].astimezone(pytz.utc).replace(tzinfo=None)), ('work_entry_type_id', work_entry_type.id), ('employee_id', employee.id), ('contract_id', contract.id), ('company_id', contract.company_id.id), ('state', 'draft'), ] + contract._get_more_vals_attendance_interval(interval))] for interval in real_leaves: # Could happen when a leave is configured on the interface on a day for which the # employee is not supposed to work, i.e. no attendance_ids on the calendar. # In that case, do try to generate an empty work entry, as this would raise a # sql constraint error if interval[0] == interval[1]: # if start == stop continue leave_entry_type = contract._get_interval_leave_work_entry_type(interval, leaves, bypassing_work_entry_type_codes) interval_start = interval[0].astimezone(pytz.utc).replace(tzinfo=None) interval_stop = interval[1].astimezone(pytz.utc).replace(tzinfo=None) contract_vals += [dict([ ('name', "%s%s" % (leave_entry_type.name + ": " if leave_entry_type else "", employee.name)), ('date_start', interval_start), ('date_stop', interval_stop), ('work_entry_type_id', leave_entry_type.id), ('employee_id', employee.id), ('company_id', contract.company_id.id), ('state', 'draft'), ('contract_id', contract.id), ] + contract._get_more_vals_leave_interval(interval, leaves))] return contract_vals
def _get_contract_work_entries_values(self, date_start, date_stop): contract_vals = [] bypassing_work_entry_type_codes = self._get_bypassing_work_entry_type_codes() for contract in self: employee = contract.employee_id calendar = contract.resource_calendar_id resource = employee.resource_id tz = pytz.timezone(calendar.tz) start_dt = pytz.utc.localize(date_start) if not date_start.tzinfo else date_start end_dt = pytz.utc.localize(date_stop) if not date_stop.tzinfo else date_stop attendances = calendar._attendance_intervals_batch( start_dt, end_dt, resources=resource, tz=tz )[resource.id] # Other calendars: In case the employee has declared time off in another calendar # Example: Take a time off, then a credit time. # YTI TODO: This mimics the behavior of _leave_intervals_batch, while waiting to be cleaned # in master. resources_list = [self.env['resource.resource'], resource] resource_ids = [False, resource.id] leave_domain = [ ('time_type', '=', 'leave'), # ('calendar_id', '=', self.id), --> Get all the time offs ('resource_id', 'in', resource_ids), ('date_from', '<=', datetime_to_string(end_dt)), ('date_to', '>=', datetime_to_string(start_dt)), ('company_id', '=', self.env.company.id), ] result = defaultdict(lambda: []) tz_dates = {} for leave in self.env['resource.calendar.leaves'].search(leave_domain): for resource in resources_list: if leave.resource_id.id not in [False, resource.id]: continue tz = tz if tz else pytz.timezone((resource or contract).tz) if (tz, start_dt) in tz_dates: start = tz_dates[(tz, start_dt)] else: start = start_dt.astimezone(tz) tz_dates[(tz, start_dt)] = start if (tz, end_dt) in tz_dates: end = tz_dates[(tz, end_dt)] else: end = end_dt.astimezone(tz) tz_dates[(tz, end_dt)] = end dt0 = string_to_datetime(leave.date_from).astimezone(tz) dt1 = string_to_datetime(leave.date_to).astimezone(tz) result[resource.id].append((max(start, dt0), min(end, dt1), leave)) mapped_leaves = {r.id: Intervals(result[r.id]) for r in resources_list} leaves = mapped_leaves[resource.id] real_attendances = attendances - leaves real_leaves = attendances - real_attendances # A leave period can be linked to several resource.calendar.leave split_leaves = [] for leave_interval in leaves: if leave_interval[2] and len(leave_interval[2]) > 1: split_leaves += [(leave_interval[0], leave_interval[1], l) for l in leave_interval[2]] else: split_leaves += [(leave_interval[0], leave_interval[1], leave_interval[2])] leaves = split_leaves # Attendances default_work_entry_type = contract._get_default_work_entry_type() for interval in real_attendances: work_entry_type_id = interval[2].mapped('work_entry_type_id')[:1] or default_work_entry_type # All benefits generated here are using datetimes converted from the employee's timezone contract_vals += [{ 'name': "%s: %s" % (work_entry_type_id.name, employee.name), 'date_start': interval[0].astimezone(pytz.utc).replace(tzinfo=None), 'date_stop': interval[1].astimezone(pytz.utc).replace(tzinfo=None), 'work_entry_type_id': work_entry_type_id.id, 'employee_id': employee.id, 'contract_id': contract.id, 'company_id': contract.company_id.id, 'state': 'draft', }] for interval in real_leaves: # Could happen when a leave is configured on the interface on a day for which the # employee is not supposed to work, i.e. no attendance_ids on the calendar. # In that case, do try to generate an empty work entry, as this would raise a # sql constraint error if interval[0] == interval[1]: # if start == stop continue leave_entry_type = contract._get_interval_leave_work_entry_type(interval, leaves, bypassing_work_entry_type_codes) interval_start = interval[0].astimezone(pytz.utc).replace(tzinfo=None) interval_stop = interval[1].astimezone(pytz.utc).replace(tzinfo=None) contract_vals += [dict([ ('name', "%s%s" % (leave_entry_type.name + ": " if leave_entry_type else "", employee.name)), ('date_start', interval_start), ('date_stop', interval_stop), ('work_entry_type_id', leave_entry_type.id), ('employee_id', employee.id), ('company_id', contract.company_id.id), ('state', 'draft'), ('contract_id', contract.id), ] + contract._get_more_vals_leave_interval(interval, leaves))] return contract_vals