Exemple #1
0
def split_booking(booking, new_booking_data):
    is_ongoing_booking = booking.start_dt.date() < date.today() < booking.end_dt.date()
    if not is_ongoing_booking:
        return

    cancelled_dates = []
    rejected_occs = {}
    room = booking.room
    occurrences = sorted(booking.occurrences, key=attrgetter('start_dt'))
    old_frequency = booking.repeat_frequency
    occurrences_to_cancel = [occ for occ in occurrences if occ.start_dt >= datetime.now() and occ.is_valid]

    if old_frequency != RepeatFrequency.NEVER and new_booking_data['repeat_frequency'] == RepeatFrequency.NEVER:
        new_start_dt = new_booking_data['start_dt']
    else:
        new_start_dt = datetime.combine(occurrences_to_cancel[0].start_dt.date(), new_booking_data['start_dt'].time())
        cancelled_dates = [occ.start_dt.date() for occ in occurrences if occ.is_cancelled]
        rejected_occs = {occ.start_dt.date(): occ.rejection_reason for occ in occurrences if occ.is_rejected}

        new_end_dt = [occ for occ in occurrences if occ.start_dt < datetime.now()][-1].end_dt
        old_booking_data = {
            'booking_reason': booking.booking_reason,
            'booked_for_user': booking.booked_for_user,
            'start_dt': booking.start_dt,
            'end_dt': new_end_dt,
            'repeat_frequency': booking.repeat_frequency,
            'repeat_interval': booking.repeat_interval,
        }

        booking.modify(old_booking_data, session.user)

    for occurrence_to_cancel in occurrences_to_cancel:
        occurrence_to_cancel.cancel(session.user, silent=True)

    prebook = not room.can_book(session.user, allow_admin=False) and room.can_prebook(session.user, allow_admin=False)
    resv = Reservation.create_from_data(room, dict(new_booking_data, start_dt=new_start_dt), session.user,
                                        prebook=prebook, ignore_admin=True)
    for new_occ in resv.occurrences:
        new_occ_start = new_occ.start_dt.date()
        if new_occ_start in cancelled_dates:
            new_occ.cancel(None, silent=True)
        if new_occ_start in rejected_occs:
            new_occ.reject(None, rejected_occs[new_occ_start], silent=True)

    booking.edit_logs.append(ReservationEditLog(user_name=session.user.full_name, info=[
        'Split into a new booking',
        f'booking_link:{resv.id}'
    ]))
    resv.edit_logs.append(ReservationEditLog(user_name=session.user.full_name, info=[
        'Split from another booking',
        f'booking_link:{booking.id}'
    ]))
    return resv
Exemple #2
0
 def reject(self, user, reason, silent=False):
     self.is_rejected = True
     self.rejection_reason = reason
     self.occurrences.filter_by(is_valid=True).update({'is_rejected': True, 'rejection_reason': reason},
                                                      synchronize_session='fetch')
     if not silent:
         notify_rejection(self)
         log_msg = u'Reservation rejected: {}'.format(reason)
         self.add_edit_log(ReservationEditLog(user_name=user.full_name, info=[log_msg]))
 def reject(self, user, reason, silent=False):
     self.is_rejected = True
     self.rejection_reason = reason
     if not silent:
         log = [u'Day rejected: {}'.format(format_date(self.date).decode('utf-8')),
                u'Reason: {}'.format(reason)]
         self.reservation.add_edit_log(ReservationEditLog(user_name=user.full_name, info=log))
         from indico.modules.rb.notifications.reservation_occurrences import notify_rejection
         notify_rejection(self)
Exemple #4
0
 def reject(self, user, reason, silent=False):
     self.state = ReservationOccurrenceState.rejected
     self.rejection_reason = reason or None
     signals.rb.booking_occurrence_state_changed.send(self)
     if not silent:
         log = ['Day rejected: {}'.format(format_date(self.date)),
                f'Reason: {reason}']
         self.reservation.add_edit_log(ReservationEditLog(user_name=user.full_name, info=log))
         from indico.modules.rb.notifications.reservation_occurrences import notify_rejection
         notify_rejection(self)
 def cancel(self, user, reason=None, silent=False):
     self.state = ReservationOccurrenceState.cancelled
     self.rejection_reason = reason or None
     if not silent:
         log = [u'Day cancelled: {}'.format(format_date(self.date).decode('utf-8'))]
         if reason:
             log.append(u'Reason: {}'.format(reason))
         self.reservation.add_edit_log(ReservationEditLog(user_name=user.full_name, info=log))
         from indico.modules.rb.notifications.reservation_occurrences import notify_cancellation
         notify_cancellation(self)
 def reject(self, user, reason, silent=False):
     self.state = ReservationState.rejected
     self.rejection_reason = reason or None
     self.occurrences.filter_by(is_valid=True).update({
         ReservationOccurrence.state: ReservationOccurrenceState.rejected,
         ReservationOccurrence.rejection_reason: reason
     }, synchronize_session='fetch')
     if not silent:
         notify_rejection(self)
         log_msg = u'Reservation rejected: {}'.format(reason)
         self.add_edit_log(ReservationEditLog(user_name=user.full_name, info=[log_msg]))
 def cancel(self, user, reason=None, silent=False):
     self.is_cancelled = True
     self.rejection_reason = reason
     if not silent:
         log = [u'Day cancelled: {}'.format(format_date(self.date))]
         if reason:
             log.append(u'Reason: {}'.format(reason))
         self.reservation.add_edit_log(
             ReservationEditLog(user_name=user.getFullName(), info=log))
         from indico.modules.rb.notifications.reservation_occurrences import notify_cancellation
         notify_cancellation(self)
