Skip to content
Snippets Groups Projects
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