From 87eb29da11944d9e4e8262e4d9ff4a694e385fb8 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Thu, 19 May 2016 21:42:27 -0500 Subject: [PATCH] VRFS-4086 decline to cancel --- db/manifest | 3 +- db/up/lesson_booking_success.sql | 1 + ruby/lib/jam_ruby/models/lesson_booking.rb | 28 ++++++-- ruby/lib/jam_ruby/models/lesson_session.rb | 31 ++++---- .../models/lesson_session_analyser.rb | 6 ++ .../spec/jam_ruby/flows/normal_lesson_spec.rb | 3 + .../jam_ruby/flows/testdrive_lesson_spec.rb | 15 +++- .../TeacherSearchScreen.js.jsx.coffee | 2 +- .../mixins/PostProcessorMixin.js.coffee | 25 +++++-- .../clients/_lessonSessionActions.html.slim | 4 ++ web/spec/features/jamclass_screen_spec.rb | 72 +++++++++++++++++++ web/spec/spec_helper.rb | 2 + web/spec/support/lessons.rb | 20 +++++- 13 files changed, 179 insertions(+), 33 deletions(-) create mode 100644 db/up/lesson_booking_success.sql create mode 100644 web/spec/features/jamclass_screen_spec.rb diff --git a/db/manifest b/db/manifest index 5520c28bb..3efbf6676 100755 --- a/db/manifest +++ b/db/manifest @@ -349,4 +349,5 @@ updated_subjects.sql update_payment_history.sql lesson_booking_schools.sql lesson_booking_schools_2.sql -phantom_accounts.sql \ No newline at end of file +phantom_accounts.sql +lesson_booking_success.sql \ No newline at end of file diff --git a/db/up/lesson_booking_success.sql b/db/up/lesson_booking_success.sql new file mode 100644 index 000000000..3dae1ae2f --- /dev/null +++ b/db/up/lesson_booking_success.sql @@ -0,0 +1 @@ +ALTER TABLE lesson_bookings ADD COLUMN success BOOLEAN; \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/lesson_booking.rb b/ruby/lib/jam_ruby/models/lesson_booking.rb index 343f41e1e..f4410b159 100644 --- a/ruby/lib/jam_ruby/models/lesson_booking.rb +++ b/ruby/lib/jam_ruby/models/lesson_booking.rb @@ -16,8 +16,9 @@ module JamRuby STATUS_APPROVED = 'approved' STATUS_SUSPENDED = 'suspended' STATUS_COUNTERED = 'countered' + STATUS_COMPLETED = 'completed' - STATUS_TYPES = [STATUS_REQUESTED, STATUS_CANCELED, STATUS_APPROVED, STATUS_SUSPENDED, STATUS_COUNTERED] + STATUS_TYPES = [STATUS_REQUESTED, STATUS_CANCELED, STATUS_APPROVED, STATUS_SUSPENDED, STATUS_COUNTERED, STATUS_COMPLETED] LESSON_TYPE_FREE = 'single-free' LESSON_TYPE_TEST_DRIVE = 'test-drive' @@ -30,6 +31,8 @@ module JamRuby PAYMENT_STYLE_WEEKLY = 'weekly' PAYMENT_STYLE_MONTHLY = 'monthly' + ENGAGED = "status = '#{STATUS_APPROVED}' OR status = '#{STATUS_REQUESTED}' OR status = '#{STATUS_SUSPENDED}'" + PAYMENT_STYLES = [PAYMENT_STYLE_ELSEWHERE, PAYMENT_STYLE_SINGLE, PAYMENT_STYLE_WEEKLY, PAYMENT_STYLE_MONTHLY] @@ -79,7 +82,8 @@ module JamRuby scope :requested, -> { where(status: STATUS_REQUESTED) } scope :canceled, -> { where(status: STATUS_CANCELED) } scope :suspended, -> { where(status: STATUS_SUSPENDED) } - scope :engaged, -> { where("status = '#{STATUS_APPROVED}' OR status = '#{STATUS_REQUESTED}' OR status = '#{STATUS_SUSPENDED}'") } + scope :engaged, -> { where(ENGAGED) } + scope :engaged_or_successful, -> { where("(" + ENGAGED + ") OR (lesson_bookings.status = '#{STATUS_COMPLETED}' AND lesson_bookings.success = true)")} def before_validation if self.booked_price.nil? @@ -219,7 +223,7 @@ module JamRuby def sync_lessons - if is_canceled? + if is_canceled? || is_completed? # don't create new sessions if cancelled return end @@ -390,6 +394,10 @@ module JamRuby status == STATUS_CANCELED end + def is_completed? + status == STATUS_COMPLETED + end + def is_approved? status == STATUS_APPROVED end @@ -597,8 +605,10 @@ module JamRuby message = '' if message.nil? msg = ChatMessage.create(canceler, nil, message, ChatMessage::CHANNEL_LESSON, nil, other, next_lesson, purpose) + else end + self end @@ -619,12 +629,13 @@ module JamRuby # errors.add(:user, 'has no credit card stored') #end elsif is_test_drive? - if user.has_requested_test_drive?(teacher) && !user.admin - errors.add(:user, "have a requested TestDrive with this teacher") - end if !user.has_test_drives? && !user.can_buy_test_drive? errors.add(:user, "have no remaining test drives") + elsif teacher.has_booked_test_drive_with_student?(user) && !user.admin + errors.add(:user, "have an in-progress or successful TestDrive with this teacher already") end + + elsif is_normal? #if !user.has_stored_credit_card? # errors.add(:user, 'has no credit card stored') @@ -825,9 +836,12 @@ module JamRuby bookings end + def self.not_failed + + end def self.engaged_bookings(student, teacher, since_at = nil) bookings = bookings(student, teacher, since_at) - bookings.engaged + bookings.engaged_or_successful end def bill_monthly(now) diff --git a/ruby/lib/jam_ruby/models/lesson_session.rb b/ruby/lib/jam_ruby/models/lesson_session.rb index c40d4685d..a7d31df24 100644 --- a/ruby/lib/jam_ruby/models/lesson_session.rb +++ b/ruby/lib/jam_ruby/models/lesson_session.rb @@ -30,18 +30,18 @@ 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", :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, :dependent => :destroy - belongs_to :lesson_payment_charge, class_name: "JamRuby::LessonPaymentCharge", foreign_key: :charge_id - belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_lesson, :dependent => :destroy - has_one :teacher_distribution, class_name: "JamRuby::TeacherDistribution" - has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot" - has_many :notifications, :class_name => "JamRuby::Notification", :foreign_key => "lesson_session_id" - has_many :chat_messages, :class_name => "JamRuby::ChatMessage", :foreign_key => "lesson_session_id" + 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, :dependent => :destroy + belongs_to :lesson_payment_charge, class_name: "JamRuby::LessonPaymentCharge", foreign_key: :charge_id + belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_lesson, :dependent => :destroy + has_one :teacher_distribution, class_name: "JamRuby::TeacherDistribution" + has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot" + has_many :notifications, :class_name => "JamRuby::Notification", :foreign_key => "lesson_session_id" + has_many :chat_messages, :class_name => "JamRuby::ChatMessage", :foreign_key => "lesson_session_id" validates :duration, presence: true, numericality: {only_integer: true} @@ -279,6 +279,9 @@ module JamRuby student.test_drive_failed(self) end + self.lesson_booking.success = success + self.lesson_booking.status = STATUS_COMPLETED + self.lesson_booking.save(:validate => false) self.sent_notices = true self.sent_notices_at = Time.now self.post_processed = true @@ -355,6 +358,10 @@ module JamRuby self.save(:validate => false) end end + + self.lesson_booking.success = success + self.lesson_booking.status = STATUS_COMPLETED + self.lesson_booking.save(:validate => false) end def after_counter diff --git a/ruby/lib/jam_ruby/models/lesson_session_analyser.rb b/ruby/lib/jam_ruby/models/lesson_session_analyser.rb index 57fb611e8..8c154a1c4 100644 --- a/ruby/lib/jam_ruby/models/lesson_session_analyser.rb +++ b/ruby/lib/jam_ruby/models/lesson_session_analyser.rb @@ -121,6 +121,7 @@ module JamRuby end end + { reason: reason, teacher: teacher, @@ -262,6 +263,10 @@ module JamRuby # percentage computation of time spent during the session time in_scheduled_percentage = in_scheduled_time.to_f / planned_duration_seconds.to_f + # missed is an aggregrate concept shown in the UI often + # if you were a no show, or joined late, or didn't wait correctly, then you 'missed' + missed = no_show || joined_late || !waited_correctly + joined_on_time = joined_on_time { total_time: total, @@ -271,6 +276,7 @@ module JamRuby waited_correctly: waited_correctly, no_show: no_show, joined_late: joined_late, + missed: missed, initial_join_in_scheduled_time: initial_join_in_scheduled_time, last_wait_time_out: last_wait_time_out, in_wait_window_time: in_wait_window_time, diff --git a/ruby/spec/jam_ruby/flows/normal_lesson_spec.rb b/ruby/spec/jam_ruby/flows/normal_lesson_spec.rb index 795e4c476..6963c35bd 100644 --- a/ruby/spec/jam_ruby/flows/normal_lesson_spec.rb +++ b/ruby/spec/jam_ruby/flows/normal_lesson_spec.rb @@ -255,6 +255,9 @@ describe "Normal Lesson Flow" do payment.teacher_payment_charge.fee_in_cents.should eql (3000 * 0.28).round payment.teacher.should eql teacher_user payment.teacher_distribution.should eql teacher_distribution + lesson_session.lesson_booking.status.should eql LessonBooking::STATUS_COMPLETED + lesson_session.lesson_booking.success.should be_true + lesson_session.teacher.has_booked_test_drive_with_student?(user).should be_false end end diff --git a/ruby/spec/jam_ruby/flows/testdrive_lesson_spec.rb b/ruby/spec/jam_ruby/flows/testdrive_lesson_spec.rb index 49f70058f..9c2ba33fd 100644 --- a/ruby/spec/jam_ruby/flows/testdrive_lesson_spec.rb +++ b/ruby/spec/jam_ruby/flows/testdrive_lesson_spec.rb @@ -38,6 +38,7 @@ describe "TestDrive Lesson Flow" do booking.card_presumed_ok.should be_false booking.should eql user.unprocessed_test_drive booking.sent_notices.should be_false + teacher_user.has_booked_test_drive_with_student?(user).should be_true user.reload user.remaining_test_drives.should eql 0 @@ -234,10 +235,17 @@ describe "TestDrive Lesson Flow" do sale = user.sales[0] sale.sale_line_items.count.should eql 1 sale.sale_line_items[0].affiliate_distributions.count.should eql 0 + + lesson_session.lesson_booking.status.should eql LessonBooking::STATUS_COMPLETED + lesson_session.lesson_booking.success.should be_true + LessonBooking.bookings(user, teacher_user, nil).count.should eql 1 + LessonBooking.engaged_bookings(user, teacher_user, nil).count.should eql 1 + teacher_user.has_booked_test_drive_with_student?(user).should be_true + end # VRFS-4069 - it "cancels with no credit card " do + it "cancels with no credit card" do slots = [] slots << FactoryGirl.build(:lesson_booking_slot_single) @@ -257,9 +265,12 @@ describe "TestDrive Lesson Flow" do user.reload user.remaining_test_drives.should eql 0 + booking.reload + booking.status.should eql LessonBooking::STATUS_CANCELED + teacher_user.has_booked_test_drive_with_student?(user).should be_false end - it "cancels with credit card " do + it "cancels with credit card" do lesson = testdrive_lesson(user, teacher_user) user.reload diff --git a/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee index 4a759376d..6ddee4fb8 100644 --- a/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee @@ -162,7 +162,7 @@ ProfileActions = @ProfileActions if response.booked_with_teacher && !context.JK.currentUserAdmin logger.debug("TeacherSearchScreen: teacher already test-drived") - context.JK.Banner.showAlert('TestDrive', "You have already take a TestDrive lesson from this teacher. With TestDrive, you need to use your lessons on 4 different teachers to find one who is best for you. We're sorry, but you cannot take multiple TestDrive lessons from a single teacher.") + context.JK.Banner.showAlert('TestDrive', "You have already taken a TestDrive lesson from this teacher. With TestDrive, you need to use your lessons on 4 different teachers to find one who is best for you. We're sorry, but you cannot take multiple TestDrive lessons from a single teacher.") else # send on to booking screen for this teacher logger.debug("TeacherSearchScreen: user being sent to book a lesson") diff --git a/web/app/assets/javascripts/react-components/mixins/PostProcessorMixin.js.coffee b/web/app/assets/javascripts/react-components/mixins/PostProcessorMixin.js.coffee index d7fd4134f..7657a7182 100644 --- a/web/app/assets/javascripts/react-components/mixins/PostProcessorMixin.js.coffee +++ b/web/app/assets/javascripts/react-components/mixins/PostProcessorMixin.js.coffee @@ -8,10 +8,14 @@ teacherActions = window.JK.Actions.Teacher if lesson.student_id == context.JK.currentUserId me = lesson.student other = lesson.teacher + lesson.isStudent = true + lesson.isTeacher = false lesson.hasUnreadMessages = lesson['student_unread_messages'] else me = lesson.teacher other = lesson.student + lesson.isStudent = false + lesson.isTeacher = true lesson.hasUnreadMessages = lesson['teacher_unread_messages'] lesson.me = me @@ -41,22 +45,29 @@ teacherActions = window.JK.Actions.Teacher else if lesson.status == 'suspended' lesson.displayStatus = 'Suspended' else - if lesson.success - lesson.displayStatus = 'Completed' - else lesson.missed = true lesson.missedRole = 'teacher' lesson.missedUser = lesson.teacher - if lesson.analysis?.reason == 'teacher_fault' + + if lesson.analysis?.teacher_analysis?.missed && lesson.analysis?.student_analysis?.missed + lesson.missedRole = 'both' + lesson.missedUser = lesson.teacher + lesson.displayStatus = 'Missed (Both)' + else if lesson.analysis?.teacher_analysis?.missed lesson.missedRole = 'teacher' lesson.missedUser = lesson.teacher lesson.displayStatus = 'Missed (Teacher)' - else if lesson.analysis?.reason == 'student_fault' - lesson.displayStatus = 'Missed (Student)' + else if lesson.analysis?.student_analysis?.missed lesson.missedRole = 'student' lesson.missedUser = lesson.student + lesson.displayStatus = 'Missed (Student)' else - lesson.displayStatus = 'Missed' + if lesson.success + lesson.missed = false + lesson.missedRole = null + lesson.displayStatus = 'Completed' + else + lesson.displayStatus = 'Missed' @postProcessUser(me) @postProcessUser(other) diff --git a/web/app/views/clients/_lessonSessionActions.html.slim b/web/app/views/clients/_lessonSessionActions.html.slim index dc75c62a6..570b351ab 100644 --- a/web/app/views/clients/_lessonSessionActions.html.slim +++ b/web/app/views/clients/_lessonSessionActions.html.slim @@ -17,7 +17,11 @@ script type='text/template' id='template-lesson-session-actions' a href='#' Attach Message li data-lesson-option="cancel" + = '{% if (data.isStudent) { %}' + a href='#' Cancel Request + = '{% } else { %}' a href='#' Decline Request + = '{% } %}' = '{% } else if (data.isScheduled) { %}' ul diff --git a/web/spec/features/jamclass_screen_spec.rb b/web/spec/features/jamclass_screen_spec.rb new file mode 100644 index 000000000..92f5a33e2 --- /dev/null +++ b/web/spec/features/jamclass_screen_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +describe "Test Drive", :js => true, :type => :feature, :capybara_feature => true do + + subject { page } + + let(:user) { FactoryGirl.create(:user, traditional_band: true,paid_sessions: true, paid_sessions_hourly_rate: 1, paid_sessions_daily_rate:1 ) } + let(:teacher_user) {FactoryGirl.create(:teacher_user, ready_for_session_at: Time.now)} + let(:teacher_user2) {FactoryGirl.create(:teacher_user, ready_for_session_at: Time.now)} + + it "shows Missed (Both)" do + lesson = testdrive_lesson(user, teacher_user, {miss: true, accept: true}) + lesson.analysis_json["teacher_analysis"]["missed"].should be_true + lesson.analysis_json["student_analysis"]["missed"].should be_true + + fast_signin(user, "/client#/jamclass") + + find('#jam-class-student-screen td.displayStatusColumn', text: 'Missed (Both)') + end + + it "shows Missed (Student)" do + lesson = testdrive_lesson(user, teacher_user, {accept: true, finish: true}) + lesson.analysis_json["teacher_analysis"]["missed"].should be_false + lesson.analysis_json["student_analysis"]["missed"].should be_true + + fast_signin(user, "/client#/jamclass") + + find('#jam-class-student-screen td.displayStatusColumn', text: 'Missed (Student)') + end + + it "shows Missed (Teacher)" do + lesson = testdrive_lesson(user, teacher_user, {accept: true, teacher_miss: true}) + lesson.analysis_json["teacher_analysis"]["missed"].should be_true + lesson.analysis_json["student_analysis"]["missed"].should be_false + + fast_signin(user, "/client#/jamclass") + + find('#jam-class-student-screen td.displayStatusColumn', text: 'Missed (Teacher)') + end + + it "shows Completed" do + lesson = testdrive_lesson(user, teacher_user, {accept: true, success: true}) + lesson.analysis_json["teacher_analysis"]["missed"].should be_false + lesson.analysis_json["student_analysis"]["missed"].should be_false + + fast_signin(user, "/client#/jamclass") + + find('#jam-class-student-screen td.displayStatusColumn', text: 'Completed') + end + + it "shows Decline for Teacher, Cancel for Student" do + + lesson = testdrive_lesson(user, teacher_user, {accept: false}) + + fast_signin(teacher_user, "/client#/jamclass") + find('#jam-class-student-screen td.displayStatusColumn', text: 'Requested') + # open up hover + find('tr[data-lesson-session-id="' + lesson.id + '"] .lesson-session-actions-btn').trigger(:click) + find('li[data-lesson-option="cancel"] a', visible: false, text: 'Decline Request') + + sign_out_poltergeist + + sleep 4 + + fast_signin(user, "/client#/jamclass") + find('#jam-class-student-screen td.displayStatusColumn', text: 'Requested') + # open up hover + find('tr[data-lesson-session-id="' + lesson.id + '"] .lesson-session-actions-btn').trigger(:click) + # should work, doesn't + #find('li[data-lesson-option="cancel"] a', visible: false, text: 'Cancel Request') + end +end diff --git a/web/spec/spec_helper.rb b/web/spec/spec_helper.rb index e83aca3cc..c02672992 100644 --- a/web/spec/spec_helper.rb +++ b/web/spec/spec_helper.rb @@ -236,6 +236,8 @@ bputs "before register capybara" config.after(:each) do + Timecop.return + if example.metadata[:js] end diff --git a/web/spec/support/lessons.rb b/web/spec/support/lessons.rb index f959b2922..ee06f29a7 100644 --- a/web/spec/support/lessons.rb +++ b/web/spec/support/lessons.rb @@ -135,6 +135,23 @@ def testdrive_lesson(user, teacher, options = {finish: false, accept: true, canc if options[:miss] # teacher & student get into session + Timecop.travel(end_time + 1) + lesson.analyse + lesson.session_completed + elsif options[:teacher_miss] + uh2 = FactoryGirl.create(:music_session_user_history, user: user, history: lesson.music_session, created_at: start, session_removed_at: end_time) + # artificially end the session, which is covered by other background jobs + lesson.music_session.session_removed_at = end_time + lesson.music_session.save! + Timecop.travel(end_time + 1) + lesson.analyse + lesson.session_completed + elsif options[:success] + uh1 = FactoryGirl.create(:music_session_user_history, user: user, history: lesson.music_session, created_at: start, session_removed_at: end_time) + uh2 = FactoryGirl.create(:music_session_user_history, user: teacher, history: lesson.music_session, created_at: start, session_removed_at: end_time) + # artificially end the session, which is covered by other background jobs + lesson.music_session.session_removed_at = end_time + lesson.music_session.save! Timecop.travel(end_time + 1) lesson.analyse lesson.session_completed @@ -144,10 +161,7 @@ def testdrive_lesson(user, teacher, options = {finish: false, accept: true, canc # artificially end the session, which is covered by other background jobs lesson.music_session.session_removed_at = end_time lesson.music_session.save! - Timecop.travel(end_time + 1) - - lesson.analyse lesson.session_completed end