-
Tyler Lemburg authoredTyler Lemburg authored
resources.rb 23.03 KiB
require 'models/resource'
require 'models/reservation'
require 'models/event'
require 'models/event_type'
require 'models/event_signup'
require 'models/space_hour'
get '/:service_space_url_name/resources/?' do
require_login
load_service_space
@breadcrumbs << {:text => 'Resources'}
# show resources that the user is authorized to use, as well as all those that do not require authorization
resources = Resource.where(:service_space_id => @space.id).all.to_a
resources.reject! {|resource| resource.needs_authorization && !@user.authorized_resource_ids.include?(resource.id)}
erb :resources, :layout => :fixed, :locals => {
:available_resources => resources
}
end
# page to see the calendar for a resource
get '/:service_space_url_name/resources/:resource_id/calendar/?' do
check_login
load_service_space
# check that the user has authorization to reserve this resource, if resource requires auth
resource = Resource.find_by(:service_space_id => @space.id, :id => params[:resource_id])
if resource.nil?
flash(:alert, 'Not Found', 'That resource does not exist.')
redirect @space.resources_href
end
@breadcrumbs << {:text => 'Resources', :href => @space.resources_href} << {:text => "#{resource.name} Calendar"}
date = params[:date].nil? ? Time.now.midnight.in_time_zone : Time.parse(params[:date]).midnight.in_time_zone
sunday = date.in_time_zone.week_start
# get the reservations for this week
reservations = Reservation.includes(:event).where(:resource_id => resource.id).in_week(date).all
# get the space's hours for the week
hours = SpaceHour.where(:service_space_id => @space.id)
.where('effective_date < ?', (sunday+1.week+1.hour).midnight.utc.strftime('%Y-%m-%d %H:%M:%S'))
.order(:day_of_week, :effective_date => :desc, :id => :desc).all.to_a
hours_days = hours.group_by do |space_hour|
space_hour.day_of_week
end
week_hours = {}
hours_days.each do |number_of_days, array|
this_day = (sunday + number_of_days.days + 1.hour).midnight
# find the correct hour record to use for this day
array.each do |space_hour|
if space_hour.effective_date.in_time_zone.midnight == this_day.in_time_zone.midnight || (!space_hour.one_off && space_hour.effective_date.in_time_zone.midnight <= this_day.in_time_zone.midnight)
week_hours[number_of_days] = space_hour
break
end
end
end
erb :resource_calendar, :layout => :fixed, :locals => {
:date => date,
:sunday => sunday,
:reservations => reservations,
:resource => resource,
:week_hours => week_hours,
:kiosk_mode => params[:kiosk_mode]
}
end
# form for reserving a resource
get '/:service_space_url_name/resources/:resource_id/reserve/?' do
require_login
load_service_space
@breadcrumbs << {:text => 'Resources', :href => @space.resources_href} << {:text => 'Reserve'}
# check that the user has authorization to reserve this resource, if resource requires auth
resource = Resource.find_by(:service_space_id => @space.id, :id => params[:resource_id])
if resource.nil?
flash(:alert, 'Not Found', 'That resource does not exist.')
redirect @space.resources_href
end
date = params[:date].nil? ? Time.now.midnight.in_time_zone : Time.parse(params[:date]).midnight.in_time_zone
# get the studio's hours for this day
# is there a one_off
space_hour = SpaceHour.where(:service_space_id => @space.id)
.where('effective_date = ?', date.utc.strftime('%Y-%m-%d %H:%M:%S'))
.where(:day_of_week => date.wday).where(:one_off => true).first
if space_hour.nil?
space_hour = SpaceHour.where(:service_space_id => @space.id)
.where('effective_date <= ?', date.utc.strftime('%Y-%m-%d %H:%M:%S'))
.where(:day_of_week => date.wday).where(:one_off => false)
.order(:effective_date => :desc, :id => :desc).first
end
available_start_times = []
# calculate the available start times for reservation
if space_hour.nil?
start = 0
while start + (resource.minutes_per_reservation || resource.min_minutes_per_reservation || 15) <= 1440
available_start_times << start
start += (resource.minutes_per_reservation || resource.min_minutes_per_reservation || 15)
end
else
space_hour.hours.sort{|x,y| x[:start] <=> y[:start]}.each do |record|
if record[:status] == 'open'
start = record[:start]
while start + (resource.minutes_per_reservation || resource.min_minutes_per_reservation || 15) <= record[:end]
available_start_times << start
start += (resource.minutes_per_reservation || resource.min_minutes_per_reservation || 15)
end
end
end
end
# filter out times when resource is reserved
reservations = Reservation.includes(:event).where(:resource_id => resource.id).in_day(date).all
available_start_times = available_start_times - reservations.map{|res|res.start_time.in_time_zone.minutes_after_midnight}
erb :reserve, :layout => :fixed, :locals => {
:resource => resource,
:reservations => reservations,
:available_start_times => available_start_times,
:space_hour => space_hour,
:day => date,
:reservation => nil,
:kiosk_mode => params[:kiosk_mode]
}
end
# submit form for reserving a resource
post '/:service_space_url_name/resources/:resource_id/reserve/?' do
require_login
load_service_space
resource = Resource.find_by(:service_space_id => @space.id, :id => params[:resource_id])
if resource.nil?
flash(:alert, 'Not Found', 'That resource does not exist.')
redirect @space.resources_href
end
if params[:start_minutes].nil?
flash(:alert, 'Please specify a start time', 'Please specify a start time for your reservation.')
redirect back
end
hour = (params[:start_minutes].to_i / 60).floor
am_pm = hour >= 12 ? 'pm' : 'am'
hour = hour % 12
hour += 12 if hour == 0
minutes = params[:start_minutes].to_i % 60
start_time = calculate_time(params[:date], hour, minutes, am_pm)
end_time = start_time + params[:length].to_i.minutes
date = start_time.midnight
# validate that the requested time slot falls within the open hours of the day
# get the studio's hours for this day
# is there a one_off
space_hour = SpaceHour.where(:service_space_id => @space.id)
.where('effective_date = ?', date.utc.strftime('%Y-%m-%d %H:%M:%S'))
.where(:day_of_week => date.wday).where(:one_off => true).first
if space_hour.nil?
space_hour = SpaceHour.where(:service_space_id => @space.id)
.where('effective_date <= ?', date.utc.strftime('%Y-%m-%d %H:%M:%S'))
.where(:day_of_week => date.wday).where(:one_off => false)
.order(:effective_date => :desc, :id => :desc).first
end
unless space_hour.nil?
# figure out where the closed sections need to be
# we can assume that all records in this space_hour are non-intertwined
closed_start = 0
closed_end = 0
starts = space_hour.hours.map{|record| record[:start]}
ends = space_hour.hours.map{|record| record[:end]}
closeds = []
(0..1439).each do |j|
if starts.include?(j)
closed_end = j
closeds << {:status => 'closed', :start => closed_start, :end => closed_end}
closed_start = 0
closed_end = 0
end
if ends.include?(j)
closed_start = j
end
end
closed_end = 1440
closeds << {:status => 'closed', :start => closed_start, :end => closed_end}
# for each record, ensure that the time does not overlap if the record is not "open"
(space_hour.hours + closeds).each do |record|
if record[:status] != 'open'
start_time_minutes = 60 * start_time.hour + start_time.min
end_time_minutes = 60 * end_time.hour + end_time.min
if (record[:start]+1..record[:end]-1).include?(start_time_minutes) || (record[:start]+1..record[:end]-1).include?(end_time_minutes) ||
(start_time_minutes < record[:start] && end_time_minutes > record[:end])
# there is an overlap, this time is invalid
flash :alert, 'Invalid Time Slot', 'Sorry, that time slot is invalid for reservations.'
redirect back
end
end
end
end
# if no record studio is open
# check for possible other reservations during this time period
other_reservations = Reservation.where(:resource_id => params[:resource_id]).in_day(date).all
other_reservations.each do |reservation|
if (start_time >= reservation.start_time && start_time < reservation.end_time) ||
(end_time > reservation.start_time && end_time <= reservation.end_time) ||
(start_time < reservation.start_time && end_time > reservation.end_time)
flash :alert, "Resource is being used.", "Sorry, that resource is reserved during that time period. Please try another time slot."
redirect back
end
end
if params.checked?('recurring')
begin
recurs_until_date = Time.strptime(params[:recurs_until_date], '%m/%d/%Y').midnight.in_time_zone
rescue
recurs_until_date = nil
end
if recurs_until_date.nil? || recurs_until_date - 365.days > Time.now || recurs_until_date < start_time.midnight.in_time_zone
flash :error, 'Invalid Recurs Until Date', 'Your recurs-until date must be less than a year in the future.'
redirect back
end
# use the recurring type to increment the date here
starts = []
if params[:recurring_type] == 'weekly' || params[:recurring_type] == 'biweeekly'
inc = 7.days
inc = 14.days if params[:recurring_type] == 'biweeekly'
new_start = start_time.dup
while (new_start = new_start + inc) <= recurs_until_date + 1.day
# reset in case we moved past DST change
new_start = (new_start + 1.hour).midnight.in_time_zone
starts << new_start + start_time.minutes_after_midnight.minutes
end
elsif %w(first second third fourth).include?(params[:recurring_type])
day_of_week = start_time.wday
new_start = start_time.dup
while (new_start <= recurs_until_date + 1.day)
# calculate when this is next month
year = new_start.year
month = new_start.month + 1
month = 1 and year += 1 if month == 13
start_day = Time.new(year, month, 1).midnight.in_time_zone
while (start_day.wday != day_of_week)
start_day = start_day + 1.day
end
# now add weeks
weeks = {
"first" => 1,
"second" => 2,
"third" => 3,
"fourth" => 4
}
start_day += (weeks[params[:recurring_type]] - 1).weeks
# reset this to midnight in case of DST change
start_day = (start_day + 1.hour).midnight.in_time_zone
# and set the start time
start_day += start_time.hour.hours + start_time.min.minutes
if start_day <= recurs_until_date + 1.day
starts << start_day
end
new_start = start_day
end
elsif params[:recurring_type] == 'last'
day_of_week = start_time.wday
new_start = start_time.dup
while (new_start <= recurs_until_date + 1.day)
# calculate when this is next month
year = new_start.year
month = new_start.month + 1
month = 1 and year += 1 if month == 13
start_day = Time.new(year, month, 1).midnight.in_time_zone
while (start_day.wday != day_of_week)
start_day = start_day + 1.day
end
# now add weeks (go until end of month)
while (start_day + 1.week).month == month
start_day += 1.week
end
# reset this to midnight in case of DST change
start_day = (start_day + 1.hour).midnight.in_time_zone
# and set the start time
start_day += start_time.hour.hours + start_time.min.minutes
if start_day <= recurs_until_date + 1.day
starts << start_day
end
new_start = start_day
end
elsif params[:recurring_type] == 'day'
new_start = start_time.dup
while (new_start <= recurs_until_date + 1.day)
# calculate when this is next month
year = new_start.year
month = new_start.month + 1
month = 1 and year += 1 if month == 13
start_day = Time.new(year, month, [start_time.day, Time.days_in_month(month, year)].min).midnight.in_time_zone
# and set the start time
start_day += start_time.hour.hours + start_time.min.minutes
if start_day <= recurs_until_date + 1.day
starts << start_day
end
new_start = start_day
end
else
flash :error, 'Invalid Recurring Type', 'Please select a recurrence frequency below.'
redirect back
end
messages = []
successful = 0
starts.each do |new_start|
invalid = false
new_end = new_start + params[:length].to_i.minutes
date = new_start.midnight
# validate that the requested time slot falls within the open hours of the day
# get the studio's hours for this day
# is there a one_off
space_hour = SpaceHour.where(:service_space_id => @space.id)
.where('effective_date = ?', date.utc.strftime('%Y-%m-%d %H:%M:%S'))
.where(:day_of_week => date.wday).where(:one_off => true).first
if space_hour.nil?
space_hour = SpaceHour.where(:service_space_id => @space.id)
.where('effective_date <= ?', date.utc.strftime('%Y-%m-%d %H:%M:%S'))
.where(:day_of_week => date.wday).where(:one_off => false)
.order(:effective_date => :desc, :id => :desc).first
end
unless space_hour.nil?
# figure out where the closed sections need to be
# we can assume that all records in this space_hour are non-intertwined
closed_start = 0
closed_end = 0
starts = space_hour.hours.map{|record| record[:start]}
ends = space_hour.hours.map{|record| record[:end]}
closeds = []
(0..1439).each do |j|
if starts.include?(j)
closed_end = j
closeds << {:status => 'closed', :start => closed_start, :end => closed_end}
closed_start = 0
closed_end = 0
end
if ends.include?(j)
closed_start = j
end
end
closed_end = 1440
closeds << {:status => 'closed', :start => closed_start, :end => closed_end}
# for each record, ensure that the time does not overlap if the record is not "open"
(space_hour.hours + closeds).each do |record|
if record[:status] != 'open'
start_time_minutes = 60 * new_start.hour + new_start.min
end_time_minutes = 60 * new_end.hour + new_end.min
if (record[:start]+1..record[:end]-1).include?(start_time_minutes) || (record[:start]+1..record[:end]-1).include?(end_time_minutes) ||
(start_time_minutes < record[:start] && end_time_minutes > record[:end])
# there is an overlap, this time is invalid
messages << "Sorry, the time slot at #{new_start.strftime('%A, %B %d at %l:%M %P')} is invalid for reservations."
invalid = true
end
end
end
end
# if no record studio is open
next if invalid
# check for possible other reservations during this time period
other_reservations = Reservation.where(:resource_id => params[:resource_id]).in_day(date).all
other_reservations.each do |reservation|
if (new_start >= reservation.start_time && new_start < reservation.end_time) ||
(new_end > reservation.start_time && new_end <= reservation.end_time) ||
(new_start < reservation.start_time && new_end > reservation.end_time)
messages << "Sorry, that resource is reserved for the #{new_start.strftime('%A, %B %d at %l:%M %P')} attempt."
invalid = true
end
end
next if invalid
Reservation.create(
:resource_id => resource.id,
:event_id => nil,
:start_time => new_start,
:end_time => new_end,
:is_training => false,
:user_id => @user.id,
:title => params[:title]
)
successful += 1
end
if successful > 0
flash :success, 'Recurring Reservations Created', "You have created #{successful+1} total reservations."
end
unless messages.empty?
flash :alert, 'Some recurring reservations were not created', "<ul><li>#{messages.join('</li><li>')}</li></ul>"
end
end
Reservation.create(
:resource_id => resource.id,
:event_id => nil,
:start_time => start_time,
:end_time => end_time,
:is_training => false,
:user_id => @user.id,
:title => params[:title]
)
flash(:success, 'Reservation Created', "You have successfully reserved #{resource.name} for #{params[:length]} minutes at #{start_time.in_time_zone.strftime('%A, %B %d at %l:%M %P')}")
redirect "/#{@space.url_name}/resources/#{resource.id}/calendar/#{params[:kiosk_mode] ? '?kiosk_mode=true' : ''}"
end
get '/:service_space_url_name/resources/:resource_id/edit_reservation/:reservation_id/?' do
require_login
load_service_space
@breadcrumbs << {:text => 'Resources', :href => @space.resources_href} << {:text => 'Edit Reservation'}
resource = Resource.find_by(:service_space_id => @space.id, :id => params[:resource_id])
if resource.nil?
flash(:alert, 'Not Found', 'That resource does not exist.')
redirect @space.resources_href
end
# check that this reservation exists
reservation = Reservation.find(params[:reservation_id])
if reservation.nil?
flash(:alert, 'Not Found', 'That reservation does not exist.')
redirect back
end
date = params[:date].nil? ? reservation.start_time.in_time_zone.midnight : Time.parse(params[:date]).midnight.in_time_zone
# get the studio's hours for this day
# is there a one_off
space_hour = SpaceHour.where(:service_space_id => @space.id)
.where('effective_date = ?', date.utc.strftime('%Y-%m-%d %H:%M:%S'))
.where(:day_of_week => date.wday).where(:one_off => true).first
if space_hour.nil?
space_hour = SpaceHour.where(:service_space_id => @space.id)
.where('effective_date <= ?', date.utc.strftime('%Y-%m-%d %H:%M:%S'))
.where(:day_of_week => date.wday).where(:one_off => false)
.order(:effective_date => :desc, :id => :desc).first
end
available_start_times = []
# calculate the available start times for reservation
if space_hour.nil?
start = 0
while start + (resource.minutes_per_reservation || 15) <= 1440
available_start_times << start
start += (resource.minutes_per_reservation || 15)
end
else
space_hour.hours.sort{|x,y| x[:start] <=> y[:start]}.each do |record|
if record[:status] == 'open'
start = record[:start]
while start + (resource.minutes_per_reservation || 15) <= record[:end]
available_start_times << start
start += (resource.minutes_per_reservation || 15)
end
end
end
end
# filter out times when resource is reserved
reservations = Reservation.includes(:event).where(:resource_id => resource.id).in_day(date).all
available_start_times = (available_start_times - reservations.map{|res|res.start_time.in_time_zone.minutes_after_midnight})
if date == reservation.start_time.in_time_zone.midnight
available_start_times = available_start_times + [reservation.start_time.in_time_zone.minutes_after_midnight]
end
available_start_times.sort!
erb :reserve, :layout => :fixed, :locals => {
:resource => resource,
:reservations => reservations,
:available_start_times => available_start_times,
:space_hour => space_hour,
:day => date,
:reservation => reservation
}
end
post '/:service_space_url_name/resources/:resource_id/edit_reservation/:reservation_id/?' do
require_login
load_service_space
resource = Resource.find_by(:service_space_id => @space.id, :id => params[:resource_id])
if resource.nil?
flash(:alert, 'Not Found', 'That resource does not exist.')
redirect @space.resources_href
end
# check that this reservation exists
reservation = Reservation.find(params[:reservation_id])
if reservation.nil?
flash(:alert, 'Not Found', 'That reservation does not exist.')
redirect back
end
hour = (params[:start_minutes].to_i / 60).floor
am_pm = hour >= 12 ? 'pm' : 'am'
hour = hour % 12
hour += 12 if hour == 0
minutes = params[:start_minutes].to_i % 60
start_time = calculate_time(params[:date], hour, minutes, am_pm)
end_time = start_time + params[:length].to_i.minutes
date = start_time.midnight
# validate that the requested time slot falls within the open hours of the day
# get the studio's hours for this day
# is there a one_off
space_hour = SpaceHour.where(:service_space_id => @space.id)
.where('effective_date = ?', date.utc.strftime('%Y-%m-%d %H:%M:%S'))
.where(:day_of_week => date.wday).where(:one_off => true).first
if space_hour.nil?
space_hour = SpaceHour.where(:service_space_id => @space.id)
.where('effective_date <= ?', date.utc.strftime('%Y-%m-%d %H:%M:%S'))
.where(:day_of_week => date.wday).where(:one_off => false)
.order(:effective_date => :desc, :id => :desc).first
end
unless space_hour.nil?
# figure out where the closed sections need to be
# we can assume that all records in this space_hour are non-intertwined
closed_start = 0
closed_end = 0
starts = space_hour.hours.map{|record| record[:start]}
ends = space_hour.hours.map{|record| record[:end]}
closeds = []
(0..1439).each do |j|
if starts.include?(j)
closed_end = j
closeds << {:status => 'closed', :start => closed_start, :end => closed_end}
closed_start = 0
closed_end = 0
end
if ends.include?(j)
closed_start = j
end
end
closed_end = 1440
closeds << {:status => 'closed', :start => closed_start, :end => closed_end}
# for each record, ensure that the time does not overlap if the record is not "open"
(space_hour.hours + closeds).each do |record|
if record[:status] != 'open'
start_time_minutes = 60 * start_time.hour + start_time.min
end_time_minutes = 60 * end_time.hour + end_time.min
if (record[:start]+1..record[:end]-1).include?(start_time_minutes) || (record[:start]+1..record[:end]-1).include?(end_time_minutes) ||
(start_time_minutes < record[:start] && end_time_minutes > record[:end])
# there is an overlap, this time is invalid
flash :alert, 'Invalid Time Slot', 'Sorry, that time slot is invalid for reservations.'
redirect back
end
end
end
end
# if no record studio is open
# check for possible other reservations during this time period
other_reservations = Reservation.where(:resource_id => params[:resource_id]).where.not(:id => reservation.id).in_day(date).all
other_reservations.each do |reservation|
if (start_time >= reservation.start_time && start_time < reservation.end_time) ||
(end_time >= reservation.start_time && end_time < reservation.end_time) ||
(start_time < reservation.start_time && end_time > reservation.end_time)
flash :alert, "Resource is being used.", "Sorry, that resource is reserved during that time period. Please try another time slot."
redirect back
end
end
reservation.update(
:start_time => start_time,
:end_time => end_time,
:title => params[:title]
)
flash(:success, 'Reservation Updated', "You have successfully updated your reservation for #{resource.name}: it is now for #{params[:length]} minutes at #{start_time.in_time_zone.strftime('%A, %B %d at %l:%M %P')}")
redirect "/#{@space.url_name}/resources/#{resource.id}/calendar/"
end
post '/:service_space_url_name/resources/:resource_id/cancel/:reservation_id/?' do
require_login
load_service_space
# check that the user requesting cancel is the same as the one on the reservation
reservation = Reservation.find(params[:reservation_id])
if reservation.nil?
flash :alert, 'Not Found', 'That reservation was not found.'
redirect back
end
if reservation.user_id != @user.id
flash :alert, 'Unauthorized', 'That is not your reservation.'
redirect back
end
reservation.delete
flash :success, 'Reservation Cancelled', 'Your reservation has been removed.'
redirect back
end