From 5701e888a82a05de4a99aa18cd8f82972125a956 Mon Sep 17 00:00:00 2001
From: Seth Call
+
+ <% if @message.present? %>
+
+ Your lesson with <%= @teacher.name %> is scheduled to begin on JamKazam in less than 30 minutes.
+
+ JAMCLASS HOME
+
+ Your lesson with <%= @student.name %> is scheduled to begin on JamKazam in less than 30 minutes.
+
+ JAMCLASS HOME
+
<%= @sender.name %> says:
+
<%= @message %>
+
+ <% end %>
+
Click the button below to view the entire lesson conversation.
<%= @message %>
<% end %>
-
Click the button below to get more information and to add this lesson to your calendar!
+
We strongly suggest adding this to your calendar so you don't forget it, or you'll end up paying for a lesson you don't get.
VIEW LESSON DETAILS diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_scheduled_jamclass_invitation.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_scheduled_jamclass_invitation.html.erb index 67590256e..f192b7b9c 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_scheduled_jamclass_invitation.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_scheduled_jamclass_invitation.html.erb @@ -8,4 +8,4 @@ <%= @session_date %>
- \ No newline at end of file + \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_welcome_message.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_welcome_message.html.erb index 88aacda6e..574aa3885 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_welcome_message.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_welcome_message.html.erb @@ -32,7 +32,7 @@ teacher for you. Finding the right teacher is the single most important determinant of success in your lessons. Would you marry the first person you ever dated? No? Same here. Pick 4 teachers who look great, and then see who you click with. It's a phenomenal value, and then you can stick with the best teacher for you. - Click this link + Click this link to sign up now for TestDrive. Then you can book 4 TestDrive lessons to get rolling. diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/teacher_lesson_accepted.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/teacher_lesson_accepted.html.erb index 7e76b8c0c..ab838c208 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/teacher_lesson_accepted.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/teacher_lesson_accepted.html.erb @@ -8,7 +8,7 @@ <% else %> This student has accepted your lesson request! <% end %> -VIEW LESSON DETAILS diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/teacher_scheduled_jamclass_invitation.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/teacher_scheduled_jamclass_invitation.html.erb index 67590256e..f192b7b9c 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/teacher_scheduled_jamclass_invitation.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/teacher_scheduled_jamclass_invitation.html.erb @@ -8,4 +8,4 @@ <%= @session_date %>
- \ No newline at end of file + \ No newline at end of file diff --git a/ruby/lib/jam_ruby/message_factory.rb b/ruby/lib/jam_ruby/message_factory.rb index 30763c028..53e63e9e3 100644 --- a/ruby/lib/jam_ruby/message_factory.rb +++ b/ruby/lib/jam_ruby/message_factory.rb @@ -993,14 +993,16 @@ module JamRuby end # creates the chat message - def chat_message(session_id, sender_name, sender_id, msg, msg_id, created_at, channel) + def chat_message(session_id, sender_name, sender_id, msg, msg_id, created_at, channel, lesson_session_id, purpose) chat_message = Jampb::ChatMessage.new( :sender_id => sender_id, :sender_name => sender_name, :msg => msg, :msg_id => msg_id, :created_at => created_at, - :channel => channel + :channel => channel, + :lesson_session_id => lesson_session_id, + :purpose => purpose ) if session_id diff --git a/ruby/lib/jam_ruby/models/affiliate_partner.rb b/ruby/lib/jam_ruby/models/affiliate_partner.rb index aaf05fc35..0461490f7 100644 --- a/ruby/lib/jam_ruby/models/affiliate_partner.rb +++ b/ruby/lib/jam_ruby/models/affiliate_partner.rb @@ -357,7 +357,7 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base UPDATE affiliate_quarterly_payments SET closed = TRUE, closed_at = NOW() - WHERE year < #{year} OR quarter < #{quarter} + WHERE year < #{year} OR (year = #{year} AND quarter < #{quarter}) } ActiveRecord::Base.connection.execute(sql) diff --git a/ruby/lib/jam_ruby/models/chat_message.rb b/ruby/lib/jam_ruby/models/chat_message.rb index 3c6b056db..7c14dbd84 100644 --- a/ruby/lib/jam_ruby/models/chat_message.rb +++ b/ruby/lib/jam_ruby/models/chat_message.rb @@ -16,26 +16,44 @@ module JamRuby belongs_to :user belongs_to :music_session belongs_to :target_user, class_name: "JamRuby::User" - belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking" + belongs_to :lesson_session, class_name: "JamRuby::LessonSession" validates :user, presence: true validates :message, length: {minimum: 1, maximum: 255}, no_profanity: true, unless: :ignore_message_checks - def self.create(user, music_session, message, channel, client_id, target_user = nil, lesson_booking = nil) + def self.create(user, music_session, message, channel, client_id, target_user = nil, lesson_session = nil, purpose = nil) chat_msg = ChatMessage.new chat_msg.user_id = user.id chat_msg.music_session_id = music_session.id if music_session chat_msg.message = message chat_msg.channel = channel chat_msg.target_user = target_user - chat_msg.lesson_booking = lesson_booking + chat_msg.lesson_session = lesson_session + chat_msg.purpose = purpose - if lesson_booking + + if lesson_session chat_msg.ignore_message_checks = true + + if user.id == lesson_session.student.id + lesson_session.teacher_unread_messages = true + Notification.send_lesson_message('chat', lesson_session, false, message) + else + lesson_session.student_unread_messages = true + Notification.send_lesson_message('chat', lesson_session, true, message) + end + + lesson_session.save(validate: false) + + # a nil purpose means 'normal chat', which is the only time we should send an email + if !target_user.online? && purpose.nil? && message.present? + UserMailer.lesson_chat(chat_msg).deliver! + + end end if chat_msg.save - ChatMessage.send_chat_msg music_session, chat_msg, user, client_id, channel + ChatMessage.send_chat_msg music_session, chat_msg, user, client_id, channel, lesson_session, purpose, target_user end chat_msg end @@ -60,6 +78,11 @@ module JamRuby query = ChatMessage.where('music_session_id = ?', music_session_id) end + if params.has_key? (:lesson_session) + lesson_session_id = params[:lesson_session] + query = ChatMessage.where('lesson_session_id = ?', lesson_session_id) + end + query = query.offset(start).limit(limit).order('created_at DESC').includes([:user]) if query.length == 0 @@ -71,7 +94,7 @@ module JamRuby end end - def send_chat_msg(music_session, chat_msg, user, client_id, channel) + def send_chat_msg(music_session, chat_msg, user, client_id, channel, lesson_session, purpose, target_user) music_session_id = music_session.id if music_session msg = @@message_factory.chat_message( @@ -81,14 +104,19 @@ module JamRuby chat_msg.message, chat_msg.id, chat_msg.created_at.utc.iso8601, - channel + channel, + lesson_session.id, + purpose ) if channel == 'session' @@mq_router.server_publish_to_session(music_session, msg, sender = {:client_id => client_id}) elsif channel == 'global' @@mq_router.publish_to_active_clients(msg) + elsif channel == 'lesson' + @@mq_router.publish_to_user(target_user.id, msg, sender = {:client_id => client_id}) end + end end end diff --git a/ruby/lib/jam_ruby/models/lesson_booking.rb b/ruby/lib/jam_ruby/models/lesson_booking.rb index 384009aaa..4d7be82b9 100644 --- a/ruby/lib/jam_ruby/models/lesson_booking.rb +++ b/ruby/lib/jam_ruby/models/lesson_booking.rb @@ -9,7 +9,7 @@ module JamRuby @@log = Logging.logger[LessonBooking] - attr_accessor :accepting, :countering, :countered_slot, :countered_lesson + attr_accessor :accepting, :countering, :canceling, :countered_slot, :countered_lesson STATUS_REQUESTED = 'requested' STATUS_CANCELED = 'canceled' @@ -37,12 +37,11 @@ module JamRuby belongs_to :teacher, class_name: "JamRuby::User" belongs_to :accepter, class_name: "JamRuby::User" belongs_to :canceler, class_name: "JamRuby::User" - belongs_to :default_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :default_slot_id, inverse_of: :defaulted_booking - belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_booking - - has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot" - has_many :lesson_sessions, class_name: "JamRuby::LessonSession" - has_many :lesson_package_purchases, class_name: "JamRuby::LessonPackagePurchase" + belongs_to :default_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :default_slot_id, inverse_of: :defaulted_booking, :dependent => :destroy + belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_booking, :dependent => :destroy + has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot", :dependent => :delete_all + has_many :lesson_sessions, class_name: "JamRuby::LessonSession", :dependent => :destroy + has_many :lesson_package_purchases, class_name: "JamRuby::LessonPackagePurchase", :dependent => :destroy validates :user, presence: true validates :teacher, presence: true @@ -63,6 +62,8 @@ module JamRuby validate :validate_lesson_length validate :validate_payment_style validate :validate_accepted, :if => :accepting + validate :validate_canceled, :if => :canceling + before_save :before_save @@ -136,6 +137,9 @@ module JamRuby def next_lesson if recurring session = lesson_sessions.joins(:music_session).where("scheduled_start is not null").where("scheduled_start > ?", Time.now).order(:created_at).first + if session.nil? + session = lesson_sessions[0] + end LessonSession.find(session.id) if session else lesson_sessions[0] @@ -390,6 +394,14 @@ module JamRuby self.accepting = false end + def validate_canceled + if !is_canceled? + self.errors.add(:status, "This session is already #{self.status}.") + end + + self.canceling = false + end + def send_notices UserMailer.student_lesson_request(self).deliver UserMailer.teacher_lesson_request(self).deliver @@ -500,41 +512,49 @@ module JamRuby def approved_before? !self.accepter_id.nil? end + def cancel(canceler, other, message) + self.canceling = true self.active = false self.status = STATUS_CANCELED self.cancel_message = message self.canceler = canceler success = save if success + lesson_sessions.past_cancel_window.each do |lesson_session| + lesson_session = LessonSession.find(lesson_session.id) # because .upcoming creates ReadOnly records + lesson_session.cancel_lesson(canceler, message) + if !lesson_session.save + return lesson_session + end + end if approved_before? # just tell both people it's cancelled, to act as confirmation Notification.send_lesson_message('canceled', next_lesson, false) Notification.send_lesson_message('canceled', next_lesson, true) UserMailer.student_lesson_booking_canceled(self, message).deliver UserMailer.teacher_lesson_booking_canceled(self, message).deliver - chat_message_prefix = "Lesson Canceled" + purpose = "Lesson Canceled" else if canceler == student # if it's the first time acceptance student canceling, we call it a 'cancel' Notification.send_lesson_message('canceled', next_lesson, false) UserMailer.teacher_lesson_booking_canceled(self, message).deliver - chat_message_prefix = "Lesson Canceled" + purpose = "Lesson Canceled" else # if it's the first time acceptance teacher, it was declined UserMailer.student_lesson_booking_declined(self, message).deliver Notification.send_lesson_message('declined', next_lesson, true) - chat_message_prefix = "Lesson Declined" + purpose = "Lesson Declined" end end - chat_message = message.nil? ? chat_message_prefix : "#{chat_message_prefix}: #{message}" - msg = ChatMessage.create(canceler, nil, chat_message, ChatMessage::CHANNEL_LESSON, nil, other, self) - + message = '' if message.nil? + msg = ChatMessage.create(canceler, nil, message, ChatMessage::CHANNEL_LESSON, nil, other, next_lesson, purpose) end - success + self end def card_approved @@ -641,7 +661,8 @@ module JamRuby end if lesson_booking_slots if lesson_booking.save - msg = ChatMessage.create(user, lesson_booking.lesson_sessions[0], description, ChatMessage::CHANNEL_LESSON, nil, teacher, lesson_booking) + description = '' if description.nil? + msg = ChatMessage.create(user, lesson_booking.lesson_sessions[0], description, ChatMessage::CHANNEL_LESSON, nil, teacher, lesson_booking.lesson_sessions[0], 'Lesson Requested') end end lesson_booking @@ -806,7 +827,6 @@ module JamRuby end end - def home_url APP_CONFIG.external_root_url + "/client#/jamclass" end diff --git a/ruby/lib/jam_ruby/models/lesson_session.rb b/ruby/lib/jam_ruby/models/lesson_session.rb index dec9512fd..28d27b90a 100644 --- a/ruby/lib/jam_ruby/models/lesson_session.rb +++ b/ruby/lib/jam_ruby/models/lesson_session.rb @@ -10,8 +10,8 @@ module JamRuby @@log = Logging.logger[LessonSession] - delegate :sent_billing_notices, :last_billing_attempt_at, :billing_attempts, :billing_should_retry, :billed, :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 - delegate :is_test_drive?, :is_single_free?, :is_normal?, :approved_before?, :is_active?, to: :lesson_booking + 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, to: :lesson_booking delegate :pretty_scheduled_start, to: :music_session @@ -30,16 +30,19 @@ module JamRuby 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" + 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 :lesson_package_purchase, class_name: "JamRuby::LessonPackagePurchase" belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking" - belongs_to :slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :slot_id + 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 + 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} @@ -69,6 +72,8 @@ module JamRuby 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 ) } def create_charge if !is_test_drive? @@ -96,23 +101,51 @@ module JamRuby self.save end + def music_session_id + music_session.id + end def self.hourly_check analyse_sessions complete_sessions end + def self.minutely_check + upcoming_sessions_reminder + end + def self.analyse_sessions - MusicSession.joins(lesson_session: :lesson_booking).where('session_removed_at IS NOT NULL').where('analysed = false').each do |music_session| + MusicSession.joins(lesson_session: :lesson_booking).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = false').each do |music_session| lession_session = music_session.lesson_session lession_session.analyse end end def self.complete_sessions - MusicSession.joins(lesson_session: [:lesson_booking, :lesson_payment_charge]).where('session_removed_at IS NOT NULL').where('analysed = true').where('lesson_sessions.post_processed = false').where('billing_should_retry = true').each do |music_session| + # this will find any paid session (recurring monthly paid, recurring single paid, single paid) + MusicSession.joins(lesson_session: [:lesson_booking, :lesson_payment_charge]).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = true').where('lesson_sessions.post_processed = false').where('billing_should_retry = true').each do |music_session| lession_session = music_session.lesson_session lession_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.lesson_type = ?', LESSON_TYPE_TEST_DRIVE).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").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('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 analyse @@ -127,6 +160,8 @@ module JamRuby self.analysed_at = Time.now self.analysed = true + self.status = STATUS_COMPLETED + if lesson_booking.requires_teacher_distribution?(self) self.teacher_distribution = TeacherDistribution.create_for_lesson(self) end @@ -137,6 +172,13 @@ module JamRuby end end + def billed + if lesson_booking.is_test_drive? + false + else + lesson_payment_charge.billed + end + end def amount_charged lesson_payment_charge.amount_in_cents / 100.0 @@ -163,6 +205,13 @@ module JamRuby json.to_json end + def send_starting_notice + UserMailer.lesson_starting_soon_student(self).deliver! + UserMailer.lesson_starting_soon_teacher(self).deliver! + + self.sent_starting_notice = true + self.save(validate: false) + end def session_completed LessonSession.transaction do self.lock! @@ -273,7 +322,7 @@ module JamRuby else if !sent_notices UserMailer.student_lesson_normal_no_bill(self).deliver - UserMailer.teacher_lesson_no_bill(self).deliver + UserMailer.teacher_lesson_normal_no_bill(self).deliver self.sent_notices = true self.sent_notices_at = Time.now self.post_processed = true @@ -314,10 +363,6 @@ module JamRuby status == STATUS_COMPLETED end - def is_missed? - status == STATUS_MISSED - end - def is_approved? status == STATUS_APPROVED end @@ -330,6 +375,10 @@ module JamRuby status == STATUS_COUNTERED end + def analysis_json + @parsed_analysis || analysis ? JSON.parse(analysis) : nil + end + def validate_creating if !is_requested? && !is_approved? self.errors.add(:status, "is not valid for a new lesson session.") @@ -398,16 +447,22 @@ module JamRuby limit ||= 100 limit = limit.to_i - query = LessonSession.joins(:music_session).joins(music_session: :creator) - query = query.includes([:teacher, :music_session]) + 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] - query = query.where('lesson_sessions.teacher_id = ?', user.id) + if params[:as_teacher].present? + if params[:as_teacher] + query = query.where('lesson_sessions.teacher_id = ?', user.id) + else + query = query.where('music_sessions.user_id = ?', user.id) + end else - query = query.where('music_sessions.user_id = ?', user.id) + 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 = ?)', user.id) current_page = params[:page].nil? ? 1 : params[:page].to_i next_page = current_page + 1 @@ -451,6 +506,17 @@ module JamRuby 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 @@ -480,8 +546,8 @@ module JamRuby end UserMailer.student_lesson_accepted(self, message, slot).deliver UserMailer.teacher_lesson_accepted(self, message, slot).deliver - chat_message = message ? "Lesson Approved: '#{message}'" : "Lesson Approved" - msg = ChatMessage.create(teacher, nil, chat_message, ChatMessage::CHANNEL_LESSON, nil, student, lesson_booking) + 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) @@ -501,8 +567,8 @@ module JamRuby response = lesson_booking raise ActiveRecord::Rollback end - chat_message = message ? "All Lesson Times Updated: '#{message}'" : "All Lesson Times Updated" - msg = ChatMessage.create(slot.proposer, nil, chat_message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, lesson_booking) + 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 UserMailer.teacher_lesson_update_all(self, message, slot).deliver @@ -515,8 +581,8 @@ module JamRuby puts("unable to accept slot #{slot.id} for lesson #{self.id} because it's in the past") raise ActiveRecord::Rollback end - chat_message = message ? "Lesson Updated Time Approved: '#{message}'" : "Lesson Updated Time Approved" - msg = ChatMessage.create(slot.proposer, nil, chat_message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, lesson_booking) + 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 UserMailer.teacher_lesson_accepted(self, message, slot).deliver end @@ -551,25 +617,42 @@ module JamRuby self.counter_slot = slot end if self.save - if update_all - if !lesson_booking.counter(self, proposer, slot) - response = lesson_booking - raise ActiveRecord::Rollback - end + if update_all && !lesson_booking.counter(self, proposer, slot) + response = lesson_booking + raise ActiveRecord::Rollback end else response = self raise ActiveRecord::Rollback end - - msg = ChatMessage.create(slot.proposer, music_session, message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, lesson_booking) + 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 # teacher accepts the lesson def cancel(params) @@ -577,39 +660,39 @@ module JamRuby LessonSession.transaction do canceler = params[:canceler] - other = canceler == teacher ? student : teacher + canceled_by_student = canceler == student + other = canceled_by_student ? teacher : student message = params[:message] + message = '' if message.nil? - if params[:update_all].present? + if lesson_booking.recurring update_all = params[:update_all] else - update_all = !lesson_booking.recurring + update_all = true end + if lesson_booking.is_test_drive? + student.test_drive_declined(self) + end - self.status = STATUS_CANCELED - self.cancel_message = message - self.canceler = canceler - self.canceling = true - - if self.save - if update_all - if !lesson_booking.cancel(canceler, other, message) - response = lesson_booking - raise ActiveRecord::Rollback - end - else - msg = ChatMessage.create(canceler, nil, message, ChatMessage::CHANNEL_LESSON, nil, other, lesson_booking) - Notification.send_lesson_message('canceled', self, false) - Notification.send_lesson_message('canceled', self, true) - UserMailer.student_lesson_canceled(self, message).deliver - UserMailer.teacher_lesson_canceled(self, message).deliver + if update_all + response = lesson_booking.cancel(canceler, other, message) + if response.errors.any? + raise ActiveRecord::Rollback end else - response = self - raise ActiveRecord::Rollback - end + 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 + UserMailer.teacher_lesson_canceled(self, message).deliver + end end response @@ -638,5 +721,9 @@ module JamRuby 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 diff --git a/ruby/lib/jam_ruby/models/lesson_session_analyser.rb b/ruby/lib/jam_ruby/models/lesson_session_analyser.rb index d1883a77d..0526ce385 100644 --- a/ruby/lib/jam_ruby/models/lesson_session_analyser.rb +++ b/ruby/lib/jam_ruby/models/lesson_session_analyser.rb @@ -1,7 +1,7 @@ module JamRuby class LessonSessionAnalyser - SUCCESS = 'success' + SUCCESS = 'success' SESSION_ONGOING = 'session_ongoing' THRESHOLD_MET = 'threshold_met' WAITED_CORRECTLY = 'waited_correctly' @@ -69,19 +69,19 @@ module JamRuby # spec: https://jamkazam.atlassian.net/wiki/display/PS/Product+Specification+-+JamClass#ProductSpecification-JamClass-TeacherReceives&RespondstoLessonBookingRequest - if music_session.session_removed_at.nil? + if music_session.session_removed_at.nil? || (music_session.scheduled_start + (lesson_session.duration * 60)) < Time.now reason = SESSION_ONGOING bill = false else - if lesson_session.is_canceled? && lesson_session.canceled_by_teacher? && lesson_session.canceled_late? - # If the lesson was cancelled less than 24 hours before the start time by the teacher, then we do not bill the student. - teacher = LATE_CANCELLATION - bill = false - elsif lesson_session.is_canceled? && lesson_session.canceled_by_student? && lesson_session.canceled_late? - # If the lesson was cancelled less than 24 hours before the start time by the student (if that is even possible, I can’t remember now), then we do bill the student. - student = LATE_CANCELLATION - bill = true - elsif together_analysis[:session_time] / 60 > APP_CONFIG.lesson_together_threshold_minutes + #if lesson_session.is_canceled? && lesson_session.canceled_by_teacher? && lesson_session.canceled_late? + # # If the lesson was cancelled less than 24 hours before the start time by the teacher, then we do not bill the student. + # teacher = LATE_CANCELLATION + # bill = false + #elsif lesson_session.is_canceled? && lesson_session.canceled_by_student? && lesson_session.canceled_late? + # # If the lesson was cancelled less than 24 hours before the start time by the student (if that is even possible, I can’t remember now), then we do bill the student. + # student = LATE_CANCELLATION + # bill = true + if together_analysis[:session_time] / 60 > APP_CONFIG.lesson_together_threshold_minutes bill = true reason = SUCCESS elsif teacher_analysis[:joined_on_time] && teacher_analysis[:waited_correctly] diff --git a/ruby/lib/jam_ruby/models/music_session.rb b/ruby/lib/jam_ruby/models/music_session.rb index d63c798db..14947dd74 100644 --- a/ruby/lib/jam_ruby/models/music_session.rb +++ b/ruby/lib/jam_ruby/models/music_session.rb @@ -875,7 +875,7 @@ SQL result end - def scheduled_start_date + def scheduled_start_date if self.scheduled_start_time.blank? "" else @@ -936,20 +936,16 @@ SQL end duration end - # should create a timestamp like: - # - # with_timezone = TRUE - # Tuesday, April 29, 8:00-9:00 PM TIMEZONE (where TIMEZONE is the TIMEZONE defined in the MusicSession when it was created) - # - # with_timezone = FALSE - # Thursday, July 10 - 10:00pm - # this should be in a helper - def pretty_scheduled_start(with_timezone = true) + + def pretty_scheduled_start(with_timezone = true, shorter = false) if scheduled_start && scheduled_duration start_time = scheduled_start timezone_display = 'UTC' + utc_offset_display = '00:00' tz_identifier, tz_display = MusicSession.split_timezone(timezone) + short_tz = 'GMT' + begin tz = TZInfo::Timezone.get(tz_identifier) rescue Exception => e @@ -960,6 +956,15 @@ SQL begin start_time = tz.utc_to_local(scheduled_start.utc) timezone_display = tz_display + utc_offset_hours = tz.current_period.utc_total_offset / (60*60) + hour = sprintf '%02d', utc_offset_hours.abs + minutes = sprintf '%02d', ((tz.current_period.utc_total_offset.abs % 3600) / 3600) * 60 + utc_offset_display = "#{utc_offset_hours < 0 ? '-' : ' '}#{hour}:#{minutes}" + short_tz = start_time.strftime("%Z") + if short_tz == 'UTC' + short_tz = 'GMT' + end + rescue Exception => e @@log.error("unable to convert #{scheduled_start} to #{tz}, e=#{e}") puts "unable to convert #{e}" @@ -969,7 +974,11 @@ SQL duration = safe_scheduled_duration 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} #{timezone_display}" + if shorter + "#{start_time.strftime("%a, %b %e %Y")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} (#{short_tz}#{utc_offset_display})" + else + "#{start_time.strftime("%A, %B %e")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} #{timezone_display}" + end else "#{start_time.strftime("%A, %B %e")} - #{start_time.strftime("%l:%M%P").strip}" end diff --git a/ruby/lib/jam_ruby/models/notification.rb b/ruby/lib/jam_ruby/models/notification.rb index d47ecff00..05298a1f5 100644 --- a/ruby/lib/jam_ruby/models/notification.rb +++ b/ruby/lib/jam_ruby/models/notification.rb @@ -63,7 +63,7 @@ module JamRuby end end - self.class.format_msg(self.description, {:user => source_user, target: target_user, :band => band, :session => session, purpose: purpose, student_directed: student_directed}) + self.class.format_msg(self.description, {:user => source_user, target: target_user, :band => band, :session => session, purpose: purpose, student_directed: student_directed, msg: message}) end # TODO: MAKE ALL METHODS BELOW ASYNC SO THE CLIENT DOESN'T BLOCK ON NOTIFICATION LOGIC @@ -135,6 +135,7 @@ module JamRuby session = options[:session] purpose = options[:purpose] student_directed = options[:student_directed] + msg = options[:msg] name, band_name = "" unless user.nil? @@ -271,6 +272,8 @@ module JamRuby end elsif purpose == 'reschedule' 'A lesson reschedule has been requested' + elsif purpose == 'chat' + notification_msg = "Lesson Message: #{msg}" end return notification_msg @@ -404,7 +407,7 @@ module JamRuby end end - def send_lesson_message(purpose, lesson_session, student_directed) + def send_lesson_message(purpose, lesson_session, student_directed, msg = nil) notification = Notification.new notification.description = NotificationTypes::LESSON_MESSAGE @@ -422,9 +425,11 @@ module JamRuby notification.session_id = lesson_session.music_session.id notification.lesson_session_id = lesson_session.id - notification_msg = format_msg(NotificationTypes::LESSON_MESSAGE, {purpose: purpose}) + notification_msg = format_msg(NotificationTypes::LESSON_MESSAGE, {purpose: purpose, msg: msg}) - #notification.message = notification_msg + if purpose == 'chat' + notification.message = msg + end notification.save @@ -756,7 +761,7 @@ module JamRuby end begin - UserMailer.teacher_scheduled_jamclass_invitation(music_session.lesson_session.teacher, notification_msg, music_session).deliver + #UserMailer.teacher_scheduled_jamclass_invitation(music_session.lesson_session.teacher, notification_msg, music_session).deliver rescue => e @@log.error("Unable to send SCHEDULED_JAMCLASS_INVITATION email to user #{music_session.lesson_session.teacher.email} #{e}") end @@ -796,7 +801,7 @@ module JamRuby end begin - UserMailer.student_scheduled_jamclass_invitation(student, notification_msg, music_session).deliver + #UserMailer.student_scheduled_jamclass_invitation(student, notification_msg, music_session).deliver rescue => e @@log.error("Unable to send SCHEDULED_JAMCLASS_INVITATION email to user #{student.email} #{e}") end @@ -1094,7 +1099,7 @@ module JamRuby # start in less than 24 hours, and haven't been # notified for a particular interval yet: def send_session_reminders - MusicSession.where("scheduled_start > NOW() AND scheduled_start <= (NOW()+INTERVAL '1 DAYS')").each do |candidate_session| + MusicSession.where("scheduled_start > NOW() AND scheduled_start <= (NOW()+INTERVAL '1 DAYS') AND lesson_session_id IS NULL").each do |candidate_session| tm = candidate_session.scheduled_start if (tm>(12.hours.from_now) && !notified?(candidate_session, NotificationTypes::SCHEDULED_SESSION_REMINDER_DAY)) # Send 24 hour reminders: diff --git a/ruby/lib/jam_ruby/models/sale_line_item.rb b/ruby/lib/jam_ruby/models/sale_line_item.rb index 61e753bbe..f5ca99b80 100644 --- a/ruby/lib/jam_ruby/models/sale_line_item.rb +++ b/ruby/lib/jam_ruby/models/sale_line_item.rb @@ -43,14 +43,17 @@ module JamRuby JamTrack.find_by_id(product_id) elsif product_type == GIFTCARD GiftCardType.find_by_id(product_id) + elsif product_type == LESSON + lesson_package_purchase else + raise 'unsupported product type' end end def product_info item = product - { name: product.name } if item + { name: product.name, product_type: product_type } if item end def state diff --git a/ruby/lib/jam_ruby/models/signup_hint.rb b/ruby/lib/jam_ruby/models/signup_hint.rb index 69640f35a..05ef88c1f 100644 --- a/ruby/lib/jam_ruby/models/signup_hint.rb +++ b/ruby/lib/jam_ruby/models/signup_hint.rb @@ -44,10 +44,25 @@ module JamRuby SignupHint.where("created_at < :week", {:week => 1.week.ago}).delete_all end - def self.most_recent_redirect(user, default) + def self.most_recent_redirect(user, default, queryParams=nil) + puts "jquery params" hint = SignupHint.where(user_id: user.id).order('created_at desc').first + if hint - hint.redirect_location + redirect = hint.redirect_location + puts "redirect #{redirect}" + uri = URI.parse(redirect) + bits = uri.query ? URI.decode_www_form(uri.query) : [] + if queryParams + queryParams.each do |k, v| + bits << [k, v] + end + end + + puts "bits #{bits}" + uri.query = URI.encode_www_form(bits) + puts "oh yeah #{uri.to_s}" + return uri.to_s else default end diff --git a/ruby/lib/jam_ruby/models/teacher.rb b/ruby/lib/jam_ruby/models/teacher.rb index a6982e944..5d0f583f9 100644 --- a/ruby/lib/jam_ruby/models/teacher.rb +++ b/ruby/lib/jam_ruby/models/teacher.rb @@ -145,7 +145,11 @@ module JamRuby def self.save_teacher(user, params) teacher = build_teacher(user, params) - teacher.save + if teacher.save + # flag the user as a teacher + teacher.user.is_a_teacher = true + teacher.user.save(validate: false) + end teacher end @@ -192,6 +196,7 @@ module JamRuby teacher.test_drives_per_week = params[:test_drives_per_week] if params.key?(:test_drives_per_week) teacher.school_id = params[:school_id] if params.key?(:school_id) + # Many-to-many relations: if params.key?(:genres) genres = params[:genres] @@ -317,7 +322,7 @@ module JamRuby end def has_stripe_billing? - false + user.has_stripe_connect? end def has_instruments_or_subject? diff --git a/ruby/lib/jam_ruby/models/teacher_distribution.rb b/ruby/lib/jam_ruby/models/teacher_distribution.rb index 585d8e61b..b124ed586 100644 --- a/ruby/lib/jam_ruby/models/teacher_distribution.rb +++ b/ruby/lib/jam_ruby/models/teacher_distribution.rb @@ -9,6 +9,28 @@ module JamRuby validates :teacher, presence: true validates :amount_in_cents, presence: true + def self.index(current_user, params) + limit = params[:per_page] + limit ||= 100 + limit = limit.to_i + + query = TeacherDistribution.where(teacher_id: current_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 self.create_for_lesson(lesson_session) distribution = create(lesson_session) distribution.lesson_session = lesson_session diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 3e187b491..de83de27b 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -1906,6 +1906,15 @@ module JamRuby !requested_test_drive(teacher).nil? end + def stripe_auth + user_authorizations.where(provider: "stripe_connect").first + end + + def has_stripe_connect? + auth = stripe_auth + auth && (!auth.token_expiration || auth.token_expiration > Time.now) + end + def fetch_stripe_customer Stripe::Customer.retrieve(stripe_customer_id) end @@ -1931,7 +1940,7 @@ module JamRuby customer end - def card_approved(token, zip) + def card_approved(token, zip, booking_id) approved_booking = nil User.transaction do @@ -1941,10 +1950,11 @@ module JamRuby self.stripe_customer_id = customer.id self.stored_credit_card = true if self.save - # we can also 'unlock' any booked sessions that still need to be done so - LessonBooking.unprocessed(self).each do |booking| - booking.card_approved - approved_booking = booking + if booking_id + approved_booking = LessonBooking.find_by_id(booking_id) + if approved_booking + approved_booking.card_approved + end end end end @@ -1985,7 +1995,7 @@ module JamRuby end end - booking = card_approved(params[:token], params[:zip]) + booking = card_approved(params[:token], params[:zip], params[:booking_id]) if params[:test_drive] self.reload result = Sale.purchase_test_drive(self, booking) @@ -1995,10 +2005,9 @@ module JamRuby self.reload end - intent = TeacherIntent.recent_test_drive(self) end - {lesson: booking, test_drive: test_drive, intent:intent, purchase: purchase} + {lesson: booking, test_drive: test_drive, purchase: purchase} end def requested_test_drive(teacher = nil) @@ -2031,6 +2040,12 @@ module JamRuby end end + def test_drive_declined(lesson_session) + # because we decrement test_drive credits as soon as you book, we need to bring it back now + self.remaining_test_drives = self.remaining_test_drives + 1 + self.save(validate: false) + end + def test_drive_failed(lesson_session) # because we decrement test_drive credits as soon as you book, we need to bring it back now self.remaining_test_drives = self.remaining_test_drives + 1 diff --git a/ruby/lib/jam_ruby/resque/scheduled/minutely_job.rb b/ruby/lib/jam_ruby/resque/scheduled/minutely_job.rb new file mode 100644 index 000000000..ad9df11a3 --- /dev/null +++ b/ruby/lib/jam_ruby/resque/scheduled/minutely_job.rb @@ -0,0 +1,16 @@ +module JamRuby + class MinutelyJob + extend Resque::Plugins::JamLonelyJob + + @queue = :scheduled_minutely_job + @@log = Logging.logger[MinutelyJob] + + def self.perform + @@log.debug("waking up") + + LessonSession.minutely_check + + @@log.debug("done") + end + end +end diff --git a/ruby/spec/jam_ruby/flows/monthly_recurring_lesson_spec.rb b/ruby/spec/jam_ruby/flows/monthly_recurring_lesson_spec.rb index 8dc2eb3e6..2f3935b42 100644 --- a/ruby/spec/jam_ruby/flows/monthly_recurring_lesson_spec.rb +++ b/ruby/spec/jam_ruby/flows/monthly_recurring_lesson_spec.rb @@ -116,7 +116,7 @@ describe "Monthly Recurring Lesson Flow" do # puts del.inspect end # get acceptance emails, as well as 'your stuff is accepted' - UserMailer.deliveries.length.should eql 6 + UserMailer.deliveries.length.should eql 2 lesson_session.errors.any?.should be_false lesson_session.reload lesson_session.slot.should eql student_counter @@ -126,9 +126,10 @@ describe "Monthly Recurring Lesson Flow" do lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0) booking.status.should eql LessonBooking::STATUS_APPROVED - UserMailer.deliveries.length.should eql 6 + UserMailer.deliveries.length.should eql 2 chat = ChatMessage.unscoped.order(:created_at).last - chat.message.should eql "Lesson Approved: 'Yeah I got this'" + chat.message.should eql 'Yeah I got this' + chat.purpose.should eql 'Lesson Approved' chat.channel.should eql ChatMessage::CHANNEL_LESSON chat.user.should eql teacher_user chat.target_user.should eql user @@ -185,7 +186,7 @@ describe "Monthly Recurring Lesson Flow" do lesson_purchase.sale_line_item.should eql line_item TeacherPayment.count.should eql 0 - TeacherPayment.daily_check + TeacherPayment.hourly_check teacher_distribution.reload teacher_distribution.distributed.should be_true TeacherPayment.count.should eql 1 diff --git a/ruby/spec/jam_ruby/flows/normal_lesson_spec.rb b/ruby/spec/jam_ruby/flows/normal_lesson_spec.rb index fa408e64d..9bb60d232 100644 --- a/ruby/spec/jam_ruby/flows/normal_lesson_spec.rb +++ b/ruby/spec/jam_ruby/flows/normal_lesson_spec.rb @@ -71,9 +71,10 @@ describe "Normal Lesson Flow" do lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0) booking.status.should eql LessonBooking::STATUS_APPROVED - UserMailer.deliveries.length.should eql 4 + UserMailer.deliveries.length.should eql 2 chat = ChatMessage.unscoped.order(:created_at).last - chat.message.should eql "Lesson Approved: 'Yeah I got this'" + chat.message.should eql 'Yeah I got this' + chat.purpose.should eql 'Lesson Approved' chat.channel.should eql ChatMessage::CHANNEL_LESSON chat.user.should eql teacher_user chat.target_user.should eql user @@ -351,9 +352,10 @@ describe "Normal Lesson Flow" do lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0) booking.status.should eql LessonBooking::STATUS_APPROVED - UserMailer.deliveries.length.should eql 4 + UserMailer.deliveries.length.should eql 2 chat = ChatMessage.unscoped.order(:created_at).last - chat.message.should eql "Lesson Approved: 'Yeah I got this'" + chat.message.should eql 'Yeah I got this' + chat.purpose.should eql 'Lesson Approved' chat.channel.should eql ChatMessage::CHANNEL_LESSON chat.user.should eql teacher_user chat.target_user.should eql user diff --git a/ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb b/ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb index 3946eecdc..f91871129 100644 --- a/ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb +++ b/ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb @@ -107,7 +107,7 @@ describe "Recurring Lesson Flow" do # puts del.inspect end # get acceptance emails, as well as 'your stuff is accepted' - UserMailer.deliveries.length.should eql 6 + UserMailer.deliveries.length.should eql 2 lesson_session.errors.any?.should be_false lesson_session.reload lesson_session.slot.should eql student_counter @@ -117,9 +117,10 @@ describe "Recurring Lesson Flow" do lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0) booking.status.should eql LessonBooking::STATUS_APPROVED - UserMailer.deliveries.length.should eql 6 + UserMailer.deliveries.length.should eql 2 chat = ChatMessage.unscoped.order(:created_at).last - chat.message.should eql "Lesson Approved: 'Yeah I got this'" + chat.message.should eql "Yeah I got this" + chat.purpose.should eql 'Lesson Approved' chat.channel.should eql ChatMessage::CHANNEL_LESSON chat.user.should eql teacher_user chat.target_user.should eql user diff --git a/ruby/spec/jam_ruby/flows/testdrive_lesson_spec.rb b/ruby/spec/jam_ruby/flows/testdrive_lesson_spec.rb index e9545cfbe..2fca45714 100644 --- a/ruby/spec/jam_ruby/flows/testdrive_lesson_spec.rb +++ b/ruby/spec/jam_ruby/flows/testdrive_lesson_spec.rb @@ -134,7 +134,7 @@ describe "TestDrive Lesson Flow" do lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0) booking.status.should eql LessonBooking::STATUS_APPROVED - UserMailer.deliveries.length.should eql 4 + UserMailer.deliveries.length.should eql 2 chat = ChatMessage.unscoped.order(:created_at).last chat.message.should eql 'Yeah I got this' chat.channel.should eql ChatMessage::CHANNEL_LESSON @@ -145,7 +145,7 @@ describe "TestDrive Lesson Flow" do notification.student_directed.should eql true notification.purpose.should eql 'accept' notification.description.should eql NotificationTypes::LESSON_MESSAGE - notification.message.should eql "Your lesson request is confirmed!" + notification.message.should be_nil # teacher & student get into session @@ -194,7 +194,7 @@ describe "TestDrive Lesson Flow" do teacher_distribution.distributed.should be_false TeacherPayment.count.should eql 0 - TeacherPayment.daily_check + TeacherPayment.hourly_check TeacherPayment.count.should eql 1 lesson_session.reload diff --git a/ruby/spec/jam_ruby/models/affiliate_partner_spec.rb b/ruby/spec/jam_ruby/models/affiliate_partner_spec.rb index 99fd536a5..d5f080cfc 100644 --- a/ruby/spec/jam_ruby/models/affiliate_partner_spec.rb +++ b/ruby/spec/jam_ruby/models/affiliate_partner_spec.rb @@ -783,6 +783,22 @@ describe AffiliatePartner do end end + describe "edge case" do + it "year change" do + partner.touch + last_day_of_year = Date.new(2015, 12, 31) + first_day_of_next_year = Date.new(2016, 01, 01) + AffiliatePartner.tally_up(last_day_of_year) + AffiliatePartner.tally_up(first_day_of_next_year) + quarterly_payment = AffiliateQuarterlyPayment.where(year: 2016, quarter: 0, affiliate_partner_id: partner.id).first + quarterly_payment.closed.should be_false + AffiliatePartner.tally_up(Date.new(2016, 01, 02)) + quarterly_payment.reload + quarterly_payment.closed.should be_false + end + end + + describe "boundary_dates_for_month" do it "invalid month" do expect{AffiliatePartner.boundary_dates_for_month(2015, 0)}.to raise_error diff --git a/ruby/spec/jam_ruby/models/lesson_booking_spec.rb b/ruby/spec/jam_ruby/models/lesson_booking_spec.rb index ac64a0f3a..4a17aea5b 100644 --- a/ruby/spec/jam_ruby/models/lesson_booking_spec.rb +++ b/ruby/spec/jam_ruby/models/lesson_booking_spec.rb @@ -426,7 +426,7 @@ describe LessonBooking do booking.errors.any?.should be false - chat_message = ChatMessage.where(lesson_booking_id: booking.id).first + chat_message = ChatMessage.where(lesson_session_id: booking.next_lesson.id).first chat_message.should_not be_nil chat_message.message.should eq booking.description end @@ -488,7 +488,7 @@ describe LessonBooking do booking.lesson_type.should eq LessonBooking::LESSON_TYPE_TEST_DRIVE booking.lesson_booking_slots.length.should eq 2 - chat_message = ChatMessage.where(lesson_booking_id: booking.id).first + chat_message = ChatMessage.where(lesson_session_id: booking.next_lesson.id).first chat_message.should_not be_nil chat_message.message.should eq booking.description @@ -502,7 +502,7 @@ describe LessonBooking do booking.errors.any?.should be false - chat_message = ChatMessage.where(lesson_booking_id: booking.id).first + chat_message = ChatMessage.where(lesson_session_id: booking.next_lesson.id).first chat_message.should_not be_nil chat_message.message.should eq booking.description end @@ -549,7 +549,7 @@ describe LessonBooking do booking.lesson_type.should eq LessonBooking::LESSON_TYPE_PAID booking.lesson_booking_slots.length.should eq 2 - chat_message = ChatMessage.where(lesson_booking_id: booking.id).first + chat_message = ChatMessage.where(lesson_session_id: booking.next_lesson.id).first chat_message.should_not be_nil chat_message.message.should eq booking.description @@ -570,7 +570,7 @@ describe LessonBooking do booking.lesson_type.should eq LessonBooking::LESSON_TYPE_PAID booking.lesson_booking_slots.length.should eq 2 - chat_message = ChatMessage.where(lesson_booking_id: booking.id).first + chat_message = ChatMessage.where(lesson_session_id: booking.next_lesson.id).first chat_message.should_not be_nil chat_message.message.should eq booking.description @@ -579,12 +579,12 @@ describe LessonBooking do user.remaining_test_drives.should eq 1 end - it "allows long message to flow through chat" do + it "allows long message to flow through chat" do booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, Faker::Lorem.characters(10000), true, LessonBooking::PAYMENT_STYLE_WEEKLY, 60) booking.errors.any?.should be false - chat_message = ChatMessage.where(lesson_booking_id: booking.id).first + chat_message = ChatMessage.where(lesson_session_id: booking.next_lesson.id).first chat_message.should_not be_nil chat_message.message.should eq booking.description end @@ -709,6 +709,7 @@ describe LessonBooking do Timecop.freeze(7.days.ago) lesson_session.cancel({canceler: teacher_user, message: 'meh', slot: booking.default_slot.id, update_all: false}) lesson_session.errors.any?.should be_false + lesson_session.reload lesson_session.status.should eql LessonSession::STATUS_CANCELED lesson_session.reload booking.reload @@ -732,8 +733,8 @@ describe LessonBooking do Timecop.freeze(7.days.ago) lesson_session.cancel({canceler: user, message: 'meh', slot: booking.default_slot.id, update_all: false}) lesson_session.errors.any?.should be_false - lesson_session.status.should eql LessonSession::STATUS_CANCELED lesson_session.reload + lesson_session.status.should eql LessonSession::STATUS_CANCELED booking.reload booking.status.should eql LessonSession::STATUS_CANCELED UserMailer.deliveries.length.should eql 2 @@ -784,8 +785,8 @@ describe LessonBooking do Timecop.freeze(7.days.ago) lesson_session.cancel({canceler: user, message: 'meh', slot: booking.default_slot.id, update_all: true}) lesson_session.errors.any?.should be_false - lesson_session.status.should eql LessonSession::STATUS_CANCELED lesson_session.reload + lesson_session.status.should eql LessonSession::STATUS_CANCELED booking.reload booking.status.should eql LessonSession::STATUS_CANCELED booking.canceler.should eql user diff --git a/ruby/spec/jam_ruby/models/lesson_session_spec.rb b/ruby/spec/jam_ruby/models/lesson_session_spec.rb index e1470bdff..bbf60d93a 100644 --- a/ruby/spec/jam_ruby/models/lesson_session_spec.rb +++ b/ruby/spec/jam_ruby/models/lesson_session_spec.rb @@ -7,7 +7,7 @@ describe LessonSession do let(:slot1) { FactoryGirl.build(:lesson_booking_slot_single) } let(:slot2) { FactoryGirl.build(:lesson_booking_slot_single) } - let(:lesson_booking) {LessonBooking.book_normal(user, teacher, [slot1, slot2], "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60)} + let(:lesson_booking) {b = LessonBooking.book_normal(user, teacher, [slot1, slot2], "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60); b.card_presumed_ok = true; b.save!; b} let(:lesson_session) {lesson_booking.lesson_sessions[0]} describe "accept" do @@ -16,10 +16,28 @@ describe LessonSession do end end + describe "upcoming_sessions_reminder" do + it "succeeds" do + UserMailer.deliveries.clear + LessonSession.upcoming_sessions_reminder + lesson_session.touch + lesson_session.sent_starting_notice.should be_false + lesson_session.is_requested?.should be_true + lesson_session.music_session.scheduled_start = 15.minutes.from_now + lesson_session.music_session.save! + LessonSession.upcoming_sessions_reminder + UserMailer.deliveries.count.should eql 2 + UserMailer.deliveries.clear + lesson_session.reload + lesson_session.sent_starting_notice.should be_true + LessonSession.upcoming_sessions_reminder + UserMailer.deliveries.count.should eql 0 + end + end + describe "index" do it "finds single lesson as student" do - lesson_booking.touch lesson_session.touch lesson_session.music_session.creator.should eql lesson_session.lesson_booking.user lesson_session.lesson_booking.teacher.should eql teacher diff --git a/ruby/spec/jam_ruby/models/teacher_distribution_spec.rb b/ruby/spec/jam_ruby/models/teacher_distribution_spec.rb new file mode 100644 index 000000000..dbf54d562 --- /dev/null +++ b/ruby/spec/jam_ruby/models/teacher_distribution_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe TeacherDistribution do + + let(:teacher) {FactoryGirl.create(:teacher_user)} + + + describe "index" do + it "empty" do + TeacherDistribution.index(teacher, {})[:query].count.should eql 0 + end + + it "returns single" do + distribution = FactoryGirl.create(:teacher_distribution, teacher: teacher) + + TeacherDistribution.index(teacher, {})[:query].count.should eql 1 + + distribution = FactoryGirl.create(:teacher_distribution) # some random teacher + + TeacherDistribution.index(teacher, {})[:query].count.should eql 1 + end + end +end diff --git a/ruby/spec/mailers/render_emails_spec.rb b/ruby/spec/mailers/render_emails_spec.rb index 9fe7a4e37..659e172ac 100644 --- a/ruby/spec/mailers/render_emails_spec.rb +++ b/ruby/spec/mailers/render_emails_spec.rb @@ -49,6 +49,7 @@ describe "RenderMailers", :slow => true do @filename = "teacher_welcome_message" UserMailer.teacher_welcome_message(teacher).deliver end + it "teacher_lesson_request" do @filename = "teacher_lesson_request" @@ -120,6 +121,22 @@ describe "RenderMailers", :slow => true do UserMailer.deliveries.clear UserMailer.teacher_lesson_completed(lesson).deliver end + + it "lesson_starting_soon_teacher" do + @filename = "lesson_starting_soon_teacher" + lesson = testdrive_lesson(user, teacher) + + UserMailer.deliveries.clear + UserMailer.lesson_starting_soon_teacher(lesson).deliver + end + + it "lesson_starting_soon_student" do + @filename = "lesson_starting_soon_student" + lesson = testdrive_lesson(user, teacher) + + UserMailer.deliveries.clear + UserMailer.lesson_starting_soon_student(lesson).deliver + end end end diff --git a/web/Gemfile b/web/Gemfile index bde372771..b78176c6f 100644 --- a/web/Gemfile +++ b/web/Gemfile @@ -159,6 +159,7 @@ group :test, :cucumber do # gem 'growl', '1.0.3' gem 'poltergeist' gem 'resque_spec' + gem 'timecop' #gem 'thin' end diff --git a/web/app/assets/images/content/icon_unread_mail.png b/web/app/assets/images/content/icon_unread_mail.png new file mode 100644 index 0000000000000000000000000000000000000000..32f098bf51fed66a04697dafa25dcdd953aa9205 GIT binary patch literal 1537 zcmV+c2LAbpP)| DATE | +METHOD | +DESCRIPTION | +STATUS | +AMOUNT | +
|---|
We’re sorry, but you cannot reschedule this recurring lesson right now because it is less than 24 hours before the lesson start time. This is not allowed in the terms of service because the instructor cannot reasonably be expected to be able to backfill the time slot that has been lost. You may reschedule this recurring lesson anytime starting from the end of this next scheduled lesson, so please plan to do it then.
") + else + context.JK.Banner.showAlert("Policy Issue", "We’re sorry, but you cannot reschedule this recurring lesson right now because it is less than 24 hours before the lesson start time. This is not allowed in the terms of service. You may reschedule this recurring lesson anytime starting from the end of this next scheduled lesson, so please plan to do it then.
") + else + if @viewerStudent() + context.JK.Banner.showAlert("Policy Issue", "We’re sorry, but you cannot reschedule a lesson less than 24 hours before the lesson start time. This is not allowed in the terms of service because the instructor cannot reasonably be expected to be able to backfill the time slot that has been lost.
") + else + context.JK.Banner.showAlert("Policy Issue", "We’re sorry, but you cannot reschedule a lesson less than 24 hours before the lesson start time. This is not allowed in the terms of service.
") + else + @app.ajaxError(jqXHR) + )) + + refreshLesson: (lessonId) -> + rest.getLesson({id: lessonId}).done((response) => @refreshLessonDone(response)).fail((jqXHR) => @refreshLessonFail(jqXHR)) + + refreshLessonDone: (lesson_session) -> + @updateLessonState(lesson_session) + + refreshLessonFail: (jqXHR) -> + @app.ajaxError(jqXHR) + + issueCancelLesson: (lesson, update_all) -> + request = {} + request.message = '' + request.id = lesson.lesson_booking_id + request.lesson_session_id = lesson.id + request.update_all = update_all + + rest.cancelLessonBooking(request).done((response) => @cancelLessonBookingDone(response, lesson)).fail((response) => @cancelLessonBookingFail(response)) + + cancelLessonBookingDone: (booking, lesson) -> + if booking.focused_lesson.teacher_short_canceled + if @teacherViewing() + context.JK.Banner.showAlert('late cancellation warning', 'Cancelling a lesson less than 24 hours before it’s scheduled to start should be avoided, as it’s an inconvenience to the student. Repeated violations of this policy will negatively affect your teacher score.') + + @refreshLesson(lesson.id) + + cancelLessonBookingFail: (jqXHR) -> + @app.ajaxError(jqXHR) + + cancelSelected: (lesson, recurring) -> + rest.checkLessonCancel({id: lesson.id, update_all: recurring}).done((response) => (@issueCancelLesson(lesson, recurring))).fail((jqXHR) => (@cancelSelectedFail(jqXHR))) + + cancelSelectedFailed: (jqXHR) -> + if jqXHR.status == 422 + + if recurring + if @viewerStudent() + buttons = [] + buttons.push({name: 'CLOSE', buttonStyle: 'button-grey'}) + buttons.push({name: 'CANCEL RECURRING LESSONS', buttonStyle: 'button-orange', click:(() => (@issueCancelLesson(lesson, true)))}) + context.JK.Banner.show({title: "Policy Issue", html: "You may cancel this recurring series of lessons, but it is too late to cancel the next scheduled lesson because it is less than 24 hours before the lesson start time. This is not allowed in the terms of service because the instructor cannot reasonably be expected to be able to backfill the time slot that has been lost.", buttons: buttons}) + else + # path should not be taken + context.JK.Banner.showAlert("Policy Issue", "We’re sorry, but you cannot cancel a lesson less than 24 hours before the lesson start time. This is not allowed in the terms of service because the instructor cannot reasonably be expected to be able to backfill the time slot that has been lost.
") + else + if @viewerStudent() + context.JK.Banner.showAlert("Policy Issue", "We’re sorry, but you cannot cancel a lesson less than 24 hours before the lesson start time. This is not allowed in the terms of service because the instructor cannot reasonably be expected to be able to backfill the time slot that has been lost.
") + else + # path should not be taken + context.JK.Banner.showAlert("Policy Issue", "We’re sorry, but you cannot cancel a lesson less than 24 hours before the lesson start time. This is not allowed in the terms of service.
") + else + @app.ajaxError(jqXHR) + + rescheduleLesson: (lesson) -> + if lesson.recurring + buttons = [] + buttons.push({name: 'THIS SESSION', buttonStyle: 'button-orange', click:(() => (@rescheduleSelected(lesson, false)))}) + buttons.push({name: 'ALL SESSIONS', buttonStyle: 'button-orange', click:(() => (@rescheduleSelected(lesson, true)))}) + context.JK.Banner.show({title: 'Rescheduling Selection', html:'Do you wish to all reschedule all lessons or just the one selected?', buttons: buttons}) + else + @rescheduleSelected(lesson, false) + + + cancelLesson: (lesson) -> + + if lesson.isRequested + confirmTitle = 'Confirm Decline' + verbLower = 'decline' + else + confirmTitle = 'Confirm Cancelation' + verbLower = 'cancel' + if !lesson.isRequested || lesson.recurring + buttons = [] + buttons.push({name: 'CANCEL', buttonStyle: 'button-grey'}) + buttons.push({name: 'THIS SESSION', buttonStyle: 'button-orange', click:(() => (@cancelSelected(lesson, false)))}) + buttons.push({name: 'ALL SESSIONS', buttonStyle: 'button-orange', click:(() => (@cancelSelected(lesson, true)))}) + context.JK.Banner.show({title: 'Select One', html:"Do you wish to all #{verbLower} all lessons or just the one selected?", buttons: buttons}) + else + context.JK.Banner.showYesNo({title: confirmTitle, html:"Are you sure you want to #{verbLower} this lesson?", yes: () =>(@cancelSelected(lesson, lesson.recurring))}) + + getInitialState: () -> + { + user: null, + } + + beforeHide: (e) -> + + + beforeShow: (e) -> + + afterShow: (e) -> + @checkStripeSuccessReturn() + @setState({updating: true}) + rest.getLessonSessions().done((response) => @jamClassLoaded(response)).fail((jqXHR) => @failedJamClassLoad(jqXHR)) + + checkStripeSuccessReturn: () -> + if $.QueryString['stripe-success']? + if $.QueryString['stripe-success'] == 'true' + context.JK.Banner.showNotice('stripe connected', 'Congratulations, you have successfully connected your Stripe account, so payments for student lessons can now be processed.') + + if window.history.replaceState #ie9 proofing + window.history.replaceState({}, "", "/client#/jamclass") + + resetState: () -> + @setState({updating: false, lesson: null}) + + jamClassLoaded: (response) -> + @setState({updating: false}) + + @postProcess(response) + + @setState({lesson_sessions: response}) + + failedJamClassLoad: (jqXHR) -> + @setState({updating: false}) + if jqXHR.status == 404 + @app.layout.notify({title: "Unable to load JamClass info", text: "Try refreshing the web page"}) + + lessons: () -> + if @state.lesson_sessions? + @state.lesson_sessions.entries + else + [] + + postProcess: (data) -> + + for lesson in data.entries + + # calculate: + # .other (user object), + # .me (user object), + # other/me.musician_profile (link to musician profile) + # other/me.resolved_photo_url + # .hasUnreadMessages + # .isRequested (doesn't matter if countered or not). but can't be done + # .isScheduled (doesn't matter if countered or not). + @postProcessLesson(lesson) + + onReadMessage: (lesson_session, e) -> + e.preventDefault() + + data = {id: lesson_session.id} + data["#{this.myRole()}_unread_messages"] = false + + rest.updateLessonSessionUnreadMessages(data).done((response) => @updatedLessonSessionReadDone(response)).fail((jqXHR) => @updatedLessonSessionReadFail(jqXHR)) + + updateLessonState: (lesson_session) -> + @postProcessLesson(lesson_session) + + for lesson in @lessons() + if lesson.id == lesson_session.id + $.extend(lesson, lesson_session) + @setState({lesson_sessions: this.state.lesson_sessions}) + + updatedLessonSessionReadDone: (lesson_session) -> + # update lesson session data in local state + + @updateLessonState(lesson_session) + + @app.layout.showDialog('chat-dialog', {d1: 'lesson_' + lesson_session.id}) + + + updatedLessonSessionReadFail: (jqXHR) -> + @app.ajaxError(jqXHR) + + + openMenu: (lesson, e) -> + $this = $(e.target) + if !$this.is('.lesson-session-actions-btn') + $this = $this.closest('.lesson-session-actions-btn') + $this.btOn() + + constructMissingLinks: (user) -> + + links = [] + + matches = {} + for problem, isPresent of user.teacher.profile_pct_summary + data = @lookup[problem] + if data? && !isPresent + matches[data.name] = data + + for name, data of matches + if !data.text? + links.push(`{data.name}`) + + for name, data of matches + if data.text? + links.push(`{data.name}`) + `JamClass instructors are each individually screened to ensure that they are highly qualified music + teachers, + equipped to teach effectively online, and background checked. +
+ ++ JamClass is the best way to take music lessons, offering significant advantages over both traditional + face-to-face lessons + and online skype lessons. +
` + signupTestDrive = `+ There are two awesome, painless ways to get started with JamClass. +
+ ++ Sign up for TestDrive and take 4 full 30-minute lessons - one each from 4 different instructors - for just + $49.99. + You wouldn't marry the first person you date, right? Find the best teacher for you. It's the most important + factor in the success for your lessons! +
+ ++ Or take one JamClass lesson free. It's on us! We're confident you'll take more. +
+ ++ Sign up for TestDrive using the button below, or to take one free lesson, search our teachers, and click the + Book Free Lesson on your favorite. +
+ ++ JamClass is the best way to teach music lessons, offering significant advantages over both traditional + face-to-face lessons + and online skype lessons. +
` + if this.state.user? + teacherProfileUri = "/client#/profile/teacher/#{this.state.user.id}" + pct = Math.round(this.state.user.teacher?.profile_pct) + + if pct == 100 + pctCompleteMsg = `Your teacher profile is {pct}% complete.
` + else + pctCompleteMsg = `Your teacher profile is {pct}% complete. The following sections of your profile are missing information. Click any of these links to view and add missing information:
` + missingLinks = @constructMissingLinks(this.state.user) + + signupTestDrive = `| {this.otherRole()} | +DATE/TIME | +STATUS | ++ | ACTIONS | +
|---|
Be sure to set up and test the JamKazam app in an online music session a few days before + your first lesson! We're happy to help, and we'll even get in a session with you to make sure everything + is working properly. Ping us at support@jamkazam.com anytime, and + read our + JamClass user guide to learn how to use all the lesson features. +
+| TEACHER | -DATE/TIME | -STATUS | -- | ACTIONS | -
|---|
JamClass instructors are each individually screened to ensure that they are highly qualified music - teachers, - equipped to teach effectively online, and background checked. -
- -- JamClass is the best way to make music lessons, offering significant advantadges over both traditional - face-to-face lessons - and online skype lessons. -
- -- There are two awesome, painless ways to get started with JamClass. -
- -- Sign up for TestDrive and take 4 full 30-minute lessons - one each from 4 different instructors - for just - $49.99. - You wouldn't marry the first person you date, right? Find the best teacher for you. It's the most important - factor in the success for your lessons! -
- -- Or take one JamClass lesson free. It's on us! We're confident you'll take more. -
- -- Sign up for TestDrive using the button below, or to take one free lesson, search our teachers, and click the - Book Free Lesson on your favorite. -
- -Be sure to set up and test the JamKazam app in an online music session a few days before - your first lesson! We're happy to help, and we'll even get in a session with you to make sure everything - is working properly. Ping us at support@jamkazam.com anytime, and - read our - JamClass user guide to learn how to use all the lesson features. -
-This lesson is over.
` else - data = `This lesson is scheduled to start at {this.state.booking.next_lesson.pretty_scheduled_start}
` + data = `This lesson is scheduled to start at {this.displayableLesson().pretty_scheduled_start}
` else if @isRequested() - data = `This lesson is scheduled to start at {this.state.booking.next_lesson.pretty_scheduled_start}
` + data = `This lesson is scheduled to start at {this.displayableLesson().pretty_scheduled_start}
` `This lesson is over.
` else - `This lesson is scheduled to start at {this.state.booking.next_lesson.pretty_scheduled_start}
` + `This lesson is scheduled to start at {this.displayableLesson().pretty_scheduled_start}
` else if @isRequested() - `This lesson is scheduled to start at {this.state.booking.next_lesson.pretty_scheduled_start}
` + `This lesson is scheduled to start at {this.displayableLesson().pretty_scheduled_start}
` renderCancelLesson: () -> `Has requested a TestDrive {this.lessonLength()}-minute lesson, for which you will be paid $10.
` else + console.log("normal") action = `Has requested a {this.lessonDesc()} lesson, for which you will be paid {this.lessonPaymentAmt()}.
` `You are booking a single free {lesson_length}-minute lesson.
` - bookingDetail = `To book this lesson, you will need to enter your credit card information.
- You will absolutely not be charged for this free lesson, and you have no further commitment to purchase
- anything. We have to collect a credit card to prevent abuse by some users who would otherwise set up
- multiple free accounts to get multiple free lessons.
-
-
You are booking a {lesson_length} minute lesson for - ${this.state.lesson.booked_price.toFixed(2)}
` + ${this.bookedPrice()}` bookingDetail = `Your card will not be charged until the day of the lesson. You must cancel at least 24 hours before your lesson is scheduled, or you will be charged for the lesson in full. @@ -368,7 +427,7 @@ UserStore = context.UserStore
` else bookingInfo = `You are booking a {lesson_length} minute lesson for - ${this.state.lesson.booked_price.toFixed(2)}
` + ${this.bookedPrice()}` bookingDetail = `Your card will not be charged until the day of the lesson. You must cancel at least 24 hours before your lesson is scheduled, or you will be charged for the lesson in full. @@ -378,7 +437,11 @@ UserStore = context.UserStore policies
` else - header = `You are entering your credit card info so that later checkouts go quickly. You can skip this for now.
` bookingDetail = ` @@ -391,7 +454,7 @@ UserStore = context.UserStore ` - submitClassNames = {'button-orange': true, disabled: disabled && @state.updating} + submitClassNames = {'button-orange': true, 'purchase-btn': true, disabled: disabled && @state.updating} updateCardClassNames = {'button-grey': true, disabled: disabled && @state.updating} backClassNames = {'button-grey': true, disabled: disabled && @state.updating} diff --git a/web/app/assets/javascripts/react-components/RescheduleLessonDialog.js.jsx.coffee b/web/app/assets/javascripts/react-components/RescheduleLessonDialog.js.jsx.coffee new file mode 100644 index 000000000..daa614670 --- /dev/null +++ b/web/app/assets/javascripts/react-components/RescheduleLessonDialog.js.jsx.coffee @@ -0,0 +1,82 @@ +context = window + +@RescheduleLessonDialog = React.createClass({ + + mixins: [@PostProcessorMixin, Reflux.listenTo(@AppStore, "onAppInit")] + teacher: false + + beforeShow: (args) -> + logger.debug("RescheduleLessonDialog.beforeShow", args.d1) + + + @setState({id: args.d1, lesson_session: null}) + + rest.getLesson({id: args.d1}).done((response) => @getLessonDone(response)).fail((jqXHR) => @getLessonFail(jqXHR)) + + getLessonDone: (lesson_session) -> + @postProcessLesson(lesson_session) + @setState({lesson_session: lesson_session}) + + getLessonFail: (jqXHR) -> + @app.ajaxError(jqXHR) + + afterHide: () -> + + onAppInit: (@app) -> + dialogBindings = { + 'beforeShow': @beforeShow, + 'afterHide': @afterHide + }; + + @app.bindDialog('reschedule-lesson-dialog', dialogBindings); + + componentDidMount: () -> + @root = $(@getDOMNode()) + + getInitialState: () -> + {id: null, lesson_session: null} + + onCloseClicked: (e) -> + e.preventDefault() + @app.layout.closeDialog('reschedule-lesson-dialog'); + + lesson: () -> + this.state.lesson_session + + viewerStudent: () -> + @lesson().student.id == context.JK.currentUserId + + viewerTeacher: () -> + !@viewerStudent() + + + render: () -> + + title = 'loading...' + if this.state.lesson_session? + title = "reschedule lesson" + lessonSessionId = this.state.lesson_session.id + other = this.state.lesson_session.other + + if @viewerStudent() && @sessionStartingSoon() + title = 'Policy Issue' + content = + `We’re sorry, but you cannot reschedule a lesson less than 24 hours before the lesson start time. This is not allowed in the terms of service because the instructor cannot reasonably be expected to be able to backfill the time slot that has been lost.
+Your Stripe account is properly set up and connected to enable transfer of student payments. To view lesson payment history, click the button below.
+ VIEW PAYMENTS +