1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-11-28 09:24:24 +01:00

refactor slots model

This commit is contained in:
Sylvain 2022-07-12 17:46:01 +02:00
parent 5012912edd
commit 37b24a8d2f
39 changed files with 1300 additions and 213 deletions

View File

@ -4,7 +4,7 @@
class API::AvailabilitiesController < API::ApiController
before_action :authenticate_user!, except: [:public]
before_action :set_availability, only: %i[show update reservations lock]
before_action :set_operator_role, only: %i[machine spaces]
before_action :set_operator_role, only: %i[machine spaces trainings]
before_action :set_customer, only: %i[machine spaces trainings]
respond_to :json
@ -101,9 +101,9 @@ class API::AvailabilitiesController < API::ApiController
def reservations
authorize Availability
@reservation_slots = @availability.slots
.includes(slots_reservations: [reservations: [statistic_profile: [user: [:profile]]]])
.order('slots.start_at ASC')
@slots_reservations = @availability.slots_reservations
.includes(:slot, reservation: [statistic_profile: [user: [:profile]]])
.order('slots.start_at ASC')
end
def export_availabilities
@ -151,7 +151,7 @@ class API::AvailabilitiesController < API::ApiController
end
def set_operator_role
@current_user_role = current_user.role
@operator_role = current_user.role
end
def set_availability

View File

@ -1,35 +0,0 @@
# frozen_string_literal: true
# API Controller for resources of type Slot
# Slots are used to cut Availabilities into reservable slots. The duration of these slots is configured per
# availability by Availability.slot_duration, or otherwise globally by Setting.get('slot_duration')
class API::SlotsController < API::ApiController
before_action :authenticate_user!
before_action :set_slot, only: %i[update cancel]
respond_to :json
def update
authorize @slot
if @slot.update(slot_params)
SubscriptionExtensionAfterReservation.new(@slot.reservation).extend_subscription_if_eligible
render :show, status: :created, location: @slot
else
render json: @slot.errors, status: :unprocessable_entity
end
end
def cancel
authorize @slot
SlotService.new.cancel(@slot)
end
private
def set_slot
@slot = Slot.find(params[:id])
end
def slot_params
params.require(:slot).permit(:start_at, :end_at, :availability_id)
end
end

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
# API Controller for resources of type Slot
# Slots are used to cut Availabilities into reservable slots. The duration of these slots is configured per
# availability by Availability.slot_duration, or otherwise globally by Setting.get('slot_duration')
class API::SlotsReservationsController < API::ApiController
before_action :authenticate_user!
before_action :set_slots_reservation, only: %i[update cancel]
respond_to :json
def update
authorize @slot_reservation
if @slot_reservation.update(slot_params)
SubscriptionExtensionAfterReservation.new(@slot_reservation.reservation).extend_subscription_if_eligible
render :show, status: :created, location: @slot_reservation
else
render json: @slot_reservation.errors, status: :unprocessable_entity
end
end
def cancel
authorize @slot_reservation
SlotsReservationsService.cancel(@slot_reservation)
end
private
def set_slots_reservation
@slot_reservation = SlotsReservation.find(params[:id])
end
def slot_params
params.require(:slots_reservation).permit(:slot_id)
end
end

View File

