def test_create_series(creation_params): for occ1, occ2 in izip( list(ReservationOccurrence.iter_create_occurrences(**creation_params)), ReservationOccurrence.create_series(**creation_params), ): assert occ1.start_dt == occ2.start_dt assert occ1.end_dt == occ2.end_dt
def _get_all_occurrences(self, room_ids, form, flexible_days=0, reservation_id=None): start_dt = form.start_dt.data end_dt = form.end_dt.data repeat_frequency = form.repeat_frequency.data repeat_interval = form.repeat_interval.data day_start_dt = datetime.combine(start_dt.date(), time()) day_end_dt = datetime.combine(end_dt.date(), time(23, 59)) today_start_dt = datetime.combine(date.today(), time()) flexible_start_dt = day_start_dt - timedelta(days=flexible_days) if not session.user.isAdmin(): flexible_start_dt = max(today_start_dt, flexible_start_dt) flexible_end_dt = day_end_dt + timedelta(days=flexible_days) occurrences = ReservationOccurrence.find_all( Reservation.room_id.in_(room_ids), Reservation.id != reservation_id, ReservationOccurrence.start_dt >= flexible_start_dt, ReservationOccurrence.end_dt <= flexible_end_dt, ReservationOccurrence.is_valid, _join=Reservation, _eager=ReservationOccurrence.reservation ) candidates = {} for days in xrange(-flexible_days, flexible_days + 1): offset = timedelta(days=days) series_start = start_dt + offset series_end = end_dt + offset if series_start < flexible_start_dt: continue candidates[series_start, series_end] = ReservationOccurrence.create_series(series_start, series_end, (repeat_frequency, repeat_interval)) return occurrences, candidates
def get_rooms_availability(rooms, start_dt, end_dt, repeat_frequency, repeat_interval): availability = OrderedDict() candidates = ReservationOccurrence.create_series(start_dt, end_dt, (repeat_frequency, repeat_interval)) date_range = sorted(set(cand.start_dt.date() for cand in candidates)) occurrences = get_existing_rooms_occurrences(rooms, start_dt.replace(hour=0, minute=0), end_dt.replace(hour=23, minute=59), repeat_frequency, repeat_interval) blocked_rooms = get_rooms_blockings(rooms, start_dt.date(), end_dt.date()) unbookable_hours = get_rooms_unbookable_hours(rooms) nonbookable_periods = get_rooms_nonbookable_periods(rooms, start_dt, end_dt) conflicts, pre_conflicts = get_rooms_conflicts(rooms, start_dt.replace(tzinfo=None), end_dt.replace(tzinfo=None), repeat_frequency, repeat_interval, blocked_rooms, nonbookable_periods, unbookable_hours) dates = list(candidate.start_dt.date() for candidate in candidates) for room in rooms: room_occurrences = occurrences.get(room.id, []) room_conflicts = conflicts.get(room.id, []) pre_room_conflicts = pre_conflicts.get(room.id, []) pre_bookings = [occ for occ in room_occurrences if not occ.reservation.is_accepted] existing_bookings = [occ for occ in room_occurrences if occ.reservation.is_accepted] room_blocked_rooms = blocked_rooms.get(room.id, []) room_nonbookable_periods = nonbookable_periods.get(room.id, []) room_unbookable_hours = unbookable_hours.get(room.id, []) availability[room.id] = {'room_id': room.id, 'candidates': group_by_occurrence_date(candidates), 'pre_bookings': group_by_occurrence_date(pre_bookings), 'bookings': group_by_occurrence_date(existing_bookings), 'conflicts': group_by_occurrence_date(room_conflicts), 'pre_conflicts': group_by_occurrence_date(pre_room_conflicts), 'blockings': group_blockings(room_blocked_rooms, dates), 'nonbookable_periods': group_nonbookable_periods(room_nonbookable_periods, dates), 'unbookable_hours': room_unbookable_hours} return date_range, availability
def filter_available(start_dt, end_dt, repetition, include_pre_bookings=True, include_pending_blockings=True): """Returns a SQLAlchemy filter criterion ensuring that the room is available during the given time.""" # Check availability against reservation occurrences dummy_occurrences = ReservationOccurrence.create_series(start_dt, end_dt, repetition) overlap_criteria = ReservationOccurrence.filter_overlap(dummy_occurrences) reservation_criteria = [Reservation.room_id == Room.id, ReservationOccurrence.is_valid, overlap_criteria] if not include_pre_bookings: reservation_criteria.append(Reservation.is_accepted) occurrences_filter = (Reservation.query .join(ReservationOccurrence.reservation) .filter(and_(*reservation_criteria))) # Check availability against blockings if include_pending_blockings: valid_states = (BlockedRoom.State.accepted, BlockedRoom.State.pending) else: valid_states = (BlockedRoom.State.accepted,) blocking_criteria = [Room.id == BlockedRoom.room_id, BlockedRoom.state.in_(valid_states), db_dates_overlap(Blocking, 'start_date', end_dt.date(), 'end_date', start_dt.date(), inclusive=True)] blockings_filter = (BlockedRoom.query .join(Blocking.blocked_rooms) .filter(and_(*blocking_criteria))) return ~occurrences_filter.exists() & ~blockings_filter.exists()
def _get_all_occurrences(self, room_ids, form, flexible_days=0, reservation_id=None): start_dt = form.start_dt.data end_dt = form.end_dt.data repeat_frequency = form.repeat_frequency.data repeat_interval = form.repeat_interval.data day_start_dt = datetime.combine(start_dt.date(), time()) day_end_dt = datetime.combine(end_dt.date(), time(23, 59)) flexible_start_dt = day_start_dt - timedelta(days=flexible_days) flexible_end_dt = day_end_dt + timedelta(days=flexible_days) occurrences = ( ReservationOccurrence.find( Reservation.room_id.in_(room_ids), Reservation.id != reservation_id, ReservationOccurrence.start_dt >= flexible_start_dt, ReservationOccurrence.end_dt <= flexible_end_dt, ReservationOccurrence.is_valid, _join=ReservationOccurrence.reservation, _eager=ReservationOccurrence.reservation, ) .options(ReservationOccurrence.NO_RESERVATION_USER_STRATEGY) .all() ) candidates = {} for days in xrange(-flexible_days, flexible_days + 1): offset = timedelta(days=days) series_start = start_dt + offset series_end = end_dt + offset if series_start < flexible_start_dt: continue candidates[series_start, series_end] = ReservationOccurrence.create_series( series_start, series_end, (repeat_frequency, repeat_interval) ) return occurrences, candidates
def get_rooms_conflicts(rooms, start_dt, end_dt, repeat_frequency, repeat_interval, blocked_rooms, nonbookable_periods, unbookable_hours): rooms_conflicts = defaultdict(list) rooms_pre_conflicts = defaultdict(list) candidates = ReservationOccurrence.create_series(start_dt, end_dt, (repeat_frequency, repeat_interval)) room_ids = [room.id for room in rooms] query = (ReservationOccurrence.query .filter(Reservation.room_id.in_(room_ids), ReservationOccurrence.is_valid, ReservationOccurrence.filter_overlap(candidates)) .join(ReservationOccurrence.reservation) .options(ReservationOccurrence.NO_RESERVATION_USER_STRATEGY, contains_eager(ReservationOccurrence.reservation))) overlapping_occurrences = group_list(query, key=lambda obj: obj.reservation.room.id) for room_id, occurrences in overlapping_occurrences.iteritems(): rooms_conflicts[room_id], rooms_pre_conflicts[room_id] = get_room_bookings_conflicts(candidates, occurrences) for room_id, occurrences in blocked_rooms.iteritems(): rooms_conflicts[room_id] += get_room_blockings_conflicts(room_id, candidates, occurrences) for room_id, occurrences in nonbookable_periods.iteritems(): rooms_conflicts[room_id] += get_room_nonbookable_periods_conflicts(candidates, occurrences) for room_id, occurrences in unbookable_hours.iteritems(): rooms_conflicts[room_id] += get_room_unbookable_hours_conflicts(candidates, occurrences) return rooms_conflicts, rooms_pre_conflicts
def get_recurring_booking_suggestions(rooms, start_dt, end_dt, repeat_frequency, repeat_interval, limit=None): data = [] booking_days = end_dt - start_dt booking_length = booking_days.days + 1 candidates = ReservationOccurrence.create_series(start_dt, end_dt, (repeat_frequency, repeat_interval)) blocked_rooms = group_blocked_rooms(get_rooms_blockings(rooms, start_dt.date(), end_dt.date())) unbookable_hours = get_rooms_unbookable_hours(rooms) nonbookable_periods = get_rooms_nonbookable_periods(rooms, start_dt, end_dt) conflicts = get_rooms_conflicts(rooms, start_dt, end_dt, repeat_frequency, repeat_interval, blocked_rooms, nonbookable_periods, unbookable_hours)[0] for room in rooms: if limit and len(data) == limit: break suggestions = {} booking_limit = room.booking_limit_days or rb_settings.get('booking_limit') limit_exceeded = booking_limit is not None and booking_limit < booking_length if limit_exceeded: excess_days = booking_length - booking_limit suggestions['shorten'] = excess_days if not limit_exceeded: number_of_conflicting_days = len(group_by_occurrence_date(conflicts.get(room.id, []))) if number_of_conflicting_days and number_of_conflicting_days < len(candidates): suggestions['skip'] = number_of_conflicting_days if suggestions: data.append({'room': room, 'suggestions': suggestions}) return data
def get_room_events(room, start_dt, end_dt, repeat_frequency, repeat_interval): occurrences = ReservationOccurrence.create_series(start_dt, end_dt, (repeat_frequency, repeat_interval)) excluded_categories = rb_settings.get('excluded_categories') return (Event.query .filter(~Event.is_deleted, Event.own_room == room, db.or_(Event.happens_between(as_utc(occ.start_dt), as_utc(occ.end_dt)) for occ in occurrences), Event.timezone == config.DEFAULT_TIMEZONE, db.and_(Event.category_id != cat['id'] for cat in excluded_categories), Event.acl_entries.any(db.and_(EventPrincipal.type == PrincipalType.user, EventPrincipal.user_id == session.user.id, EventPrincipal.full_access))) .all())
def get_number_of_skipped_days_for_rooms(rooms, start_dt, end_dt, repeat_frequency, repeat_interval, limit=None): data = [] candidates = ReservationOccurrence.create_series(start_dt, end_dt, (repeat_frequency, repeat_interval)) blocked_rooms = get_rooms_blockings(rooms, start_dt.date(), end_dt.date()) unbookable_hours = get_rooms_unbookable_hours(rooms) nonbookable_periods = get_rooms_nonbookable_periods(rooms, start_dt, end_dt) conflicts, _ = get_rooms_conflicts(rooms, start_dt, end_dt, repeat_frequency, repeat_interval, blocked_rooms, nonbookable_periods, unbookable_hours) for room in rooms: if limit and len(data) == limit: break number_of_conflicting_days = len(group_by_occurrence_date(conflicts.get(room.id, []))) if number_of_conflicting_days and number_of_conflicting_days < len(candidates): data.append({'room': room, 'suggestions': {'skip': number_of_conflicting_days}}) return sorted(data, key=lambda item: item['suggestions']['skip'])
def _get_all_conflicts(self, room, form, reservation_id=None): conflicts = defaultdict(list) pre_conflicts = defaultdict(list) candidates = ReservationOccurrence.create_series(form.start_dt.data, form.end_dt.data, (form.repeat_frequency.data, form.repeat_interval.data)) occurrences = ReservationOccurrence.find_overlapping_with(room, candidates, reservation_id).all() for cand in candidates: for occ in occurrences: if cand.overlaps(occ): if occ.reservation.is_accepted: conflicts[cand].append(occ) else: pre_conflicts[cand].append(occ) return conflicts, pre_conflicts
def get_rooms_conflicts(rooms, start_dt, end_dt, repeat_frequency, repeat_interval, blocked_rooms, nonbookable_periods, unbookable_hours, skip_conflicts_with=None, allow_admin=False): rooms_conflicts = defaultdict(set) rooms_pre_conflicts = defaultdict(set) rooms_conflicting_candidates = defaultdict(set) skip_conflicts_with = skip_conflicts_with or [] candidates = ReservationOccurrence.create_series(start_dt, end_dt, (repeat_frequency, repeat_interval)) room_ids = [room.id for room in rooms] query = (ReservationOccurrence.query .filter(Reservation.room_id.in_(room_ids), ReservationOccurrence.is_valid, ReservationOccurrence.filter_overlap(candidates)) .join(ReservationOccurrence.reservation) .options(ReservationOccurrence.NO_RESERVATION_USER_STRATEGY, contains_eager(ReservationOccurrence.reservation))) if skip_conflicts_with: query = query.filter(~Reservation.id.in_(skip_conflicts_with)) overlapping_occurrences = group_list(query, key=lambda obj: obj.reservation.room.id) for room_id, occurrences in overlapping_occurrences.iteritems(): conflicts = get_room_bookings_conflicts(candidates, occurrences, room_id, skip_conflicts_with) rooms_conflicts[room_id], rooms_pre_conflicts[room_id], rooms_conflicting_candidates[room_id] = conflicts for room_id, occurrences in blocked_rooms.iteritems(): conflicts, conflicting_candidates = get_room_blockings_conflicts(room_id, candidates, occurrences) rooms_conflicts[room_id] |= conflicts rooms_conflicting_candidates[room_id] |= conflicting_candidates if not (allow_admin and rb_is_admin(session.user)): for room_id, occurrences in nonbookable_periods.iteritems(): room = Room.get_one(room_id) if not room.can_override(session.user, allow_admin=allow_admin): conflicts, conflicting_candidates = get_room_nonbookable_periods_conflicts(candidates, occurrences) rooms_conflicts[room_id] |= conflicts rooms_conflicting_candidates[room_id] |= conflicting_candidates for room_id, occurrences in unbookable_hours.iteritems(): room = Room.get_one(room_id) if not room.can_override(session.user, allow_admin=allow_admin): conflicts, conflicting_candidates = get_room_unbookable_hours_conflicts(candidates, occurrences) rooms_conflicts[room_id] |= conflicts rooms_conflicting_candidates[room_id] |= conflicting_candidates rooms_conflicting_candidates = defaultdict(list, ((k, list(v)) for k, v in rooms_conflicting_candidates.items())) return rooms_conflicts, rooms_pre_conflicts, rooms_conflicting_candidates
def get_matching_events(start_dt, end_dt, repeat_frequency, repeat_interval): """Get events suitable for booking linking. This finds events that overlap with an occurrence of a booking with the given dates where the user is a manager. """ occurrences = ReservationOccurrence.create_series(start_dt, end_dt, (repeat_frequency, repeat_interval)) excluded_categories = rb_settings.get('excluded_categories') return (Event.query .filter(~Event.is_deleted, ~Event.room_reservation_links.any(ReservationLink.reservation.has(Reservation.is_accepted)), db.or_(Event.happens_between(as_utc(occ.start_dt), as_utc(occ.end_dt)) for occ in occurrences), Event.timezone == config.DEFAULT_TIMEZONE, db.and_(Event.category_id != cat.id for cat in excluded_categories), Event.acl_entries.any(db.and_(EventPrincipal.type == PrincipalType.user, EventPrincipal.user_id == session.user.id, EventPrincipal.full_access))) .all())
def get_matching_events(start_dt, end_dt, repeat_frequency, repeat_interval): """Get events suitable for booking linking. This finds events that overlap with an occurrence of a booking with the given dates where the user is a manager. """ occurrences = ReservationOccurrence.create_series( start_dt, end_dt, (repeat_frequency, repeat_interval)) excluded_categories = rb_settings.get('excluded_categories') return (Event.query.filter( ~Event.is_deleted, ~Event.room_reservation_links.any( ReservationLink.reservation.has(Reservation.is_accepted)), db.or_( Event.happens_between(server_to_utc(occ.start_dt), server_to_utc(occ.end_dt)) for occ in occurrences), Event.timezone == config.DEFAULT_TIMEZONE, db.and_(Event.category_id != cat.id for cat in excluded_categories), Event.acl_entries.any( db.and_(EventPrincipal.type == PrincipalType.user, EventPrincipal.user_id == session.user.id, EventPrincipal.full_access))).all())
def get_recurring_booking_suggestions(rooms, start_dt, end_dt, repeat_frequency, repeat_interval, limit=None): data = [] booking_days = end_dt - start_dt booking_length = booking_days.days + 1 candidates = ReservationOccurrence.create_series( start_dt, end_dt, (repeat_frequency, repeat_interval)) blocked_rooms = get_rooms_blockings(rooms, start_dt.date(), end_dt.date()) unbookable_hours = get_rooms_unbookable_hours(rooms) nonbookable_periods = get_rooms_nonbookable_periods( rooms, start_dt, end_dt) conflicts, _ = get_rooms_conflicts(rooms, start_dt, end_dt, repeat_frequency, repeat_interval, blocked_rooms, nonbookable_periods, unbookable_hours) for room in rooms: if limit and len(data) == limit: break suggestions = {} booking_limit = room.booking_limit_days or rb_settings.get( 'booking_limit') limit_exceeded = booking_limit is not None and booking_limit < booking_length if limit_exceeded: excess_days = booking_length - booking_limit suggestions['shorten'] = excess_days if not limit_exceeded: number_of_conflicting_days = len( group_by_occurrence_date(conflicts.get(room.id, []))) if number_of_conflicting_days and number_of_conflicting_days < len( candidates): suggestions['skip'] = number_of_conflicting_days if suggestions: data.append({'room': room, 'suggestions': suggestions}) return data
def filter_available(start_dt, end_dt, repetition, include_pre_bookings=True, include_pending_blockings=True): """Returns a SQLAlchemy filter criterion ensuring that the room is available during the given time.""" # Check availability against reservation occurrences dummy_occurrences = ReservationOccurrence.create_series( start_dt, end_dt, repetition) overlap_criteria = ReservationOccurrence.filter_overlap( dummy_occurrences) reservation_criteria = [ Reservation.room_id == Room.id, ReservationOccurrence.is_valid, overlap_criteria ] if not include_pre_bookings: reservation_criteria.append(Reservation.is_accepted) occurrences_filter = (Reservation.query.join( ReservationOccurrence.reservation).filter( and_(*reservation_criteria))) # Check availability against blockings if include_pending_blockings: valid_states = (BlockedRoom.State.accepted, BlockedRoom.State.pending) else: valid_states = (BlockedRoom.State.accepted, ) # TODO: only take blockings into account which the user cannot override blocking_criteria = [ Room.id == BlockedRoom.room_id, BlockedRoom.state.in_(valid_states), db_dates_overlap(Blocking, 'start_date', end_dt.date(), 'end_date', start_dt.date(), inclusive=True) ] blockings_filter = (BlockedRoom.query.join( Blocking.blocked_rooms).filter(and_(*blocking_criteria))) return ~occurrences_filter.exists() & ~blockings_filter.exists()
def get_existing_rooms_occurrences(rooms, start_dt, end_dt, repeat_frequency, repeat_interval, allow_overlapping=False, only_accepted=False, skip_booking_id=None): room_ids = [room.id for room in rooms] query = (ReservationOccurrence.query.filter( ReservationOccurrence.is_valid, Reservation.room_id.in_(room_ids)).join( ReservationOccurrence.reservation).options( ReservationOccurrence.NO_RESERVATION_USER_STRATEGY, contains_eager(ReservationOccurrence.reservation))) if allow_overlapping: query = query.filter( db_dates_overlap(ReservationOccurrence, 'start_dt', start_dt, 'end_dt', end_dt)) else: query = query.filter(ReservationOccurrence.start_dt >= start_dt, ReservationOccurrence.end_dt <= end_dt) if only_accepted: query = query.filter(Reservation.is_accepted) if repeat_frequency != RepeatFrequency.NEVER: candidates = ReservationOccurrence.create_series( start_dt, end_dt, (repeat_frequency, repeat_interval)) dates = [candidate.start_dt for candidate in candidates] query = query.filter( db.cast(ReservationOccurrence.start_dt, db.Date).in_(dates)) if skip_booking_id is not None: query = query.filter( ReservationOccurrence.reservation_id != skip_booking_id) return group_list(query, key=lambda obj: obj.reservation.room_id, sort_by=lambda obj: (obj.reservation.room_id, obj.start_dt))
def filter_available(start_dt, end_dt, repetition, include_pre_bookings=True, include_pending_blockings=True): """Returns a SQLAlchemy filter criterion ensuring that the room is available during the given time.""" # Check availability against reservation occurrences dummy_occurrences = ReservationOccurrence.create_series(start_dt, end_dt, repetition) overlap_criteria = ReservationOccurrence.filter_overlap(dummy_occurrences) reservation_criteria = [Reservation.room_id == Room.id, ReservationOccurrence.is_valid, overlap_criteria] if not include_pre_bookings: reservation_criteria.append(Reservation.is_accepted) occurrences_filter = Reservation.occurrences.any(and_(*reservation_criteria)) # Check availability against blockings if include_pending_blockings: valid_states = (BlockedRoom.State.accepted, BlockedRoom.State.pending) else: valid_states = (BlockedRoom.State.accepted,) blocking_criteria = [BlockedRoom.blocking_id == Blocking.id, BlockedRoom.state.in_(valid_states), Blocking.start_date <= start_dt.date(), Blocking.end_date >= end_dt.date()] blockings_filter = Room.blocked_rooms.any(and_(*blocking_criteria)) return ~occurrences_filter & ~blockings_filter
def get_existing_rooms_occurrences(rooms, start_dt, end_dt, repeat_frequency, repeat_interval, allow_overlapping=False, only_accepted=False): room_ids = [room.id for room in rooms] query = (ReservationOccurrence.query .filter(ReservationOccurrence.is_valid, Reservation.room_id.in_(room_ids)) .join(ReservationOccurrence.reservation) .options(ReservationOccurrence.NO_RESERVATION_USER_STRATEGY, contains_eager(ReservationOccurrence.reservation))) if allow_overlapping: query = query.filter(db_dates_overlap(ReservationOccurrence, 'start_dt', start_dt, 'end_dt', end_dt)) else: query = query.filter(ReservationOccurrence.start_dt >= start_dt, ReservationOccurrence.end_dt <= end_dt) if only_accepted: query = query.filter(Reservation.is_accepted) if repeat_frequency != RepeatFrequency.NEVER: candidates = ReservationOccurrence.create_series(start_dt, end_dt, (repeat_frequency, repeat_interval)) dates = [candidate.start_dt for candidate in candidates] query = query.filter(db.cast(ReservationOccurrence.start_dt, db.Date).in_(dates)) return group_list(query, key=lambda obj: obj.reservation.room.id, sort_by=lambda obj: (obj.reservation.room_id, obj.start_dt))
def _get_all_occurrences(self, room_ids, form, flexible_days=0, reservation_id=None): start_dt = form.start_dt.data end_dt = form.end_dt.data repeat_frequency = form.repeat_frequency.data repeat_interval = form.repeat_interval.data day_start_dt = datetime.combine(start_dt.date(), time()) day_end_dt = datetime.combine(end_dt.date(), time(23, 59)) today_start_dt = datetime.combine(date.today(), time()) flexible_start_dt = day_start_dt - timedelta(days=flexible_days) if not session.user.isAdmin(): flexible_start_dt = max(today_start_dt, flexible_start_dt) flexible_end_dt = day_end_dt + timedelta(days=flexible_days) occurrences = ReservationOccurrence.find_all( Reservation.room_id.in_(room_ids), Reservation.id != reservation_id, ReservationOccurrence.start_dt >= flexible_start_dt, ReservationOccurrence.end_dt <= flexible_end_dt, ReservationOccurrence.is_valid, _join=Reservation, _eager=ReservationOccurrence.reservation) candidates = {} for days in xrange(-flexible_days, flexible_days + 1): offset = timedelta(days=days) series_start = start_dt + offset series_end = end_dt + offset if series_start < flexible_start_dt: continue candidates[series_start, series_end] = ReservationOccurrence.create_series( series_start, series_end, (repeat_frequency, repeat_interval)) return occurrences, candidates
def get_rooms_availability(rooms, start_dt, end_dt, repeat_frequency, repeat_interval, flexibility): period_days = (end_dt - start_dt).days availability = {} candidates = ReservationOccurrence.create_series( start_dt, end_dt, (repeat_frequency, repeat_interval)) date_range = sorted(set(cand.start_dt.date() for cand in candidates)) for room in rooms: booking_limit_days = room.booking_limit_days or rb_settings.get( 'booking_limit') if period_days > booking_limit_days: continue start_dt = start_dt + timedelta(days=flexibility) end_dt = end_dt + timedelta(days=flexibility) occurrences = get_existing_room_occurrences( room, start_dt.replace(hour=0, minute=0), end_dt.replace(hour=23, minute=59)) conflicts, pre_conflicts = get_room_conflicts( room, start_dt.replace(tzinfo=None), end_dt.replace(tzinfo=None), repeat_frequency, repeat_interval) pre_bookings = [ occ for occ in occurrences if not occ.reservation.is_accepted ] existing_bookings = [ occ for occ in occurrences if occ.reservation.is_accepted ] availability[room.id] = { 'room': room, 'candidates': group_by_occurrence_date(candidates), 'pre_bookings': group_by_occurrence_date(pre_bookings), 'bookings': group_by_occurrence_date(existing_bookings), 'conflicts': group_by_occurrence_date(conflicts), 'pre_conflicts': group_by_occurrence_date(pre_conflicts) } return date_range, availability
def get_room_conflicts(room, start_dt, end_dt, repeat_frequency, repeat_interval): conflicts = [] pre_conflicts = [] candidates = ReservationOccurrence.create_series( start_dt, end_dt, (repeat_frequency, repeat_interval)) occurrences = ReservationOccurrence.find_overlapping_with( room, candidates).all() ReservationOccurrenceTmp = namedtuple( 'ReservationOccurrenceTmp', ('start_dt', 'end_dt', 'reservation')) for candidate in candidates: for occurrence in occurrences: if candidate.overlaps(occurrence): overlap = candidate.get_overlap(occurrence) obj = ReservationOccurrenceTmp( *overlap, reservation=occurrence.reservation) if occurrence.reservation.is_accepted: conflicts.append(obj) else: pre_conflicts.append(obj) return conflicts, pre_conflicts
def get_rooms_availability(rooms, start_dt, end_dt, repeat_frequency, repeat_interval): period_days = (end_dt - start_dt).days availability = OrderedDict() candidates = ReservationOccurrence.create_series(start_dt, end_dt, (repeat_frequency, repeat_interval)) date_range = sorted(set(cand.start_dt.date() for cand in candidates)) occurrences = get_existing_rooms_occurrences(rooms, start_dt.replace(hour=0, minute=0), end_dt.replace(hour=23, minute=59), repeat_frequency, repeat_interval) blocked_rooms = get_rooms_blockings(rooms, start_dt.date(), end_dt.date()) unbookable_hours = get_rooms_unbookable_hours(rooms) nonbookable_periods = get_rooms_nonbookable_periods(rooms, start_dt, end_dt) conflicts, pre_conflicts = get_rooms_conflicts(rooms, start_dt.replace(tzinfo=None), end_dt.replace(tzinfo=None), repeat_frequency, repeat_interval, blocked_rooms, nonbookable_periods, unbookable_hours) dates = list(candidate.start_dt.date() for candidate in candidates) for room in rooms: booking_limit_days = room.booking_limit_days or rb_settings.get('booking_limit') if period_days > booking_limit_days: continue room_occurrences = occurrences.get(room.id, []) room_conflicts = conflicts.get(room.id, []) pre_room_conflicts = pre_conflicts.get(room.id, []) pre_bookings = [occ for occ in room_occurrences if not occ.reservation.is_accepted] existing_bookings = [occ for occ in room_occurrences if occ.reservation.is_accepted] room_blocked_rooms = blocked_rooms.get(room.id, []) room_nonbookable_periods = nonbookable_periods.get(room.id, []) room_unbookable_hours = unbookable_hours.get(room.id, []) availability[room.id] = {'room_id': room.id, 'candidates': group_by_occurrence_date(candidates), 'pre_bookings': group_by_occurrence_date(pre_bookings), 'bookings': group_by_occurrence_date(existing_bookings), 'conflicts': group_by_occurrence_date(room_conflicts), 'pre_conflicts': group_by_occurrence_date(pre_room_conflicts), 'blockings': group_blockings(room_blocked_rooms, dates), 'nonbookable_periods': group_nonbookable_periods(room_nonbookable_periods, dates), 'unbookable_hours': room_unbookable_hours} return date_range, availability
def get_rooms_availability(rooms, start_dt, end_dt, repeat_frequency, repeat_interval, skip_conflicts_with=None, admin_override_enabled=False): availability = OrderedDict() candidates = ReservationOccurrence.create_series( start_dt, end_dt, (repeat_frequency, repeat_interval)) date_range = sorted(set(cand.start_dt.date() for cand in candidates)) occurrences = get_existing_rooms_occurrences( rooms, start_dt.replace(hour=0, minute=0), end_dt.replace(hour=23, minute=59), repeat_frequency, repeat_interval) blocked_rooms = get_rooms_blockings(rooms, start_dt.date(), end_dt.date()) nonoverridable_blocked_rooms = group_blocked_rooms( filter_blocked_rooms(blocked_rooms, nonoverridable_only=True, explicit=True)) overridable_blocked_rooms = group_blocked_rooms( filter_blocked_rooms(blocked_rooms, overridable_only=True, explicit=True)) unbookable_hours = get_rooms_unbookable_hours(rooms) nonbookable_periods = get_rooms_nonbookable_periods( rooms, start_dt, end_dt) conflicts, pre_conflicts, conflicting_candidates = get_rooms_conflicts( rooms, start_dt.replace(tzinfo=None), end_dt.replace(tzinfo=None), repeat_frequency, repeat_interval, nonoverridable_blocked_rooms, nonbookable_periods, unbookable_hours, skip_conflicts_with, allow_admin=admin_override_enabled) dates = list(candidate.start_dt.date() for candidate in candidates) for room in rooms: room_occurrences = occurrences.get(room.id, []) room_conflicting_candidates = conflicting_candidates.get(room.id, []) room_conflicts = conflicts.get(room.id, []) pre_room_conflicts = pre_conflicts.get(room.id, []) pre_bookings = [ occ for occ in room_occurrences if not occ.reservation.is_accepted ] existing_bookings = [ occ for occ in room_occurrences if occ.reservation.is_accepted ] room_nonoverridable_blocked_rooms = nonoverridable_blocked_rooms.get( room.id, []) room_overridable_blocked_rooms = overridable_blocked_rooms.get( room.id, []) room_nonbookable_periods = nonbookable_periods.get(room.id, []) room_unbookable_hours = unbookable_hours.get(room.id, []) room_candidates = get_room_candidates(candidates, room_conflicts, pre_room_conflicts) availability[room.id] = { 'room_id': room.id, 'candidates': group_by_occurrence_date(room_candidates), 'conflicting_candidates': group_by_occurrence_date(room_conflicting_candidates), 'pre_bookings': group_by_occurrence_date(pre_bookings), 'bookings': group_by_occurrence_date(existing_bookings), 'conflicts': group_by_occurrence_date(room_conflicts), 'pre_conflicts': group_by_occurrence_date(pre_room_conflicts), 'blockings': group_blockings(room_nonoverridable_blocked_rooms, dates), 'overridable_blockings': group_blockings(room_overridable_blocked_rooms, dates), 'nonbookable_periods': group_nonbookable_periods(room_nonbookable_periods, dates), 'unbookable_hours': room_unbookable_hours } return date_range, availability
def test_create_series(creation_params): for occ1, occ2 in izip(list(ReservationOccurrence.iter_create_occurrences(**creation_params)), ReservationOccurrence.create_series(**creation_params)): assert occ1.start_dt == occ2.start_dt assert occ1.end_dt == occ2.end_dt
def get_rooms_conflicts(rooms, start_dt, end_dt, repeat_frequency, repeat_interval, blocked_rooms, nonbookable_periods, unbookable_hours, skip_conflicts_with=None, allow_admin=False, skip_past_conflicts=False): rooms_conflicts = defaultdict(set) rooms_pre_conflicts = defaultdict(set) rooms_conflicting_candidates = defaultdict(set) skip_conflicts_with = skip_conflicts_with or [] candidates = ReservationOccurrence.create_series( start_dt, end_dt, (repeat_frequency, repeat_interval)) room_ids = [room.id for room in rooms] query = (ReservationOccurrence.query.filter( Reservation.room_id.in_(room_ids), ReservationOccurrence.is_valid, ReservationOccurrence.filter_overlap(candidates)).join( ReservationOccurrence.reservation).options( ReservationOccurrence.NO_RESERVATION_USER_STRATEGY, contains_eager(ReservationOccurrence.reservation))) if skip_conflicts_with: query = query.filter(~Reservation.id.in_(skip_conflicts_with)) if skip_past_conflicts: query = query.filter(ReservationOccurrence.start_dt > datetime.now()) overlapping_occurrences = group_list( query, key=lambda obj: obj.reservation.room.id) for room_id, occurrences in overlapping_occurrences.iteritems(): conflicts = get_room_bookings_conflicts(candidates, occurrences, skip_conflicts_with) rooms_conflicts[room_id], rooms_pre_conflicts[ room_id], rooms_conflicting_candidates[room_id] = conflicts for room_id, occurrences in blocked_rooms.iteritems(): conflicts, conflicting_candidates = get_room_blockings_conflicts( room_id, candidates, occurrences) rooms_conflicts[room_id] |= conflicts rooms_conflicting_candidates[room_id] |= conflicting_candidates if not (allow_admin and rb_is_admin(session.user)): for room_id, occurrences in nonbookable_periods.iteritems(): room = Room.get_one(room_id) if not room.can_override(session.user, allow_admin=allow_admin): conflicts, conflicting_candidates = get_room_nonbookable_periods_conflicts( candidates, occurrences) rooms_conflicts[room_id] |= conflicts rooms_conflicting_candidates[room_id] |= conflicting_candidates for room_id, occurrences in unbookable_hours.iteritems(): room = Room.get_one(room_id) if not room.can_override(session.user, allow_admin=allow_admin): conflicts, conflicting_candidates = get_room_unbookable_hours_conflicts( candidates, occurrences) rooms_conflicts[room_id] |= conflicts rooms_conflicting_candidates[room_id] |= conflicting_candidates rooms_conflicting_candidates = defaultdict( list, ((k, list(v)) for k, v in rooms_conflicting_candidates.items())) return rooms_conflicts, rooms_pre_conflicts, rooms_conflicting_candidates