def test_find_overlapping_with_different_room(overlapping_occurrences,
                                              create_room):
    db_occ, occ = overlapping_occurrences
    assert db_occ in ReservationOccurrence.find_overlapping_with(
        room=db_occ.reservation.room, occurrences=[occ]).all()
    assert db_occ not in ReservationOccurrence.find_overlapping_with(
        room=create_room(), occurrences=[occ]).all()
 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 test_find_overlapping_with_is_not_valid(db, overlapping_occurrences):
    db_occ, occ = overlapping_occurrences
    assert db_occ in ReservationOccurrence.find_overlapping_with(
        room=db_occ.reservation.room, occurrences=[occ]).all()
    db_occ.is_cancelled = True
    db.session.flush()
    assert db_occ not in ReservationOccurrence.find_overlapping_with(
        room=db_occ.reservation.room, occurrences=[occ]).all()
def test_find_overlapping_with_skip_reservation(overlapping_occurrences):
    db_occ, occ = overlapping_occurrences
    assert db_occ in ReservationOccurrence.find_overlapping_with(
        room=db_occ.reservation.room, occurrences=[occ]).all()
    assert db_occ not in ReservationOccurrence.find_overlapping_with(
        room=db_occ.reservation.room,
        occurrences=[occ],
        skip_reservation_id=db_occ.reservation.id).all()
def test_overlaps(overlapping_combination_from_2am_to_4am):
    start_hour, end_hour, expected = overlapping_combination_from_2am_to_4am()
    occ1 = ReservationOccurrence(start_dt=date.today() + relativedelta(hour=2),
                                 end_dt=date.today() + relativedelta(hour=4))
    occ2 = ReservationOccurrence(
        start_dt=date.today() + relativedelta(hour=start_hour),
        end_dt=date.today() + relativedelta(hour=end_hour))
    assert occ1.overlaps(occ2) == expected
def test_iter_start_time_invalid():
    invalid_frequency = -1
    assert invalid_frequency not in RepeatFrequency
    with pytest.raises(fossirError):
        ReservationOccurrence.iter_start_time(start=date.today(),
                                              end=date.today(),
                                              repetition=(invalid_frequency,
                                                          0))
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 test_filter_overlap(create_occurrence,
                        overlapping_combination_from_2am_to_4am):
    start_hour, end_hour, expected = overlapping_combination_from_2am_to_4am()
    occ1 = create_occurrence(start_dt=date.today() + relativedelta(hour=2),
                             end_dt=date.today() + relativedelta(hour=4))
    occ2 = ReservationOccurrence(
        start_dt=date.today() + relativedelta(hour=start_hour),
        end_dt=date.today() + relativedelta(hour=end_hour))
    overlap_filter = ReservationOccurrence.filter_overlap([occ2])
    assert (occ1 in ReservationOccurrence.find_all(overlap_filter)) == expected
def test_get_overlap(overlapping_combination_from_2am_to_4am):
    start_hour, end_hour, expected_overlap = overlapping_combination_from_2am_to_4am(
        boolean=False)
    occ1 = ReservationOccurrence(start_dt=date.today() + relativedelta(hour=2),
                                 end_dt=date.today() + relativedelta(hour=4))
    occ2 = ReservationOccurrence(
        start_dt=date.today() + relativedelta(hour=start_hour),
        end_dt=date.today() + relativedelta(hour=end_hour))
    if expected_overlap != (None, None):
        overlap_start = date.today() + relativedelta(hour=expected_overlap[0])
        overlap_end = date.today() + relativedelta(hour=expected_overlap[1])
        assert occ1.get_overlap(occ2) == (overlap_start, overlap_end)
    else:
        assert occ1.get_overlap(occ2) == expected_overlap
