def test_create_series_for_reservation(dummy_reservation): ReservationOccurrence.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 create_occurrences(self, skip_conflicts, user=None, allow_admin=True): ReservationOccurrence.create_series_for_reservation(self) db.session.flush() if user is None: user = self.created_by_user # Check for conflicts with nonbookable periods admin = allow_admin and rb_is_admin(user) if not admin and not self.room.can_manage(user, permission='override'): 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, '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_override(user, room=self.room, allow_admin=allow_admin): continue for occurrence in self.occurrences: if occurrence.is_valid and blocking.is_active_at(occurrence.start_dt.date()): # Cancel OUR occurrence msg = '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.items(): if not occurrence.is_valid: continue if conflicts['confirmed']: if not skip_conflicts: raise ConflictingOccurrences() # Cancel OUR occurrence msg = '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, 'Rejected due to collision with a confirmed reservation')
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 user.isRBAdmin(): 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')
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") # Mark occurrences created within the notification window as notified for occurrence in self.occurrences: if occurrence.is_valid and occurrence.is_in_notification_window(): occurrence.notification_sent = True # Mark occurrences created within the digest window as notified if self.repeat_frequency == RepeatFrequency.WEEK: if self.room.is_in_digest_window(): digest_start = round_up_month(date.today()) else: digest_start = date.today() digest_end = get_month_end(digest_start) self.occurrences.filter(ReservationOccurrence.start_dt <= digest_end).update({"notification_sent": True})
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 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 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 user.isRBAdmin() 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' ) # Mark occurrences created within the notification window as notified for occurrence in self.occurrences: if occurrence.is_valid and occurrence.is_in_notification_window(): occurrence.notification_sent = True # Mark occurrences created within the digest window as notified if self.repeat_frequency == RepeatFrequency.WEEK: if self.room.is_in_digest_window(): digest_start = round_up_month(date.today()) else: digest_start = date.today() digest_end = get_month_end(digest_start) self.occurrences.filter( ReservationOccurrence.start_dt <= digest_end).update( {'notification_sent': True})