2016-04-06 02:23:15 +00:00
# represenst the type of lesson package
module JamRuby
class LessonSession < ActiveRecord :: Base
include HtmlSanitize
html_sanitize strict : [ :cancel_message ]
2016-06-01 00:20:03 +00:00
attr_accessor :accepting , :creating , :countering , :countering_flag , :autocanceling , :countered_slot , :countered_lesson , :canceling , :assigned_student
2016-04-06 02:23:15 +00:00
@@log = Logging . logger [ LessonSession ]
2016-04-21 14:23:29 +00:00
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
2017-10-18 02:36:53 +00:00
delegate :is_test_drive? , :is_single_free? , :is_normal? , :approved_before? , :is_active? , :recurring , :is_monthly_payment? , :school_on_school? , :school_on_school_payment? , :no_school_on_school_payment? , :payment_if_school_on_school? , :scheduling_email , :teacher_school_emails , :school_and_teacher , :school_over_teacher , :school_and_teacher_ids , :school_over_teacher_ids , :posa_card , :remaining_roll_forward_amount_in_cents , :has_recurring_counter? , :ever_accepted? , :has_access? , to : :lesson_booking
2016-04-06 02:23:15 +00:00
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'
2016-05-28 23:48:40 +00:00
STATUS_UNCONFIRMED = 'unconfirmed'
2016-04-06 02:23:15 +00:00
2016-05-28 23:48:40 +00:00
STATUS_TYPES = [ STATUS_REQUESTED , STATUS_CANCELED , STATUS_MISSED , STATUS_COMPLETED , STATUS_APPROVED , STATUS_SUSPENDED , STATUS_COUNTERED , STATUS_UNCONFIRMED ]
2016-04-06 02:23:15 +00:00
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 ]
2016-05-28 23:48:40 +00:00
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
2016-09-08 10:59:58 +00:00
has_many :teacher_distributions , class_name : " JamRuby::TeacherDistribution " , dependent : :destroy
2016-05-28 23:48:40 +00:00
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 "
2016-04-21 14:23:29 +00:00
2016-04-06 02:23:15 +00:00
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
2016-06-01 00:20:03 +00:00
validate :validate_countering , :if = > :countering_flag
2016-04-06 02:23:15 +00:00
validate :validate_accepted , :if = > :accepting
validate :validate_canceled , :if = > :canceling
2016-05-28 23:48:40 +00:00
validate :validate_autocancel , :if = > :autocanceling
2016-04-06 02:23:15 +00:00
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 ) }
2016-04-21 14:23:29 +00:00
scope :upcoming , - > { joins ( :music_session ) . where ( 'music_sessions.scheduled_start > ?' , Time . now ) }
2016-05-07 18:45:02 +00:00
scope :past_cancel_window , - > { joins ( :music_session ) . where ( 'music_sessions.scheduled_start > ?' , 24 . hours . from_now ) }
2016-05-28 23:48:40 +00:00
# show all requested/countered sessions where the student was the last to communicate
2016-05-29 19:37:54 +00:00
scope :slow_responses , - > { joins ( :lesson_booking ) . where ( 'lesson_sessions.status = ? OR lesson_sessions.status = ?' , LessonSession :: STATUS_REQUESTED , LessonSession :: STATUS_COUNTERED )
2018-01-27 22:18:04 +00:00
. where ( " (? - (COALESCE(lesson_sessions.countered_at, lesson_bookings.sent_notices_at))) > INTERVAL '72 hours' " , Time . now )
2017-07-17 00:38:40 +00:00
. order ( '(COALESCE(lesson_sessions.countered_at, lesson_bookings.sent_notices_at)) ASC' ) }
2016-05-29 19:37:54 +00:00
2016-04-06 02:23:15 +00:00
def create_charge
2016-09-08 10:59:58 +00:00
if payment_if_school_on_school? && ! is_test_drive? && ! is_monthly_payment?
2016-04-06 02:23:15 +00:00
self . lesson_payment_charge = LessonPaymentCharge . new
2016-05-11 21:03:55 +00:00
lesson_payment_charge . user = @assigned_student
2016-04-06 02:23:15 +00:00
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
2016-09-08 10:59:58 +00:00
def teacher_distribution
2018-02-25 22:28:12 +00:00
teacher_distributions . where ( education : false ) . where ( 'retailer_id is null' ) . first
2016-09-08 10:59:58 +00:00
end
def education_distribution
2018-02-25 22:28:12 +00:00
teacher_distributions . where ( education : true ) . first
2016-09-08 10:59:58 +00:00
end
2017-01-17 18:24:49 +00:00
def retailer_distribution
teacher_distributions . where ( 'retailer_id is not null' ) . first
end
2016-04-06 02:23:15 +00:00
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
2016-04-21 14:23:29 +00:00
def music_session_id
music_session . id
end
2016-05-07 18:45:02 +00:00
2016-04-06 02:23:15 +00:00
def self . hourly_check
2016-05-28 23:48:40 +00:00
auto_cancel
2016-04-06 02:23:15 +00:00
analyse_sessions
complete_sessions
2016-05-28 23:48:40 +00:00
remind_counters
2017-07-17 00:38:40 +00:00
remind_counters_recurring
2018-02-25 22:28:12 +00:00
onboarding_email_reminders
2016-04-06 02:23:15 +00:00
end
2016-04-21 14:23:29 +00:00
def self . minutely_check
upcoming_sessions_reminder
2018-05-26 17:19:59 +00:00
upcoming_sessions_reminder_early
2016-04-21 14:23:29 +00:00
end
2016-05-28 23:48:40 +00:00
def self . remind_counters
MusicSession . joins ( lesson_session : :lesson_booking )
2018-01-24 03:48:23 +00:00
. where ( 'lesson_bookings.recurring = FALSE' )
2016-05-28 23:48:40 +00:00
. where ( 'lesson_sessions.status = ? OR lesson_sessions.status = ?' , LessonSession :: STATUS_REQUESTED , LessonSession :: STATUS_COUNTERED )
2018-01-24 03:48:23 +00:00
. where ( " ? > (COALESCE(lesson_sessions.sent_counter_reminder_at, lesson_sessions.countered_at, lesson_bookings.sent_notices_at) + (INTERVAL '24 hours')) " , Time . now ) . each do | music_session |
2017-07-17 00:38:40 +00:00
lesson_session = music_session . lesson_session
if lesson_session . student_last_proposed?
2018-02-25 22:28:12 +00:00
relevant_time = [ lesson_session . countered_at , lesson_session . lesson_booking . sent_notices_at ] . find { | x | ! x . nil? }
2018-01-24 03:48:23 +00:00
2018-03-03 21:39:55 +00:00
UserMailer . student_no_comm_other ( lesson_session . lesson_booking , relevant_time < 3 . days . ago ) . deliver_now
2017-07-17 00:38:40 +00:00
UserMailer . teacher_counter_reminder ( lesson_session ) . deliver_now
else
2018-01-24 03:48:23 +00:00
UserMailer . teacher_no_comm_other ( lesson_session . lesson_booking ) . deliver_now
2017-07-17 00:38:40 +00:00
UserMailer . student_counter_reminder ( lesson_session ) . deliver_now
end
2018-01-24 03:48:23 +00:00
lesson_session . sent_counter_reminder_at = Time . now
2017-07-17 00:38:40 +00:00
lesson_session . save ( validate : false )
end
end
def self . remind_counters_recurring
MusicSession . joins ( lesson_session : :lesson_booking )
2018-02-25 22:28:12 +00:00
. where ( " lesson_bookings.status = ? AND lesson_bookings.sent_counter_reminder = false AND lesson_bookings.recurring = TRUE " , LessonBooking :: STATUS_COUNTERED )
2017-07-17 00:38:40 +00:00
. where ( " ? > (COALESCE(lesson_sessions.countered_at, lesson_bookings.sent_notices_at) + (INTERVAL '24 hours')) " , Time . now ) . each do | music_session |
2016-05-28 23:48:40 +00:00
lesson_session = music_session . lesson_session
if lesson_session . student_last_proposed?
2016-08-03 01:46:15 +00:00
UserMailer . teacher_counter_reminder ( lesson_session ) . deliver_now
2016-05-28 23:48:40 +00:00
else
2016-08-03 01:46:15 +00:00
UserMailer . student_counter_reminder ( lesson_session ) . deliver_now
2016-05-28 23:48:40 +00:00
end
lesson_session . lesson_booking . sent_counter_reminder = true
lesson_session . lesson_booking . save ( validate : false )
end
end
2016-05-29 19:37:54 +00:00
2018-02-25 22:28:12 +00:00
def self . remindable_onboarding_users
User . where ( 'first_onboarding_paid_lesson_at IS NULL AND onboarding_onboarded_at IS NOT NULL' )
. where ( 'stuck_take_flesson = FALSE and stuck_take_2nd_flesson = FALSE and stuck_take_plesson = FALSE' )
end
def self . onboarding_email_reminders
remindable_onboarding_users . each do | user |
if user . second_onboarding_free_lesson_at && user . sent_take_plesson_email_times < 4
last_sent = user . sent_take_plesson_email_at || user . second_onboarding_free_lesson_at
if last_sent < 2 . days . ago
if user . sent_take_plesson_email_times == 3
2018-03-20 01:25:09 +00:00
User . where ( id : user . id ) . update_all ( stuck_take_plesson : true )
2018-02-25 22:28:12 +00:00
else
UserMailer . take_paid_lesson ( user ) . deliver_now
2018-03-20 01:25:09 +00:00
User . where ( id : user . id ) . update_all ( sent_take_plesson_email_times : user . sent_take_plesson_email_times + 1 , sent_take_plesson_email_at : Time . now )
2018-02-25 22:28:12 +00:00
end
end
elsif user . first_onboarding_free_lesson_at && user . sent_take_2nd_flesson_email_times < 4
last_sent = user . sent_take_2nd_flesson_email_at || user . first_onboarding_free_lesson_at
if last_sent < 2 . days . ago
if user . sent_take_2nd_flesson_email_times == 3
2018-03-20 01:25:09 +00:00
User . where ( id : user . id ) . update_all ( stuck_take_2nd_flesson : true )
2018-02-25 22:28:12 +00:00
else
UserMailer . take_second_free_lesson ( user ) . deliver_now
2018-03-20 01:25:09 +00:00
User . where ( id : user . id ) . update_all ( sent_take_2nd_flesson_email_times : user . sent_take_2nd_flesson_email_times + 1 , sent_take_2nd_flesson_email_at : Time . now )
2018-02-25 22:28:12 +00:00
end
end
elsif user . sent_take_flesson_email_times < 4
last_sent = user . sent_take_flesson_email_at || user . onboarding_onboarded_at
if last_sent < 2 . days . ago
if user . sent_take_flesson_email_times == 3
2018-03-20 01:25:09 +00:00
User . where ( id : user . id ) . update_all ( stuck_take_flesson : true )
2018-02-25 22:28:12 +00:00
else
UserMailer . take_first_free_lesson ( user ) . deliver_now
2018-03-20 01:25:09 +00:00
User . where ( id : user . id ) . update_all ( sent_take_flesson_email_times : user . sent_take_flesson_email_times + 1 , sent_take_flesson_email_at : Time . now )
2018-02-25 22:28:12 +00:00
end
end
end
end
end
2018-01-14 16:12:15 +00:00
# give 2 days to auto-cancel; this lets people just jump in a session and mark it done
2016-05-28 23:48:40 +00:00
def self . auto_cancel
2018-01-23 03:50:45 +00:00
MusicSession . joins ( lesson_session : :lesson_booking ) . where ( 'lesson_sessions.status = ? OR lesson_bookings.status = ?' , LessonSession :: STATUS_REQUESTED , LessonBooking :: STATUS_COUNTERED ) . where ( " ? > scheduled_start + (INTERVAL '2 days') + (INTERVAL '1 minutes' * (duration)) " , Time . now ) . each do | music_session |
2016-05-28 23:48:40 +00:00
lesson_session = music_session . lesson_session
lesson_session . autocancel
end
end
2016-04-06 02:23:15 +00:00
def self . analyse_sessions
2018-01-14 16:12:15 +00:00
MusicSession . joins ( lesson_session : :lesson_booking ) . where ( '(lesson_sessions.status = ? OR lesson_sessions.status = ?) AND lesson_bookings.status != ?' , LessonSession :: STATUS_APPROVED , LessonBooking :: STATUS_REQUESTED , LessonBooking :: STATUS_COUNTERED ) . where ( " ? > scheduled_start + (INTERVAL '1 minutes' * (duration)) " , Time . now ) . where ( 'analysed = false' ) . each do | music_session |
2016-05-28 23:48:40 +00:00
lesson_session = music_session . lesson_session
lesson_session . analyse
2016-04-06 02:23:15 +00:00
end
end
def self . complete_sessions
2016-04-21 14:23:29 +00:00
# this will find any paid session (recurring monthly paid, recurring single paid, single paid)
2016-05-28 23:48:40 +00:00
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
2016-04-21 14:23:29 +00:00
end
# test drives don't have a lesson_payment_charge, so we don't join against them
2016-05-31 16:13:49 +00:00
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 |
2016-04-06 02:23:15 +00:00
lession_session = music_session . lesson_session
lession_session . session_completed
end
end
2016-04-21 14:23:29 +00:00
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
2016-05-07 18:45:02 +00:00
2018-05-26 17:19:59 +00:00
2016-04-26 03:01:15 +00:00
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 |
2016-04-21 14:23:29 +00:00
lession_session = music_session . lesson_session
2018-05-26 17:19:59 +00:00
lession_session . send_starting_notice ( false )
end
end
def self . upcoming_sessions_reminder_early
now = Time . now
two_hours_from_now = 120 . minutes . from_now
if Time . zone
now = Time . zone . local_to_utc ( now )
two_hours_from_now = Time . zone . local_to_utc ( two_hours_from_now )
end
MusicSession . joins ( lesson_session : [ :lesson_booking ] ) . where ( 'lesson_sessions.status = ?' , LessonSession :: STATUS_APPROVED ) . where ( 'sent_starting_notice = false' ) . where ( 'sent_early_starting_notice = false' ) . where ( '(scheduled_start > ? and scheduled_start < ?)' , now , two_hours_from_now ) . each do | music_session |
lession_session = music_session . lesson_session
lession_session . send_starting_notice ( true )
2016-04-21 14:23:29 +00:00
end
end
2016-05-28 23:48:40 +00:00
def student_last_proposed?
counterer_id . nil? || counterer_id == student_id
end
2018-01-27 22:18:04 +00:00
def late_responder
if student_last_proposed?
teacher
else
student
end
end
def last_proposer
if student_last_proposed?
student
else
teacher
end
end
2018-01-23 03:50:45 +00:00
def lesson_is_free?
lesson_booking . booked_price == 0 . 00
end
2017-01-17 18:24:49 +00:00
def mark_lesson ( success , administratively_marked = false )
2018-01-14 16:12:15 +00:00
if ! self . success && ( is_requested? || is_unconfirmed? )
# what's going on here is that we will just mark a session as a success if the the people got in in case of requested sessions
# but otherwise, leave it alone, so that the people can after-the-fact reschedule it (don't close it out and treat as a failure)
return
end
2017-01-17 18:24:49 +00:00
self . success = success
2016-04-06 02:23:15 +00:00
self . analysed_at = Time . now
self . analysed = true
2016-04-21 14:23:29 +00:00
self . status = STATUS_COMPLETED
2017-01-17 18:24:49 +00:00
self . admin_marked = administratively_marked
2016-12-15 18:47:08 +00:00
2016-04-26 03:01:15 +00:00
if success && lesson_booking . requires_teacher_distribution? ( self )
2016-12-15 18:47:08 +00:00
2017-01-17 18:24:49 +00:00
if lesson_booking . posa_card . nil? && lesson_booking . payment
2017-01-06 12:43:38 +00:00
# if there is a payment object, it will describe how everything gets doled out
2017-01-17 18:24:49 +00:00
payment = JSON . parse ( lesson_booking . payment )
teacher_split = payment [ " teacher " ]
retailer_split = payment [ " retailer " ]
retailer_rate = teacher . teacher . retailer . jamkazam_rate + APP_CONFIG . stripe [ :charge_fee ] # add 0.03 to account for stripe deduction
# but we must also take the correct rate out of the
2017-01-06 12:43:38 +00:00
2017-01-17 18:24:49 +00:00
if is_test_drive?
# test drives do not get subject to split behavior, per quick convo with David
self . teacher_distributions << TeacherDistribution . create_for_lesson ( self , false )
else
if teacher_split && teacher_split > 0
teacher_dist = TeacherDistribution . create_for_lesson ( self , false , ( teacher_split / 100 . 0 ) . round ( 2 ) , retailer_rate )
self . teacher_distributions << teacher_dist
end
if retailer_split && retailer_split > 0
teacher_dist = TeacherDistribution . create_for_lesson ( self , false , ( retailer_split / 100 . 0 ) . round ( 2 ) , retailer_rate )
teacher_dist . retailer = teacher . teacher . retailer
teacher_dist . save
end
end
2017-01-06 12:43:38 +00:00
else
2017-03-22 12:39:06 +00:00
is_school_on_school = lesson_booking . school_on_school_payment?
2016-12-15 18:47:08 +00:00
2017-01-06 12:43:38 +00:00
self . teacher_distributions << TeacherDistribution . create_for_lesson ( self , false )
2017-03-22 12:39:06 +00:00
if is_school_on_school && lesson_booking . school . education
2017-01-06 12:43:38 +00:00
self . teacher_distributions << TeacherDistribution . create_for_lesson ( self , true )
end
end
2016-04-06 02:23:15 +00:00
end
if self . save
# send out emails appropriate for this type of session
session_completed
end
end
2017-01-17 18:24:49 +00:00
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
2018-01-23 03:50:45 +00:00
# record how it went
if analysis [ :student_analysis ] && analysis [ :student_analysis ] [ :total_time ] > 0
if lesson_is_free?
if student . first_onboarding_free_lesson_at . nil?
student . first_onboarding_free_lesson_at = Time . now
student . save
2018-02-25 22:28:12 +00:00
elsif student . second_onboarding_free_lesson_at . nil?
student . second_onboarding_free_lesson_at = Time . now
student . save
2018-01-23 03:50:45 +00:00
end
else
if student . first_onboarding_paid_lesson_at . nil?
student . first_onboarding_paid_lesson_at = Time . now
student . save
end
end
end
2017-01-17 18:24:49 +00:00
mark_lesson ( analysis [ :bill ] )
end
2016-04-21 14:23:29 +00:00
def billed
if lesson_booking . is_test_drive?
false
2016-05-16 16:39:20 +00:00
elsif lesson_payment_charge
2016-04-21 14:23:29 +00:00
lesson_payment_charge . billed
2016-05-16 16:39:20 +00:00
else
false
2016-04-21 14:23:29 +00:00
end
end
2016-04-06 02:23:15 +00:00
def amount_charged
2016-05-16 16:39:20 +00:00
if lesson_payment_charge
lesson_payment_charge . amount_in_cents / 100 . 0
else
0 . 0
end
2016-04-06 02:23:15 +00:00
end
2016-05-07 18:45:02 +00:00
def self . analysis_to_json ( analysis , preserve_object = false )
2016-04-06 02:23:15 +00:00
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
2016-05-07 18:45:02 +00:00
if preserve_object
json
else
json . to_json
end
2016-04-06 02:23:15 +00:00
end
2018-05-26 17:19:59 +00:00
def send_starting_notice ( early_notice )
UserMailer . lesson_starting_soon_student ( self , early_notice , student . local_hour_stmt ( scheduled_start ) ) . deliver_now
UserMailer . lesson_starting_soon_teacher ( self , early_notice , teacher . local_hour_stmt ( scheduled_start ) ) . deliver_now
if early_notice
self . sent_early_starting_notice = true
else
self . sent_starting_notice = true
end
2016-04-21 14:23:29 +00:00
self . save ( validate : false )
end
2016-05-07 18:45:02 +00:00
2017-10-18 02:36:53 +00:00
def video_uploaded ( user , recording , data )
claim = recording . claim_for_user ( user )
2017-10-19 02:28:11 +00:00
msg = ChatMessage . create ( user , nil , " ' #{ claim . name } ' " , ChatMessage :: CHANNEL_LESSON , nil , other_user ( user ) , self , " Video Uploaded " , nil , claim )
2017-10-18 02:36:53 +00:00
Notification . send_lesson_message ( 'video uploaded' , self , false )
Notification . send_lesson_message ( 'video uploaded' , self , true )
end
def other_user ( user )
if user . id == student . id
teacher
else
student
end
end
2018-02-25 22:28:12 +00:00
2016-04-06 02:23:15 +00:00
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
2016-09-08 10:59:58 +00:00
if no_school_on_school_payment?
2016-05-12 21:29:27 +00:00
success = true
else
lesson_payment_charge . charge
success = lesson_payment_charge . billed
end
2016-04-06 02:23:15 +00:00
2016-05-12 21:29:27 +00:00
if success
2016-04-06 02:23:15 +00:00
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
2016-05-24 16:45:04 +00:00
def student_missed
2016-05-27 01:32:58 +00:00
analysed && ! success && analysis_json [ " student_analysis " ] [ " missed " ]
2016-05-24 16:45:04 +00:00
end
def teacher_missed
2016-05-27 01:32:58 +00:00
analysed && ! success && analysis_json [ " teacher_analysis " ] [ " missed " ]
2016-05-24 16:45:04 +00:00
end
2016-05-24 15:14:53 +00:00
def error_display
if analysed
2016-05-24 15:45:15 +00:00
if ! success
2016-05-24 16:45:04 +00:00
if student_missed && teacher_missed
2016-05-24 15:45:15 +00:00
" both the student and teacher missed the lesson "
2016-05-24 16:45:04 +00:00
elsif teacher_missed
2016-05-24 15:45:15 +00:00
" the teacher missed the lesson "
2016-05-24 16:45:04 +00:00
elsif student_missed
2016-05-24 15:45:15 +00:00
" the student missed the lesson "
else
" unknown error reason "
end
else
2016-05-28 23:48:40 +00:00
" because no error "
2016-05-24 15:45:15 +00:00
end
2016-05-24 15:14:53 +00:00
else
2016-05-24 15:45:15 +00:00
" not yet analysed "
2016-05-24 15:14:53 +00:00
end
end
2016-04-06 02:23:15 +00:00
def test_drive_completed
if ! sent_notices
if success
2018-02-25 22:28:12 +00:00
teacher_distributions . update_all ( ready : true ) # possibly there are 0 distributions on this lesson
2016-04-06 02:23:15 +00:00
student . test_drive_succeeded ( self )
else
student . test_drive_failed ( self )
end
2016-05-20 02:42:27 +00:00
self . lesson_booking . success = success
self . lesson_booking . status = STATUS_COMPLETED
self . lesson_booking . save ( :validate = > false )
2016-04-06 02:23:15 +00:00
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
if success
2016-05-31 16:13:49 +00:00
2016-04-06 02:23:15 +00:00
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
2016-07-17 15:16:27 +00:00
UserMailer . monthly_recurring_done ( self ) . deliver_now
2016-04-06 02:23:15 +00:00
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
2016-09-08 10:59:58 +00:00
if payment_if_school_on_school?
2016-05-12 21:29:27 +00:00
# bad session; just poke user
2016-07-17 15:16:27 +00:00
UserMailer . monthly_recurring_no_bill ( self ) . deliver_now
2016-05-12 21:29:27 +00:00
end
2016-04-06 02:23:15 +00:00
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
2016-09-08 10:59:58 +00:00
if payment_if_school_on_school?
2016-05-12 21:29:27 +00:00
# bad session; just poke user
2016-07-17 15:16:27 +00:00
UserMailer . student_lesson_normal_no_bill ( self ) . deliver_now
2016-05-12 21:29:27 +00:00
end
2016-04-06 02:23:15 +00:00
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
2016-09-08 10:59:58 +00:00
if payment_if_school_on_school?
2016-07-17 15:16:27 +00:00
UserMailer . student_lesson_normal_no_bill ( self ) . deliver_now
UserMailer . teacher_lesson_normal_no_bill ( self ) . deliver_now
2016-05-12 21:29:27 +00:00
end
2016-04-06 02:23:15 +00:00
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
2016-05-20 02:42:27 +00:00
self . lesson_booking . success = success
self . lesson_booking . status = STATUS_COMPLETED
self . lesson_booking . save ( :validate = > false )
2016-04-06 02:23:15 +00:00
end
def scheduled_start
2016-05-24 19:25:50 +00:00
if music_session
music_session . scheduled_start
else
raise " lesson session #{ id } has no music session in scheduled_start "
end
2016-04-06 02:23:15 +00:00
end
2017-07-17 00:38:40 +00:00
def send_counter_for_lesson ( countered_lesson , countered_slot )
2016-06-03 04:32:09 +00:00
if ! lesson_booking . errors . any?
if countered_slot . is_teacher_created?
2016-07-17 15:16:27 +00:00
UserMailer . student_lesson_counter ( countered_lesson , countered_slot ) . deliver_now
2016-06-03 04:32:09 +00:00
else
2016-07-17 15:16:27 +00:00
UserMailer . teacher_lesson_counter ( countered_lesson , countered_slot ) . deliver_now
2016-06-03 04:32:09 +00:00
end
2016-04-06 02:23:15 +00:00
end
self . countering = false
end
2017-07-17 00:38:40 +00:00
def send_counter_for_lesson_recurring ( countered_booking , countered_slot )
if ! countered_booking . errors . any?
if countered_slot . is_teacher_created?
UserMailer . student_lesson_counter_recurring ( countered_booking , countered_slot ) . deliver_now
else
UserMailer . teacher_lesson_counter_recurring ( countered_booking , countered_slot ) . deliver_now
end
end
self . countering = false
end
2016-04-06 02:23:15 +00:00
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
2018-01-14 16:12:15 +00:00
def is_unconfirmed?
status == STATUS_UNCONFIRMED
end
2016-04-21 14:23:29 +00:00
def analysis_json
2016-07-17 15:16:27 +00:00
@parsed_analysis || analysis
2016-04-21 14:23:29 +00:00
end
2016-04-06 02:23:15 +00:00
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
2016-06-01 00:20:03 +00:00
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
2016-06-03 04:32:09 +00:00
2016-04-06 02:23:15 +00:00
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
2016-05-21 03:13:39 +00:00
# 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
2017-07-17 00:38:40 +00:00
if ! slot . is_recurring? && self . slot . scheduled_time ( 0 ) < = duration . minutes . ago # let someone accept a very late meeting so that they can just hope in the session and do it
2016-05-21 03:13:39 +00:00
self . errors . add ( :slot , " is in the past " )
end
2016-04-06 02:23:15 +00:00
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
2016-05-28 23:48:40 +00:00
def validate_autocancel
if scheduled_start + ( duration * 60 ) > Time . now
self . errors . add ( :status , " This session is not in the past. " )
end
2017-07-17 00:38:40 +00:00
if self . status_was != STATUS_REQUESTED && ! self . lesson_booking . is_countered?
self . errors . add ( :status , " This session is #{ self . status_was } /booking= #{ lesson_booking . status } and can not be autocanceled " )
2016-05-28 23:48:40 +00:00
end
self . autocanceling = false
end
2016-05-29 19:37:54 +00:00
2016-04-06 02:23:15 +00:00
def validate_canceled
if ! is_canceled?
self . errors . add ( :status , " This session is already #{ self . status } . " )
end
# check 24 hour window
2017-07-17 00:38:40 +00:00
if ever_accepted? && ! student_short_canceled && ! teacher_short_canceled && scheduled_start . to_i - Time . now . to_i < 24 * 60 * 60
2016-04-06 02:23:15 +00:00
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
2016-05-11 21:03:55 +00:00
lesson_session . assigned_student = booking . student
2016-05-24 20:17:42 +00:00
lesson_session . user = booking . student
2016-09-27 02:56:12 +00:00
if booking . is_test_drive?
if booking . student . jamclass_credits > 0
lesson_session . lesson_package_purchase = booking . student . most_recent_posa_purchase
elsif booking . student . remaining_test_drives > 0
lesson_session . lesson_package_purchase = booking . student . most_recent_test_drive_purchase
end
2016-04-06 02:23:15 +00:00
end
lesson_session . save
if lesson_session . errors . any?
puts " Lesson Session errors #{ lesson_session . errors . inspect } "
2018-03-18 21:41:40 +00:00
else
begin
lesson_type = 'unknown'
if booking . is_test_drive?
if booking . student . first_onboarding_free_lesson_at
lesson_type = 'second free lesson'
else
lesson_type = 'first free lesson'
end
elsif lesson_session . slot . is_recurring?
lesson_type = 'monthly lesson'
else
lesson_type = 'single paid lesson'
end
2018-05-26 17:19:59 +00:00
updated = User . where ( id : booking . student . id ) . where ( 'first_lesson_booked_at is NULL' ) . update_all ( first_lesson_booked_at : Time . now )
if updated == 1
UserMailer . amazon_first_lesson_scheduled ( booking . student ) . deliver_now
end
2018-05-21 02:57:53 +00:00
2018-03-18 21:41:40 +00:00
body = " Student has requested a #{ lesson_type } ! \n \n User: #{ booking . student . name } , #{ booking . student . email } \n User Admin URL: #{ booking . student . admin_url } \n \n "
body += " Lesson Session URL: #{ lesson_session . admin_url } "
AdminMailer . ugly ( { to : APP_CONFIG . email_support_alias ,
subject : " Student ' #{ booking . student . name } ' requested #{ lesson_type } ! " ,
body : body } ) . deliver_now
2018-05-21 02:57:53 +00:00
rescue = > e
2018-03-18 21:41:40 +00:00
puts " Unable to alert that student requested lesson "
2018-05-21 02:57:53 +00:00
puts e
2018-03-18 21:41:40 +00:00
end
2016-04-06 02:23:15 +00:00
end
2018-03-18 21:41:40 +00:00
2016-04-06 02:23:15 +00:00
lesson_session
end
def student
music_session . creator
end
2016-05-09 21:47:55 +00:00
def student_id
music_session . creator . id
end
2016-04-06 02:23:15 +00:00
def self . index ( user , params = { } )
limit = params [ :per_page ]
limit || = 100
limit = limit . to_i
2016-05-29 19:37:54 +00:00
school_owner = user . owned_school && user . owned_school . scheduling_comm?
is_teacher = ! user . teacher . nil?
2016-04-21 14:23:29 +00:00
query = LessonSession . unscoped . joins ( [ :music_session , :lesson_booking ] ) . joins ( music_session : :creator )
#query = query.includes([:teacher, :music_session])
query = query . includes ( [ :music_session ] )
2016-04-06 02:23:15 +00:00
query = query . order ( 'music_sessions.scheduled_start DESC' )
2016-05-29 19:37:54 +00:00
2016-04-21 14:23:29 +00:00
if params [ :as_teacher ] . present?
2016-05-29 19:37:54 +00:00
if school_owner || params [ :as_teacher ] == true || params [ :as_teacher ] == " true "
2016-05-20 19:31:05 +00:00
2016-05-29 19:37:54 +00:00
# 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 } ') "
2016-05-20 19:31:05 +00:00
else
school_extra = ''
end
2016-05-29 19:37:54 +00:00
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
2016-06-03 04:32:09 +00:00
query = query . where ( 'lesson_sessions.teacher_id in (?)' + extra_teacher , user . owned_school . teachers . map { | t | t . user . id } )
2016-05-29 19:37:54 +00:00
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
2016-04-21 14:23:29 +00:00
else
2016-05-20 19:31:05 +00:00
if user . school_id
school_extra = " OR (lesson_bookings.school_id = ' #{ user . school_id } ') "
else
school_extra = ''
end
2016-04-21 14:23:29 +00:00
query = query . where ( 'music_sessions.user_id = ?' , user . id )
end
2016-04-06 02:23:15 +00:00
else
2016-05-20 19:31:05 +00:00
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 )
2016-05-16 16:39:20 +00:00
end
2016-10-03 02:51:34 +00:00
# only show 'fully booked lessons'; not those they can not possibly be paid for
2017-07-10 02:21:29 +00:00
query = query . where ( '(lesson_bookings.posa_card_id IS NOT NULL AND lesson_bookings.posa_card_purchased) OR lesson_bookings.card_presumed_ok = true OR (music_sessions.user_id = ?) ' + school_extra , user . id )
2016-04-06 02:23:15 +00:00
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
2016-04-21 14:23:29 +00:00
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
2016-04-06 02:23:15 +00:00
# teacher accepts the lesson
def accept ( params )
response = self
LessonSession . transaction do
message = params [ :message ]
slot = params [ :slot ]
accepter = params [ :accepter ]
2016-07-17 15:16:27 +00:00
raise " LessonBookingSlot " if slot . is_a? ( LessonBookingSlot )
2017-07-17 00:38:40 +00:00
slot = LessonBookingSlot . find ( slot )
2016-04-06 02:23:15 +00:00
self . slot . accept_message = message
2017-07-17 00:38:40 +00:00
self . slot . save
if ! slot . is_recurring?
self . slot = slot
self . accepting = true
self . status = STATUS_APPROVED
end
if slot . is_recurring? && ! approved_before?
# we need to approve the lessons, cuz the booking is going to be approved too with this.
#self.slot = slot
self . accepting = true
self . status = STATUS_APPROVED
end
2016-04-06 02:23:15 +00:00
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?
2016-09-27 02:56:12 +00:00
if student . jamclass_credits > 0
self . lesson_package_purchase = student . most_recent_posa_purchase
elsif student . remaining_test_drives > 0
self . lesson_package_purchase = student . most_recent_test_drive_purchase
end
2016-04-06 02:23:15 +00:00
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
2016-07-17 15:16:27 +00:00
UserMailer . student_lesson_accepted ( self , message , slot ) . deliver_now
UserMailer . teacher_lesson_accepted ( self , message , slot ) . deliver_now
2016-04-21 14:23:29 +00:00
message = '' if message . nil?
msg = ChatMessage . create ( teacher , nil , message , ChatMessage :: CHANNEL_LESSON , nil , student , self , " Lesson Approved " )
2016-04-06 02:23:15 +00:00
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
2017-07-17 00:38:40 +00:00
if slot . is_recurring?
2016-04-06 02:23:15 +00:00
if ! lesson_booking . accept ( self , slot , accepter )
response = lesson_booking
raise ActiveRecord :: Rollback
end
2016-04-21 14:23:29 +00:00
message = '' if message . nil?
msg = ChatMessage . create ( slot . proposer , nil , message , ChatMessage :: CHANNEL_LESSON , nil , slot . recipient , self , " All Lesson Times Updated " )
2016-04-06 02:23:15 +00:00
Notification . send_lesson_message ( 'accept' , self , true ) # TODO: this isn't quite an 'accept'
2016-07-17 15:16:27 +00:00
UserMailer . student_lesson_update_all ( self , message , slot ) . deliver_now
UserMailer . teacher_lesson_update_all ( self , message , slot ) . deliver_now
2016-04-06 02:23:15 +00:00
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
2016-04-21 14:23:29 +00:00
message = '' if message . nil?
msg = ChatMessage . create ( slot . proposer , nil , message , ChatMessage :: CHANNEL_LESSON , nil , slot . recipient , self , " Lesson Updated Time Approved " )
2016-07-17 15:16:27 +00:00
UserMailer . student_lesson_accepted ( self , message , slot ) . deliver_now
UserMailer . teacher_lesson_accepted ( self , message , slot ) . deliver_now
2016-04-06 02:23:15 +00:00
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 ]
slot . proposer = proposer
slot . lesson_session = self
slot . message = message
2017-07-17 00:38:40 +00:00
if ! slot . is_recurring?
self . countering = true
self . countering_flag = true
self . counterer = proposer
self . countered_at = Time . now
self . lesson_booking_slots << slot
self . countered_lesson = self
self . status = STATUS_COUNTERED
self . sent_counter_reminder = false
#if !update_all
self . counter_slot = slot
self . countered_slot = slot
end
2016-05-31 13:35:04 +00:00
#end
2016-04-06 02:23:15 +00:00
if self . save
2018-02-15 04:16:32 +00:00
proposer . update_timezone ( slot . timezone ) if proposer
2016-05-31 13:35:04 +00:00
#if update_all && !lesson_booking.counter(self, proposer, slot)
if ! lesson_booking . counter ( self , proposer , slot )
2016-04-21 14:23:29 +00:00
response = lesson_booking
raise ActiveRecord :: Rollback
2016-04-06 02:23:15 +00:00
end
else
response = self
raise ActiveRecord :: Rollback
end
2017-07-17 00:38:40 +00:00
if slot . is_recurring?
send_counter_for_lesson_recurring ( lesson_booking , lesson_booking . counter_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? )
else
send_counter_for_lesson ( @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
2016-04-06 02:23:15 +00:00
end
response
end
2018-01-27 22:18:04 +00:00
def cancel_lesson ( canceler , message , canceled_by_admin = false )
2016-04-21 14:23:29 +00:00
canceled_by_student = canceler == student
2017-07-17 00:38:40 +00:00
2016-04-21 14:23:29 +00:00
self . status = STATUS_CANCELED
self . cancel_message = message
2018-01-27 22:18:04 +00:00
self . canceler = canceler if ! canceled_by_admin
2016-04-21 14:23:29 +00:00
self . canceling = true
2018-01-27 22:18:04 +00:00
if canceled_by_admin
elsif canceled_by_student
2016-04-21 14:23:29 +00:00
self . student_canceled = true
self . student_canceled_at = Time . now
self . student_canceled_reason = message
2017-07-17 00:38:40 +00:00
self . student_short_canceled = in_no_cancel_window && ever_accepted?
2016-04-21 14:23:29 +00:00
else
self . teacher_canceled = true
self . teacher_canceled_at = Time . now
self . teacher_canceled_reason = message
2017-07-17 00:38:40 +00:00
self . teacher_short_canceled = in_no_cancel_window && ever_accepted?
2016-04-21 14:23:29 +00:00
end
end
2016-04-06 02:23:15 +00:00
2017-07-17 00:38:40 +00:00
def in_no_cancel_window
Time . now > scheduled_start - 24 . hours
end
2016-05-28 23:48:40 +00:00
# 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
2017-07-17 00:38:40 +00:00
@@log . error ( " unable to autocancel lesson #{ self . id } #{ self . errors . inspect } " )
2016-05-28 23:48:40 +00:00
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
2016-04-06 02:23:15 +00:00
# teacher accepts the lesson
def cancel ( params )
response = self
LessonSession . transaction do
2016-05-24 20:17:42 +00:00
self . status = LessonSession :: STATUS_CANCELED
2016-04-06 02:23:15 +00:00
canceler = params [ :canceler ]
2016-04-21 14:23:29 +00:00
canceled_by_student = canceler == student
other = canceled_by_student ? teacher : student
2016-04-06 02:23:15 +00:00
message = params [ :message ]
2016-04-21 14:23:29 +00:00
message = '' if message . nil?
2018-01-27 22:18:04 +00:00
if params [ :canceled_by_admin ]
self . canceled_by_admin = Time . now
end
2016-04-06 02:23:15 +00:00
2016-04-21 14:23:29 +00:00
if lesson_booking . recurring
2016-04-06 02:23:15 +00:00
update_all = params [ :update_all ]
else
2016-04-21 14:23:29 +00:00
update_all = true
2016-04-06 02:23:15 +00:00
end
2016-04-21 14:23:29 +00:00
if lesson_booking . is_test_drive?
student . test_drive_declined ( self )
end
2016-04-06 02:23:15 +00:00
2016-04-21 14:23:29 +00:00
if update_all
2018-01-27 22:18:04 +00:00
response = lesson_booking . cancel ( canceler , other , message , params [ :canceled_by_admin ] )
2016-04-21 14:23:29 +00:00
if response . errors . any?
raise ActiveRecord :: Rollback
2016-04-06 02:23:15 +00:00
end
2016-05-24 20:17:42 +00:00
if ! save
response = self
raise ActiveRecord :: Rollback
end
2016-04-06 02:23:15 +00:00
else
2018-01-27 22:18:04 +00:00
cancel_lesson ( canceler , message , params [ :canceled_by_admin ] )
2016-04-21 14:23:29 +00:00
if ! save
response = self
raise ActiveRecord :: Rollback
end
2016-04-06 02:23:15 +00:00
2018-01-27 22:18:04 +00:00
if canceled_by_admin
msg = ChatMessage . create ( student , nil , message , ChatMessage :: CHANNEL_LESSON , nil , teacher , self , " Lesson Canceled " )
msg = ChatMessage . create ( teacher , nil , message , ChatMessage :: CHANNEL_LESSON , nil , student , self , " Lesson Canceled " )
else
msg = ChatMessage . create ( canceler , nil , message , ChatMessage :: CHANNEL_LESSON , nil , other , self , " Lesson Canceled " )
end
2016-04-21 14:23:29 +00:00
Notification . send_lesson_message ( 'canceled' , self , false )
Notification . send_lesson_message ( 'canceled' , self , true )
2016-07-17 15:16:27 +00:00
UserMailer . student_lesson_canceled ( self , message ) . deliver_now
UserMailer . teacher_lesson_canceled ( self , message ) . deliver_now
2016-04-21 14:23:29 +00:00
end
2016-04-06 02:23:15 +00:00
end
response
end
def description ( lesson_booking )
2016-07-10 01:48:22 +00:00
lesson_booking . lesson_package_type . description ( lesson_booking ) + " with #{ teacher . admin_name } "
2016-04-06 02:23:15 +00:00
end
2018-01-11 12:00:32 +00:00
def time_for_admin
if self . scheduled_start
self . scheduled_start . to_date . strftime ( '%B %d, %Y' )
else
'TBD'
end
end
2018-02-25 22:28:12 +00:00
2016-05-11 21:03:55 +00:00
def timed_description
if is_test_drive?
2018-01-11 12:00:32 +00:00
" TestDrive session with #{ self . lesson_booking . student . name } on #{ time_for_admin } "
2016-05-11 21:03:55 +00:00
else
if self . lesson_booking . is_monthly_payment?
2018-01-11 12:00:32 +00:00
" Monthly Lesson with #{ self . lesson_booking . student . name } on #{ time_for_admin } "
2016-05-11 21:03:55 +00:00
else
2018-01-11 12:00:32 +00:00
" Lesson with #{ self . lesson_booking . student . name } on #{ time_for_admin } "
2016-05-11 21:03:55 +00:00
end
end
end
2018-01-27 22:18:04 +00:00
def cancel_by_admin
self . cancel ( {
2018-02-25 22:28:12 +00:00
canceler : nil ,
canceled_by_admin : true ,
message : 'Canceled by JamKazam administrator' ,
update_all : false
} )
2018-01-27 22:18:04 +00:00
end
def intervened
self . intervened_at = Time . now
self . save
end
2016-05-28 23:48:40 +00:00
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
2018-01-27 22:18:04 +00:00
def last_response_time
2018-02-25 22:28:12 +00:00
[ countered_at , lesson_booking . sent_notices_at ] . find { | x | ! x . nil? }
2016-05-28 23:48:40 +00:00
end
2016-05-11 21:03:55 +00:00
2016-04-06 02:23:15 +00:00
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
2016-04-21 14:23:29 +00:00
def chat_url
APP_CONFIG . external_root_url + " /client # /jamclass/chat-dialog/d1=lesson_ " + id
end
2016-04-06 02:23:15 +00:00
end
end