def test_iter_start_time_daily(interval, days_elapsed, expected_length):
    assert days_elapsed >= 0
    params = {
        'start': date.today() + relativedelta(hour=8),
        'end': date.today() + relativedelta(days=days_elapsed, hour=17),
        'repetition': (RepeatFrequency.DAY, interval)
    }
    if expected_length is None:
        with pytest.raises(fossirError):
            ReservationOccurrence.iter_start_time(**params)
    else:
        days = list(ReservationOccurrence.iter_start_time(**params))
        assert len(days) == expected_length
        for i, day in enumerate(days):
            assert day.date() == date.today() + relativedelta(days=i)
def test_iter_start_time_monthly(interval, days_elapsed, expected_length):
    assert days_elapsed >= 0
    params = {
        'start': date.today() + relativedelta(hour=8),
        'end': date.today() + relativedelta(days=days_elapsed, hour=17),
        'repetition': (RepeatFrequency.MONTH, interval)
    }
    if expected_length is None:
        with pytest.raises(fossirError):
            ReservationOccurrence.iter_start_time(**params)
    else:
        days = list(ReservationOccurrence.iter_start_time(**params))
        weekday = params['start'].weekday()
        assert len(days) == expected_length
        assert all(day.weekday() == weekday for day in days)
    def _process(self):
        form = self._form
        if self._is_submitted() and form.validate():
            if form.data.get('is_only_my_rooms'):
                form.room_ids.data = [
                    room.id for room in Room.find_all()
                    if room.is_owned_by(session.user)
                ]

            occurrences = ReservationOccurrence.find_with_filters(
                form.data, session.user).all()
            rooms = self._filter_displayed_rooms(
                [r for r in self._rooms if r.id in set(form.room_ids.data)],
                occurrences)
            return WPRoomBookingSearchBookingsResults(
                self,
                rooms=rooms,
                occurrences=occurrences,
                show_blockings=self.show_blockings,
                start_dt=form.start_dt.data,
                end_dt=form.end_dt.data,
                form=form,
                form_data=self._form_data,
                menu_item=self.menu_item).display()

        my_rooms = [r.id for r in Room.get_owned_by(session.user)]
        return WPRoomBookingSearchBookings(self,
                                           errors=form.error_list,
                                           rooms=self._rooms,
                                           my_rooms=my_rooms,
                                           form=form).display()
def test_is_valid(db, dummy_occurrence, is_rejected, is_cancelled, expected):
    dummy_occurrence.is_rejected = is_rejected
    dummy_occurrence.is_cancelled = is_cancelled
    db.session.flush()
    assert dummy_occurrence.is_valid == expected
    assert ReservationOccurrence.find_first(
        is_valid=expected) == dummy_occurrence
def test_iter_start_time_single(interval):
    days = list(
        ReservationOccurrence.iter_start_time(
            start=date.today() + relativedelta(hour=8),
            end=date.today() + relativedelta(hour=17),
            repetition=(RepeatFrequency.NEVER, interval)))
    assert len(days) == 1
