def rb_merge_users(new_id, old_id): """Updates RB data after an Avatar merge :param new_id: Target user :param old_id: Source user (being deleted in the merge) """ from indico.modules.rb import settings as rb_settings from indico.modules.rb.models.blocking_principals import BlockingPrincipal from indico.modules.rb.models.blockings import Blocking from indico.modules.rb.models.reservations import Reservation from indico.modules.rb.models.rooms import Room old_user = User.get(int(old_id)) new_user = User.get(int(new_id)) for bp in BlockingPrincipal.find(): if bp.principal == old_user: bp.principal = new_user Blocking.find(created_by_id=old_id).update({'created_by_id': new_id}) Reservation.find(created_by_id=old_id).update({'created_by_id': new_id}) Reservation.find(booked_for_id=old_id).update({'booked_for_id': new_id}) Room.find(owner_id=old_id).update({'owner_id': new_id}) for key in ('authorized_principals', 'admin_principals'): principals = rb_settings.get(key) principals = principals_merge_users(principals, new_id, old_id) rb_settings.set(key, principals)
def _merge_users(target, source, **kwargs): BlockingPrincipal.merge_users(target, source, 'blocking') Blocking.find(created_by_id=source.id).update({Blocking.created_by_id: target.id}) Reservation.find(created_by_id=source.id).update({Reservation.created_by_id: target.id}) Reservation.find(booked_for_id=source.id).update({Reservation.booked_for_id: target.id}) Room.find(owner_id=source.id).update({Room.owner_id: target.id}) rb_settings.acls.merge_users(target, source)
def test_find_overlapping_with_is_not_valid(overlapping_reservation, dummy_user): reservation, occurrence = overlapping_reservation assert reservation in Reservation.find_overlapping_with(room=reservation.room, occurrences=[occurrence]).all() reservation.cancel(dummy_user, silent=True) assert reservation not in Reservation.find_overlapping_with(room=reservation.room, occurrences=[occurrence]).all()
def test_find_overlapping_with_different_room(overlapping_reservation, create_room): reservation, occurrence = overlapping_reservation assert reservation in Reservation.find_overlapping_with( room=reservation.room, occurrences=[occurrence]).all() assert reservation not in Reservation.find_overlapping_with( room=create_room(), occurrences=[occurrence]).all()
def _process(self, args): room = Room.get_one(args.pop('room_id')) user_id = args.pop('user_id', None) booked_for = User.get_one(user_id) if user_id else session.user is_prebooking = args.pop('is_prebooking') # Check that the booking is not longer than allowed booking_limit_days = room.booking_limit_days or rb_settings.get( 'booking_limit') if not self._validate_room_booking_limit( args['start_dt'], args['end_dt'], booking_limit_days): msg = ( _('Bookings for the room "{}" may not be longer than {} days' ).format(room.name, booking_limit_days)) return jsonify(success=False, msg=msg) try: Reservation.create_from_data(room, dict(args, booked_for_user=booked_for), session.user, prebook=is_prebooking) db.session.flush() except NoReportError as e: db.session.rollback() return jsonify(success=False, msg=unicode(e)) return jsonify(success=True, is_prebooking=is_prebooking)
def test_find_overlapping_with_skip_reservation(overlapping_reservation): reservation, occurrence = overlapping_reservation assert reservation in Reservation.find_overlapping_with( room=reservation.room, occurrences=[occurrence]).all() assert reservation not in Reservation.find_overlapping_with( room=reservation.room, occurrences=[occurrence], skip_reservation_id=reservation.id).all()
def test_find_overlapping_with_is_not_valid(overlapping_reservation, dummy_user, freeze_time): freeze_time(datetime.combine(date.today(), time(1))) reservation, occurrence = overlapping_reservation assert reservation in Reservation.find_overlapping_with(room=reservation.room, occurrences=[occurrence]).all() reservation.cancel(dummy_user, silent=True) assert reservation not in Reservation.find_overlapping_with(room=reservation.room, occurrences=[occurrence]).all()
def test_find_overlapping_with_skip_reservation(overlapping_reservation): reservation, occurrence = overlapping_reservation assert reservation in Reservation.find_overlapping_with(room=reservation.room, occurrences=[occurrence]).all() assert ( reservation not in Reservation.find_overlapping_with( room=reservation.room, occurrences=[occurrence], skip_reservation_id=reservation.id ).all() )
def _merge_users(target, source, **kwargs): source_principals = set(source.in_blocking_acls.options(joinedload(BlockingPrincipal.blocking))) target_blockings = {x.blocking for x in target.in_blocking_acls.options(joinedload(BlockingPrincipal.blocking))} for principal in source_principals: if principal.blocking not in target_blockings: principal.user_id = target.id else: db.session.delete(principal) Blocking.find(created_by_id=source.id).update({Blocking.created_by_id: target.id}) Reservation.find(created_by_id=source.id).update({Reservation.created_by_id: target.id}) Reservation.find(booked_for_id=source.id).update({Reservation.booked_for_id: target.id}) Room.find(owner_id=source.id).update({Room.owner_id: target.id}) settings.acls.merge_users(target, source)
def _create_reservation(**params): params.setdefault('start_dt', date.today() + relativedelta(hour=8, minute=30)) params.setdefault('end_dt', date.today() + relativedelta(hour=17, minute=30)) params.setdefault('repeat_frequency', RepeatFrequency.NEVER) params.setdefault('repeat_interval', int(params['repeat_frequency'] != RepeatFrequency.NEVER)) params.setdefault('booking_reason', u'Testing') params.setdefault('room', dummy_room) params.setdefault('booked_for_user', dummy_user) params.setdefault('created_by_user', dummy_user) reservation = Reservation(**params) reservation.create_occurrences(skip_conflicts=False) db.session.add(reservation) db.session.flush() return reservation
def _create_reservation(**params): params.setdefault('start_dt', date.today() + relativedelta(hour=8, minute=30)) params.setdefault('end_dt', date.today() + relativedelta(hour=17, minute=30)) params.setdefault('repeat_frequency', RepeatFrequency.NEVER) params.setdefault('repeat_interval', int(params['repeat_frequency'] != RepeatFrequency.NEVER)) params.setdefault('contact_email', dummy_avatar.email) params.setdefault('is_accepted', True) params.setdefault('booking_reason', u'Testing') params.setdefault('room', dummy_room) params.setdefault('booked_for_user', dummy_avatar.user) params.setdefault('created_by_user', dummy_avatar.user) reservation = Reservation(**params) reservation.create_occurrences(skip_conflicts=False) db.session.add(reservation) db.session.flush() return reservation
def _process(self): args = self.args args.setdefault('booked_for_user', session.user) if not is_booking_start_within_grace_period(args['start_dt'], session.user, args['admin_override_enabled']): raise ExpectedError(_('You cannot create a booking which starts in the past')) # Check that the booking is not longer than allowed booking_limit_days = self.room.booking_limit_days or rb_settings.get('booking_limit') if not self._validate_room_booking_limit(args['start_dt'], args['end_dt'], booking_limit_days): msg = (_('Bookings for the room "{}" may not be longer than {} days') .format(self.room.name, booking_limit_days)) raise ExpectedError(msg) try: resv = Reservation.create_from_data(self.room, args, session.user, prebook=self.prebook) if args.get('link_type') is not None and args.get('link_id') is not None: self._link_booking(resv, args['link_type'], args['link_id'], args['link_back']) db.session.flush() except NoReportError as e: db.session.rollback() raise ExpectedError(unicode(e)) serialized_occurrences = serialize_occurrences(group_by_occurrence_date(resv.occurrences.all())) if self.prebook: data = {'pre_bookings': serialized_occurrences} else: data = {'bookings': serialized_occurrences} return jsonify(room_id=self.room.id, booking=reservation_details_schema.dump(resv), calendar_data=data)
def compose_rooms_stats(rooms): reservations = Reservation.find( Reservation.room_id.in_(r.id for r in rooms)) return { 'active': { 'valid': reservations.filter(Reservation.is_accepted, ~Reservation.is_archived).count(), 'pending': reservations.filter(Reservation.is_pending, ~Reservation.is_archived).count(), 'cancelled': reservations.filter(Reservation.is_cancelled, ~Reservation.is_archived).count(), 'rejected': reservations.filter(Reservation.is_rejected, ~Reservation.is_archived).count(), }, 'archived': { 'valid': reservations.filter(Reservation.is_accepted, Reservation.is_archived).count(), 'pending': reservations.filter(Reservation.is_pending, Reservation.is_archived).count(), 'cancelled': reservations.filter(Reservation.is_cancelled, Reservation.is_archived).count(), 'rejected': reservations.filter(Reservation.is_rejected, Reservation.is_archived).count() } }
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 room = booking.room occurrences = sorted(booking.occurrences, key=attrgetter('start_dt')) 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} occurrences_to_cancel = [occ for occ in occurrences if occ.start_dt >= datetime.now()] new_start_dt = datetime.combine(occurrences_to_cancel[0].start_dt.date(), new_booking_data['start_dt'].time()) for occurrence_to_cancel in occurrences_to_cancel: occurrence_to_cancel.cancel(session.user, silent=True) 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) 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) 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) return resv
def _event_deleted(event, user, **kwargs): reservations = Reservation.find(Reservation.event_id == int(event.id), ~Reservation.is_cancelled, ~Reservation.is_rejected) for resv in reservations: resv.event_id = None resv.cancel(user or session.user, 'Associated event was deleted')
def _process(self): rooms = sorted(self._location.rooms, key=lambda r: natural_sort_key(r.full_name)) kpi = {} if self._with_kpi: kpi['occupancy'] = calculate_rooms_occupancy(self._location.rooms) kpi['total_rooms'] = len(self._location.rooms) kpi['active_rooms'] = sum(1 for room in self._location.rooms if room.is_active) kpi['reservable_rooms'] = sum(1 for room in self._location.rooms if room.is_reservable) kpi['reservable_capacity'] = sum(room.capacity or 0 for room in self._location.rooms if room.is_reservable) kpi['reservable_surface'] = sum(room.surface_area or 0 for room in self._location.rooms if room.is_reservable) kpi['booking_stats'] = compose_rooms_stats(self._location.rooms) kpi['booking_count'] = Reservation.find( Reservation.room.has(Room.location == self._location)).count() return WPRoomBookingAdminLocation( self, 'rb-rooms', location=self._location, rooms=rooms, action_succeeded=self._actionSucceeded, equipment_types=self._location.equipment_types.all(), attributes=self._location.attributes.all(), kpi=kpi).display()
def calculate_rooms_booked_time(rooms, start_date=None, end_date=None): if end_date is None: end_date = date.today() - relativedelta(days=1) if start_date is None: start_date = end_date - relativedelta(days=29) # Reservations on working days reservations = Reservation.find(Reservation.room_id.in_(r.id for r in rooms), db.extract('dow', ReservationOccurrence.start_dt).between(1, 5), db.cast(ReservationOccurrence.start_dt, db.Date) >= start_date, db.cast(ReservationOccurrence.end_dt, db.Date) <= end_date, ReservationOccurrence.is_valid, _join=ReservationOccurrence) rsv_start = db.cast(ReservationOccurrence.start_dt, db.TIME) rsv_end = db.cast(ReservationOccurrence.end_dt, db.TIME) slots = ((db.cast(start, db.TIME), db.cast(end, db.TIME)) for start, end in WORKING_TIME_PERIODS) # this basically handles all possible ways an occurrence overlaps with each one of the working time slots overlaps = sum(db.case([ ((rsv_start < start) & (rsv_end > end), db.extract('epoch', end - start)), ((rsv_start < start) & (rsv_end > start) & (rsv_end <= end), db.extract('epoch', rsv_end - start)), ((rsv_start >= start) & (rsv_start < end) & (rsv_end > end), db.extract('epoch', end - rsv_start)), ((rsv_start >= start) & (rsv_end <= end), db.extract('epoch', rsv_end - rsv_start)) ], else_=0) for start, end in slots) return reservations.with_entities(db.func.sum(overlaps)).scalar() or 0
def _process(self): rooms = sorted(self._location.rooms, key=lambda r: natural_sort_key(r.full_name)) kpi = {} if self._with_kpi: kpi['occupancy'] = calculate_rooms_occupancy( self._location.rooms.all()) kpi['total_rooms'] = self._location.rooms.count() kpi['active_rooms'] = self._location.rooms.filter_by( is_active=True).count() kpi['reservable_rooms'] = self._location.rooms.filter_by( is_reservable=True).count() kpi['reservable_capacity'] = (self._location.rooms.with_entities( func.sum( Room.capacity)).filter_by(is_reservable=True).scalar()) kpi['reservable_surface'] = (self._location.rooms.with_entities( func.sum( Room.surface_area)).filter_by(is_reservable=True).scalar()) kpi['booking_stats'] = compose_rooms_stats( self._location.rooms.all()) kpi['booking_count'] = Reservation.find( Reservation.room_id.in_( r.id for r in self._location.rooms)).count() return WPRoomBookingAdminLocation( self, location=self._location, rooms=rooms, action_succeeded=self._actionSucceeded, equipment_types=self._location.equipment_types.all(), attributes=self._location.attributes.all(), kpi=kpi).display()
def _export_reservations(hook, limit_per_room, include_rooms, extra_filters=None): """Exports reservations. :param hook: The HTTPAPIHook instance :param limit_per_room: Should the limit/offset be applied per room :param include_rooms: Should reservations include room information """ filters = list(extra_filters) if extra_filters else [] if hook._fromDT and hook._toDT: filters.append(cast(Reservation.start_dt, Date) <= hook._toDT.date()) filters.append(cast(Reservation.end_dt, Date) >= hook._fromDT.date()) filters.append(cast(Reservation.start_dt, Time) <= hook._toDT.time()) filters.append(cast(Reservation.end_dt, Time) >= hook._fromDT.time()) elif hook._toDT: filters.append(cast(Reservation.end_dt, Date) <= hook._toDT.date()) filters.append(cast(Reservation.end_dt, Time) <= hook._toDT.time()) elif hook._fromDT: filters.append(cast(Reservation.start_dt, Date) >= hook._fromDT.date()) filters.append(cast(Reservation.start_dt, Time) >= hook._fromDT.time()) filters += _get_reservation_state_filter(hook._queryParams) occurs = [datetime.strptime(x, '%Y-%m-%d').date() for x in filter(None, get_query_parameter(hook._queryParams, ['occurs'], '').split(','))] data = [] if hook._occurrences: data.append('occurrences') order = { 'start': Reservation.start_dt, 'end': Reservation.end_dt }.get(hook._orderBy, Reservation.start_dt) if hook._descending: order = order.desc() reservations_data = Reservation.get_with_data(*data, filters=filters, limit=hook._limit, offset=hook._offset, order=order, limit_per_room=limit_per_room, occurs_on=occurs) for result in reservations_data: yield result['reservation'].room_id, _serializable_reservation(result, include_rooms)
def test_is_pending(create_reservation, is_accepted, is_rejected, is_cancelled, expected): reservation = create_reservation(is_accepted=is_accepted, is_rejected=is_rejected, is_cancelled=is_cancelled) assert reservation.is_pending == expected assert Reservation.find_first(is_pending=expected) == reservation
def _process(self, args): room = Room.get_one(args.pop('room_id')) user_id = args.pop('user_id', None) booked_for = User.get_one(user_id) if user_id else session.user is_prebooking = args.pop('is_prebooking') # Check that the booking is not longer than allowed booking_limit_days = room.booking_limit_days or rb_settings.get('booking_limit') if not self._validate_room_booking_limit(args['start_dt'], args['end_dt'], booking_limit_days): msg = (_('Bookings for the room "{}" may not be longer than {} days') .format(room.name, booking_limit_days)) raise ExpectedError(msg) try: resv = Reservation.create_from_data(room, dict(args, booked_for_user=booked_for), session.user, prebook=is_prebooking) db.session.flush() except NoReportError as e: db.session.rollback() raise ExpectedError(unicode(e)) serialized_occurrences = serialize_occurrences(group_by_occurrence_date(resv.occurrences.all())) if is_prebooking: data = {'pre_bookings': serialized_occurrences} else: data = {'bookings': serialized_occurrences} return jsonify(room_id=room.id, **data)
def create_booking_for_event(room_id, event): try: room = Room.get_one(room_id) default_timezone = timezone(config.DEFAULT_TIMEZONE) start_dt = event.start_dt.astimezone(default_timezone).replace( tzinfo=None) end_dt = event.end_dt.astimezone(default_timezone).replace(tzinfo=None) booking_reason = "Event '{}'".format(event.title) data = dict(start_dt=start_dt, end_dt=end_dt, booked_for_user=event.creator, booking_reason=booking_reason, repeat_frequency=RepeatFrequency.NEVER, event_id=event.id) booking = Reservation.create_from_data(room, data, session.user, ignore_admin=True) booking.linked_object = event return booking except NoReportError: flash( _("Booking could not be created. Probably somebody else booked the room in the meantime." ), 'error') return None
def _process(self): args = self.args user_id = args.pop('user_id', None) booked_for = User.get_one(user_id, is_deleted=False) if user_id else session.user # Check that the booking is not longer than allowed booking_limit_days = self.room.booking_limit_days or rb_settings.get('booking_limit') if not self._validate_room_booking_limit(args['start_dt'], args['end_dt'], booking_limit_days): msg = (_('Bookings for the room "{}" may not be longer than {} days') .format(self.room.name, booking_limit_days)) raise ExpectedError(msg) try: resv = Reservation.create_from_data(self.room, dict(args, booked_for_user=booked_for), session.user, prebook=self.prebook) if args.get('link_type') is not None and args.get('link_id') is not None: self._link_booking(resv, args['link_type'], args['link_id'], args['link_back']) db.session.flush() except NoReportError as e: db.session.rollback() raise ExpectedError(unicode(e)) serialized_occurrences = serialize_occurrences(group_by_occurrence_date(resv.occurrences.all())) if self.prebook: data = {'pre_bookings': serialized_occurrences} else: data = {'bookings': serialized_occurrences} return jsonify(room_id=self.room.id, booking=reservation_details_schema.dump(resv).data, calendar_data=data)
def _process(self, args): room = Room.get_one(args.pop('room_id')) user_id = args.pop('user_id', None) booked_for = User.get_one(user_id) if user_id else session.user is_prebooking = args.pop('is_prebooking') # Check that the booking is not longer than allowed booking_limit_days = room.booking_limit_days or rb_settings.get('booking_limit') if not self._validate_room_booking_limit(args['start_dt'], args['end_dt'], booking_limit_days): msg = (_('Bookings for the room "{}" may not be longer than {} days') .format(room.name, booking_limit_days)) raise ExpectedError(msg) try: resv = Reservation.create_from_data(room, dict(args, booked_for_user=booked_for), session.user, prebook=is_prebooking) db.session.flush() except NoReportError as e: db.session.rollback() raise ExpectedError(unicode(e)) serialized_occurrences = serialize_occurrences(group_by_occurrence_date(resv.occurrences.all())) if is_prebooking: data = {'pre_bookings': serialized_occurrences} else: data = {'bookings': serialized_occurrences} return jsonify(room_id=room.id, booking=reservation_details_schema.dump(resv).data, calendar_data=data)
def calculate_rooms_booked_time(rooms, start_date=None, end_date=None): if end_date is None: end_date = date.today() - relativedelta(days=1) if start_date is None: start_date = end_date - relativedelta(days=29) # Reservations on working days reservations = Reservation.find( Reservation.room_id.in_(r.id for r in rooms), db.extract('dow', ReservationOccurrence.start_dt).between(1, 5), db.cast(ReservationOccurrence.start_dt, db.Date) >= start_date, db.cast(ReservationOccurrence.end_dt, db.Date) <= end_date, ReservationOccurrence.is_valid, _join=ReservationOccurrence) rsv_start = db.cast(ReservationOccurrence.start_dt, db.TIME) rsv_end = db.cast(ReservationOccurrence.end_dt, db.TIME) slots = ((db.cast(start, db.TIME), db.cast(end, db.TIME)) for start, end in Location.working_time_periods) # this basically handles all possible ways an occurrence overlaps with each one of the working time slots overlaps = sum( db.case([((rsv_start < start) & (rsv_end > end), db.extract('epoch', end - start)), ((rsv_start < start) & (rsv_end > start) & (rsv_end <= end), db.extract('epoch', rsv_end - start)), ((rsv_start >= start) & (rsv_start < end) & (rsv_end > end), db.extract('epoch', end - rsv_start)), ((rsv_start >= start) & (rsv_end <= end), db.extract('epoch', rsv_end - rsv_start))], else_=0) for start, end in slots) return reservations.with_entities(db.func.sum(overlaps)).scalar() or 0
def _export_reservations(hook, limit_per_room, include_rooms, extra_filters=None): """Exports reservations. :param hook: The HTTPAPIHook instance :param limit_per_room: Should the limit/offset be applied per room :param include_rooms: Should reservations include room information """ filters = list(extra_filters) if extra_filters else [] if hook._fromDT and hook._toDT: filters.append(cast(Reservation.start_dt, Date) <= hook._toDT.date()) filters.append(cast(Reservation.end_dt, Date) >= hook._fromDT.date()) filters.append(cast(Reservation.start_dt, Time) <= hook._toDT.time()) filters.append(cast(Reservation.end_dt, Time) >= hook._fromDT.time()) elif hook._toDT: filters.append(cast(Reservation.end_dt, Date) <= hook._toDT.date()) filters.append(cast(Reservation.end_dt, Time) <= hook._toDT.time()) elif hook._fromDT: filters.append(cast(Reservation.start_dt, Date) >= hook._fromDT.date()) filters.append(cast(Reservation.start_dt, Time) >= hook._fromDT.time()) filters += _get_reservation_state_filter(hook._queryParams) data = ['vc_equipment'] if hook._occurrences: data.append('occurrences') order = { 'start': Reservation.start_dt, 'end': Reservation.end_dt }.get(hook._orderBy, Reservation.start_dt) if hook._descending: order = order.desc() reservations_data = Reservation.get_with_data(*data, filters=filters, limit=hook._limit, offset=hook._offset, order=order, limit_per_room=limit_per_room) for result in reservations_data: yield result['reservation'].room_id, _serializable_reservation(result, include_rooms)
def _process(self): reservations = Reservation.find_all(event_id=self.event_id) if not reservations: self._redirect(url_for('event_mgmt.rooms_choose_event', self.event)) return return WPRoomBookingEventBookingList( self, self.event, reservations=reservations).display()
def _createTabCtrl(self): self._tabCtrl = TabControl() self._tabExistBookings = self._tabCtrl.newTab('existing', 'Existing Bookings', url_for('event_mgmt.rooms_booking_list', self._conf)) self._tabNewBooking = self._tabCtrl.newTab('new', 'New Booking', url_for('event_mgmt.rooms_choose_event', self._conf)) if not Reservation.find(event_id=self._conf.getId()).count(): self._tabExistBookings.setEnabled(False) self._setActiveTab()
def _create_booking(self, form, room): if 'submit_book' in form and 'submit_prebook' in form: # Admins have the choice prebook = form.submit_prebook.data else: # Otherwise the existence of the book submit button means the user can book prebook = 'submit_book' not in form reservation = Reservation.create_from_data(room, form.data, session.user, prebook) db.session.add(reservation) db.session.flush() return reservation
def rb_merge_users(new_id, old_id): """Updates RB data after an Avatar merge :param new_id: Target user :param old_id: Source user (being deleted in the merge) """ from indico.modules.rb import settings from indico.modules.rb.models.blocking_principals import BlockingPrincipal from indico.modules.rb.models.blockings import Blocking from indico.modules.rb.models.reservations import Reservation from indico.modules.rb.models.rooms import Room BlockingPrincipal.find(entity_type='Avatar', entity_id=old_id).update({'entity_id': new_id}) Blocking.find(created_by_id=old_id).update({'created_by_id': new_id}) Reservation.find(created_by_id=old_id).update({'created_by_id': new_id}) Reservation.find(booked_for_id=old_id).update({'booked_for_id': new_id}) Room.find(owner_id=old_id).update({'owner_id': new_id}) for key in ('authorized_principals', 'admin_principals'): principals = settings.get(key, []) principals = principals_merge_users(principals, new_id, old_id) settings.set(key, principals)
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
def create_booking_for_event(room_id, event): try: room = Room.get_one(room_id) default_timezone = timezone(config.DEFAULT_TIMEZONE) start_dt = event.start_dt.astimezone(default_timezone).replace(tzinfo=None) end_dt = event.end_dt.astimezone(default_timezone).replace(tzinfo=None) booking_reason = "Event '%s'" % (event.title) data = dict(start_dt=start_dt, end_dt=end_dt, booked_for_user=event.creator, booking_reason=booking_reason, repeat_frequency=RepeatFrequency.NEVER, event_id=event.id) resv = Reservation.create_from_data(room, data, session.user, ignore_admin=True) return resv except NoReportError: flash(_("Booking could not be created. Probably somebody else booked the room in the meantime."), 'error')
def _checkSplitRooms(self, room, occs): if room in self.splitmap: for ri in self.splitmap[room]: oroom = Room.get_or_404(ri, is_deleted=False) overlap = Reservation.find_overlapping_with(oroom, occs).all() if overlap: raise ExpectedError( 'Overlaps with other reservations in {}.'.format( oroom.name)) else: for ri, splits in self.splitmap.items(): if room in splits: oroom = Room.get_or_404(ri, is_deleted=False) overlap = Reservation.find_overlapping_with(oroom, occs).all() if overlap: raise ExpectedError( 'Overlaps with other reservations in {}.'.format( oroom.name)) break
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 = f'Conflict with blocking {self.blocking.id}: {self.blocking.reason}' for reservation in reservations: if self.blocking.can_override(reservation.created_by_user, room=reservation.room): continue reservation.reject(self.blocking.created_by_user, reason) for occurrence in occurrences: reservation = occurrence.reservation if self.blocking.can_override(reservation.created_by_user, room=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 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 compose_rooms_stats(rooms): reservations = Reservation.find(Reservation.room_id.in_(r.id for r in rooms)) return { 'active': { 'valid': reservations.filter(Reservation.is_valid, ~Reservation.is_archived).count(), 'pending': reservations.filter(Reservation.is_pending, ~Reservation.is_archived).count(), 'cancelled': reservations.filter(Reservation.is_cancelled, ~Reservation.is_archived).count(), 'rejected': reservations.filter(Reservation.is_rejected, ~Reservation.is_archived).count(), }, 'archived': { 'valid': reservations.filter(Reservation.is_valid, Reservation.is_archived).count(), 'pending': reservations.filter(Reservation.is_pending, Reservation.is_archived).count(), 'cancelled': reservations.filter(Reservation.is_cancelled, Reservation.is_archived).count(), 'rejected': reservations.filter(Reservation.is_rejected, Reservation.is_archived).count() } }
def calculate_rooms_booked_time(rooms, start_date=None, end_date=None): if end_date is None: end_date = date.today() - relativedelta(days=1) if start_date is None: start_date = end_date - relativedelta(days=29) # Reservations on working days reservations = Reservation.find(Reservation.room_id.in_(r.id for r in rooms), extract('dow', ReservationOccurrence.start_dt).between(1, 5), ReservationOccurrence.start_dt >= start_date, ReservationOccurrence.end_dt <= end_date, ReservationOccurrence.is_valid, _join=ReservationOccurrence) # Take into account only working hours earliest_time = greatest(cast(ReservationOccurrence.start_dt, TIME), Location.working_time_start) latest_time = least(cast(ReservationOccurrence.end_dt, TIME), Location.working_time_end) booked_time = reservations.with_entities(func.sum(latest_time - earliest_time)).scalar() return (booked_time or timedelta()).total_seconds()
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 room = booking.room occurrences = sorted(booking.occurrences, key=attrgetter('start_dt')) occurrences_to_cancel = [ occ for occ in occurrences if occ.start_dt.date() >= date.today() ] new_start_dt = datetime.combine(occurrences_to_cancel[0].start_dt.date(), new_booking_data['start_dt'].time()) for occurrence_to_cancel in occurrences_to_cancel: occurrence_to_cancel.cancel(session.user, silent=True) new_end_dt = [ occ for occ in occurrences if occ.start_dt.date() < date.today() ][-1].end_dt old_booking_data = { 'booking_reason': booking.booking_reason, 'room_usage': 'current_user' if booking.booked_for_user == session.user else 'someone', '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) prebook = not room.can_book(session.user, allow_admin=False) and room.can_prebook( session.user, allow_admin=False) return Reservation.create_from_data(room, dict(new_booking_data, start_dt=new_start_dt), session.user, prebook=prebook)
def api_roomBooking(self, user): data = MultiDict({ 'start_dt': self._params['from'], 'end_dt': self._params['to'], 'repeat_frequency': RepeatFrequency.NEVER, 'repeat_interval': 0, 'room_id': self._room.id, 'booked_for_user': self._params['booked_for'], 'booking_reason': self._params['reason'] }) try: reservation = Reservation.create_from_data(self._room, data, user) except ConflictingOccurrences: raise HTTPAPIError('Failed to create the booking due to conflicts with other bookings') except IndicoError as e: raise HTTPAPIError('Failed to create the booking: {}'.format(e)) db.session.add(reservation) db.session.flush() return {'reservationID': reservation.id}
def calculate_rooms_booked_time(rooms, start_date=None, end_date=None): if end_date is None: end_date = date.today() - relativedelta(days=1) if start_date is None: start_date = end_date - relativedelta(days=29) # Reservations on working days reservations = Reservation.find(Reservation.room_id.in_(r.id for r in rooms), extract('dow', ReservationOccurrence.start_dt).between(1, 5), ReservationOccurrence.start_dt >= start_date, ReservationOccurrence.end_dt <= end_date, ReservationOccurrence.is_valid, _join=ReservationOccurrence) # Take into account only working hours earliest_time = least(greatest(cast(ReservationOccurrence.start_dt, TIME), Location.working_time_start), Location.working_time_end) latest_time = greatest(least(cast(ReservationOccurrence.end_dt, TIME), Location.working_time_end), Location.working_time_start) booked_time = reservations.with_entities(func.sum(latest_time - earliest_time)).scalar() return (booked_time or timedelta()).total_seconds()
def _process(self): rooms = sorted(self._location.rooms, key=lambda r: natural_sort_key(r.full_name)) kpi = {} if self._with_kpi: kpi['occupancy'] = calculate_rooms_occupancy(self._location.rooms) kpi['total_rooms'] = len(self._location.rooms) kpi['active_rooms'] = sum(1 for room in self._location.rooms if room.is_active) kpi['reservable_rooms'] = sum(1 for room in self._location.rooms if room.is_reservable) kpi['reservable_capacity'] = sum(room.capacity or 0 for room in self._location.rooms if room.is_reservable) kpi['reservable_surface'] = sum(room.surface_area or 0 for room in self._location.rooms if room.is_reservable) kpi['booking_stats'] = compose_rooms_stats(self._location.rooms) kpi['booking_count'] = Reservation.find(Reservation.room.has(Room.location == self._location)).count() return WPRoomBookingAdminLocation(self, 'rb-rooms', location=self._location, rooms=rooms, action_succeeded=self._actionSucceeded, equipment_types=self._location.equipment_types.all(), attributes=self._location.attributes.all(), kpi=kpi).display()
def _process(self): rooms = sorted(self._location.rooms, key=lambda r: natural_sort_key(r.full_name)) kpi = {} if self._with_kpi: kpi['occupancy'] = calculate_rooms_occupancy(self._location.rooms.all()) kpi['total_rooms'] = self._location.rooms.count() kpi['active_rooms'] = self._location.rooms.filter_by(is_active=True).count() kpi['reservable_rooms'] = self._location.rooms.filter_by(is_reservable=True).count() kpi['reservable_capacity'] = (self._location.rooms.with_entities(func.sum(Room.capacity)) .filter_by(is_reservable=True).scalar()) kpi['reservable_surface'] = (self._location.rooms.with_entities(func.sum(Room.surface_area)) .filter_by(is_reservable=True).scalar()) kpi['booking_stats'] = compose_rooms_stats(self._location.rooms.all()) kpi['booking_count'] = Reservation.find(Reservation.room_id.in_(r.id for r in self._location.rooms)).count() return WPRoomBookingAdminLocation(self, location=self._location, rooms=rooms, action_succeeded=self._actionSucceeded, equipment_types=self._location.equipment_types.all(), attributes=self._location.attributes.all(), kpi=kpi).display()
def _checkParams(self): resv_id = request.view_args.get("resvID") self._reservation = Reservation.get(request.view_args["resvID"]) if not self._reservation: raise IndicoError("No booking with id: {}".format(resv_id))
def _process_args(self): self.booking = Reservation.get_one(request.view_args['booking_id'])
def test_find_overlapping_with_different_room(overlapping_reservation, create_room): reservation, occurrence = overlapping_reservation assert reservation in Reservation.find_overlapping_with(room=reservation.room, occurrences=[occurrence]).all() assert reservation not in Reservation.find_overlapping_with(room=create_room(), occurrences=[occurrence]).all()
def _process_args(self): self._reservation = Reservation.get_one(request.view_args['resvID'])
def _checkParams(self): resv_id = request.view_args['resvID'] self._reservation = Reservation.get(resv_id) if not self._reservation: raise NoReportError('No booking with id: {}'.format(resv_id))
def _process(self): reservations = Reservation.find_all(event_id=self.event_id) if not reservations: self._redirect(url_for('event_mgmt.rooms_choose_event', self.event)) return return WPRoomBookingEventBookingList(self, self.event, reservations=reservations).display()
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()