Exemple #8
0
 def accept(self, user):
     self.state = ReservationState.accepted
     self.add_edit_log(ReservationEditLog(user_name=user.full_name, info=['Reservation accepted']))
     notify_confirmation(self)
     signals.rb.booking_state_changed.send(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')
Exemple #9
0
 def reject(self, user, reason, silent=False):
     self.state = ReservationState.rejected
     self.rejection_reason = reason or None
     self.occurrences.filter_by(is_valid=True).update({
         ReservationOccurrence.state: ReservationOccurrenceState.rejected,
         ReservationOccurrence.rejection_reason: reason
     }, synchronize_session='fetch')
     signals.rb.booking_state_changed.send(self)
     if not silent:
         notify_rejection(self)
         log_msg = f'Reservation rejected: {reason}'
         self.add_edit_log(ReservationEditLog(user_name=user.full_name, info=[log_msg]))
 def cancel(self, user, reason=None, silent=False):
     self.state = ReservationOccurrenceState.cancelled
     self.rejection_reason = reason or None
     signals.rb.booking_occurrence_state_changed.send(self)
     if not silent:
         log = [f'Day cancelled: {format_date(self.date)}']
         if reason:
             log.append(f'Reason: {reason}')
         self.reservation.add_edit_log(
             ReservationEditLog(user_name=user.full_name, info=log))
         from indico.modules.rb.notifications.reservation_occurrences import notify_cancellation
         notify_cancellation(self)
Exemple #11
0
 def accept(self, user, reason=None):
     self.state = ReservationState.accepted
     if reason:
         log_msg = f'Reservation accepted: {reason}'
     else:
         log_msg = 'Reservation accepted'
     self.add_edit_log(ReservationEditLog(user_name=user.full_name, info=[log_msg]))
     notify_confirmation(self, reason)
     signals.rb.booking_state_changed.send(self)
     pre_occurrences = get_prebooking_collisions(self)
     for occurrence in pre_occurrences:
         occurrence.reject(user, 'Rejected due to collision with a confirmed reservation')
Exemple #12
0
 def cancel(self, user, reason=None, silent=False):
     self.state = ReservationState.cancelled
     self.rejection_reason = reason or None
     criteria = (ReservationOccurrence.is_valid, ReservationOccurrence.is_within_cancel_grace_period)
     self.occurrences.filter(*criteria).update({
         ReservationOccurrence.state: ReservationOccurrenceState.cancelled,
         ReservationOccurrence.rejection_reason: reason
     }, synchronize_session='fetch')
     signals.rb.booking_state_changed.send(self)
     if not silent:
         notify_cancellation(self)
         log_msg = u'Reservation cancelled: {}'.format(reason) if reason else 'Reservation cancelled'
         self.add_edit_log(ReservationEditLog(user_name=user.full_name, info=[log_msg]))
 def cancel(self, user, reason=None, silent=False):
     self.is_cancelled = True
     self.rejection_reason = reason
     self.occurrences.filter_by(is_valid=True).update(
         {
             'is_cancelled': True,
             'rejection_reason': reason
         },
         synchronize_session='fetch')
     if not silent:
         notify_cancellation(self)
         log_msg = u'Reservation cancelled: {}'.format(
             reason) if reason else 'Reservation cancelled'
         self.add_edit_log(
             ReservationEditLog(user_name=user.getFullName(),
                                info=[log_msg]))
    def accept(self, user):
        self.is_accepted = True
        self.add_edit_log(
            ReservationEditLog(user_name=user.getFullName(),
                               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')
Exemple #15
0
    def modify(self, data, user):
        """Modifies an existing reservation.

        :param data: A dict containing the booking data, usually from a :class:`ModifyBookingForm` instance
        :param user: The :class:`.User` who modifies the booking.
        """

        populate_fields = ('start_dt', 'end_dt', 'repeat_frequency', 'repeat_interval', 'booked_for_user',
                           'booking_reason')
        # fields affecting occurrences
        occurrence_fields = {'start_dt', 'end_dt', 'repeat_frequency', 'repeat_interval'}
        # fields where date and time are compared separately
        date_time_fields = {'start_dt', 'end_dt'}
        # fields for the repetition
        repetition_fields = {'repeat_frequency', 'repeat_interval'}
        # pretty names for logging
        field_names = {
            'start_dt/date': u"start date",
            'end_dt/date': u"end date",
            'start_dt/time': u"start time",
            'end_dt/time': u"end time",
            'repetition': u"booking type",
            'booked_for_user': u"'Booked for' user",
            'booking_reason': u"booking reason",
        }

        self.room.check_advance_days(data['end_dt'].date(), user)
        self.room.check_bookable_hours(data['start_dt'].time(), data['end_dt'].time(), user)

        changes = {}
        update_occurrences = False
        old_repetition = self.repetition

        for field in populate_fields:
            if field not in data:
                continue
            old = getattr(self, field)
            new = data[field]
            converter = unicode
            if old != new:
                # Booked for user updates the (redundant) name
                if field == 'booked_for_user':
                    old = self.booked_for_name
                    new = self.booked_for_name = data[field].full_name
                # Apply the change
                setattr(self, field, data[field])
                # If any occurrence-related field changed we need to recreate the occurrences
                if field in occurrence_fields:
                    update_occurrences = True
                # Record change for history entry
                if field in date_time_fields:
                    # The date/time fields create separate entries for the date and time parts
                    if old.date() != new.date():
                        changes[field + '/date'] = {'old': old.date(), 'new': new.date(), 'converter': format_date}
                    if old.time() != new.time():
                        changes[field + '/time'] = {'old': old.time(), 'new': new.time(), 'converter': format_time}
                elif field in repetition_fields:
                    # Repetition needs special handling since it consists of two fields but they are tied together
                    # We simply update it whenever we encounter such a change; after the last change we end up with
                    # the correct change data
                    changes['repetition'] = {'old': old_repetition,
                                             'new': self.repetition,
                                             'converter': lambda x: RepeatMapping.get_message(*x)}
                else:
                    changes[field] = {'old': old, 'new': new, 'converter': converter}

        if not changes:
            return False

        # Create a verbose log entry for the modification
        log = [u'Booking modified']
        for field, change in changes.iteritems():
            field_title = field_names.get(field, field)
            converter = change['converter']
            old = to_unicode(converter(change['old']))
            new = to_unicode(converter(change['new']))
            if not old:
                log.append(u"The {} was set to '{}'".format(field_title, new))
            elif not new:
                log.append(u"The {} was cleared".format(field_title))
            else:
                log.append(u"The {} was changed from '{}' to '{}'".format(field_title, old, new))

        self.edit_logs.append(ReservationEditLog(user_name=user.full_name, info=log))

        # Recreate all occurrences if necessary
        if update_occurrences:
            cols = [col.name for col in ReservationOccurrence.__table__.columns
                    if not col.primary_key and col.name not in {'start_dt', 'end_dt'}]

            old_occurrences = {occ.date: occ for occ in self.occurrences}
            self.occurrences.delete(synchronize_session='fetch')
            self.create_occurrences(True, user)
            db.session.flush()
            # Restore rejection data etc. for recreated occurrences
            for occurrence in self.occurrences:
                old_occurrence = old_occurrences.get(occurrence.date)
                # Copy data from old occurrence UNLESS the new one is invalid (e.g. because of collisions)
                # Otherwise we'd end up with valid occurrences ignoring collisions!
                if old_occurrence and occurrence.is_valid:
                    for col in cols:
                        setattr(occurrence, col, getattr(old_occurrence, col))
            # Don't cause new notifications for the entire booking in case of daily repetition
            if self.repeat_frequency == RepeatFrequency.DAY and all(occ.notification_sent
                                                                    for occ in old_occurrences.itervalues()):
                for occurrence in self.occurrences:
                    occurrence.notification_sent = True

        # Sanity check so we don't end up with an "empty" booking
        if not any(occ.is_valid for occ in self.occurrences):
            raise NoReportError(_(u'Reservation has no valid occurrences'))

        notify_modification(self, changes)
        return True
Exemple #16
0
 def reset_approval(self, user):
     self.state = ReservationState.pending
     notify_reset_approval(self)
     self.add_edit_log(ReservationEditLog(user_name=user.full_name, info=['Requiring new approval due to change']))
Exemple #17
0
    def migrate_reservations(self):
        print cformat('%{white!}migrating reservations')
        i = 1
        for rid, v in self.rb_root['Reservations'].iteritems():
            room = Room.get(v.room.id)
            if room is None:
                print cformat(
                    '  %{red!}skipping resv for dead room {0.room.id}: {0.id} ({0._utcCreatedDT})'
                ).format(v)
                continue

            repeat_frequency, repeat_interval = RepeatMapping.convert_legacy_repeatability(
                v.repeatability)
            booked_for_id = getattr(v, 'bookedForId', None)

            r = Reservation(
                id=v.id,
                created_dt=as_utc(v._utcCreatedDT),
                start_dt=utc_to_local(v._utcStartDT),
                end_dt=utc_to_local(v._utcEndDT),
                booked_for_id=self.merged_avatars.get(booked_for_id,
                                                      booked_for_id) or None,
                booked_for_name=convert_to_unicode(v.bookedForName),
                contact_email=convert_to_unicode(v.contactEmail),
                contact_phone=convert_to_unicode(
                    getattr(v, 'contactPhone', None)),
                created_by_id=self.merged_avatars.get(v.createdBy, v.createdBy)
                or None,
                is_cancelled=v.isCancelled,
                is_accepted=v.isConfirmed,
                is_rejected=v.isRejected,
                booking_reason=convert_to_unicode(v.reason),
                rejection_reason=convert_to_unicode(
                    getattr(v, 'rejectionReason', None)),
                repeat_frequency=repeat_frequency,
                repeat_interval=repeat_interval,
                uses_vc=getattr(v, 'usesAVC', False),
                needs_vc_assistance=getattr(v, 'needsAVCSupport', False),
                needs_assistance=getattr(v, 'needsAssistance', False))

            for eq_name in getattr(v, 'useVC', []):
                eq = room.location.get_equipment_by_name(eq_name)
                if eq:
                    r.used_equipment.append(eq)

            occurrence_rejection_reasons = {}
            if getattr(v, 'resvHistory', None):
                for h in reversed(v.resvHistory._entries):
                    ts = as_utc(parse_dt_string(h._timestamp))

                    if len(h._info) == 2:
                        possible_rejection_date, possible_rejection_reason = h._info
                        m = re.match(
                            r'Booking occurrence of the (\d{1,2} \w{3} \d{4}) rejected',
                            possible_rejection_reason)
                        if m:
                            d = datetime.strptime(m.group(1), '%d %b %Y')
                            occurrence_rejection_reasons[
                                d] = possible_rejection_reason[9:].strip('\'')

                    el = ReservationEditLog(timestamp=ts,
                                            user_name=h._responsibleUser,
                                            info=map(convert_to_unicode,
                                                     h._info))
                    r.edit_logs.append(el)

            notifications = getattr(v, 'startEndNotification', []) or []
            excluded_days = getattr(v, '_excludedDays', []) or []
            ReservationOccurrence.create_series_for_reservation(r)
            for occ in r.occurrences:
                occ.notification_sent = occ.date in notifications
                occ.is_rejected = r.is_rejected
                occ.is_cancelled = r.is_cancelled or occ.date in excluded_days
                occ.rejection_reason = (
                    convert_to_unicode(occurrence_rejection_reasons[occ.date])
                    if occ.date in occurrence_rejection_reasons else None)

            event_id = getattr(v, '_ReservationBase__owner', None)
            if hasattr(event_id, '_Impersistant__obj'):  # Impersistant object
                event_id = event_id._Impersistant__obj
            if event_id is not None:
                event = self.zodb_root['conferences'].get(event_id)
                if event:
                    # For some stupid reason there are bookings in the database which have a completely unrelated parent
                    guids = getattr(event, '_Conference__roomBookingGuids', [])
                    if any(
                            int(x.id) == v.id for x in guids
                            if x.id is not None):
                        r.event_id = int(event_id)
                    else:
                        print cformat(
                            '  %{red}event {} does not contain booking {}'
                        ).format(event_id, v.id)

            print cformat(
                '- [%{cyan}{}%{reset}/%{green!}{}%{reset}]  %{grey!}{}%{reset}  {}'
            ).format(room.location_name, room.name, r.id, r.created_dt.date())

            room.reservations.append(r)
            db.session.add(room)
            i = (i + 1) % 1000
            if not i:
                db.session.commit()
        db.session.commit()
def test_add_edit_log(db, dummy_reservation):
    dummy_reservation.add_edit_log(
        ReservationEditLog(user_name='user', info='Some change'))
    assert dummy_reservation.edit_logs.count() == 1