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
Beispiel #2
0
    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
Beispiel #3
0
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
Beispiel #4
0
 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()
Beispiel #5
0
    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
Beispiel #6
0
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
Beispiel #7
0
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
Beispiel #8
0
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())
Beispiel #9
0
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'])
Beispiel #10
0
    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
Beispiel #11
0
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
Beispiel #12
0
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())
Beispiel #13
0
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())
Beispiel #14
0
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
Beispiel #15
0
 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()
Beispiel #16
0
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))
Beispiel #17
0
 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
Beispiel #18
0
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))
Beispiel #19
0
    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
Beispiel #20
0
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
Beispiel #21
0
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
Beispiel #22
0
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
Beispiel #23
0
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
Beispiel #25
0
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