def test_iter_create_occurrences(creation_params):
    occurrences = list(
        ReservationOccurrence.iter_create_occurrences(**creation_params))
    assert len(occurrences) == 2
    for occ in occurrences:
        assert occ.start_dt.time() == time(8)
        assert occ.end_dt.time() == time(17)
    def create_occurrences(self, skip_conflicts, user=None):
        ReservationOccurrence.create_series_for_reservation(self)
        db.session.flush()

        if user is None:
            user = self.created_by_user

        # Check for conflicts with nonbookable periods
        if not rb_is_admin(user) and not self.room.is_owned_by(user):
            nonbookable_periods = self.room.nonbookable_periods.filter(NonBookablePeriod.end_dt > self.start_dt)
            for occurrence in self.occurrences:
                if not occurrence.is_valid:
                    continue
                for nbd in nonbookable_periods:
                    if nbd.overlaps(occurrence.start_dt, occurrence.end_dt):
                        if not skip_conflicts:
                            raise ConflictingOccurrences()
                        occurrence.cancel(user, u'Skipped due to nonbookable date', silent=True, propagate=False)
                        break

        # Check for conflicts with blockings
        blocked_rooms = self.room.get_blocked_rooms(*(occurrence.start_dt for occurrence in self.occurrences))
        for br in blocked_rooms:
            blocking = br.blocking
            if blocking.can_be_overridden(user, self.room):
                continue
            for occurrence in self.occurrences:
                if occurrence.is_valid and blocking.is_active_at(occurrence.start_dt.date()):
                    # Cancel OUR occurrence
                    msg = u'Skipped due to collision with a blocking ({})'
                    occurrence.cancel(user, msg.format(blocking.reason), silent=True, propagate=False)

        # Check for conflicts with other occurrences
        conflicting_occurrences = self.get_conflicting_occurrences()
        for occurrence, conflicts in conflicting_occurrences.iteritems():
            if not occurrence.is_valid:
                continue
            if conflicts['confirmed']:
                if not skip_conflicts:
                    raise ConflictingOccurrences()
                # Cancel OUR occurrence
                msg = u'Skipped due to collision with {} reservation(s)'
                occurrence.cancel(user, msg.format(len(conflicts['confirmed'])), silent=True, propagate=False)
            elif conflicts['pending'] and self.is_accepted:
                # Reject OTHER occurrences
                for conflict in conflicts['pending']:
                    conflict.reject(user, u'Rejected due to collision with a confirmed reservation')
