# represenst the type of lesson package module JamRuby class LessonSession < ActiveRecord::Base include HtmlSanitize html_sanitize strict: [:cancel_message] attr_accessor :accepting, :creating, :countering, :countering_flag, :autocanceling, :countered_slot, :countered_lesson, :canceling, :assigned_student @@log = Logging.logger[LessonSession] delegate :sent_billing_notices, :last_billing_attempt_at, :billing_attempts, :billing_should_retry, :billed_at, :billing_error_detail, :billing_error_reason, :is_card_declined?, :is_card_expired?, :last_billed_at_date, :sent_billing_notices, to: :lesson_payment_charge, allow_nil: true delegate :is_test_drive?, :is_single_free?, :is_normal?, :approved_before?, :is_active?, :recurring, :is_monthly_payment?, :school_on_school?, :scheduling_email, :teacher_school_emails, :school_and_teacher, :school_over_teacher, :school_and_teacher_ids, :school_over_teacher_ids, to: :lesson_booking delegate :pretty_scheduled_start, to: :music_session STATUS_REQUESTED = 'requested' STATUS_CANCELED = 'canceled' STATUS_MISSED = 'missed' STATUS_COMPLETED = 'completed' STATUS_APPROVED = 'approved' STATUS_SUSPENDED = 'suspended' STATUS_COUNTERED = 'countered' STATUS_UNCONFIRMED = 'unconfirmed' STATUS_TYPES = [STATUS_REQUESTED, STATUS_CANCELED, STATUS_MISSED, STATUS_COMPLETED, STATUS_APPROVED, STATUS_SUSPENDED, STATUS_COUNTERED, STATUS_UNCONFIRMED] LESSON_TYPE_SINGLE = 'paid' LESSON_TYPE_SINGLE_FREE = 'single-free' LESSON_TYPE_TEST_DRIVE = 'test-drive' LESSON_TYPES = [LESSON_TYPE_SINGLE, LESSON_TYPE_SINGLE_FREE, LESSON_TYPE_TEST_DRIVE] has_one :music_session, class_name: "JamRuby::MusicSession", :dependent => :destroy belongs_to :teacher, class_name: "JamRuby::User", foreign_key: :teacher_id, inverse_of: :taught_lessons belongs_to :canceler, class_name: "JamRuby::User", foreign_key: :canceler_id belongs_to :counterer, class_name: "JamRuby::User", foreign_key: :counterer_id belongs_to :lesson_package_purchase, class_name: "JamRuby::LessonPackagePurchase" belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking" belongs_to :user, class_name: "JamRuby::User" belongs_to :slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :slot_id, :dependent => :destroy belongs_to :lesson_payment_charge, class_name: "JamRuby::LessonPaymentCharge", foreign_key: :charge_id belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_lesson, :dependent => :destroy has_one :teacher_distribution, class_name: "JamRuby::TeacherDistribution" has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot" has_many :notifications, :class_name => "JamRuby::Notification", :foreign_key => "lesson_session_id" has_many :chat_messages, :class_name => "JamRuby::ChatMessage", :foreign_key => "lesson_session_id" validates :duration, presence: true, numericality: {only_integer: true} validates :lesson_booking, presence: true validates :lesson_type, inclusion: {in: LESSON_TYPES} validates :booked_price, presence: true validates :status, presence: true, inclusion: {in: STATUS_TYPES} validates :teacher_complete, inclusion: {in: [true, false]} validates :student_complete, inclusion: {in: [true, false]} validates :teacher_canceled, inclusion: {in: [true, false]} validates :student_canceled, inclusion: {in: [true, false]} validates :success, inclusion: {in: [true, false]} validates :sent_notices, inclusion: {in: [true, false]} validates :post_processed, inclusion: {in: [true, false]} validate :validate_creating, :if => :creating validate :validate_countering, :if => :countering_flag validate :validate_accepted, :if => :accepting validate :validate_canceled, :if => :canceling validate :validate_autocancel, :if => :autocanceling after_save :manage_slot_changes after_create :create_charge scope :approved, -> { where(status: STATUS_APPROVED) } scope :requested, -> { where(status: STATUS_REQUESTED) } scope :canceled, -> { where(status: STATUS_CANCELED) } scope :suspended, -> { where(status: STATUS_SUSPENDED) } scope :completed, -> { where(status: STATUS_COMPLETED) } scope :missed, -> { where(status: STATUS_MISSED) } scope :upcoming, -> { joins(:music_session).where('music_sessions.scheduled_start > ?', Time.now) } scope :past_cancel_window, -> { joins(:music_session).where('music_sessions.scheduled_start > ?', 24.hours.from_now) } # show all requested/countered sessions where the student was the last to communicate scope :slow_responses, -> { joins(:lesson_booking).where('lesson_sessions.status = ? OR lesson_sessions.status = ?', LessonSession::STATUS_REQUESTED, LessonSession::STATUS_COUNTERED) .where('lesson_bookings.counterer_id IS NULL OR lesson_bookings.user_id = lesson_bookings.counterer_id') .order('(COALESCE(lesson_bookings.countered_at, lesson_bookings.sent_notices_at)) ASC') } scope :least_time_left, -> { joins(:lesson_booking, :music_session).where('lesson_sessions.status = ? OR lesson_sessions.status = ?', LessonSession::STATUS_REQUESTED, LessonSession::STATUS_COUNTERED) .where('lesson_bookings.counterer_id IS NULL OR lesson_bookings.user_id = lesson_bookings.counterer_id') .order('music_sessions.scheduled_start DESC') } def create_charge if !school_on_school? && !is_test_drive? && !is_monthly_payment? self.lesson_payment_charge = LessonPaymentCharge.new lesson_payment_charge.user = @assigned_student lesson_payment_charge.amount_in_cents = 0 lesson_payment_charge.fee_in_cents = 0 lesson_payment_charge.lesson_session = self lesson_payment_charge.save! end end def manage_slot_changes # if this slot changed, we need to update the time. But LessonBooking does this for us, for requested/accepted . # TODO: what to do, what to do. end def suspend! self.status = STATUS_SUSPENDED self.save end def unsuspend! self.status = STATUS_APPROVED self.save end def music_session_id music_session.id end def self.hourly_check auto_cancel analyse_sessions complete_sessions remind_counters end def self.minutely_check upcoming_sessions_reminder end def self.remind_counters MusicSession.joins(lesson_session: :lesson_booking) .where("lesson_bookings.sent_counter_reminder = false") .where('lesson_sessions.status = ? OR lesson_sessions.status = ?', LessonSession::STATUS_REQUESTED, LessonSession::STATUS_COUNTERED) .where("? > (COALESCE(lesson_bookings.countered_at, lesson_bookings.sent_notices_at)) + (INTERVAL '24 hours')", Time.now).each do |music_session| lesson_session = music_session.lesson_session if lesson_session.student_last_proposed? UserMailer.teacher_counter_reminder(lesson_session).deliver! else UserMailer.student_counter_reminder(lesson_session).deliver! end lesson_session.lesson_booking.sent_counter_reminder = true lesson_session.lesson_booking.save(validate: false) end end def self.auto_cancel MusicSession.joins(lesson_session: :lesson_booking).where('lesson_sessions.status = ?', LessonSession::STATUS_REQUESTED).where("? > scheduled_start + (INTERVAL '1 minutes' * (duration))", Time.now).each do |music_session| lesson_session = music_session.lesson_session lesson_session.autocancel end end def self.analyse_sessions MusicSession.joins(lesson_session: :lesson_booking).where('lesson_sessions.status = ?', LessonSession::STATUS_APPROVED).where("? > scheduled_start + (INTERVAL '1 minutes' * (duration))", Time.now).where('analysed = false').each do |music_session| lesson_session = music_session.lesson_session lesson_session.analyse end end def self.complete_sessions # this will find any paid session (recurring monthly paid, recurring single paid, single paid) MusicSession.joins(lesson_session: [:lesson_booking, :lesson_payment_charge]).where('lesson_sessions.status = ?', LessonSession::STATUS_COMPLETED).where("? > scheduled_start + (INTERVAL '1 minutes' * (duration))", Time.now).where('analysed = true').where('lesson_sessions.post_processed = false').where('billing_should_retry = true').each do |music_session| lesson_session = music_session.lesson_session lesson_session.session_completed end # test drives don't have a lesson_payment_charge, so we don't join against them MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.status = ?', LessonSession::STATUS_COMPLETED).where('lesson_sessions.lesson_type = ?', LESSON_TYPE_TEST_DRIVE).where("? > scheduled_start + (INTERVAL '1 minutes' * duration)", Time.now).where('analysed = true').where('lesson_sessions.post_processed = false').each do |music_session| lession_session = music_session.lesson_session lession_session.session_completed end end def self.upcoming_sessions_reminder now = Time.now half_hour_from_now = 30.minutes.from_now if Time.zone now = Time.zone.local_to_utc(now) half_hour_from_now = Time.zone.local_to_utc(half_hour_from_now) end MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.status = ?', LessonSession::STATUS_APPROVED).where('sent_starting_notice = false').where('(scheduled_start > ? and scheduled_start < ?)', now, half_hour_from_now).each do |music_session| lession_session = music_session.lesson_session lession_session.send_starting_notice end end def student_last_proposed? counterer_id.nil? || counterer_id == student_id end def analyse if self.analysed return end analysis = LessonSessionAnalyser.analyse(self) self.analysis = LessonSession.analysis_to_json(analysis) if analysis[:reason] == LessonSessionAnalyser::SESSION_ONGOING # extra protection against bad code somewhere return end self.success = analysis[:bill] self.analysed_at = Time.now self.analysed = true self.status = STATUS_COMPLETED if success && lesson_booking.requires_teacher_distribution?(self) self.teacher_distribution = TeacherDistribution.create_for_lesson(self) end if self.save # send out emails appropriate for this type of session session_completed end end def billed if lesson_booking.is_test_drive? false elsif lesson_payment_charge lesson_payment_charge.billed else false end end def amount_charged if lesson_payment_charge lesson_payment_charge.amount_in_cents / 100.0 else 0.0 end end def self.analysis_to_json(analysis, preserve_object = false) json = {} analysis.each do |k, v| if v.is_a?(Array) array = [] v.each do |item| if item.is_a?(Range) array << {begin: item.begin, end: item.end} else raise "expected range" end end json[k] = array else json[k] = v end end if preserve_object json else json.to_json end end def send_starting_notice UserMailer.lesson_starting_soon_student(self).deliver_now UserMailer.lesson_starting_soon_teacher(self).deliver_now self.sent_starting_notice = true self.save(validate: false) end def session_completed LessonSession.transaction do self.lock! if post_processed # nothing to do. because this is an async job, it's possible this was queued up with another session_completed fired return end if lesson_booking.is_test_drive? test_drive_completed elsif lesson_booking.is_normal? if lesson_booking.is_weekly_payment? || lesson_booking.is_monthly_payment? recurring_completed else normal_lesson_completed end end end end def bill_lesson if school_on_school? success = true else lesson_payment_charge.charge success = lesson_payment_charge.billed end if success self.sent_notices = true self.sent_notices_at = Time.now self.post_processed = true self.post_processed_at = Time.now self.save(:validate => false) end end def student_missed analysed && !success && analysis_json["student_analysis"]["missed"] end def teacher_missed analysed && !success && analysis_json["teacher_analysis"]["missed"] end def error_display if analysed if !success if student_missed && teacher_missed "both the student and teacher missed the lesson" elsif teacher_missed "the teacher missed the lesson" elsif student_missed "the student missed the lesson" else "unknown error reason" end else "because no error" end else "not yet analysed" end end def test_drive_completed distribution = teacher_distribution if !sent_notices if success if distribution # not all lessons/payment charges have a distribution distribution.ready = true distribution.save(validate: false) end student.test_drive_succeeded(self) else student.test_drive_failed(self) end self.lesson_booking.success = success self.lesson_booking.status = STATUS_COMPLETED self.lesson_booking.save(:validate => false) self.sent_notices = true self.sent_notices_at = Time.now self.post_processed = true self.post_processed_at = Time.now self.save(:validate => false) end end def recurring_completed puts "RECURRING COMPLETED #{success}" if success if lesson_booking.is_monthly_payment? # monthly payments are handled at beginning of month; just poke with email, and move on if !sent_notices # not in spec; just poke user and tell them we saw it was successfully completed UserMailer.monthly_recurring_done(self).deliver_now self.sent_notices = true self.sent_notices_at = Time.now self.post_processed = true self.post_processed_at = Time.now self.save(:validate => false) end else bill_lesson end else if lesson_booking.is_monthly_payment? if !sent_notices if !school_on_school? # bad session; just poke user UserMailer.monthly_recurring_no_bill(self).deliver_now end self.sent_notices = true self.sent_notices_at = Time.now self.post_processed = true self.post_processed_at = Time.now self.save(:validate => false) end else if !sent_notices if !school_on_school? # bad session; just poke user UserMailer.student_lesson_normal_no_bill(self).deliver_now end self.sent_notices = true self.sent_notices_at = Time.now self.post_processed = true self.post_processed_at = Time.now self.save(:validate => false) end end end end def normal_lesson_completed if success bill_lesson else if !sent_notices if !school_on_school? puts "STUDENT NO BILL SENT #{success}" UserMailer.student_lesson_normal_no_bill(self).deliver_now UserMailer.teacher_lesson_normal_no_bill(self).deliver_now end self.sent_notices = true self.sent_notices_at = Time.now self.post_processed = true self.post_processed_at = Time.now self.save(:validate => false) end end self.lesson_booking.success = success self.lesson_booking.status = STATUS_COMPLETED self.lesson_booking.save(:validate => false) end def scheduled_start if music_session music_session.scheduled_start else raise "lesson session #{id} has no music session in scheduled_start" end end def send_counter(countered_lesson, countered_slot) if !lesson_booking.errors.any? if countered_slot.is_teacher_created? UserMailer.student_lesson_counter(countered_lesson, countered_slot).deliver_now else UserMailer.teacher_lesson_counter(countered_lesson, countered_slot).deliver_now end end self.countering = false end default_scope { order('lesson_sessions.created_at') } def is_requested? status == STATUS_REQUESTED end def is_canceled? status == STATUS_CANCELED end def is_completed? status == STATUS_COMPLETED end def is_approved? status == STATUS_APPROVED end def is_suspended? status == STATUS_SUSPENDED end def is_countered? status == STATUS_COUNTERED end def analysis_json @parsed_analysis || analysis end def validate_creating if !is_requested? && !is_approved? self.errors.add(:status, "is not valid for a new lesson session.") end if is_approved? && lesson_booking && lesson_booking.is_test_drive? && lesson_package_purchase.nil? self.errors.add(:lesson_package_purchase, "must be specified for a test drive purchase") end end def validate_countering if counter_slot.nil? errors.add(:counter_slot, "must be specified") elsif !approved_before? && (status == STATUS_REQUESTED || status == STATUS_COUNTERED) if recurring && !counter_slot.update_all errors.add(:counter_slot, "Only 'update all' counter-proposals are allowed for un-approved, recurring lessons") end if recurring && !counter_slot.is_recurring? errors.add(:counter_slot, "Only 'recurring' counter-proposals are allowed for un-approved, recurring lessons") end end self.countering_flag = false end def validate_accepted if self.status_was != STATUS_REQUESTED && self.status_was != STATUS_COUNTERED self.errors.add(:status, "This session is already #{self.status_was}.") end # if this is a single lesson (testdrive, paid), but the scheduled time is before now, then it's in the past, and we have to reject if !recurring && self.slot.scheduled_time(0) <= Time.now self.errors.add(:slot, "is in the past") end if approved_before? # only checking for this on 1st time through acceptance if lesson_booking && lesson_booking.is_test_drive? && lesson_package_purchase.nil? self.errors.add(:lesson_package_purchase, "must be specified for a test drive purchase") end end self.accepting = false end def validate_autocancel if scheduled_start + (duration * 60) > Time.now self.errors.add(:status, "This session is not in the past.") end if self.status_was != STATUS_REQUESTED self.errors.add(:status, "This session is #{self.status_was} and can not be autocanceled") end self.autocanceling = false end def validate_canceled if !is_canceled? self.errors.add(:status, "This session is already #{self.status}.") end # check 24 hour window if scheduled_start.to_i - Time.now.to_i < 24 * 60 * 60 self.errors.add(:base, "This session is due to start within 24 hours and can not be canceled.") end self.canceling = false end def self.create(booking) lesson_session = LessonSession.new lesson_session.creating = true lesson_session.duration = booking.lesson_length lesson_session.lesson_type = booking.lesson_type lesson_session.lesson_booking = booking lesson_session.booked_price = booking.booked_price lesson_session.teacher = booking.teacher lesson_session.status = booking.status lesson_session.slot = booking.default_slot lesson_session.assigned_student = booking.student lesson_session.user = booking.student if booking.is_test_drive? && booking.student.remaining_test_drives > 0 lesson_session.lesson_package_purchase = booking.student.most_recent_test_drive_purchase end lesson_session.save if lesson_session.errors.any? puts "Lesson Session errors #{lesson_session.errors.inspect}" end lesson_session end def student music_session.creator end def student_id music_session.creator.id end def self.index(user, params = {}) limit = params[:per_page] limit ||= 100 limit = limit.to_i school_owner = user.owned_school && user.owned_school.scheduling_comm? is_teacher = !user.teacher.nil? query = LessonSession.unscoped.joins([:music_session, :lesson_booking]).joins(music_session: :creator) #query = query.includes([:teacher, :music_session]) query = query.includes([:music_session]) query = query.order('music_sessions.scheduled_start DESC') if params[:as_teacher].present? if school_owner || params[:as_teacher] == true || params[:as_teacher] == "true" # if a school owner is viewing, grab his owned school ID. Otherwise use a teacher's school ID, if applicable school_id = school_owner ? user.owned_school.id : (user.teacher ? user.teacher.school_id : nil) if school_id school_extra = "OR (lesson_bookings.school_id = '#{school_id}')" else school_extra = '' end if school_owner extra_teacher= '' if is_teacher # if the school owner is a teacher, show his bookings too extra_teacher = " OR lesson_sessions.teacher_id = '#{user.teacher.id}'" end query = query.where('lesson_sessions.teacher_id in (?)' + extra_teacher, user.owned_school.teachers.map { |t| t.user.id }) query = query.where('lesson_sessions.status = ? OR lesson_sessions.status = ?', LessonSession::STATUS_REQUESTED, LessonSession::STATUS_COUNTERED) else # this is a normal teacher (not a school owner) if school_id && user.teacher.school.scheduling_comm? # the school wants to control scheduling communication. So hide sessions in the requested/countered status from them query = query.where('lesson_sessions.status != ? AND lesson_sessions.status != ?', LessonSession::STATUS_REQUESTED, LessonSession::STATUS_COUNTERED) end query = query.where('lesson_sessions.teacher_id = ?', user.id) end else if user.school_id school_extra = "OR (lesson_bookings.school_id = '#{user.school_id}')" else school_extra = '' end query = query.where('music_sessions.user_id = ?', user.id) end else if user.school_id school_extra = "OR (lesson_bookings.school_id = '#{user.school_id}')" else school_extra = '' end query = query.where('(lesson_sessions.teacher_id = ? or music_sessions.user_id = ?)', user.id, user.id) end query = query.where('lesson_bookings.card_presumed_ok = true OR (music_sessions.user_id = ?) ' + school_extra, user.id) current_page = params[:page].nil? ? 1 : params[:page].to_i next_page = current_page + 1 # will_paginate gem query = query.paginate(:page => current_page, :per_page => limit) if query.length == 0 # no more results {query: query, next_page: nil} elsif query.length < limit # no more results {query: query, next_page: nil} else {query: query, next_page: next_page} end end def update_scheduled_start(week_offset) music_session.scheduled_start = slot.scheduled_time(week_offset) music_session.save! end # grabs the next available time that's after the present, to avoid times being scheduled in the past def update_next_available_time(attempt = 0) max_attempts = attempt + 10 while attempt < max_attempts test = slot.scheduled_time(attempt) if test >= Time.now time = test # valid time found! break end attempt += 1 end if time music_session.scheduled_start = time music_session.save end time.nil? ? nil : attempt end def school_owner_id school = teacher.teacher.school if school school.owner.id end end def access?(user) user.id == music_session.user_id || user.id == teacher.id || user.id == school_owner_id end # teacher accepts the lesson def accept(params) response = self LessonSession.transaction do message = params[:message] slot = params[:slot] accepter = params[:accepter] raise "LessonBookingSlot" if slot.is_a?(LessonBookingSlot) self.slot = slot = LessonBookingSlot.find(slot) self.slot.accept_message = message self.slot.save! self.accepting = true self.status = STATUS_APPROVED if !approved_before? # 1st time this has ever been approved; there are other things we need to do if lesson_package_purchase.nil? && lesson_booking.is_test_drive? self.lesson_package_purchase = student.most_recent_test_drive_purchase end if self.save # also let the lesson_booking know we got accepted if !lesson_booking.accept(self, slot, accepter) response = lesson_booking raise ActiveRecord::Rollback end UserMailer.student_lesson_accepted(self, message, slot).deliver_now UserMailer.teacher_lesson_accepted(self, message, slot).deliver_now message = '' if message.nil? msg = ChatMessage.create(teacher, nil, message, ChatMessage::CHANNEL_LESSON, nil, student, self, "Lesson Approved") Notification.send_jamclass_invitation_teacher(music_session, teacher) Notification.send_student_jamclass_invitation(music_session, student) Notification.send_lesson_message('accept', self, true) else @@log.error("unable to accept slot #{slot.id} for lesson #{self.id}") puts("unable to accept slot #{slot.id} for lesson #{self.id}") response = self raise ActiveRecord::Rollback end else # this implies a new slot has been countered, and now approved if self.save if slot.update_all if !lesson_booking.accept(self, slot, accepter) response = lesson_booking raise ActiveRecord::Rollback end message = '' if message.nil? msg = ChatMessage.create(slot.proposer, nil, message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, self, "All Lesson Times Updated") Notification.send_lesson_message('accept', self, true) # TODO: this isn't quite an 'accept' UserMailer.student_lesson_update_all(self, message, slot).deliver_now UserMailer.teacher_lesson_update_all(self, message, slot).deliver_now else # nothing to do with the original booking (since we are not changing all times), so we update just ourself time = update_next_available_time # XXX: week offset as 0? This *could* still be in the past. But the user is approving it. So do we just trust them and get out of their way? if time.nil? @@log.error("unable to accept slot #{slot.id} for lesson #{self.id} because it's in the past") puts("unable to accept slot #{slot.id} for lesson #{self.id} because it's in the past") raise ActiveRecord::Rollback end message = '' if message.nil? msg = ChatMessage.create(slot.proposer, nil, message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, self, "Lesson Updated Time Approved") UserMailer.student_lesson_accepted(self, message, slot).deliver_now UserMailer.teacher_lesson_accepted(self, message, slot).deliver_now end else @@log.error("unable to accept slot #{slot.id} for lesson #{self.id} #{errors.inspect}") puts("unable to accept slot #{slot.id} for lesson #{self.id} #{errors.inspect}") response = self raise ActiveRecord::Rollback end end end response end def counter(params) response = self LessonSession.transaction do proposer = params[:proposer] slot = params[:slot] message = params[:message] update_all = slot.update_all || !lesson_booking.recurring self.countering = true self.countering_flag = true slot.proposer = proposer slot.lesson_session = self slot.message = message self.counterer = proposer self.countered_at = Time.now self.lesson_booking_slots << slot self.countered_slot = slot self.countered_lesson = self self.status = STATUS_COUNTERED #if !update_all self.counter_slot = slot #end if self.save puts "SAVING OK" #if update_all && !lesson_booking.counter(self, proposer, slot) if !lesson_booking.counter(self, proposer, slot) response = lesson_booking puts "ROLLBACK" raise ActiveRecord::Rollback end else response = self raise ActiveRecord::Rollback end send_counter(@countered_lesson, @countered_slot) message = '' if message.nil? msg = ChatMessage.create(slot.proposer, music_session, message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, self, "New Time Proposed") Notification.send_lesson_message('counter', self, slot.is_teacher_created?) end response end def cancel_lesson(canceler, message) canceled_by_student = canceler == student self.status = STATUS_CANCELED self.cancel_message = message self.canceler = canceler self.canceling = true if canceled_by_student self.student_canceled = true self.student_canceled_at = Time.now self.student_canceled_reason = message self.student_short_canceled = 24.hours.from_now > scheduled_start else self.teacher_canceled = true self.teacher_canceled_at = Time.now self.teacher_canceled_reason = message self.teacher_short_canceled = 24.hours.from_now > scheduled_start end end # canceled by the system because it is requested, and the end time has gone by def autocancel response = self LessonSession.transaction do self.autocanceling = true self.status = LessonSession::STATUS_UNCONFIRMED if self.save if !lesson_booking.recurring response = lesson_booking.autocancel if response.errors.any? raise ActiveRecord::Rollback end end else raise ActiveRecord::Rollback end msg = ChatMessage.create(teacher, nil, '', ChatMessage::CHANNEL_LESSON, nil, student, self, "Lesson Timeout") msg = ChatMessage.create(student, nil, '', ChatMessage::CHANNEL_LESSON, nil, teacher, self, "Lesson Timeout") end end # teacher accepts the lesson def cancel(params) response = self LessonSession.transaction do self.status = LessonSession::STATUS_CANCELED canceler = params[:canceler] canceled_by_student = canceler == student other = canceled_by_student ? teacher : student message = params[:message] message = '' if message.nil? if lesson_booking.recurring update_all = params[:update_all] else update_all = true end if lesson_booking.is_test_drive? student.test_drive_declined(self) end if update_all response = lesson_booking.cancel(canceler, other, message) if response.errors.any? raise ActiveRecord::Rollback end if !save response = self raise ActiveRecord::Rollback end else cancel_lesson(canceler, message) if !save response = self raise ActiveRecord::Rollback end msg = ChatMessage.create(canceler, nil, message, ChatMessage::CHANNEL_LESSON, nil, other, self, "Lesson Canceled") Notification.send_lesson_message('canceled', self, false) Notification.send_lesson_message('canceled', self, true) UserMailer.student_lesson_canceled(self, message).deliver_now UserMailer.teacher_lesson_canceled(self, message).deliver_now end end response end def description(lesson_booking) lesson_booking.lesson_package_type.description(lesson_booking) + " with #{teacher.admin_name}" end def timed_description if is_test_drive? "TestDrive session with #{self.lesson_booking.student.name} on #{self.scheduled_start.to_date.strftime('%B %d, %Y')}" else if self.lesson_booking.is_monthly_payment? "Monthly Lesson with #{self.lesson_booking.student.name} on #{self.scheduled_start.to_date.strftime('%B %d, %Y')}" else "Lesson with #{self.lesson_booking.student.name} on #{self.scheduled_start.to_date.strftime('%B %d, %Y')}" end end end def display_type if is_test_drive? 'TestDrive' elsif recurring 'Recurring' else 'Single' end end def last_student_comm_date counterer_id == user_id ? countered_at : sent_notices_at end def days_no_response date = last_student_comm_date if date.nil? '?' else (Time.now - last_student_comm_date) / (60 * 60 * 24) end end def stripe_description(lesson_booking) description(lesson_booking) end def home_url APP_CONFIG.external_root_url + "/client#/jamclass" end def web_url APP_CONFIG.external_root_url + "/client#/jamclass/lesson-booking/" + id end def update_payment_url APP_CONFIG.external_root_url + "/client#/jamclass/update-payment" end def admin_url APP_CONFIG.admin_root_url + "/admin/lesson_sessions/" + id end def chat_url APP_CONFIG.external_root_url + "/client#/jamclass/chat-dialog/d1=lesson_" + id end end end