def _get_unavailable_dates(self, start_date, end_date): """ Returns the list of days when the current company is closed (we, or holidays) """ start_dt = datetime(year=start_date.year, month=start_date.month, day=start_date.day) end_dt = datetime(year=end_date.year, month=end_date.month, day=end_date.day, hour=23, minute=59, second=59) # naive datetimes are made explicit in UTC from_datetime, dummy = make_aware(start_dt) to_datetime, dummy = make_aware(end_dt) # We need to display in grey the unavailable full days # We start by getting the availability intervals to avoid false positive with range outside the office hours items = self.env.company.resource_calendar_id._work_intervals( from_datetime, to_datetime) # get the dates where some work can be done in the interval. It returns a list of sets. available_dates = list( map(lambda item: {item[0].date(), item[1].date()}, items)) # flatten the list of sets to get a simple list of dates and add it to the pile. avaibilities = [date for dates in available_dates for date in dates] unavailable_days = [] cur_day = from_datetime while cur_day <= to_datetime: if not cur_day.date() in avaibilities: unavailable_days.append(cur_day.date()) cur_day = cur_day + timedelta(days=1) return set(unavailable_days)
def _get_first_available_slot(self, start_datetime, duration): """Get the first available interval for the workcenter in `self`. The available interval is disjoinct with all other workorders planned on this workcenter, but can overlap the time-off of the related calendar (inverse of the working hours). Return the first available interval (start datetime, end datetime) or, if there is none before 700 days, a tuple error (False, 'error message'). :param start_datetime: begin the search at this datetime :param duration: minutes needed to make the workorder (float) :rtype: tuple """ self.ensure_one() start_datetime, revert = make_aware(start_datetime) resource = self.resource_id get_available_intervals = partial( self.resource_calendar_id._work_intervals_batch, domain=[('time_type', 'in', ['other', 'leave'])], resources=resource, tz=timezone(self.resource_calendar_id.tz)) get_workorder_intervals = partial( self.resource_calendar_id._leave_intervals_batch, domain=[('time_type', '=', 'other')], resources=resource, tz=timezone(self.resource_calendar_id.tz)) remaining = duration start_interval = start_datetime delta = timedelta(days=14) for n in range(50): # 50 * 14 = 700 days in advance (hardcoded) dt = start_datetime + delta * n available_intervals = get_available_intervals(dt, dt + delta)[resource.id] workorder_intervals = get_workorder_intervals(dt, dt + delta)[resource.id] for start, stop, dummy in available_intervals: # Shouldn't loop more than 2 times because the available_intervals contains the workorder_intervals # And remaining == duration can only occur at the first loop and at the interval intersection (cannot happen several time because available_intervals > workorder_intervals for _i in range(2): interval_minutes = (stop - start).total_seconds() / 60 # If the remaining minutes has never decrease update start_interval if remaining == duration: start_interval = start # If there is a overlap between the possible available interval and a others WO if Intervals( [(start_interval, start + timedelta(minutes=min(remaining, interval_minutes)), dummy)]) & workorder_intervals: remaining = duration elif float_compare(interval_minutes, remaining, precision_digits=3) >= 0: return revert(start_interval), revert( start + timedelta(minutes=remaining)) else: # Decrease a part of the remaining duration remaining -= interval_minutes return False, 'Not available slot 700 days after the planned start'
def plan_days_to_resource(self, days, day_dt, compute_leaves=False, resource=None, domain=None): day_dt, revert = make_aware(day_dt) # which method to use for retrieving intervals if compute_leaves: get_intervals = partial(self._work_intervals, resource=resource, domain=domain) else: get_intervals = partial(self._attendance_intervals, resource=resource) if days > 0: found = set() delta = timedelta(days=14) for n in range(100): dt = day_dt + delta * n for start, stop, meta in get_intervals(dt, dt + delta): found.add(start.date()) if len(found) == days: return revert(stop) return False elif days < 0: days = abs(days) found = set() delta = timedelta(days=14) for n in range(100): dt = day_dt - delta * n for start, stop, meta in reversed(get_intervals( dt - delta, dt)): found.add(start.date()) if len(found) == days: return revert(start) return False else: return revert(day_dt)
def __new_plan_hours(self, hours, day_dt, compute_leaves=False, domain=None, resource=None): day_dt, revert = make_aware(day_dt) # which method to use for retrieving intervals if compute_leaves: get_intervals = partial(self._work_intervals, domain=domain, resource=resource) else: get_intervals = self._attendance_intervals if hours >= 0: delta = timedelta(days=14) for n in range(100): dt = day_dt + delta * n for start, stop, meta in get_intervals(dt, dt + delta): interval_hours = self._get_work_hours_interval( start, stop, meta) if hours <= interval_hours: return revert(start + timedelta(hours=hours)) hours -= interval_hours return False else: hours = abs(hours) delta = timedelta(days=14) for n in range(100): dt = day_dt - delta * n for start, stop, meta in reversed(get_intervals( dt - delta, dt)): interval_hours = self._get_work_hours_interval( start, stop, meta) if hours <= interval_hours: return revert(stop - timedelta(hours=hours)) hours -= interval_hours return False
def plan_days_end(self, days, day_dt, compute_leaves=False, domain=None): """ Override to `plan_days` that allows you to get the nearest 'end' including today. """ day_dt, revert = make_aware(day_dt) # which method to use for retrieving intervals if compute_leaves: get_intervals = partial(self._work_intervals, domain=domain) else: get_intervals = self._attendance_intervals if days >= 0: found = set() delta = timedelta(days=14) for n in range(100): dt = day_dt + delta * n for start, stop, meta in get_intervals(dt, dt + delta): found.add(start.date()) if len(found) >= days: return revert(stop) return False elif days < 0: days = abs(days) found = set() delta = timedelta(days=14) for n in range(100): dt = day_dt - delta * n for start, stop, meta in reversed(get_intervals( dt - delta, dt)): found.add(start.date()) if len(found) == days: return revert(start) return False else: return revert(day_dt)