Exemple #17
0
def overlapping_reservation(create_reservation):
    reservation = create_reservation(
        start_dt=date.today() + relativedelta(hour=2),
        end_dt=date.today() + relativedelta(hour=4))
    occurrence = ReservationOccurrence(
        start_dt=date.today() + relativedelta(hour=1),
        end_dt=date.today() + relativedelta(hour=5))
    return reservation, occurrence
 def get_conflicting_occurrences(self):
     valid_occurrences = self.occurrences.filter(ReservationOccurrence.is_valid).all()
     colliding_occurrences = ReservationOccurrence.find_overlapping_with(self.room, valid_occurrences, self.id).all()
     conflicts = defaultdict(lambda: dict(confirmed=[], pending=[]))
     for occurrence in valid_occurrences:
         for colliding in colliding_occurrences:
             if occurrence.overlaps(colliding):
                 key = 'confirmed' if colliding.reservation.is_accepted else 'pending'
                 conflicts[occurrence][key].append(colliding)
     return conflicts
    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 test_create_series_for_reservation(dummy_reservation):
    occurrences = ReservationOccurrence.iter_create_occurrences(
        start=dummy_reservation.start_dt,
        end=dummy_reservation.end_dt,
        repetition=dummy_reservation.repetition)
    for occ1, occ2 in izip(dummy_reservation.occurrences, occurrences):
        assert occ1.start_dt == occ2.start_dt
        assert occ1.end_dt == occ2.end_dt
        assert occ1.is_cancelled == dummy_reservation.is_cancelled
        assert occ1.is_rejected == dummy_reservation.is_rejected
        assert occ1.rejection_reason == dummy_reservation.rejection_reason
    def accept(self, user):
        self.is_accepted = True
        self.add_edit_log(ReservationEditLog(user_name=user.full_name, info=['Reservation accepted']))
        notify_confirmation(self)

        valid_occurrences = self.occurrences.filter(ReservationOccurrence.is_valid).all()
        pre_occurrences = ReservationOccurrence.find_overlapping_with(self.room, valid_occurrences, self.id).all()
        for occurrence in pre_occurrences:
            if not occurrence.is_valid:
                continue
            occurrence.reject(user, u'Rejected due to collision with a confirmed reservation')
    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 test_iter_start_time_monthly_5th_monday_is_always_last():
    start_dt = date(2014, 9, 29) + relativedelta(
        hour=8)  # 5th monday of september
    end_dt = start_dt + relativedelta(days=100, hour=17)
    params = {
        'start': start_dt,
        'end': end_dt,
        'repetition': (RepeatFrequency.MONTH, 1)
    }

    days = list(ReservationOccurrence.iter_start_time(**params))
    assert len(days) == 4
    assert days[1].date() == date(2014, 10, 27)  # 4th monday of october
    assert days[2].date() == date(2014, 11, 24)  # 4th monday of october
    assert days[3].date() == date(2014, 12, 29)  # 5th monday of october
    def approve(self, notify_blocker=True):
        """Approve the room blocking, rejecting all colliding reservations/occurrences."""
        self.state = BlockedRoomState.accepted

        # Get colliding reservations
        start_dt = datetime.combine(self.blocking.start_date, time())
        end_dt = datetime.combine(self.blocking.end_date, time(23, 59, 59))

        reservation_criteria = [
            Reservation.room_id == self.room_id, ~Reservation.is_rejected,
            ~Reservation.is_cancelled
        ]

        # Whole reservations to reject
        reservations = Reservation.find_all(Reservation.start_dt >= start_dt,
                                            Reservation.end_dt <= end_dt,
                                            *reservation_criteria)

        # Single occurrences to reject
        occurrences = ReservationOccurrence.find_all(
            ReservationOccurrence.start_dt >= start_dt,
            ReservationOccurrence.end_dt <= end_dt,
            ReservationOccurrence.is_valid,
            ~ReservationOccurrence.reservation_id.in_(
                map(attrgetter('id'), reservations)) if reservations else True,
            *reservation_criteria,
            _join=Reservation)

        reason = 'Conflict with blocking {}: {}'.format(
            self.blocking.id, self.blocking.reason)

        for reservation in reservations:
            if self.blocking.can_be_overridden(reservation.created_by_user,
                                               reservation.room):
                continue
            reservation.reject(self.blocking.created_by_user, reason)

        for occurrence in occurrences:
            reservation = occurrence.reservation
            if self.blocking.can_be_overridden(reservation.created_by_user,
                                               reservation.room):
                continue
            occurrence.reject(self.blocking.created_by_user, reason)

        if notify_blocker:
            # We only need to notify the blocking creator if the blocked room wasn't approved yet.
            # This is the case if it's a new blocking for a room managed by the creator
            notify_request_response(self)
    def _process(self):
        if self._overload:
            rooms = []
            occurrences = []
        else:
            rooms = Room.find_all(is_active=True)
            occurrences = (ReservationOccurrence.find(
                Reservation.room_id.in_(room.id for room in rooms),
                ReservationOccurrence.start_dt >= self.start_dt,
                ReservationOccurrence.end_dt <= self.end_dt,
                ReservationOccurrence.is_valid,
                _join=ReservationOccurrence.reservation,
                _eager=ReservationOccurrence.reservation).options(
                    ReservationOccurrence.NO_RESERVATION_USER_STRATEGY).all())

        return WPRoomBookingCalendar(self,
                                     rooms=rooms,
                                     occurrences=occurrences,
                                     start_dt=self.start_dt,
                                     end_dt=self.end_dt,
                                     overload=self._overload,
                                     max_days=self.MAX_DAYS).display()
def test_date(dummy_occurrence):
    assert dummy_occurrence.date == date.today()
    assert ReservationOccurrence.find_first(
        date=date.today()) == dummy_occurrence
def overlapping_occurrences(create_occurrence):
    db_occ = create_occurrence(start_dt=date.today() + relativedelta(hour=2),
                               end_dt=date.today() + relativedelta(hour=4))
    occ = ReservationOccurrence(start_dt=date.today() + relativedelta(hour=1),
                                end_dt=date.today() + relativedelta(hour=5))
    return db_occ, occ
 def find_overlapping_with(room, occurrences, skip_reservation_id=None):
     return Reservation.find(Reservation.room == room,
                             Reservation.id != skip_reservation_id,
                             ReservationOccurrence.is_valid,
                             ReservationOccurrence.filter_overlap(occurrences),
                             _join=ReservationOccurrence)