jam-cloud/ruby/lib/jam_ruby/models/lesson_booking_slot.rb

273 lines
7.8 KiB
Ruby

# represenst the type of lesson package
module JamRuby
class LessonBookingSlot < ActiveRecord::Base
include HtmlSanitize
html_sanitize strict: [:message, :accept_message, :cancel_message]
@@log = Logging.logger[LessonBookingSlot]
belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking"
belongs_to :lesson_session, class_name: "JamRuby::LessonSession"
belongs_to :proposer, class_name: "JamRuby::User"
has_one :defaulted_booking, class_name: "JamRuby::LessonBooking", foreign_key: :default_slot_id, inverse_of: :default_slot
has_one :countered_booking, class_name: "JamRuby::LessonBooking", foreign_key: :counter_slot_id, inverse_of: :counter_slot
has_one :countered_lesson, class_name: "JamRuby::LessonSession", foreign_key: :counter_slot_id, inverse_of: :counter_slot
SLOT_TYPE_SINGLE = 'single'
SLOT_TYPE_RECURRING = 'recurring'
SLOT_TYPES = [SLOT_TYPE_SINGLE, SLOT_TYPE_RECURRING]
validates :proposer, presence: true
validates :slot_type, inclusion: {in: SLOT_TYPES}
#validates :preferred_day
validates :day_of_week, numericality: {only_integer: true}, allow_blank: true # 0 = sunday - 6 = saturday
validates :hour, numericality: {only_integer: true}
validates :minute, numericality: {only_integer: true}
validates :timezone, presence: true # example: 'America/New_York'
validates :update_all, inclusion: {in: [true, false]}
validate :validate_slot_type
validate :validate_slot_minimum_time, on: :create
validate :validate_proposer
before_validation :before_validation
def is_recurring?
slot_type == SLOT_TYPE_RECURRING
end
def before_validation
if proposer.nil?
self.proposer = container.student
end
end
def container
if lesson_booking
lesson_booking
else
lesson_session
end
end
def is_teacher_created?
self.proposer == container.teacher
end
def is_student_created?
!is_teacher_created?
end
def is_teacher_approved?
!is_teacher_created?
end
def recipient
if is_teacher_created?
container.student
else
container.teacher
end
end
def create_minimum_booking_time
(Time.now + APP_CONFIG.minimum_lesson_booking_hrs * 60 * 60)
end
def scheduled_times(needed_sessions, minimum_start_time)
#puts "NEEDED SESSIONS #{needed_sessions} #{minimum_start_time}"
times = []
week_offset = 0
needed_sessions.times do |i|
candidate = scheduled_time(i + week_offset)
#puts "#{i}: candidate #{candidate} week_offset:#{week_offset}"
#puts "DAY_OF_WEEK #{day_of_week}"
if day_of_week && candidate <= minimum_start_time
# move it up a week
week_offset += 1
candidate = scheduled_time(i + week_offset)
#puts "retry #1 #{candidate}"
# sanity check
if candidate <= minimum_start_time
week_offset += 1
candidate = scheduled_time(i + week_offset)
#puts "retry #2 #{candidate}"
if candidate <= minimum_start_time
week_offset += 1
candidate = scheduled_time(i + week_offset)
#puts "retry #3 #{candidate}"
if candidate <= minimum_start_time
week_offset += 1
candidate = scheduled_time(i + week_offset)
#puts "retry #4 #{candidate}"
if candidate <= minimum_start_time
raise "candidate time less than minimum start time even after scoot: #{lesson_booking.id} #{self.id}"
end
end
end
end
end
times << candidate
end
times
end
def next_day
date = Date.today
date += ((day_of_week - date.wday) % 7).abs
end
# weeks is the number of weeks in the future to compute the time for
def scheduled_time(weeks)
# get the timezone of the slot, so we can compute times
tz = TZInfo::Timezone.get(timezone)
if preferred_day
time = tz.local_to_utc(Time.new(preferred_day.year, preferred_day.month, preferred_day.day, hour, minute, 0))
else
adjusted = next_day + (weeks * 7)
# day of the week adjustment
time = tz.local_to_utc(Time.new(adjusted.year, adjusted.month, adjusted.day, hour, minute, 0))
end
time
end
def lesson_length
safe_lesson_booking.lesson_length
end
def safe_lesson_booking
found = lesson_booking
found ||= lesson_session.lesson_booking
end
def pretty_scheduled_start(with_timezone = true)
start_time = scheduled_time(0)
begin
tz = TZInfo::Timezone.get(timezone)
rescue Exception => e
@@log.error("unable to find timezone=#{tz_identifier}, e=#{e}")
end
if tz
begin
start_time = tz.utc_to_local(start_time)
rescue Exception => e
@@log.error("unable to convert #{scheduled_start} to #{tz}, e=#{e}")
puts "unable to convert #{e}"
end
end
duration = lesson_length * 60 # convert from minutes to seconds
end_time = start_time + duration
if with_timezone
"#{start_time.strftime("%A, %B %e")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} (#{tz.pretty_name})"
else
"#{start_time.strftime("%A, %B %e")} - #{start_time.strftime("%l:%M%P").strip}"
end
end
def pretty_timezone
begin
tz = TZInfo::Timezone.get(timezone)
tz.pretty_name
rescue Exception => e
@@log.error("unable to find timezone=#{tz_identifier}, e=#{e}")
'UTC'
end
end
def pretty_start_time(with_timezone = true)
start_time = scheduled_time(0)
begin
tz = TZInfo::Timezone.get(timezone)
rescue Exception => e
@@log.error("unable to find timezone=#{tz_identifier}, e=#{e}")
end
if tz
begin
start_time = tz.utc_to_local(start_time)
rescue Exception => e
@@log.error("unable to convert #{scheduled_start} to #{tz}, e=#{e}")
puts "unable to convert #{e}"
end
end
duration = lesson_length * 60 # convert from minutes to seconds
end_time = start_time + duration
if with_timezone
"#{start_time.strftime("%a, %b %e")} at #{start_time.strftime("%l:%M%P").strip} #{tz.pretty_name}"
else
"#{start_time.strftime("%a, %b %e")} at #{start_time.strftime("%l:%M%P").strip}"
end
end
def validate_proposer
if proposer && (proposer != container.student && proposer != container.teacher)
errors.add(:proposer, "must be either the student or teacher")
end
end
def validate_slot_type
if slot_type == SLOT_TYPE_SINGLE
if preferred_day.nil?
errors.add(:preferred_day, "must be specified")
end
end
if slot_type == SLOT_TYPE_RECURRING
if day_of_week.nil?
errors.add(:day_of_week, "must be specified")
end
end
end
def validate_slot_minimum_time
# this code will fail miserably if the slot is malformed
if errors.any?
return
end
if is_teacher_created?
return # the thinking is that a teacher can propose much tighter to the time; since they only counter; maybe they talked to the student
end
#
# minimum_start_time = create_minimum_booking_time
minimum_start_time = Time.now
if day_of_week
# this is recurring; it will sort itself out
else
time = scheduled_time(0)
if time <= minimum_start_time
#errors.add(:base, "must be at least #{APP_CONFIG.minimum_lesson_booking_hrs} hours in the future")
errors.add(:preferred_day, "can not be in the past")
end
end
end
end
end