@ -1,6 +1,6 @@
import React, { ReactNode, useEffect, useState } from 'react';
import { FabPanel } from '../../base/fab-panel';
import { Reservation, ReservationSlot } from '../../../models/reservation';
import { Reservation, SlotsReservation } from '../../../models/reservation';
import ReservationAPI from '../../../api/reservation';
import { useTranslation } from 'react-i18next';
import moment from 'moment';
@ -38,16 +38,16 @@ const ReservationsPanel: React.FC<SpaceReservationsProps> = ({ userId, onError,
*/
const reservationsByDate = (state: 'past' | 'futur'): Array<Reservation> => {
return reservations.filter(r => {
return !!r.slots_attributes.find(s => filterSlot(s, state));
return !!r.slots_reservations_attributes.find(s => filterSlot(s, state));
});
};
/**
* Check if the given slot if past of futur
* Check if the given slot reservation if past of futur
*/
const filterSlot = (slot: ReservationSlot, state: 'past' | 'futur'): boolean => {
return (state === 'past' && moment(slot.start_at).isBefore()) ||
(state === 'futur' && moment(slot.start_at).isAfter());
const filterSlot = (sr: SlotsReservation, state: 'past' | 'futur'): boolean => {
return (state === 'past' && moment(sr.slot_attributes.start_at).isBefore()) ||
(state === 'futur' && moment(sr.slot_attributes.start_at).isAfter());
};
/**
@ -95,12 +95,12 @@ const ReservationsPanel: React.FC<SpaceReservationsProps> = ({ userId, onError,
return (
<li key={reservation.id} className="reservation">
<a className={`reservation-title ${details[reservation.id] ? 'clicked' : ''}`} onClick={toggleDetails(reservation.id)}>
{reservation.reservable.name} - {FormatLib.date(reservation.slots_attributes[0].start_at)}
{reservation.reservable.name} - {FormatLib.date(reservation.slots_reservations_attributes[0].slot_attributes.start_at)}
</a>
{details[reservation.id] && <FabPopover title={t('app.logged.dashboard.reservations.reservations_panel.slots_details')}>
{reservation.slots_attributes.filter(s => filterSlot(s, state)).map(
slot => <span key={slot.id} className="slot-details">
{FormatLib.date(slot.start_at)}, {FormatLib.time(slot.start_at)} - {FormatLib.time(slot.end_at)}
{reservation.slots_reservations_attributes.filter(s => filterSlot(s, state)).map(
slotReservation => <span key={slotReservation.id} className="slot-details">
{FormatLib.date(slotReservation.slot_attributes.start_at)}, {FormatLib.time(slotReservation.slot_attributes.start_at)} - {FormatLib.time(slotReservation.slot_attributes.end_at)}
</span>
)}
</FabPopover>}
@ -109,7 +109,7 @@ const ReservationsPanel: React.FC<SpaceReservationsProps> = ({ userId, onError,
};
const futur = reservationsByDate('futur');
const past = _.orderBy(reservationsByDate('past'), r => r.slots_attributes[0].start_at, 'desc');
const past = _.orderBy(reservationsByDate('past'), r => r.slots_reservations_attributes[0].slot_attributes.start_at, 'desc');
return (
<FabPanel className="reservations-panel" header={header()}>

View File

@ -163,7 +163,7 @@ const ReserveButton: React.FC<ReserveButtonProps> = ({ currentUser, machineId, o
</button>
<PendingTrainingModal isOpen={pendingTraining}
toggleModal={togglePendingTrainingModal}
nextReservation={machine?.current_user_next_training_reservation?.slots_attributes[0]?.start_at} />
nextReservation={machine?.current_user_next_training_reservation?.slots_reservations_attributes[0]?.slot_attributes.start_at} />
<RequiredTrainingModal isOpen={trainingRequired}
toggleModal={toggleRequiredTrainingModal}
user={user}

View File

@ -18,8 +18,8 @@
* Controller used in the calendar management page
*/
Application.Controllers.controller('AdminCalendarController', ['$scope', '$state', '$uibModal', 'moment', 'AuthService', 'Availability', 'Slot', 'Setting', 'Export', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', 'plansPromise', 'groupsPromise', 'settingsPromise', '_t', 'uiCalendarConfig', 'CalendarConfig', 'Member', 'uiTourService',
function ($scope, $state, $uibModal, moment, AuthService, Availability, Slot, Setting, Export, growl, dialogs, bookingWindowStart, bookingWindowEnd, machinesPromise, plansPromise, groupsPromise, settingsPromise, _t, uiCalendarConfig, CalendarConfig, Member, uiTourService) {
Application.Controllers.controller('AdminCalendarController', ['$scope', '$state', '$uibModal', 'moment', 'AuthService', 'Availability', 'SlotsReservation', 'Setting', 'Export', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', 'plansPromise', 'groupsPromise', 'settingsPromise', '_t', 'uiCalendarConfig', 'CalendarConfig', 'Member', 'uiTourService',
function ($scope, $state, $uibModal, moment, AuthService, Availability, SlotsReservation, Setting, Export, growl, dialogs, bookingWindowStart, bookingWindowEnd, machinesPromise, plansPromise, groupsPromise, settingsPromise, _t, uiCalendarConfig, CalendarConfig, Member, uiTourService) {
/* PRIVATE STATIC CONSTANTS */
// The calendar is divided in slots of 30 minutes
@ -78,9 +78,9 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
/**
* Open a confirmation modal to cancel the booking of a user for the currently selected event.
* @param slot {Object} reservation slot of a user, inherited from $resource
* @param slot_reservation {Object} reserved slot, as returned by /api/availabilities/:id/reservations
*/
$scope.cancelBooking = function (slot) {
$scope.cancelBooking = function (slot_reservation) {
// open a confirmation dialog
dialogs.confirm(
{
@ -89,19 +89,19 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
return {
title: _t('app.admin.calendar.confirmation_required'),
msg: _t('app.admin.calendar.do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION'
, { GENDER: getGender($scope.currentUser), USER: slot.user.name, DATE: moment(slot.start_at).format('L'), TIME: moment(slot.start_at).format('LT'), RESERVATION: slot.reservable.name })
, { GENDER: getGender($scope.currentUser), USER: slot_reservation.user.name, DATE: moment(slot_reservation.start_at).format('L'), TIME: moment(slot_reservation.start_at).format('LT'), RESERVATION: slot_reservation.reservable.name })
};
}
}
},
function () {
// the admin has confirmed, cancel the subscription
Slot.cancel(
{ id: slot.slot_id },
// the admin has confirmed, cancel the reservation
SlotsReservation.cancel(
{ id: slot_reservation.id },
function (data, status) { // success
// update the canceled_at attribute
for (const resa of Array.from($scope.reservations)) {
if (resa.slot_id === data.id) {
if (resa.id === data.id) {
resa.canceled_at = data.canceled_at;
break;
}

View File

@ -548,7 +548,7 @@ Application.Controllers.controller('ShowEventReservationsController', ['$scope',
* @returns {boolean}
*/
$scope.isCancelled = function (reservation) {
return !!(reservation.slots_attributes[0].canceled_at);
return !!(reservation.slots_reservations_attributes[0].canceled_at);
};
}]);

View File

@ -129,8 +129,8 @@ Application.Controllers.controller('EventsController', ['$scope', '$state', 'Eve
}
]);
Application.Controllers.controller('ShowEventController', ['$scope', '$state', '$rootScope', 'Event', '$uibModal', 'Member', 'Reservation', 'Price', 'CustomAsset', 'Slot', 'eventPromise', 'growl', '_t', 'Wallet', 'AuthService', 'helpers', 'dialogs', 'priceCategoriesPromise', 'settingsPromise', 'LocalPayment',
function ($scope, $state,$rootScope, Event, $uibModal, Member, Reservation, Price, CustomAsset, Slot, eventPromise, growl, _t, Wallet, AuthService, helpers, dialogs, priceCategoriesPromise, settingsPromise, LocalPayment) {
Application.Controllers.controller('ShowEventController', ['$scope', '$state', '$rootScope', 'Event', '$uibModal', 'Member', 'Reservation', 'Price', 'CustomAsset', 'SlotsReservation', 'eventPromise', 'growl', '_t', 'Wallet', 'AuthService', 'helpers', 'dialogs', 'priceCategoriesPromise', 'settingsPromise', 'LocalPayment',
function ($scope, $state,$rootScope, Event, $uibModal, Member, Reservation, Price, CustomAsset, SlotsReservation, eventPromise, growl, _t, Wallet, AuthService, helpers, dialogs, priceCategoriesPromise, settingsPromise, LocalPayment) {
/* PUBLIC SCOPE */
// reservations for the currently shown event
@ -418,14 +418,14 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
}
}
}, function() { // cancel confirmed
Slot.cancel({
id: reservation.slots_attributes[0].id
SlotsReservation.cancel({
id: reservation.slots_reservations_attributes[0].id
}, function() { // successfully canceled
let index;
growl.success(_t('app.public.events_show.reservation_was_successfully_cancelled'));
index = $scope.reservations.indexOf(reservation);
$scope.event.nb_free_places = $scope.event.nb_free_places + reservation.total_booked_seats;
$scope.reservations[index].slots_attributes[0].canceled_at = new Date();
$scope.reservations[index].slots_reservations_attributes[0].canceled_at = new Date();
}, function(error) {
growl.warning(_t('app.public.events_show.cancellation_failed'));
});
@ -475,9 +475,9 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
return eventToPlace = e;
}
});
$scope.reservation.slots_attributes[0].start_at = eventToPlace.start_date;
$scope.reservation.slots_attributes[0].end_at = eventToPlace.end_date;
$scope.reservation.slots_attributes[0].availability_id = eventToPlace.availability_id;
$scope.reservation.slots_reservations_attributes[0].start_at = eventToPlace.start_date;
$scope.reservation.slots_reservations_attributes[0].end_at = eventToPlace.end_date;
$scope.reservation.slots_reservations_attributes[0].availability_id = eventToPlace.availability_id;
$scope.attempting = true;
Reservation.update({ id: reservation.id }, { reservation: $scope.reservation }, function (reservation) {
$uibModalInstance.close(reservation);

View File

@ -10,8 +10,8 @@
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs', 'growl', 'Auth', 'Price', 'Wallet', 'CustomAsset', 'Slot', 'AuthService', 'Payment', 'helpers', '_t',
function ($rootScope, $uibModal, dialogs, growl, Auth, Price, Wallet, CustomAsset, Slot, AuthService, Payment, helpers, _t) {
Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs', 'growl', 'Auth', 'Price', 'Wallet', 'CustomAsset', 'SlotsReservation', 'AuthService', 'Payment', 'helpers', '_t',
function ($rootScope, $uibModal, dialogs, growl, Auth, Price, Wallet, CustomAsset, SlotsReservation, AuthService, Payment, helpers, _t) {
return ({
restrict: 'E',
scope: {
@ -232,11 +232,9 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
* When modifying an already booked reservation, confirm the modification.
*/
$scope.modifySlot = function () {
Slot.update({ id: $scope.events.modifiable.slot_id }, {
SlotsReservation.update({ id: $scope.events.modifiable.slots_reservations_ids[0] }, {
slot: {
start_at: $scope.events.placable.start,
end_at: $scope.events.placable.end,
availability_id: $scope.events.placable.availability_id
slot_id: $scope.events.placable.slot_id
}
}
, function () { // success
@ -595,7 +593,7 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
}
},
function () { // cancel confirmed
Slot.cancel({ id: $scope.slot.slot_id }, function () { // successfully canceled
SlotsReservation.cancel({ id: $scope.slot.slots_reservations_ids[0] }, function () { // successfully canceled
growl.success(_t('app.shared.cart.reservation_was_cancelled_successfully'));
if (typeof $scope.onSlotCancelSuccess === 'function') { return $scope.onSlotCancelSuccess(); }
}

View File

@ -3,15 +3,18 @@ import { ApiFilter } from './api';
export type ReservableType = 'Training' | 'Event' | 'Space' | 'Machine';
export interface ReservationSlot {
export interface SlotsReservation {
id?: number,
start_at: TDateISO,
end_at: TDateISO,
canceled_at?: TDateISO,
availability_id?: number,
offered?: boolean,
is_reserved?: boolean
slot_attributes?: {
id?: number,
start_at?: TDateISO,
end_at?: TDateISO,
availability_id?: number
}
}
// TODO, refactor Reservation for cart_items (in payment) => should use slot_id instead of (start_at + end_at)
export interface Reservation {
id?: number,
@ -20,7 +23,7 @@ export interface Reservation {
message?: string,
reservable_id: number,
reservable_type: ReservableType,
slots_attributes: Array<ReservationSlot>,
slots_reservations_attributes: Array<SlotsReservation>,
reservable?: {
id: number,
name: string

View File

@ -1,15 +0,0 @@
'use strict';
Application.Services.factory('Slot', ['$resource', function ($resource) {
return $resource('/api/slots/:id',
{ id: '@id' }, {
update: {
method: 'PUT'
},
cancel: {
method: 'PUT',
url: '/api/slots/:id/cancel'
}
}
);
}]);

View File

@ -0,0 +1,15 @@
'use strict';
Application.Services.factory('SlotsReservation', ['$resource', function ($resource) {
return $resource('/api/slots_reservations/:id',
{ id: '@id' }, {
update: {
method: 'PUT'
},
cancel: {
method: 'PUT',
url: '/api/slots_reservations/:id/cancel'
}
}
);
}]);

View File

@ -9,8 +9,8 @@
<div ng-class="{'col-md-6': schedule, 'm-h-sm': !schedule}">
<div ng-if="reservation">
<p translate>{{ 'app.shared.valid_reservation_modal.here_is_the_summary_of_the_slots_to_book_for_the_current_user' }}</p>
<ul ng-repeat="slot in reservation.slots_attributes">
<li><strong>{{slot.start_at | amDateFormat: 'LL'}} : {{slot.start_at | amDateFormat:'LT'}} - {{slot.end_at | amDateFormat:'LT'}}</strong></li>
<ul ng-repeat="sr in reservation.slots_reservations_attributes">
<li><strong>{{sr.slot_attributes.start_at | amDateFormat: 'LL'}} : {{sr.slot_attributes.start_at | amDateFormat:'LT'}} - {{sr.slot_attributes.end_at | amDateFormat:'LT'}}</strong></li>
</ul>
</div>
<div ng-if="subscription">

View File

@ -8,7 +8,7 @@ module AvailabilityHelper
EVENT_COLOR = '#dd7e6b'
IS_RESERVED_BY_CURRENT_USER = '#b2e774'
MACHINE_IS_RESERVED_BY_USER = '#1d98ec'
IS_COMPLETED = '#eeeeee'
IS_FULL = '#eeeeee'
def availability_border_color(availability)
case availability.available_type
@ -25,7 +25,7 @@ module AvailabilityHelper
def machines_slot_border_color(slot)
if slot.is_reserved
slot.current_user_slots_reservations_ids.empty? ? IS_COMPLETED : IS_RESERVED_BY_CURRENT_USER
slot.current_user_slots_reservations_ids.empty? ? IS_FULL : IS_RESERVED_BY_CURRENT_USER
else
MACHINE_COLOR
end
@ -34,8 +34,8 @@ module AvailabilityHelper
def space_slot_border_color(slot)
if slot.is_reserved
IS_RESERVED_BY_CURRENT_USER
elsif slot.complete?
IS_COMPLETED
elsif slot.full?
IS_FULL
else
SPACE_COLOR
end
@ -45,7 +45,7 @@ module AvailabilityHelper
if availability.is_reserved
IS_RESERVED_BY_CURRENT_USER
elsif availability.full?
IS_COMPLETED
IS_FULL
else
case availability.available_type
when 'training'

View File

@ -20,6 +20,7 @@ class Availability < ApplicationRecord
has_many :spaces, through: :spaces_availabilities
has_many :slots
has_many :slots_reservations, through: :slots
has_many :reservations, through: :slots
has_one :event
@ -116,23 +117,25 @@ class Availability < ApplicationRecord
def full?
return false if nb_total_places.blank?
if available_type == 'training' || available_type == 'space'
nb_total_places <= slots.to_a.select { |s| s.canceled_at.nil? }.size
elsif available_type == 'event'
if available_type == 'event'
event.nb_free_places.zero?
else
slots.map(&:full?).reduce(:&)
end
end
def nb_total_places
def available_places_per_slot
case available_type
when 'training'
super.presence || trainings.map(&:nb_total_places).reduce(:+)
nb_total_places || trainings.map(&:nb_total_places).max
when 'event'
event.nb_total_places
when 'space'
super.presence || spaces.map(&:default_places).reduce(:+)
nb_total_places || spaces.map(&:default_places).max
when 'machines'
machines.count
else
nil
raise TypeError
end
end

View File

@ -50,7 +50,12 @@ class CartItem::Reservation < CartItem::BaseItem
end
if availability.available_type == 'machines'
s = Slot.includes(:reservations).where(start_at: slot[:start_at], end_at: slot[:end_at], availability_id: slot[:availability_id], canceled_at: nil, "reservations.reservable": @reservable)
s = SlotsReservation.includes(:slot, :reservation)
.where('slots.start_at': slot[:start_at],
'slots.end_at': slot[:end_at],
'slots.availability_id': slot[:availability_id],
canceled_at: nil,
'reservations.reservable': @reservable)
unless s.empty?
@errors[:slot] = 'slot is reserved'
return false

View File

@ -98,26 +98,22 @@ class Reservation < ApplicationRecord
private
def machine_not_already_reserved
already_reserved = false
slots.each do |slot|
same_hour_slots = Slot.joins(:reservations).where(
slots_reservations.each do |slot|
same_hour_slots = SlotsReservation.joins(:reservation).where(
reservations: { reservable_type: reservable_type, reservable_id: reservable_id },
start_at: slot.start_at,
end_at: slot.end_at,
availability_id: slot.availability_id,
slot_id: slot_id,
canceled_at: nil
)
if same_hour_slots.any?
already_reserved = true
).count
if same_hour_slots.positive?
errors.add(:reservable, 'already reserved')
break
end
end
errors.add(:machine, 'already reserved') if already_reserved
end
def training_not_fully_reserved
slot = slots.first
errors.add(:training, 'already fully reserved') if Availability.find(slot.availability_id).full?
full = slots_reservations.map(&:slot).map(&:full?).reduce(:&)
errors.add(:reservable, 'already fully reserved') if full
end
def slots_not_locked

View File

@ -12,7 +12,7 @@ class Slot < ApplicationRecord
attr_accessor :is_reserved, :machine, :space, :title, :can_modify, :current_user_slots_reservations_ids
def complete?
reservations.length >= availability.nb_total_places
def full?
slots_reservations.where(canceled_at: nil).count >= availability.available_places_per_slot
end
end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
# Check the access policies for API::SlotsController
class SlotPolicy < ApplicationPolicy
# Check the access policies for API::SlotsReservationsController
class SlotsReservationPolicy < ApplicationPolicy
def update?
# check that the update is allowed and the prevention delay has not expired
delay = Setting.get('booking_move_delay').to_i
@ -9,7 +9,7 @@ class SlotPolicy < ApplicationPolicy
# these condition does not apply to admins
user.admin? || user.manager? ||
(record.reservation.user == user && enabled && ((record.start_at - DateTime.current).to_i / 3600 >= delay))
(record.reservation.user == user && enabled && ((record.slot.start_at - DateTime.current).to_i / 3600 >= delay))
end
def cancel?

View File

@ -16,23 +16,23 @@ class Availabilities::AvailabilitiesService
def machines(machine, user, window)
availabilities = availabilities(machine.availabilities, 'machines', user, window[:start], window[:end])
availabilities.map(&:slots).flatten.map { |s| @service.slot_reserved_status(s, user, machine) }
availabilities.map(&:slots).flatten.map { |s| @service.slot_reserved_status(s, user, [machine]) }
end
# list all slots for the given space, with visibility relative to the given user
def spaces(space, user, window)
availabilities = availabilities(space.availabilities, 'space', user, window[:start], window[:end])
availabilities.map(&:slots).flatten.map { |s| @service.slot_reserved_status(s, user, space) }
availabilities.map(&:slots).flatten.map { |s| @service.slot_reserved_status(s, user, [space]) }
end
# list all slots for the given training, with visibility relative to the given user
# list all slots for the given training(s), with visibility relative to the given user
def trainings(trainings, user, window)
tr_availabilities = Availability.includes('trainings_availabilities')
.where('trainings_availabilities.training_id': trainings.map(&:id))
availabilities = availabilities(tr_availabilities, 'training', user, window[:start], window[:end])
availabilities.map(&:slots).flatten.map { |s| @service.slot_reserved_status(s, user, trainings) }
availabilities.map(&:slots).flatten.map { |s| @service.slot_reserved_status(s, user, s.availability.trainings) }
end
private

View File

@ -1,21 +0,0 @@
# frozen_string_literal: true
# helpers for managing slots (reservations sub-units)
class SlotService
def cancel(slot)
# first we mark ths slot as cancelled in DB, to free a ticket
slot.update_attributes(canceled_at: DateTime.current)
# then we try to remove this reservation from ElasticSearch, to keep the statistics up-to-date
model_name = slot.reservation.reservable.class.name
client = Elasticsearch::Model.client
model = "Stats::#{model_name}".constantize
client.delete_by_query(
index: model.index_name,
type: model.document_type,
conflicts: 'proceed',
body: { query: { match: { reservationId: slot.reservation.id } } }
)
end
end

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
# helpers for managing slots reservations (reservations for a time unit)
class SlotsReservationsService
class << self
def cancel(slot_reservation)
# first we mark ths slot reseravtion as cancelled in DB, to free a ticket
slot_reservation.update_attributes(canceled_at: DateTime.current)
# then we try to remove this reservation from ElasticSearch, to keep the statistics up-to-date
model_name = slot_reservation.reservation.reservable.class.name
client = Elasticsearch::Model.client
model = "Stats::#{model_name}".constantize
client.delete_by_query(
index: model.index_name,
type: model.document_type,
conflicts: 'proceed',
body: { query: { match: { reservationId: slot_reservation.reservation_id } } }
)
end
end
end

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
# Extend the user's current subscription after his first training reservation if
# he subscribed to a rolling plan
class SubscriptionExtensionAfterReservation
attr_accessor :user, :reservation

View File

@ -17,3 +17,11 @@ json.tags slot.availability.tags do |t|
json.name t.name
end
json.plan_ids slot.availability.plan_ids
# the users who booked on this slot, if any
if (%w[admin manager].include? operator_role) && !slot.slots_reservations.empty?
json.users slot.slots_reservations do |sr|
json.id sr.reservation.user&.id
json.name sr.reservation.user&.profile&.full_name
end
end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
json.array!(@slots) do |slot|
json.partial! 'api/availabilities/slot', slot: slot
json.partial! 'api/availabilities/slot', slot: slot, operator_role: @operator_role
json.borderColor machines_slot_border_color(slot)
json.machine do

View File

@ -35,7 +35,7 @@ json.array!(@availabilities) do |availability|
json.borderColor availability_border_color(availability)
if complete
json.title "#{availability.title} - #{t('trainings.completed')}"
json.borderColor AvailabilityHelper::IS_COMPLETED
json.borderColor AvailabilityHelper::IS_FULL
end
if availability.is_reserved
json.is_reserved true
@ -61,7 +61,7 @@ json.array!(@availabilities) do |availability|
elsif availability.try(:space)
json.space_id availability.space.id
json.borderColor space_slot_border_color(availability)
json.is_completed availability.complete?
json.is_completed availability.full?
else
json.title 'Unknown slot'
end

View File

@ -1,16 +1,17 @@
# frozen_string_literal: true
json.array!(@reservation_slots) do |slot|
json.slot_id slot.id
json.start_at slot.start_at.iso8601
json.end_at slot.end_at.iso8601
json.message slot.reservation.message
json.reservable slot.reservation.reservable
json.reservable_id slot.reservation.reservable_id
json.reservable_type slot.reservation.reservable_type
json.array!(@slots_reservations) do |sr|
json.id sr.id
json.slot_id sr.slot_id
json.start_at sr.slot.start_at.iso8601
json.end_at sr.slot.end_at.iso8601
json.message sr.reservation.message
json.reservable sr.reservation.reservable
json.reservable_id sr.reservation.reservable_id
json.reservable_type sr.reservation.reservable_type
json.user do
json.id slot.reservation.statistic_profile&.user_id
json.name slot.reservation.statistic_profile&.user&.profile&.full_name
json.id sr.reservation.statistic_profile&.user_id
json.name sr.reservation.statistic_profile&.user&.profile&.full_name
end
json.canceled_at slot.canceled_at
json.canceled_at sr.canceled_at
end

View File

@ -1,32 +1,12 @@
# frozen_string_literal: true
json.array!(@slots) do |slot|
json.slot_id slot.id if slot.id
json.can_modify slot.can_modify
json.title slot.title
json.start slot.start_at.iso8601
json.end slot.end_at.iso8601
json.is_reserved slot.is_reserved
json.is_completed slot.complete?
json.backgroundColor 'white'
json.partial! 'api/availabilities/slot', slot: slot, operator_role: @operator_role
json.is_completed slot.full?
json.borderColor space_slot_border_color(slot)
json.availability_id slot.availability_id
json.space do
json.id slot.space.id
json.name slot.space.name
json.id @space.id
json.name @space.name
end
# the user who booked the slot, if the slot was reserved
if (%w[admin manager].include? @current_user_role) && slot.reservation
json.user do
json.id slot.reservation.user&.id
json.name slot.reservation.user&.profile&.full_name
end
end
json.tag_ids slot.availability.tag_ids
json.tags slot.availability.tags do |t|
json.id t.id
json.name t.name
end
json.plan_ids slot.availability.plan_ids
end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
json.array!(@slots) do |slot|
json.partial! 'api/availabilities/slot', slot: slot
json.partial! 'api/availabilities/slot', slot: slot, operator_role: @operator_role
json.borderColor trainings_events_border_color(slot)
json.is_completed slot.full?

View File

@ -4,12 +4,15 @@ json.id reservation.id
json.user_id reservation.statistic_profile.user_id
json.user_full_name reservation.user&.profile&.full_name
json.message reservation.message
json.slots_attributes reservation.slots do |s|
json.id s.id
json.start_at s.start_at.iso8601
json.end_at s.end_at.iso8601
json.canceled_at s.canceled_at&.iso8601
json.is_reserved true
json.slots_reservations_attributes reservation.slots_reservations do |sr|
json.id sr.id
json.canceled_at sr.canceled_at&.iso8601
json.slot_attributes do
json.id sr.slot_id
json.start_at sr.slot.start_at.iso8601
json.end_at sr.slot.end_at.iso8601
json.availability_id sr.slot.availability_id
end
end
json.nb_reserve_places reservation.nb_reserve_places
json.tickets_attributes reservation.tickets do |t|

View File

@ -1,2 +0,0 @@
json.id @slot.id
json.canceled_at @slot.canceled_at

View File

@ -1 +0,0 @@
json.id @slot.id

View File

@ -0,0 +1,3 @@
# frozen_string_literal: true
json.extract! @slot_reservation, :id, :canceled_at

View File

@ -0,0 +1,3 @@
# frozen_string_literal: true
json.extract! @slot_reservation, :id, :slot_id, :reservation_id

View File

@ -112,7 +112,7 @@ Rails.application.routes.draw do
resources :plans do
get 'durations', on: :collection
end
resources :slots, only: [:update] do
resources :slots_reservations, only: [:update] do
put 'cancel', on: :member
end

View File

@ -4,7 +4,7 @@
# Now we save all slots in DB, so we must re-create slots for the existing availabilities
class InsertMissingSlots < ActiveRecord::Migration[5.2]
def up
Availability.all.each do |availability|
Availability.where(available_type: %w[machines space]).each do |availability|
slot_duration = availability.slot_duration || Setting.get('slot_duration').to_i
((availability.end_at - availability.start_at) / slot_duration.minutes).to_i.times do |i|
@ -15,6 +15,14 @@ class InsertMissingSlots < ActiveRecord::Migration[5.2]
)
end
end
Availability.where(available_type: %w[training event]).each do |availability|
Slot.find_or_create_by(
start_at: availability.start_at,
end_at: availability.end_at,
availability_id: availability.id
)
end
end
def down

View File

@ -50,6 +50,8 @@ The following guides should help those who want to contribute to the code.
- [Plugins](plugins.md)
- [How the data model works for Availabilities and Reservations](availabilities-reservations-models.md)
#### How to setup a development environment
- [With docker-compose](development_readme.md)

View File

@ -0,0 +1,89 @@
# Availabilities-reservations data models
## Machines
ONE Availability may have:
- MANY MachinesAvailability (=> MANY Machines)
- MANY Slot: the Availability is cut in smaller slots
ONE Slot may have:
- ONE Availability: a Slot is a slice of ONE Availability
- MANY SlotsReservation: ONE SlotsReservation per (User + Machine + Slot)
- Bob reserved a 3D printer from 8am to 9am in Availability 1 (=> ONE SlotsReservation)
- John reserved a Laser cutter from 8am to 9am in Availability 1 (=> ONE SlotsReservation)
ONE SlotsReservation have:
- ONE Slot
- ONE Reservation
ONE Reservation may have:
- MANY SlotsReservation (one per reserved slot, for the associated Machine)
- ONE User
- ONE Machine
- NO Ticket
## Spaces
ONE Availability may have:
- ONE SpacesAvailability (=> ONE Space)
- MANY Slot: the Availability is cut in smaller slots
ONE Slot may have:
- ONE Availability: a Slot is a slice of ONE Availability
- MANY SlotsReservation: ONE SlotsReservation per (User + Slot)
- Bob reserved from 8am to 9am (=> ONE SlotsReservation)
- John reserved from 8am to 9am (=> ONE SlotsReservation)
ONE SlotsReservation have:
- ONE Slot
- ONE Reservation
ONE Reservation may have:
- MANY SlotsReservation (one per reserved slot, for the associated Space)
- ONE User
- ONE Space
- NO Ticket
## Trainings
ONE Availability may have:
- ONE TrainingsAvailability (=> ONE Training)
- ONE Slot: the Availability isn't cut into smaller slots
ONE Slot may have:
- ONE Availability: a Slot as long as the Availability
- MANY SlotsReservation: ONE SlotsReservation per User
- Bob reserved (=> ONE SlotsReservation)
- John reserved (=> ONE SlotsReservation)
ONE SlotsReservation have:
- ONE Slot
- ONE Reservation
ONE Reservation have:
- ONE SlotsReservation
- ONE User
- ONE Training
- NO Tickets
## Events
ONE Availability may have:
- ONE Event (from Event.availability_id)
- ONE Slot: the Availability isn't cut into smaller slots
ONE Slot may have:
- ONE Availability: a Slot as long as the Availability
- MANY SlotsReservation: ONE SlotsReservation per User
- Bob reserved (=> ONE SlotsReservation)
- John reserved (=> ONE SlotsReservation)
ONE SlotsReservation have:
- ONE Slot
- ONE Reservation
ONE Reservation may have:
- ONE SlotsReservation
- ONE User
- ONE Training
- MANY Tickets (once per extra booked special price)

1000
test/fixtures/slots.yml vendored
View File

@ -1,16 +1,1000 @@
slot_1:
id: 1
start_at: 2012-04-11 06:00:00.000000000 Z
end_at: 2012-04-11 10:00:00.000000000 Z
created_at: 2012-03-12 13:40:22.342717000 Z
updated_at: 2012-03-12 13:40:22.342717000 Z
start_at: '2012-04-11 06:00:00.000000'
end_at: '2012-04-11 10:00:00.000000'
created_at: '2012-03-12 13:40:22.342717'
updated_at: '2012-03-12 13:40:22.342717'
availability_id: 12
slot_2:
id: 2
start_at: 2015-06-15 12:00:28.000000000 Z
end_at: 2015-06-15 13:00:28.000000000 Z
created_at: 2015-06-10 11:20:01.341130000 Z
updated_at: 2015-06-10 11:20:01.341130000 Z
start_at: '2015-06-15 12:00:28.000000'
end_at: '2015-06-15 13:00:28.000000'
created_at: '2015-06-10 11:20:01.341130'
updated_at: '2015-06-10 11:20:01.341130'
availability_id: 13
slot_9:
id: 9
start_at: <%= DateTime.current.utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= DateTime.current.utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.880751'
updated_at: '2022-07-12 15:18:43.880751'
availability_id: 3
slot_10:
id: 10
start_at: <%= DateTime.current.utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= DateTime.current.utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.882957'
updated_at: '2022-07-12 15:18:43.882957'
availability_id: 3
slot_11:
id: 11
start_at: <%= DateTime.current.utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= DateTime.current.utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.884691'
updated_at: '2022-07-12 15:18:43.884691'
availability_id: 3
slot_12:
id: 12
start_at: <%= DateTime.current.utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= DateTime.current.utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.886431'
updated_at: '2022-07-12 15:18:43.886431'
availability_id: 3
slot_13:
id: 13
start_at: <%= DateTime.current.utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= DateTime.current.utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.888074'
updated_at: '2022-07-12 15:18:43.888074'
availability_id: 3
slot_14:
id: 14
start_at: <%= DateTime.current.utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= DateTime.current.utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.889691'
updated_at: '2022-07-12 15:18:43.889691'
availability_id: 3
slot_15:
id: 15
start_at: <%= (DateTime.current + 1.day).utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 1.day).utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.893096'
updated_at: '2022-07-12 15:18:43.893096'
availability_id: 4
slot_16:
id: 16
start_at: <%= (DateTime.current + 1.day).utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 1.day).utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.894777'
updated_at: '2022-07-12 15:18:43.894777'
availability_id: 4
slot_17:
id: 17
start_at: <%= (DateTime.current + 1.day).utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 1.day).utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.896423'
updated_at: '2022-07-12 15:18:43.896423'
availability_id: 4
slot_18:
id: 18
start_at: <%= (DateTime.current + 1.day).utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 1.day).utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.898021'
updated_at: '2022-07-12 15:18:43.898021'
availability_id: 4
slot_19:
id: 19
start_at: <%= (DateTime.current + 1.day).utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 1.day).utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.899592'
updated_at: '2022-07-12 15:18:43.899592'
availability_id: 4
slot_20:
id: 20
start_at: <%= (DateTime.current + 1.day).utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 1.day).utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.900938'
updated_at: '2022-07-12 15:18:43.900938'
availability_id: 4
slot_21:
id: 21
start_at: <%= (DateTime.current + 2.day).utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 2.day).utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.904013'
updated_at: '2022-07-12 15:18:43.904013'
availability_id: 5
slot_22:
id: 22
start_at: <%= (DateTime.current + 2.day).utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 2.day).utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.905470'
updated_at: '2022-07-12 15:18:43.905470'
availability_id: 5
slot_23:
id: 23
start_at: <%= (DateTime.current + 2.day).utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 2.day).utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.907030'
updated_at: '2022-07-12 15:18:43.907030'
availability_id: 5
slot_24:
id: 24
start_at: <%= (DateTime.current + 2.day).utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 2.day).utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.908585'
updated_at: '2022-07-12 15:18:43.908585'
availability_id: 5
slot_25:
id: 25
start_at: <%= (DateTime.current + 2.day).utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 2.day).utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.910138'
updated_at: '2022-07-12 15:18:43.910138'
availability_id: 5
slot_26:
id: 26
start_at: <%= (DateTime.current + 2.day).utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 2.day).utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.911643'
updated_at: '2022-07-12 15:18:43.911643'
availability_id: 5
slot_27:
id: 27
start_at: <%= (DateTime.current + 3.day).utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 3.day).utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.914664'
updated_at: '2022-07-12 15:18:43.914664'
availability_id: 6
slot_28:
id: 28
start_at: <%= (DateTime.current + 3.day).utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 3.day).utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.916047'
updated_at: '2022-07-12 15:18:43.916047'
availability_id: 6
slot_29:
id: 29
start_at: <%= (DateTime.current + 3.day).utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 3.day).utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.917304'
updated_at: '2022-07-12 15:18:43.917304'
availability_id: 6
slot_30:
id: 30
start_at: <%= (DateTime.current + 3.day).utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 3.day).utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.918798'
updated_at: '2022-07-12 15:18:43.918798'
availability_id: 6
slot_31:
id: 31
start_at: <%= (DateTime.current + 3.day).utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 3.day).utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.920194'
updated_at: '2022-07-12 15:18:43.920194'
availability_id: 6
slot_32:
id: 32
start_at: <%= (DateTime.current + 3.day).utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 3.day).utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.921662'
updated_at: '2022-07-12 15:18:43.921662'
availability_id: 6
slot_33:
id: 33
start_at: <%= (DateTime.current + 3.day).utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 3.day).utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.924285'
updated_at: '2022-07-12 15:18:43.924285'
availability_id: 7
slot_34:
id: 34
start_at: <%= (DateTime.current + 3.day).utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 3.day).utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.925669'
updated_at: '2022-07-12 15:18:43.925669'
availability_id: 7
slot_35:
id: 35
start_at: <%= (DateTime.current + 3.day).utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 3.day).utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.927038'
updated_at: '2022-07-12 15:18:43.927038'
availability_id: 7
slot_36:
id: 36
start_at: <%= (DateTime.current + 3.day).utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 3.day).utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.928407'
updated_at: '2022-07-12 15:18:43.928407'
availability_id: 7
slot_37:
id: 37
start_at: <%= (DateTime.current + 3.day).utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 3.day).utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.929907'
updated_at: '2022-07-12 15:18:43.929907'
availability_id: 7
slot_38:
id: 38
start_at: <%= (DateTime.current + 3.day).utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 3.day).utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.931295'
updated_at: '2022-07-12 15:18:43.931295'
availability_id: 7
slot_39:
id: 39
start_at: '2015-06-15 13:00:28.000000'
end_at: '2015-06-15 14:00:28.000000'
created_at: '2022-07-12 15:18:43.934465'
updated_at: '2022-07-12 15:18:43.934465'
availability_id: 13
slot_40:
id: 40
start_at: '2015-06-15 14:00:28.000000'
end_at: '2015-06-15 15:00:28.000000'
created_at: '2022-07-12 15:18:43.935716'
updated_at: '2022-07-12 15:18:43.935716'
availability_id: 13
slot_41:
id: 41
start_at: '2015-06-15 15:00:28.000000'
end_at: '2015-06-15 16:00:28.000000'
created_at: '2022-07-12 15:18:43.937025'
updated_at: '2022-07-12 15:18:43.937025'
availability_id: 13
slot_42:
id: 42
start_at: '2015-06-15 16:00:28.000000'
end_at: '2015-06-15 17:00:28.000000'
created_at: '2022-07-12 15:18:43.938379'
updated_at: '2022-07-12 15:18:43.938379'
availability_id: 13
slot_43:
id: 43
start_at: '2015-06-15 17:00:28.000000'
end_at: '2015-06-15 18:00:28.000000'
created_at: '2022-07-12 15:18:43.939737'
updated_at: '2022-07-12 15:18:43.939737'
availability_id: 13
slot_44:
id: 44
start_at: <%= 20.days.from_now.utc.change({hour: 6}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 20.days.from_now.utc.change({hour: 7}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.942392'
updated_at: '2022-07-12 15:18:43.942392'
availability_id: 14
slot_45:
id: 45
start_at: <%= 20.days.from_now.utc.change({hour: 7}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 20.days.from_now.utc.change({hour: 8}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.943779'
updated_at: '2022-07-12 15:18:43.943779'
availability_id: 14
slot_46:
id: 46
start_at: <%= 20.days.from_now.utc.change({hour: 8}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 20.days.from_now.utc.change({hour: 9}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.945154'
updated_at: '2022-07-12 15:18:43.945154'
availability_id: 14
slot_47:
id: 47
start_at: <%= 20.days.from_now.utc.change({hour: 9}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 20.days.from_now.utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.946515'
updated_at: '2022-07-12 15:18:43.946515'
availability_id: 14
slot_48:
id: 48
start_at: <%= 40.days.from_now.utc.change({hour: 6}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 40.days.from_now.utc.change({hour: 7}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.949178'
updated_at: '2022-07-12 15:18:43.949178'
availability_id: 15
slot_49:
id: 49
start_at: <%= 40.days.from_now.utc.change({hour: 7}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 40.days.from_now.utc.change({hour: 8}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.950348'
updated_at: '2022-07-12 15:18:43.950348'
availability_id: 15
slot_50:
id: 50
start_at: <%= 40.days.from_now.utc.change({hour: 8}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 40.days.from_now.utc.change({hour: 9}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.951535'
updated_at: '2022-07-12 15:18:43.951535'
availability_id: 15
slot_51:
id: 51
start_at: <%= 40.days.from_now.utc.change({hour: 9}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 40.days.from_now.utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.952864'
updated_at: '2022-07-12 15:18:43.952864'
availability_id: 15
slot_52:
id: 52
start_at: <%= 80.days.from_now.utc.change({hour: 6}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 80.days.from_now.utc.change({hour: 7}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.955443'
updated_at: '2022-07-12 15:18:43.955443'
availability_id: 16
slot_53:
id: 53
start_at: <%= 80.days.from_now.utc.change({hour: 7}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 80.days.from_now.utc.change({hour: 8}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.956657'
updated_at: '2022-07-12 15:18:43.956657'
availability_id: 16
slot_54:
id: 54
start_at: <%= 80.days.from_now.utc.change({hour: 8}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 80.days.from_now.utc.change({hour: 9}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.957811'
updated_at: '2022-07-12 15:18:43.957811'
availability_id: 16
slot_55:
id: 55
start_at: <%= 80.days.from_now.utc.change({hour: 9}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 80.days.from_now.utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.959063'
updated_at: '2022-07-12 15:18:43.959063'
availability_id: 16
slot_56:
id: 56
start_at: <%= 10.days.from_now.utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 10.days.from_now.utc.change({hour: 11}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.961319'
updated_at: '2022-07-12 15:18:43.961319'
availability_id: 17
slot_57:
id: 57
start_at: <%= 10.days.from_now.utc.change({hour: 11}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 10.days.from_now.utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.962492'
updated_at: '2022-07-12 15:18:43.962492'
availability_id: 17
slot_58:
id: 58
start_at: <%= 10.days.from_now.utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 10.days.from_now.utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.963665'
updated_at: '2022-07-12 15:18:43.963665'
availability_id: 17
slot_59:
id: 59
start_at: <%= 10.days.from_now.utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 10.days.from_now.utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.964853'
updated_at: '2022-07-12 15:18:43.964853'
availability_id: 17
slot_60:
id: 60
start_at: <%= 10.days.from_now.utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 10.days.from_now.utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.966003'
updated_at: '2022-07-12 15:18:43.966003'
availability_id: 17
slot_61:
id: 61
start_at: <%= 10.days.from_now.utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 10.days.from_now.utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.967147'
updated_at: '2022-07-12 15:18:43.967147'
availability_id: 17
slot_62:
id: 62
start_at:<%= 10.days.from_now.utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 10.days.from_now.utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.968371'
updated_at: '2022-07-12 15:18:43.968371'
availability_id: 17
slot_63:
id: 63
start_at: <%= 10.days.from_now.utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 10.days.from_now.utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.969636'
updated_at: '2022-07-12 15:18:43.969636'
availability_id: 17
slot_64:
id: 64
start_at: <%= 10.days.from_now.utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 10.days.from_now.utc.change({hour: 19}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.970899'
updated_at: '2022-07-12 15:18:43.970899'
availability_id: 17
slot_65:
id: 65
start_at: <%= 10.days.from_now.utc.change({hour: 19}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 10.days.from_now.utc.change({hour: 20}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.972292'
updated_at: '2022-07-12 15:18:43.972292'
availability_id: 17
slot_66:
id: 66
start_at: <%= 10.days.from_now.utc.change({hour: 20}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 10.days.from_now.utc.change({hour: 21}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.973726'
updated_at: '2022-07-12 15:18:43.973726'
availability_id: 17
slot_67:
id: 67
start_at: <%= 10.days.from_now.utc.change({hour: 21}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 10.days.from_now.utc.change({hour: 22}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.975239'
updated_at: '2022-07-12 15:18:43.975239'
availability_id: 17
slot_68:
id: 68
start_at: <%= 10.days.from_now.utc.change({hour: 22}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 10.days.from_now.utc.change({hour: 23}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.976671'
updated_at: '2022-07-12 15:18:43.976671'
availability_id: 17
slot_69:
id: 69
start_at: <%= 10.days.from_now.utc.change({hour: 23}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 0}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.978191'
updated_at: '2022-07-12 15:18:43.978191'
availability_id: 17
slot_70:
id: 70
start_at: <%= 11.days.from_now.utc.change({hour: 0}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 1}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.979560'
updated_at: '2022-07-12 15:18:43.979560'
availability_id: 17
slot_71:
id: 71
start_at: <%= 11.days.from_now.utc.change({hour: 1}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 2}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.980799'
updated_at: '2022-07-12 15:18:43.980799'
availability_id: 17
slot_72:
id: 72
start_at: <%= 11.days.from_now.utc.change({hour: 2}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 3}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.982141'
updated_at: '2022-07-12 15:18:43.982141'
availability_id: 17
slot_73:
id: 73
start_at: <%= 11.days.from_now.utc.change({hour: 3}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 4}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.983501'
updated_at: '2022-07-12 15:18:43.983501'
availability_id: 17
slot_74:
id: 74
start_at: <%= 11.days.from_now.utc.change({hour: 4}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 5}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.984866'
updated_at: '2022-07-12 15:18:43.984866'
availability_id: 17
slot_75:
id: 75
start_at: <%= 11.days.from_now.utc.change({hour: 5}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 6}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.986257'
updated_at: '2022-07-12 15:18:43.986257'
availability_id: 17
slot_76:
id: 76
start_at: <%= 11.days.from_now.utc.change({hour: 6}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 7}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.987589'
updated_at: '2022-07-12 15:18:43.987589'
availability_id: 17
slot_77:
id: 77
start_at: <%= 11.days.from_now.utc.change({hour: 7}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 8}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.988913'
updated_at: '2022-07-12 15:18:43.988913'
availability_id: 17
slot_78:
id: 78
start_at: <%= 11.days.from_now.utc.change({hour: 8}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 9}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.990191'
updated_at: '2022-07-12 15:18:43.990191'
availability_id: 17
slot_79:
id: 79
start_at: <%= 11.days.from_now.utc.change({hour: 9}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.991488'
updated_at: '2022-07-12 15:18:43.991488'
availability_id: 17
slot_80:
id: 80
start_at: <%= 11.days.from_now.utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 11}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.992988'
updated_at: '2022-07-12 15:18:43.992988'
availability_id: 17
slot_81:
id: 81
start_at: <%= 11.days.from_now.utc.change({hour: 11}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.994311'
updated_at: '2022-07-12 15:18:43.994311'
availability_id: 17
slot_82:
id: 82
start_at: <%= 11.days.from_now.utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.995812'
updated_at: '2022-07-12 15:18:43.995812'
availability_id: 17
slot_83:
id: 83
start_at: <%= 11.days.from_now.utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.997242'
updated_at: '2022-07-12 15:18:43.997242'
availability_id: 17
slot_84:
id: 84
start_at: <%= 11.days.from_now.utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:43.998733'
updated_at: '2022-07-12 15:18:43.998733'
availability_id: 17
slot_85:
id: 85
start_at: <%= 11.days.from_now.utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.000009'
updated_at: '2022-07-12 15:18:44.000009'
availability_id: 17
slot_86:
id: 86
start_at: <%= 11.days.from_now.utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.001315'
updated_at: '2022-07-12 15:18:44.001315'
availability_id: 17
slot_87:
id: 87
start_at: <%= 11.days.from_now.utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.002961'
updated_at: '2022-07-12 15:18:44.002961'
availability_id: 17
slot_88:
id: 88
start_at: <%= 11.days.from_now.utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 19}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.004321'
updated_at: '2022-07-12 15:18:44.004321'
availability_id: 17
slot_89:
id: 89
start_at: <%= 11.days.from_now.utc.change({hour: 19}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 20}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.005828'
updated_at: '2022-07-12 15:18:44.005828'
availability_id: 17
slot_90:
id: 90
start_at: <%= 11.days.from_now.utc.change({hour: 20}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 21}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.007295'
updated_at: '2022-07-12 15:18:44.007295'
availability_id: 17
slot_91:
id: 91
start_at: <%= 11.days.from_now.utc.change({hour: 21}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 22}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.008631'
updated_at: '2022-07-12 15:18:44.008631'
availability_id: 17
slot_92:
id: 92
start_at: <%= 11.days.from_now.utc.change({hour: 22}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 11.days.from_now.utc.change({hour: 23}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.010249'
updated_at: '2022-07-12 15:18:44.010249'
availability_id: 17
slot_93:
id: 93
start_at: <%= 11.days.from_now.utc.change({hour: 23}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 0}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.011771'
updated_at: '2022-07-12 15:18:44.011771'
availability_id: 17
slot_94:
id: 94
start_at: <%= 12.days.from_now.utc.change({hour: 0}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 1}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.013133'
updated_at: '2022-07-12 15:18:44.013133'
availability_id: 17
slot_95:
id: 95
start_at: <%= 12.days.from_now.utc.change({hour: 1}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 2}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.014419'
updated_at: '2022-07-12 15:18:44.014419'
availability_id: 17
slot_96:
id: 96
start_at: <%= 12.days.from_now.utc.change({hour: 2}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 3}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.015693'
updated_at: '2022-07-12 15:18:44.015693'
availability_id: 17
slot_97:
id: 97
start_at: <%= 12.days.from_now.utc.change({hour: 3}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 4}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.016849'
updated_at: '2022-07-12 15:18:44.016849'
availability_id: 17
slot_98:
id: 98
start_at: <%= 12.days.from_now.utc.change({hour: 4}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 5}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.018072'
updated_at: '2022-07-12 15:18:44.018072'
availability_id: 17
slot_99:
id: 99
start_at: <%= 12.days.from_now.utc.change({hour: 5}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 6}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.019364'
updated_at: '2022-07-12 15:18:44.019364'
availability_id: 17
slot_100:
id: 100
start_at: <%= 12.days.from_now.utc.change({hour: 6}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 7}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.020527'
updated_at: '2022-07-12 15:18:44.020527'
availability_id: 17
slot_101:
id: 101
start_at: <%= 12.days.from_now.utc.change({hour: 7}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 8}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>'
created_at: '2022-07-12 15:18:44.021871'
updated_at: '2022-07-12 15:18:44.021871'
availability_id: 17
slot_102:
id: 102
start_at: <%= 12.days.from_now.utc.change({hour: 8}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 9}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.023185'
updated_at: '2022-07-12 15:18:44.023185'
availability_id: 17
slot_103:
id: 103
start_at: <%= 12.days.from_now.utc.change({hour: 9}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.024405'
updated_at: '2022-07-12 15:18:44.024405'
availability_id: 17
slot_104:
id: 104
start_at: <%= 12.days.from_now.utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 11}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.025867'
updated_at: '2022-07-12 15:18:44.025867'
availability_id: 17
slot_105:
id: 105
start_at: <%= 12.days.from_now.utc.change({hour: 11}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.027282'
updated_at: '2022-07-12 15:18:44.027282'
availability_id: 17
slot_106:
id: 106
start_at: <%= 12.days.from_now.utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.028605'
updated_at: '2022-07-12 15:18:44.028605'
availability_id: 17
slot_107:
id: 107
start_at: <%= 12.days.from_now.utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.029949'
updated_at: '2022-07-12 15:18:44.029949'
availability_id: 17
slot_108:
id: 108
start_at: <%= 12.days.from_now.utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.031298'
updated_at: '2022-07-12 15:18:44.031298'
availability_id: 17
slot_109:
id: 109
start_at: <%= 12.days.from_now.utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.032602'
updated_at: '2022-07-12 15:18:44.032602'
availability_id: 17
slot_110:
id: 110
start_at: <%= 12.days.from_now.utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.034111'
updated_at: '2022-07-12 15:18:44.034111'
availability_id: 17
slot_111:
id: 111
start_at: <%= 12.days.from_now.utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 12.days.from_now.utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.035567'
updated_at: '2022-07-12 15:18:44.035567'
availability_id: 17
slot_112:
id: 112
start_at: <%= 2.days.from_now.utc.change({hour: 8}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 2.days.from_now.utc.change({hour: 9}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.038089'
updated_at: '2022-07-12 15:18:44.038089'
availability_id: 18
slot_113:
id: 113
start_at: <%= 2.days.from_now.utc.change({hour: 9}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 2.days.from_now.utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.039392'
updated_at: '2022-07-12 15:18:44.039392'
availability_id: 18
slot_114:
id: 114
start_at: <%= 2.days.from_now.utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 2.days.from_now.utc.change({hour: 11}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.040522'
updated_at: '2022-07-12 15:18:44.040522'
availability_id: 18
slot_115:
id: 115
start_at: <%= 2.days.from_now.utc.change({hour: 11}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 2.days.from_now.utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.041937'
updated_at: '2022-07-12 15:18:44.041937'
availability_id: 18
slot_116:
id: 116
start_at: <%= 1.day.from_now.utc.change({hour: 8}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 1.day.from_now.utc.change({hour: 9}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.044421'
updated_at: '2022-07-12 15:18:44.044421'
availability_id: 19
slot_117:
id: 117
start_at: <%= 1.day.from_now.utc.change({hour: 9}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 1.day.from_now.utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.045689'
updated_at: '2022-07-12 15:18:44.045689'
availability_id: 19
slot_118:
id: 118
start_at: <%= 1.day.from_now.utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 1.day.from_now.utc.change({hour: 11}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.047009'
updated_at: '2022-07-12 15:18:44.047009'
availability_id: 19
slot_119:
id: 119
start_at: <%= 1.day.from_now.utc.change({hour: 11}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 1.day.from_now.utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.048272'
updated_at: '2022-07-12 15:18:44.048272'
availability_id: 19
slot_120:
id: 120
start_at: <%= 1.day.from_now.utc.change({hour: 12}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 1.day.from_now.utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.049599'
updated_at: '2022-07-12 15:18:44.049599'
availability_id: 19
slot_121:
id: 121
start_at: <%= 1.day.from_now.utc.change({hour: 13}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 1.day.from_now.utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.050947'
updated_at: '2022-07-12 15:18:44.050947'
availability_id: 19
slot_122:
id: 122
start_at: <%= 1.day.from_now.utc.change({hour: 14}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 1.day.from_now.utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.052817'
updated_at: '2022-07-12 15:18:44.052817'
availability_id: 19
slot_123:
id: 123
start_at: <%= 1.day.from_now.utc.change({hour: 15}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 1.day.from_now.utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.054966'
updated_at: '2022-07-12 15:18:44.054966'
availability_id: 19
slot_124:
id: 124
start_at: <%= 1.day.from_now.utc.change({hour: 16}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 1.day.from_now.utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.057217'
updated_at: '2022-07-12 15:18:44.057217'
availability_id: 19
slot_125:
id: 125
start_at: <%= 1.day.from_now.utc.change({hour: 17}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= 1.day.from_now.utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.059135'
updated_at: '2022-07-12 15:18:44.059135'
availability_id: 19
slot_126:
id: 126
start_at: <%= DateTime.current.utc.change({hour: 6}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= DateTime.current.utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.061887'
updated_at: '2022-07-12 15:18:44.061887'
availability_id: 1
slot_127:
id: 127
start_at: <%= (DateTime.current + 1.day).utc.change({hour: 6}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 1.day).utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.063528'
updated_at: '2022-07-12 15:18:44.063528'
availability_id: 2
slot_128:
id: 128
start_at: <%= (DateTime.current + 2.day).utc.change({hour: 6}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (DateTime.current + 2.day).utc.change({hour: 10}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-07-12 15:18:44.065114'
updated_at: '2022-07-12 15:18:44.065114'
availability_id: 8
slot_129:
id: 129
start_at: '2016-04-18 16:00:28.000000'
end_at: '2016-04-18 20:00:28.000000'
created_at: '2022-07-12 15:18:44.066837'
updated_at: '2022-07-12 15:18:44.066837'
availability_id: 9
slot_130:
id: 130
start_at: '2016-05-18 16:00:28.000000'
end_at: '2016-05-18 20:00:28.000000'
created_at: '2022-07-12 15:18:44.068259'
updated_at: '2022-07-12 15:18:44.068259'
availability_id: 10
slot_131:
id: 131
start_at: '2016-06-18 16:00:28.000000'
end_at: '2016-06-18 20:00:28.000000'
created_at: '2022-07-12 15:18:44.069870'
updated_at: '2022-07-12 15:18:44.069870'
availability_id: 11