diff --git a/admin/.rakeTasks b/admin/.rakeTasks index c6865d9a1..78308c2e6 100644 --- a/admin/.rakeTasks +++ b/admin/.rakeTasks @@ -4,4 +4,4 @@ You are allowed to: 1. Remove rake task 2. Add existing rake tasks To add existing rake tasks automatically delete this file and reload the project. ---> +--> diff --git a/db/manifest b/db/manifest index 68db33231..bf1ea5bc3 100755 --- a/db/manifest +++ b/db/manifest @@ -347,4 +347,5 @@ track_school_signups.sql add_test_drive_types.sql updated_subjects.sql update_payment_history.sql -lesson_booking_schools.sql \ No newline at end of file +lesson_booking_schools.sql +lesson_booking_schools_2.sql \ No newline at end of file diff --git a/db/up/lesson_booking_schools_2.sql b/db/up/lesson_booking_schools_2.sql new file mode 100644 index 000000000..7888cbeb8 --- /dev/null +++ b/db/up/lesson_booking_schools_2.sql @@ -0,0 +1,2 @@ +ALTER TABLE lesson_bookings ADD COLUMN same_school BOOLEAN DEFAULT FALSE NOT NULL; +ALTER TABLE schools ADD COLUMN affiliate_partner_id INTEGER REFERENCES affiliate_partners(id); \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/uploaders/artifact_uploader.rb b/ruby/lib/jam_ruby/app/uploaders/artifact_uploader.rb index 652e877c2..7b2bbab6e 100644 --- a/ruby/lib/jam_ruby/app/uploaders/artifact_uploader.rb +++ b/ruby/lib/jam_ruby/app/uploaders/artifact_uploader.rb @@ -52,7 +52,7 @@ class ArtifactUploader < CarrierWave::Uploader::Base # Add a white list of extensions which are allowed to be uploaded. # For images you might use something like this: def extension_white_list - %w(exe msi dmg) + %w(exe msi dmg zip) end # Override the filename of the uploaded files: diff --git a/ruby/lib/jam_ruby/models/affiliate_partner.rb b/ruby/lib/jam_ruby/models/affiliate_partner.rb index 0461490f7..2014c301a 100644 --- a/ruby/lib/jam_ruby/models/affiliate_partner.rb +++ b/ruby/lib/jam_ruby/models/affiliate_partner.rb @@ -2,6 +2,7 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base self.table_name = 'affiliate_partners' belongs_to :partner_user, :class_name => "JamRuby::User", :foreign_key => :partner_user_id, inverse_of: :affiliate_partner + has_one :school, class_name: "JamRuby::School" has_many :user_referrals, :class_name => "JamRuby::User", :foreign_key => :affiliate_referral_id belongs_to :affiliate_legalese, :class_name => "JamRuby::AffiliateLegalese", :foreign_key => :legalese_id has_many :sale_line_items, :class_name => 'JamRuby::SaleLineItem', foreign_key: :affiliate_referral_id @@ -62,7 +63,7 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base # used by admin def self.create_with_params(params={}) raise 'not supported' - oo = self.new + oo = AffiliatePartner.new oo.partner_name = params[:partner_name].try(:strip) oo.partner_code = params[:partner_code].try(:strip).try(:downcase) oo.partner_user = User.where(:email => params[:user_email].try(:strip)).limit(1).first @@ -74,15 +75,25 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base # used by web def self.create_with_web_params(user, params={}) - oo = self.new + oo = AffiliatePartner.new oo.partner_name = params[:partner_name].try(:strip) oo.partner_user = user if user # user is not required oo.entity_type = params[:entity_type] || ENTITY_TYPES.first - oo.signed_at = Time.now + signed_legalese oo.save oo end + def self.create_from_school(school) + oo = AffiliatePartner.new + oo.partner_name = "Affiliate from School #{school.id}" + oo.partner_user = school.owner + oo.entity_type = 'Other' + oo.school = school + oo.signed_at = nil + oo.save + end + def self.coded_id(code=nil) self.where(:partner_code => code).limit(1).pluck(:id).first if code.present? end @@ -126,7 +137,11 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base # subtract the total quantity from the freebie quantity, to see how much we should attribute to them real_quantity = product_info[:quantity].to_i - product_info[:marked_for_redeem].to_i - applicable_rate = shopping_cart.is_lesson? ? lesson_rate : rate + if shopping_cart.is_lesson? + applicable_rate = lesson_rate + else + applicable_rate = rate + end {fee_in_cents: (product_info[:price] * 100 * real_quantity * applicable_rate.to_f).round} else diff --git a/ruby/lib/jam_ruby/models/lesson_booking.rb b/ruby/lib/jam_ruby/models/lesson_booking.rb index 7e884fef4..343f41e1e 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, :canceling, :countered_slot, :countered_lesson + attr_accessor :accepting, :countering, :canceling, :countered_slot, :countered_lesson, :current_purchase, :current_lesson STATUS_REQUESTED = 'requested' STATUS_CANCELED = 'canceled' @@ -51,6 +51,7 @@ module JamRuby validates :recurring, inclusion: {in: [true, false]} validates :sent_notices, inclusion: {in: [true, false]} validates :card_presumed_ok, inclusion: {in: [true, false]} + validates :same_school, inclusion: {in: [true, false]} validates :active, inclusion: {in: [true, false]} validates :lesson_length, inclusion: {in: [30, 45, 60, 90, 120]} validates :payment_style, inclusion: {in: PAYMENT_STYLES} @@ -112,7 +113,20 @@ module JamRuby # here for shopping_cart def product_info - {price: booked_price, real_price: booked_price, total_price: booked_price} + if is_test_drive? + real_price = 0 + elsif is_monthly_payment? + raise "no purchase assigned to lesson booking for monthly payment!" if current_purchase.nil? + real_price = self.current_purchase.teacher_distribution.jamkazam_margin + else + if current_lesson.nil? + puts "OHOHOMOOMG #{self.inspect}" + raise "no purchase assigned to lesson booking for lesson!" + end + + real_price = self.current_lesson.teacher_distribution.jamkazam_margin + end + {price: real_price, real_price: real_price, total_price: real_price} end # here for shopping_cart def price @@ -357,7 +371,9 @@ module JamRuby end def requires_teacher_distribution?(target) - if target.is_a?(JamRuby::LessonSession) + if school_on_school? + false + elsif target.is_a?(JamRuby::LessonSession) is_test_drive? || (is_normal? && !is_monthly_payment?) elsif target.is_a?(JamRuby::LessonPackagePurchase) is_monthly_payment? @@ -694,6 +710,12 @@ module JamRuby lesson_booking.school = lesson_booking.teacher.teacher.school end + if user + lesson_booking.same_school = !!(lesson_booking.school && user.school && (lesson_booking.school.id == user.school.id)) + else + lesson_booking.same_school = false + end + # two-way association slots, for before_validation loic in slot to work lesson_booking.lesson_booking_slots = lesson_booking_slots lesson_booking_slots.each do |slot| @@ -718,7 +740,7 @@ module JamRuby end def school_on_school? - teacher.teacher.school && student.school && (teacher.teacher.school.id == student.school.id) + same_school end def self.find_bookings_needing_sessions(minimum_start_time) @@ -760,6 +782,7 @@ module JamRuby .joins("LEFT JOIN lesson_package_purchases ON (lesson_package_purchases.lesson_booking_id = lesson_bookings.id AND (lesson_package_purchases.year = #{current_month_first_day.year} AND lesson_package_purchases.month = #{current_month_first_day.month}))") .where("lesson_package_purchases.id IS NULL OR (lesson_package_purchases.id IS NOT NULL AND lesson_package_purchases.post_processed = false)") .where(payment_style: PAYMENT_STYLE_MONTHLY) + .where(same_school: false) .active .where('music_sessions.scheduled_start >= ?', current_month_first_day) .where('music_sessions.scheduled_start <= ?', current_month_last_day).uniq diff --git a/ruby/lib/jam_ruby/models/lesson_package_purchase.rb b/ruby/lib/jam_ruby/models/lesson_package_purchase.rb index 66266028f..e9f49da0c 100644 --- a/ruby/lib/jam_ruby/models/lesson_package_purchase.rb +++ b/ruby/lib/jam_ruby/models/lesson_package_purchase.rb @@ -121,15 +121,20 @@ module JamRuby user end + # test drive purchase doesn't have a tea def school_on_school? - teacher.teacher.school && student.school && (teacher.teacher.school.id == student.school.id) + if teacher + teacher.teacher.school && student.school && (teacher.teacher.school.id == student.school.id) + else + false + end end def bill_monthly(force = false) if school_on_school? - success = true + raise "school-on-school: should not be here" else lesson_payment_charge.charge(force) success = lesson_payment_charge.billed diff --git a/ruby/lib/jam_ruby/models/lesson_session.rb b/ruby/lib/jam_ruby/models/lesson_session.rb index c85624e77..c40d4685d 100644 --- a/ruby/lib/jam_ruby/models/lesson_session.rb +++ b/ruby/lib/jam_ruby/models/lesson_session.rb @@ -11,7 +11,7 @@ module JamRuby @@log = Logging.logger[LessonSession] delegate :sent_billing_notices, :last_billing_attempt_at, :billing_attempts, :billing_should_retry, :billed_at, :billing_error_detail, :billing_error_reason, :is_card_declined?, :is_card_expired?, :last_billed_at_date, :sent_billing_notices, to: :lesson_payment_charge, allow_nil: true - delegate :is_test_drive?, :is_single_free?, :is_normal?, :approved_before?, :is_active?, :recurring, :is_monthly_payment?, to: :lesson_booking + delegate :is_test_drive?, :is_single_free?, :is_normal?, :approved_before?, :is_active?, :recurring, :is_monthly_payment?, :school_on_school?, to: :lesson_booking delegate :pretty_scheduled_start, to: :music_session @@ -176,13 +176,19 @@ module JamRuby def billed if lesson_booking.is_test_drive? false - else + elsif lesson_payment_charge lesson_payment_charge.billed + else + false end end def amount_charged - lesson_payment_charge.amount_in_cents / 100.0 + if lesson_payment_charge + lesson_payment_charge.amount_in_cents / 100.0 + else + 0.0 + end end def self.analysis_to_json(analysis, preserve_object = false) @@ -398,10 +404,6 @@ module JamRuby @parsed_analysis || analysis ? JSON.parse(analysis) : nil end - def school_on_school? - teacher.teacher.school && student.school && (teacher.teacher.school.id == student.school.id) - end - def validate_creating if !is_requested? && !is_approved? self.errors.add(:status, "is not valid for a new lesson session.") @@ -490,7 +492,13 @@ module JamRuby 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) + if user.school_id + school_extra = "OR (lesson_bookings.school_id = '#{user.school_id}')" + else + school_extra = '' + end + + query = query.where('lesson_bookings.card_presumed_ok = true OR (music_sessions.user_id = ?) ' + school_extra, user.id) current_page = params[:page].nil? ? 1 : params[:page].to_i next_page = current_page + 1 diff --git a/ruby/lib/jam_ruby/models/sale.rb b/ruby/lib/jam_ruby/models/sale.rb index d3827ed84..42b435524 100644 --- a/ruby/lib/jam_ruby/models/sale.rb +++ b/ruby/lib/jam_ruby/models/sale.rb @@ -228,6 +228,11 @@ module JamRuby if sale.valid? + if lesson_booking + lesson_booking.current_lesson = lesson_session + lesson_booking.current_purchase = lesson_package_purchase + end + sale_line_item = SaleLineItem.create_from_lesson_package(current_user, sale, lesson_package_type, lesson_booking) price_info = charge_stripe_for_lesson(current_user, lesson_booking, lesson_package_type, sale_line_item, lesson_session, lesson_package_purchase, force) @@ -251,10 +256,12 @@ module JamRuby purchase = price_info[:purchase] else # should not get out of testing. This would be very rare (i.e., from a big regression). Sale is always valid at this point. + puts "invalid sale object" raise "invalid sale object" end end + {sale: sale, stripe_charge: stripe_charge, purchase: purchase} end diff --git a/ruby/lib/jam_ruby/models/sale_line_item.rb b/ruby/lib/jam_ruby/models/sale_line_item.rb index f5ca99b80..7dec6502d 100644 --- a/ruby/lib/jam_ruby/models/sale_line_item.rb +++ b/ruby/lib/jam_ruby/models/sale_line_item.rb @@ -91,6 +91,15 @@ module JamRuby line_item end + def add_referral_if_needed(user, shopping_cart, lesson_booking) + # if the teacher came from an affiliate, this is our chance to account for that (the student's affiliate status was accounted for in create_from_shopping_cart) + referral_info = user.should_attribute_sale?(shopping_cart, lesson_booking) + + if referral_info + self.affiliate_distributions << AffiliateDistribution.create(user.affiliate_referral, referral_info[:fee_in_cents], self) + self.save! + end + end # in a shopping-cart less world (ios purchase), let's reuse as much logic as possible def self.create_from_lesson_package(current_user, sale, lesson_package_type, lesson_booking) teacher = lesson_booking.teacher if lesson_booking @@ -98,12 +107,20 @@ module JamRuby line_item = create_from_shopping_cart(sale, shopping_cart, nil, nil, nil, lesson_booking) if lesson_booking - # if the teacher came from an affiliate, this is our chance to account for that (the student's affiliate status was accounted for in create_from_shopping_cart) - referral_info = teacher.should_attribute_sale?(shopping_cart, lesson_booking) - if referral_info - line_item.affiliate_distributions << AffiliateDistribution.create(teacher.affiliate_referral, referral_info[:fee_in_cents], line_item) - line_item.save! + teacher = lesson_booking.teacher + student = lesson_booking.student + + if lesson_booking.is_test_drive? + # no referral for test drives + elsif lesson_booking.school_on_school? + # no referral; we don't make money on school-on-school + else + line_item.add_referral_if_needed(student, shopping_cart, lesson_booking) + + if lesson_booking.school.nil? + line_item.add_referral_if_needed(teacher, shopping_cart, lesson_booking) + end end end @@ -131,7 +148,7 @@ module JamRuby # determine if we need to associate this sale with a partner user = shopping_cart.user - referral_info = user.should_attribute_sale?(shopping_cart, instance) + referral_info = user.should_attribute_sale?(shopping_cart, instance) if !instance || !instance.is_a?(LessonBooking) # all affiliate stuff is handled elsewhere if referral_info sale_line_item.affiliate_distributions << AffiliateDistribution.create(user.affiliate_referral, referral_info[:fee_in_cents], sale_line_item) diff --git a/ruby/lib/jam_ruby/models/school.rb b/ruby/lib/jam_ruby/models/school.rb index dc659b2d9..af10595c8 100644 --- a/ruby/lib/jam_ruby/models/school.rb +++ b/ruby/lib/jam_ruby/models/school.rb @@ -15,6 +15,7 @@ module JamRuby attr_accessible :original_fpfile, :cropped_fpfile, :cropped_large_fpfile, :cropped_s3_path, :cropped_large_s3_path, :photo_url, :large_photo_url, :crop_selection belongs_to :user, class_name: ::JamRuby::User, inverse_of: :owned_school + belongs_to :affiliate_partner, class_name: "JamRuby::AffiliatePartner" has_many :students, class_name: ::JamRuby::User has_many :teachers, class_name: ::JamRuby::Teacher has_many :school_invitations, class_name: 'JamRuby::SchoolInvitation' @@ -27,8 +28,12 @@ module JamRuby validates :correspondence_email, email: true, allow_blank: true validate :validate_avatar_info + after_create :create_affiliate before_save :stringify_avatar_info, :if => :updating_avatar + def create_affiliate + AffiliatePartner.create_from_school(self) + end def update_from_params(params) self.name = params[:name] if params[:name].present? self.scheduling_communication = params[:scheduling_communication] if params[:scheduling_communication].present? diff --git a/ruby/lib/jam_ruby/models/teacher.rb b/ruby/lib/jam_ruby/models/teacher.rb index 5135b91e4..357dc8f69 100644 --- a/ruby/lib/jam_ruby/models/teacher.rb +++ b/ruby/lib/jam_ruby/models/teacher.rb @@ -58,6 +58,10 @@ module JamRuby # only show teachers with ready for session set to true query = query.where('teachers.ready_for_session_at IS NOT NULL') + if params[:onlyMySchool] && params[:onlyMySchool] != 'false' && user.school_id + query = query.where("teachers.school_id = ?", user.school_id) + end + instruments = params[:instruments] if instruments && !instruments.blank? && instruments.length > 0 query = query.joins("inner JOIN teachers_instruments AS tinst ON tinst.teacher_id = teachers.id") diff --git a/ruby/lib/jam_ruby/models/teacher_distribution.rb b/ruby/lib/jam_ruby/models/teacher_distribution.rb index 552a5959c..316061e7d 100644 --- a/ruby/lib/jam_ruby/models/teacher_distribution.rb +++ b/ruby/lib/jam_ruby/models/teacher_distribution.rb @@ -80,11 +80,37 @@ module JamRuby '$%.2f' % real_distribution end + def jamkazam_margin_in_cents + if is_test_drive? + 0 + else + if school + # if school exists, use it's rate + rate = school.jamkazam_rate + else + # otherwise use the teacher's rate + rate = teacher.teacher.jamkazam_rate + end + amount_in_cents * (rate) + end + end + + def jamkazam_margin + (jamkazam_margin_in_cents / 100).round(2) + end + def calculate_teacher_fee if is_test_drive? 0 else - (amount_in_cents * (teacher.teacher.jamkazam_rate + 0.03)).round + if school + # if school exists, use it's rate + rate = school.jamkazam_rate + else + # otherwise use the teacher's rate + rate = teacher.teacher.jamkazam_rate + end + (amount_in_cents * (rate + 0.03)).round end end @@ -116,7 +142,7 @@ module JamRuby if lesson_session lesson_session.timed_description else - lesson_package_purchase.description + lesson_package_purchase.timed_description end end end diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 98120a686..85c9cd061 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -70,7 +70,7 @@ module JamRuby # bands has_many :band_musicians, :class_name => "JamRuby::BandMusician" has_many :bands, :through => :band_musicians, :class_name => "JamRuby::Band" - has_one :teacher, :class_name => "JamRuby::Teacher" + belongs_to :teacher, :class_name => "JamRuby::Teacher", foreign_key: :teacher_id # genres has_many :genre_players, as: :player, class_name: "JamRuby::GenrePlayer", dependent: :destroy @@ -1106,6 +1106,7 @@ module JamRuby school_id = options[:school_id] school_interest = options[:school_interest] + school = School.find(school_id) if school_id user = User.new user.validate_instruments = true UserManager.active_record_transaction do |user_manager| @@ -1139,10 +1140,12 @@ module JamRuby if school_id.present? if user.is_a_student user.school_id = school_id + user.affiliate_referral = school.affiliate_partner elsif user.is_a_teacher school = School.find_by_id(school_id) school_name = school ? school.name : 'a music school' user.teacher = Teacher.build_teacher(user, validate_introduction: true, biography: "Teaches for #{school_name}", school_id: school_id) + user.affiliate_referral = school.affiliate_partner end else if user.is_a_teacher @@ -1869,6 +1872,7 @@ module JamRuby return false end + if affiliate_referral referral_info = affiliate_referral.should_attribute_sale?(shopping_cart, self, instance) else @@ -2109,10 +2113,22 @@ module JamRuby end def has_rated_teacher(teacher) + teacher_rating(teacher).count > 0 + end + + def has_rated_student(student) + student_rating(student).count > 0 + end + + def teacher_rating(teacher) if teacher.is_a?(JamRuby::User) teacher = teacher.teacher end - Review.where(target_id: teacher.id).where(target_type: teacher.class.to_s).count > 0 + Review.where(target_id: teacher.id).where(target_type: teacher.class.to_s) + end + + def student_rating(student) + Review.where(target_id: student.id).where(target_type: "JamRuby::User") end def has_rated_student(student) 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 8b0292922..05e707fcb 100644 --- a/ruby/spec/jam_ruby/flows/monthly_recurring_lesson_spec.rb +++ b/ruby/spec/jam_ruby/flows/monthly_recurring_lesson_spec.rb @@ -12,6 +12,10 @@ describe "Monthly Recurring Lesson Flow" do let(:lesson_booking_slot_recurring2) { FactoryGirl.build(:lesson_booking_slot_recurring) } let(:valid_single_slots) { [lesson_booking_slot_single1, lesson_booking_slot_single2] } let(:valid_recurring_slots) { [lesson_booking_slot_recurring1, lesson_booking_slot_recurring2] } + let(:affiliate_partner) { FactoryGirl.create(:affiliate_partner) } + let(:affiliate_partner2) { FactoryGirl.create(:affiliate_partner, lesson_rate: 0.30) } + let(:school) {FactoryGirl.create(:school)} + after {Timecop.return} @@ -37,12 +41,15 @@ describe "Monthly Recurring Lesson Flow" do ########## Need validate their credit card token = create_stripe_token - result = user.payment_update({token: token, zip: '78759', normal: true}) + result = user.payment_update({token: token, zip: '78759', normal: true, booking_id: booking.id}) + puts "result #{result.inspect}" + booking.reload booking.card_presumed_ok.should be_true booking.errors.any?.should be_false - lesson.errors.any?.should be_false booking = result[:lesson] lesson = booking.lesson_sessions[0] + lesson.errors.any?.should be_false + booking.sent_notices.should be_true lesson.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0) lesson.amount_charged.should be 0.0 @@ -147,6 +154,9 @@ describe "Monthly Recurring Lesson Flow" do # artificially end the session, which is covered by other background jobs lesson_session.music_session.session_removed_at = end_time lesson_session.music_session.save! + + Timecop.travel(end_time + 1) + LessonSession.hourly_check lesson_session.reload lesson_session.analysed.should be_true @@ -223,9 +233,259 @@ describe "Monthly Recurring Lesson Flow" do lesson.amount_charged.should eql 0.0 lesson_session.billing_error_reason.should be_nil - lesson_session.sent_billing_notices.should be false + lesson_session.sent_billing_notices.should be nil user.reload user.remaining_test_drives.should eql 0 - UserMailer.deliveries.length.should eql 1 # one for student + UserMailer.deliveries.length.should eql 0 # one for student + end + + it "works (school on school)" do + + # get user and teacher into same school + user.school = school + user.save! + teacher.school = school + teacher.save! + + # if it's later in the month, we'll make 2 lesson_package_purchases (prorated one, and next month's), which can throw off some assertions later on + Timecop.travel(Date.new(2016, 3, 20)) + + # user has no test drives, no credit card on file, but attempts to book a lesson + booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60) + booking.errors.any?.should be_false + booking.card_presumed_ok.should be_false + booking.user.should eql user + booking.card_presumed_ok.should be_false + booking.sent_notices.should be_true + booking.booked_price.should eql 30.00 + + + user.reload + user.stripe_customer_id.should be nil + user.remaining_test_drives.should eql 0 + user.lesson_purchases.length.should eql 0 + + booking.lesson_sessions.length.should eql 1 + lesson_session = booking.lesson_sessions[0] + lesson_session.status.should eql LessonBooking::STATUS_REQUESTED + booking.status.should eql LessonBooking::STATUS_REQUESTED + + ######### Teacher counters with new slot + teacher_countered_slot = FactoryGirl.build(:lesson_booking_slot_single, hour: 14, update_all: true) + UserMailer.deliveries.clear + lesson_session.counter({proposer: teacher_user, slot: teacher_countered_slot, message: 'Does this work?'}) + booking.reload + booking.errors.any?.should be false + lesson_session.lesson_booking.errors.any?.should be false + lesson_session.lesson_booking_slots.length.should eql 1 + lesson_session.lesson_booking_slots[0].proposer.should eql teacher_user + teacher_counter = lesson_session.lesson_booking_slots.order(:created_at).last + teacher_counter.should eql teacher_countered_slot + teacher_counter.proposer.should eql teacher_user + booking.lesson_booking_slots.length.should eql 3 + UserMailer.deliveries.length.should eql 1 + chat = ChatMessage.unscoped.order(:created_at).last + chat.channel.should eql ChatMessage::CHANNEL_LESSON + chat.message.should eql 'Does this work?' + chat.user.should eql teacher_user + chat.target_user.should eql user + notification = Notification.unscoped.order(:created_at).last + notification.session_id.should eql lesson_session.music_session.id + notification.student_directed.should eql true + notification.purpose.should eql 'counter' + notification.description.should eql NotificationTypes::LESSON_MESSAGE + + ######### Student counters with new slot + student_countered_slot = FactoryGirl.build(:lesson_booking_slot_single, hour: 16, update_all: true) + UserMailer.deliveries.clear + lesson_session.counter({proposer: user, slot: student_countered_slot, message: 'Does this work better?'}) + lesson_session.errors.any?.should be false + lesson_session.lesson_booking.errors.any?.should be false + lesson_session.lesson_booking_slots.length.should eql 2 + student_counter = booking.lesson_booking_slots.order(:created_at).last + student_counter.proposer.should eql user + booking.reload + booking.lesson_booking_slots.length.should eql 4 + UserMailer.deliveries.length.should eql 1 + chat = ChatMessage.unscoped.order(:created_at).last + chat.message.should eql 'Does this work better?' + chat.channel.should eql ChatMessage::CHANNEL_LESSON + chat.user.should eql user + chat.target_user.should eql teacher_user + notification = Notification.unscoped.order(:created_at).last + notification.session_id.should eql lesson_session.music_session.id + notification.student_directed.should eql false + notification.purpose.should eql 'counter' + notification.description.should eql NotificationTypes::LESSON_MESSAGE + + ######## Teacher accepts slot + UserMailer.deliveries.clear + lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false}) + UserMailer.deliveries.each do |del| + # puts del.inspect + end + # get acceptance emails, as well as 'your stuff is accepted' + UserMailer.deliveries.length.should eql 2 + lesson_session.errors.any?.should be_false + lesson_session.reload + lesson_session.slot.should eql student_counter + lesson_session.status.should eql LessonSession::STATUS_APPROVED + booking.reload + booking.default_slot.should eql student_counter + 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 2 + chat = ChatMessage.unscoped.order(:created_at).last + 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 + notification = Notification.unscoped.order(:created_at).last + notification.session_id.should eql lesson_session.music_session.id + notification.student_directed.should eql true + notification.purpose.should eql 'accept' + notification.description.should eql NotificationTypes::LESSON_MESSAGE + + + # teacher & student get into session + start = lesson_session.scheduled_start + end_time = lesson_session.scheduled_start + (60 * lesson_session.duration) + uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time) + # artificially end the session, which is covered by other background jobs + lesson_session.music_session.session_removed_at = end_time + lesson_session.music_session.save! + + Timecop.travel(end_time + 1) + + LessonSession.hourly_check + lesson_session.reload + lesson_session.analysed.should be_true + analysis = JSON.parse(lesson_session.analysis) + analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT + analysis["student"].should eql LessonSessionAnalyser::NO_SHOW + if lesson_session.billing_error_detail + puts "monthly recurring lesson flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests + end + + # let user pay for it + LessonBooking.hourly_check + + LessonPaymentCharge.count.should eql 0 + TeacherDistribution.count.should eql 0 + + user.reload + user.lesson_purchases.length.should eql 0 + user.sales.length.should eql 0 + + + TeacherPayment.count.should eql 0 + TeacherPayment.hourly_check + TeacherPayment.count.should eql 0 + + + # teacher & student get into session + start = lesson_session.scheduled_start + end_time = lesson_session.scheduled_start + (60 * lesson_session.duration) + uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time) + # artificially end the session, which is covered by other background jobs + lesson_session.music_session.session_removed_at = end_time + lesson_session.music_session.save! + + + UserMailer.deliveries.clear + # background code comes around and analyses the session + LessonSession.hourly_check + lesson_session.reload + lesson_session.analysed.should be_true + analysis = JSON.parse(lesson_session.analysis) + analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT + analysis["student"].should eql LessonSessionAnalyser::NO_SHOW + if lesson_session.billing_error_detail + puts "monthly recurring lesson flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests + end + + lesson_session.amount_charged.should eql 0.0 + lesson_session.billing_error_reason.should be_nil + lesson_session.sent_billing_notices.should be_nil + user.reload + user.remaining_test_drives.should eql 0 + UserMailer.deliveries.length.should eql 0 # one for student + + LessonPaymentCharge.count.should eql 0 + TeacherDistribution.count.should eql 0 + end + + + it "affiliate gets their cut" do + user.affiliate_referral = affiliate_partner + user.save! + teacher_user.affiliate_referral = affiliate_partner2 + teacher_user.save! + + lesson = monthly_lesson(user, teacher_user, true) + + user.reload + + user.lesson_purchases.count.should eql 1 + lesson_package_purchase = user.lesson_purchases.first + teacher_distribution = lesson_package_purchase.teacher_distribution + + + user.sales.count.should eql 1 + user.sales[0].sale_line_items[0].affiliate_distributions.count.should eql 2 + affiliate_partner.affiliate_distributions.count.should eql 1 + affiliate_partner2.affiliate_distributions.count.should eql 1 + partner1_distribution = affiliate_partner.affiliate_distributions.first + partner2_distribution = affiliate_partner2.affiliate_distributions.first + partner1_distribution.sale_line_item.should eql partner2_distribution.sale_line_item + partner1_distribution.affiliate_referral_fee_in_cents.should eql (teacher_distribution.jamkazam_margin_in_cents * affiliate_partner.lesson_rate).round + partner2_distribution.affiliate_referral_fee_in_cents.should eql (teacher_distribution.jamkazam_margin_in_cents * affiliate_partner2.lesson_rate).round + end + + it "school affiliate gets nothing when teacher school is involved" do + teacher.school = school + teacher.save! + + user.affiliate_referral = affiliate_partner + user.save! + + lesson = monthly_lesson(user, teacher_user, true) + + user.sales.count.should eql 1 + user.sales[0].sale_line_items[0].affiliate_distributions.count.should eql 1 + user.lesson_purchases.count.should eql 1 + lesson_package_purchase = user.lesson_purchases.first + teacher_distribution = lesson_package_purchase.teacher_distribution + + + affiliate_partner.affiliate_distributions.count.should eql 1 + partner1_distribution = affiliate_partner.affiliate_distributions.first + partner1_distribution.affiliate_referral_fee_in_cents.should eql (teacher_distribution.jamkazam_margin_in_cents * affiliate_partner.lesson_rate).round + school.affiliate_partner.affiliate_distributions.count.should eql 0 + end + + it "student school affiliates gets cut when student school is involved. so does teacher's" do + user.affiliate_referral = school.affiliate_partner + user.save! + + teacher_user.affiliate_referral = affiliate_partner + teacher_user.save! + + lesson = monthly_lesson(user, teacher_user, true) + + user.sales.count.should eql 1 + user.sales[0].sale_line_items[0].affiliate_distributions.count.should eql 2 + user.lesson_purchases.count.should eql 1 + lesson_package_purchase = user.lesson_purchases.first + teacher_distribution = lesson_package_purchase.teacher_distribution + + + affiliate_partner.affiliate_distributions.count.should eql 1 + partner1_distribution = affiliate_partner.affiliate_distributions.count.should eql 1 + school.affiliate_partner.affiliate_distributions.count.should eql 1 + school_partner_distribution = school.affiliate_partner.affiliate_distributions.first + school_partner_distribution.affiliate_referral_fee_in_cents.should eql (teacher_distribution.jamkazam_margin_in_cents * school.affiliate_partner.lesson_rate).round end end diff --git a/ruby/spec/jam_ruby/flows/normal_lesson_spec.rb b/ruby/spec/jam_ruby/flows/normal_lesson_spec.rb index 404978968..795e4c476 100644 --- a/ruby/spec/jam_ruby/flows/normal_lesson_spec.rb +++ b/ruby/spec/jam_ruby/flows/normal_lesson_spec.rb @@ -12,8 +12,12 @@ describe "Normal Lesson Flow" do let(:lesson_booking_slot_recurring2) { FactoryGirl.build(:lesson_booking_slot_recurring) } let(:valid_single_slots) { [lesson_booking_slot_single1, lesson_booking_slot_single2] } let(:valid_recurring_slots) { [lesson_booking_slot_recurring1, lesson_booking_slot_recurring2] } + let(:affiliate_partner) { FactoryGirl.create(:affiliate_partner) } + let(:affiliate_partner2) { FactoryGirl.create(:affiliate_partner, lesson_rate: 0.30) } let(:school) {FactoryGirl.create(:school)} + after {Timecop.return} + describe "stripe mocked" do before { StripeMock.clear_errors @@ -22,7 +26,6 @@ describe "Normal Lesson Flow" do teacher.save! } after { StripeMock.stop } - after {Timecop.return} it "bill failure" do @@ -434,22 +437,18 @@ describe "Normal Lesson Flow" do booking.school.should be_true booking.card_presumed_ok.should be_false booking.user.should eql user - user.unprocessed_normal_lesson.should eql [] - booking.sent_notices.should be_false + user.unprocessed_normal_lesson.should be_nil + booking.sent_notices.should be_true booking.booked_price.should eql 30.00 booking.is_requested?.should be_true - booking.sent_notices.should be_true - lesson.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0) + booking.lesson_sessions[0].music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0) LessonPaymentCharge.count.should eql 0 user.reload - user.stripe_customer_id.should_not be nil + user.stripe_customer_id.should be_nil user.remaining_test_drives.should eql 0 user.lesson_purchases.length.should eql 0 - customer = Stripe::Customer.retrieve(user.stripe_customer_id) - customer.email.should eql user.email - booking.lesson_sessions.length.should eql 1 lesson_session = booking.lesson_sessions[0] lesson_session.status.should eql LessonBooking::STATUS_REQUESTED @@ -550,35 +549,75 @@ describe "Normal Lesson Flow" do if lesson_session.billing_error_detail puts "testdrive flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests end - lesson_session.billed.should be true + lesson_session.billed.should be false user.reload - user.lesson_purchases.length.should eql 1 - lesson_purchase = user.lesson_purchases[0] - lesson_purchase.price.should eql 30.00 - lesson_purchase.lesson_package_type.is_normal?.should eql true - lesson_purchase.price_in_cents.should eql 3000 - user.sales.length.should eql 1 - sale = user.sales.first - sale.stripe_charge_id.should_not be_nil - sale.recurly_tax_in_cents.should eql (100 * booking.booked_price.to_f * 0.0825).round.to_i - sale.recurly_total_in_cents.should eql ((100 * booking.booked_price.to_f * 0.0825).round + 100 * booking.booked_price.to_f).to_i - sale.recurly_subtotal_in_cents.should eql (100 * booking.booked_price).to_i - sale.recurly_currency.should eql 'USD' - sale.stripe_charge_id.should_not be_nil - line_item = sale.sale_line_items[0] - line_item.quantity.should eql 1 - line_item.product_type.should eql SaleLineItem::LESSON - line_item.product_id.should eq LessonPackageType.single.id - line_item.lesson_package_purchase.should eql lesson_purchase - lesson_purchase.sale_line_item.should eql line_item - lesson.amount_charged.should eql (sale.recurly_total_in_cents / 100.0).to_f + user.lesson_purchases.length.should eql 0 + user.sales.length.should eql 0 + lesson_session.amount_charged.should eql 0.0 lesson_session.billing_error_reason.should be_nil - lesson_session.sent_billing_notices.should be true + lesson_session.sent_billing_notices.should be_nil user.reload user.remaining_test_drives.should eql 0 - UserMailer.deliveries.length.should eql 2 # one for student, one for teacher + UserMailer.deliveries.length.should eql 0 # one for student, one for teacher LessonPaymentCharge.count.should eql 0 TeacherDistribution.count.should eql 0 end + + it "affiliate gets their cut" do + user.affiliate_referral = affiliate_partner + user.save! + teacher_user.affiliate_referral = affiliate_partner2 + teacher_user.save! + + lesson = normal_lesson(user, teacher_user, true) + + user.reload + user.sales.count.should eql 1 + user.sales[0].sale_line_items[0].affiliate_distributions.count.should eql 2 + affiliate_partner.affiliate_distributions.count.should eql 1 + affiliate_partner2.affiliate_distributions.count.should eql 1 + partner1_distribution = affiliate_partner.affiliate_distributions.first + partner2_distribution = affiliate_partner2.affiliate_distributions.first + partner1_distribution.sale_line_item.should eql partner2_distribution.sale_line_item + partner1_distribution.affiliate_referral_fee_in_cents.should eql (3000 * 0.25 * affiliate_partner.lesson_rate).round + partner2_distribution.affiliate_referral_fee_in_cents.should eql (3000 * 0.25 * affiliate_partner2.lesson_rate).round + end + + it "school affiliate gets nothing when teacher school is involved" do + teacher.school = school + teacher.save! + + user.affiliate_referral = affiliate_partner + user.save! + + lesson = normal_lesson(user, teacher_user, true) + + user.sales.count.should eql 1 + user.sales[0].sale_line_items[0].affiliate_distributions.count.should eql 1 + + affiliate_partner.affiliate_distributions.count.should eql 1 + partner1_distribution = affiliate_partner.affiliate_distributions.first + partner1_distribution.affiliate_referral_fee_in_cents.should eql (3000 * 0.25 * affiliate_partner.lesson_rate).round + school.affiliate_partner.affiliate_distributions.count.should eql 0 + end + + it "student school affiliates gets cut when student school is involved. so does teacher's" do + user.affiliate_referral = school.affiliate_partner + user.save! + + teacher_user.affiliate_referral = affiliate_partner + teacher_user.save! + + lesson = normal_lesson(user, teacher_user, true) + + user.sales.count.should eql 1 + user.sales[0].sale_line_items[0].affiliate_distributions.count.should eql 2 + + affiliate_partner.affiliate_distributions.count.should eql 1 + partner1_distribution = affiliate_partner.affiliate_distributions.count.should eql 1 + school.affiliate_partner.affiliate_distributions.count.should eql 1 + school_partner_distribution = school.affiliate_partner.affiliate_distributions.first + school_partner_distribution.affiliate_referral_fee_in_cents.should eql (3000 * 0.25 * school.affiliate_partner.lesson_rate).round + end end diff --git a/ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb b/ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb index 8eba1de70..00924d4e6 100644 --- a/ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb +++ b/ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb @@ -13,7 +13,7 @@ describe "Recurring Lesson Flow" do let(:valid_single_slots) { [lesson_booking_slot_single1, lesson_booking_slot_single2] } let(:valid_recurring_slots) { [lesson_booking_slot_recurring1, lesson_booking_slot_recurring2] } - before(:each) do + after(:each) do Timecop.return end it "works" do diff --git a/ruby/spec/jam_ruby/flows/testdrive_lesson_spec.rb b/ruby/spec/jam_ruby/flows/testdrive_lesson_spec.rb index 6cd7c13b0..320ce3b5a 100644 --- a/ruby/spec/jam_ruby/flows/testdrive_lesson_spec.rb +++ b/ruby/spec/jam_ruby/flows/testdrive_lesson_spec.rb @@ -12,12 +12,18 @@ describe "TestDrive Lesson Flow" do let(:lesson_booking_slot_recurring2) { FactoryGirl.build(:lesson_booking_slot_recurring) } let(:valid_single_slots) { [lesson_booking_slot_single1, lesson_booking_slot_single2] } let(:valid_recurring_slots) { [lesson_booking_slot_recurring1, lesson_booking_slot_recurring2] } + let(:affiliate_partner) { FactoryGirl.create(:affiliate_partner) } + let(:affiliate_partner2) { FactoryGirl.create(:affiliate_partner, lesson_rate: 0.30) } + let(:school) {FactoryGirl.create(:school)} + before { - Timecop.return teacher.stripe_account_id = stripe_account1_id teacher.save! } + after { + Timecop.return + } it "works" do @@ -224,5 +230,9 @@ describe "TestDrive Lesson Flow" do teacher_payment.teacher_payment_charge.amount_in_cents.should eql 1000 teacher_payment.teacher_payment_charge.fee_in_cents.should eql 0 + user.sales.count.should eql 1 + sale = user.sales[0] + sale.sale_line_items.count.should eql 1 + sale.sale_line_items[0].affiliate_distributions.count.should eql 0 end end diff --git a/ruby/spec/jam_ruby/models/lesson_booking_spec.rb b/ruby/spec/jam_ruby/models/lesson_booking_spec.rb index 29a5f2cec..ab7ea97d3 100644 --- a/ruby/spec/jam_ruby/models/lesson_booking_spec.rb +++ b/ruby/spec/jam_ruby/models/lesson_booking_spec.rb @@ -291,7 +291,7 @@ describe LessonBooking do end describe "billable_monthlies" do - before do + after do Timecop.return end diff --git a/ruby/spec/jam_ruby/models/lesson_session_spec.rb b/ruby/spec/jam_ruby/models/lesson_session_spec.rb index 938de25e7..be1a8963c 100644 --- a/ruby/spec/jam_ruby/models/lesson_session_spec.rb +++ b/ruby/spec/jam_ruby/models/lesson_session_spec.rb @@ -10,6 +10,7 @@ describe LessonSession do 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]} + after{Timecop.return} describe "accept" do it "can accept" do @@ -18,18 +19,20 @@ describe LessonSession do describe "upcoming_sessions_reminder" do it "succeeds" do + lesson = normal_lesson(user, teacher) 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! + #UserMailer.deliveries.count.should eql 2 + lesson.touch + lesson.sent_starting_notice.should be_false + lesson.is_approved?.should be_true + lesson.music_session.scheduled_start = 15.minutes.from_now + lesson.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 + lesson.reload + lesson.sent_starting_notice.should be_true LessonSession.upcoming_sessions_reminder UserMailer.deliveries.count.should eql 0 end diff --git a/ruby/spec/jam_ruby/models/music_session_spec.rb b/ruby/spec/jam_ruby/models/music_session_spec.rb index 4013eb804..426e5cad7 100644 --- a/ruby/spec/jam_ruby/models/music_session_spec.rb +++ b/ruby/spec/jam_ruby/models/music_session_spec.rb @@ -527,7 +527,6 @@ describe MusicSession do dd = Time.now - (interval.to_i + 1).days Timecop.travel(dd) msess1 = FactoryGirl.create(:music_session, creator: creator, scheduled_start: dd) - Timecop.return msess2 = FactoryGirl.create(:music_session, creator: creator) music_sessions, user_scores = sms(searcher, default_opts) expect(music_sessions.length).to be(1) @@ -921,7 +920,6 @@ describe MusicSession do dd = Time.now - (interval.to_i + 1).days Timecop.travel(dd) msess1 = FactoryGirl.create(:music_session) - Timecop.return msess2 = FactoryGirl.create(:music_session) purging = MusicSession.purgeable_sessions expect(purging.size).to be(1) @@ -934,7 +932,6 @@ describe MusicSession do dd = Time.now - (interval.to_i + 1).days Timecop.travel(dd) msess1 = FactoryGirl.create(:music_session, scheduled_start: Time.now) - Timecop.return msess2 = FactoryGirl.create(:music_session, scheduled_start: Time.now) purging = MusicSession.purgeable_sessions expect(purging.size).to be(1) diff --git a/ruby/spec/jam_ruby/models/music_sessions_user_history_spec.rb b/ruby/spec/jam_ruby/models/music_sessions_user_history_spec.rb index 42415be4d..edd19bbc1 100644 --- a/ruby/spec/jam_ruby/models/music_sessions_user_history_spec.rb +++ b/ruby/spec/jam_ruby/models/music_sessions_user_history_spec.rb @@ -7,6 +7,9 @@ describe MusicSessionUserHistory do let(:user_history1) { FactoryGirl.create(:music_session_user_history, :history => music_session.music_session, :user => music_session.creator) } let(:user_history2) { FactoryGirl.create(:music_session_user_history, :history => music_session.music_session, :user => some_user) } + after { + Timecop.return + } describe "create" do pending it {user_history1.music_session_id.should == music_session.id } @@ -31,7 +34,6 @@ describe MusicSessionUserHistory do users = [user_history1, user_history2] Timecop.travel(Time.now + (MusicSessionUserHistory::MIN_SESSION_DURATION_RATING * 1.5).seconds) expect( user_history1.should_rate_session? ).to eq(true) - Timecop.return end it 'should rate fails' do diff --git a/ruby/spec/jam_ruby/models/sale_spec.rb b/ruby/spec/jam_ruby/models/sale_spec.rb index 20291922f..7378cbb2a 100644 --- a/ruby/spec/jam_ruby/models/sale_spec.rb +++ b/ruby/spec/jam_ruby/models/sale_spec.rb @@ -9,7 +9,7 @@ describe Sale do let(:jam_track3) { FactoryGirl.create(:jam_track) } let(:gift_card) { GiftCardType.jam_track_5 } - before(:each) { + after(:each) { Timecop.return } def assert_free_line_item(sale_line_item, jamtrack) @@ -698,8 +698,8 @@ describe Sale do partner1_distribution = affiliate_partner.affiliate_distributions.first partner2_distribution = affiliate_partner2.affiliate_distributions.first partner1_distribution.sale_line_item.should eql partner2_distribution.sale_line_item - partner1_distribution.affiliate_referral_fee_in_cents.should eql (3000 * affiliate_partner.lesson_rate).round - partner2_distribution.affiliate_referral_fee_in_cents.should eql (3000 * affiliate_partner2.lesson_rate).round + partner1_distribution.affiliate_referral_fee_in_cents.should eql (3000 * 0.25 * affiliate_partner.lesson_rate).round + partner2_distribution.affiliate_referral_fee_in_cents.should eql (3000 * 0.25 * affiliate_partner2.lesson_rate).round end it "book recurring, monthly" do diff --git a/ruby/spec/jam_ruby/models/teacher_payment_spec.rb b/ruby/spec/jam_ruby/models/teacher_payment_spec.rb index bd85935dc..5debf2a3e 100644 --- a/ruby/spec/jam_ruby/models/teacher_payment_spec.rb +++ b/ruby/spec/jam_ruby/models/teacher_payment_spec.rb @@ -142,6 +142,7 @@ describe TeacherPayment do end it "charges school" do + teacher.touch normal_distribution.school = school normal_distribution.ready = true normal_distribution.save! diff --git a/ruby/spec/support/lesson_session.rb b/ruby/spec/support/lesson_session.rb index 291c466a5..e21866767 100644 --- a/ruby/spec/support/lesson_session.rb +++ b/ruby/spec/support/lesson_session.rb @@ -1,4 +1,3 @@ - module StripeMock class ErrorQueue def clear @@ -12,13 +11,13 @@ module StripeMock end end -def testdrive_lesson(user, teacher, slots = nil) +def testdrive_lesson(user, teacher, finish = false) - if slots.nil? - slots = [] - slots << FactoryGirl.build(:lesson_booking_slot_single) - slots << FactoryGirl.build(:lesson_booking_slot_single) - end + #if slots.nil? + slots = [] + slots << FactoryGirl.build(:lesson_booking_slot_single) + slots << FactoryGirl.build(:lesson_booking_slot_single) + #end if user.stored_credit_card == false user.stored_credit_card = true @@ -27,7 +26,10 @@ def testdrive_lesson(user, teacher, slots = nil) booking = LessonBooking.book_test_drive(user, teacher, slots, "Hey I've heard of you before.") - #puts "BOOKING #{booking.errors.inspect}" + if booking.errors.any? + puts "BOOKING #{booking.errors.inspect}" + end + booking.errors.any?.should be_false lesson = booking.lesson_sessions[0] booking.card_presumed_ok.should be_true @@ -42,21 +44,39 @@ def testdrive_lesson(user, teacher, slots = nil) lesson.slot.should eql slots[0] lesson.status.should eql LessonSession::STATUS_APPROVED + if finish + # teacher & student get into session + start = lesson.scheduled_start + end_time = lesson.scheduled_start + (60 * lesson.duration) + uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_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 + end + lesson end -def normal_lesson(user, teacher, slots = nil) +def normal_lesson(user, teacher, finish = false) - if slots.nil? - slots = [] - slots << FactoryGirl.build(:lesson_booking_slot_single) - slots << FactoryGirl.build(:lesson_booking_slot_single) - end + #if slots.nil? + slots = [] + slots << FactoryGirl.build(:lesson_booking_slot_single) + slots << FactoryGirl.build(:lesson_booking_slot_single) + #end if user.stored_credit_card == false - user.stored_credit_card = true - user.save! + token = create_stripe_token + result = user.payment_update({token: token, zip: '78759', normal: true}) + #user.stored_credit_card = true + #user.save! end booking = LessonBooking.book_normal(user, teacher, slots, "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60) @@ -76,21 +96,34 @@ def normal_lesson(user, teacher, slots = nil) lesson.status.should eql LessonSession::STATUS_APPROVED lesson.music_session.should_not be_nil + if finish + # teacher & student get into session + start = lesson.scheduled_start + end_time = lesson.scheduled_start + (60 * lesson.duration) + uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_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 + end + lesson end -def monthly_lesson(user, teacher, slots = nil) +def monthly_lesson(user, teacher, finish = true) - if slots.nil? - slots = [] - slots << FactoryGirl.build(:lesson_booking_slot_recurring) - slots << FactoryGirl.build(:lesson_booking_slot_recurring) - end + slots = [] + slots << FactoryGirl.build(:lesson_booking_slot_recurring) + slots << FactoryGirl.build(:lesson_booking_slot_recurring) if user.stored_credit_card == false - user.stored_credit_card = true - user.save! + token = create_stripe_token + result = user.payment_update({token: token, zip: '78759', normal: true}) end booking = LessonBooking.book_normal(user, teacher, slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60) @@ -110,5 +143,29 @@ def monthly_lesson(user, teacher, slots = nil) lesson.status.should eql LessonSession::STATUS_APPROVED lesson.music_session.should_not be_nil + if finish + # teacher & student get into session + start = lesson.scheduled_start + end_time = lesson.scheduled_start + (60 * lesson.duration) + uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_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 + + today = Date.today + if today.month == 12 + next_month = Date.new(today.year + 1, 1, 1) + else + next_month = Date.new(today.year, today.month + 1, 1) + end + + #Timecop.travel(next_month) + LessonBooking.hourly_check + end + lesson end \ No newline at end of file diff --git a/web/app/assets/javascripts/homeScreen.js b/web/app/assets/javascripts/homeScreen.js index ef94e580f..a745e1b65 100644 --- a/web/app/assets/javascripts/homeScreen.js +++ b/web/app/assets/javascripts/homeScreen.js @@ -92,9 +92,14 @@ $('.homecard.jamclass').on('click', function() { - context.JK.Banner.showNotice("Coming Soon!", "JamClass is just around the corner.") + if (gon.jamclass_enabled) { + return true; + } + else { + context.JK.Banner.showNotice("Coming Soon!", "JamClass is just around the corner.") + return false; - return false; + } }) } diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 677e0c4b5..6f848d596 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -2480,6 +2480,16 @@ }); } + function createReview(options) { + + return $.ajax({ + type: "POST", + url: '/api/reviews', + dataType: "json", + contentType: 'application/json', + data: JSON.stringify(options), + }); + } function initialize() { return self; @@ -2703,6 +2713,7 @@ this.deleteSchoolStudent = deleteSchoolStudent; this.listTeacherDistributions = listTeacherDistributions; this.lessonStartTime = lessonStartTime; + this.createReview = createReview; return this; }; diff --git a/web/app/assets/javascripts/react-components/BookLesson.js.jsx.coffee b/web/app/assets/javascripts/react-components/BookLesson.js.jsx.coffee index e16c0126d..e003890b6 100644 --- a/web/app/assets/javascripts/react-components/BookLesson.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/BookLesson.js.jsx.coffee @@ -96,7 +96,8 @@ UserStore = context.UserStore userDetailDone: (response) -> if response.id == @state.teacherId - @setState({teacher: response, isSelf: response.id == context.JK.currentUserId}) + school_on_school = response.teacher.school_id? && @state.user?.school_id? && response.teacher.school_id == @state.user.school_id + @setState({teacher: response, isSelf: response.id == context.JK.currentUserId, school_on_school: school_on_school}) else logger.debug("BookLesson: ignoring teacher details", response.id, @state.teacherId) @@ -226,7 +227,7 @@ UserStore = context.UserStore booked: (response) -> @setState({updating: false}) UserActions.refresh() - if response.user['has_stored_credit_card?'] + if response.user['has_stored_credit_card?'] || @state.school_on_school context.JK.Banner.showNotice("Lesson Requested","The teacher has been notified of your lesson request, and should respond soon.

We've taken you automatically to the page for this request, and sent an email to you with a link here as well. All communication with the teacher will show up on this page and in email.") url = "/client#/jamclass/lesson-booking/#{response.id}" url = "/client#/jamclass" diff --git a/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee b/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee index bca5cb162..e4e0d5b6d 100644 --- a/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee @@ -173,7 +173,7 @@ UserStore = context.UserStore acceptLessonBookingFail: (response ) -> @setState({updatingLesson: false}) - logger.debug("accept lesson booking failed", response) + logger.debug("accept lesson booking failed " + response.responseText) @app.ajaxError(arguments[0], arguments[1], arguments[2]) cancelLessonBookingFail: (jqXHR) -> @@ -699,12 +699,9 @@ UserStore = context.UserStore renderTeacherRequested: () -> - console.log("renderTeacherRequested") if @isTestDrive() - console.log("test drive") action = `

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()}.

` `
diff --git a/web/app/assets/javascripts/react-components/LessonPayment.js.jsx.coffee b/web/app/assets/javascripts/react-components/LessonPayment.js.jsx.coffee index 384ce3b9e..f79a22bce 100644 --- a/web/app/assets/javascripts/react-components/LessonPayment.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/LessonPayment.js.jsx.coffee @@ -159,6 +159,7 @@ UserStore = context.UserStore e.preventDefault() if !window.Stripe? + logger.error("no window.Stripe") @app.layout.notify({ title: 'Payment System Not Loaded', text: "Please refresh this page and try to enter your info again. Sorry for the inconvenience!" @@ -230,6 +231,7 @@ UserStore = context.UserStore @setState({updating: true}) + logger.debug("creating stripe token") window.Stripe.card.createToken(data, (status, response) => (@stripeResponseHandler(status, response))); stripeResponseHandler: (status, response) -> @@ -269,6 +271,7 @@ UserStore = context.UserStore data.name = @root.find('#set-user-on-card').val() @setState({updating: true}) + logger.debug("submitting purchase info: " + JSON.stringify(data)) rest.submitStripe(data).done((response) => @stripeSubmitted(response)).fail((jqXHR) => @stripeSubmitFailure(jqXHR)) stripeSubmitted: (response) -> @@ -287,8 +290,6 @@ UserStore = context.UserStore prefixLength = "test-drive-".length packageLength = response.lesson_package_type.package_type.length - logger.debug("prefix: " + prefixLength.toString()) - logger.debug("package: " + packageLength.toString()) testDriveCount = response.lesson_package_type.package_type.substring(prefixLength, packageLength) logger.debug("testDriveCount: " + testDriveCount) @@ -336,6 +337,7 @@ UserStore = context.UserStore window.location = "/client#/teachers/search" stripeSubmitFailure: (jqXHR) -> + logger.debug("stripe submission failure", jqXHR.responseText) @setState({updating: false}) handled = false if jqXHR.status == 422 diff --git a/web/app/assets/javascripts/react-components/RateUserDialog.js.jsx.coffee b/web/app/assets/javascripts/react-components/RateUserDialog.js.jsx.coffee new file mode 100644 index 000000000..889210ed4 --- /dev/null +++ b/web/app/assets/javascripts/react-components/RateUserDialog.js.jsx.coffee @@ -0,0 +1,103 @@ +context = window + +@RateUserDialog = React.createClass({ + + mixins: [Reflux.listenTo(@AppStore, "onAppInit")] + teacher: false + + parseId:(id) -> + if !id? + {id: null, type: null} + else + bits = id.split('_') + if bits.length == 2 + {id: bits[1], type: bits[0]} + else + {id: null, type: null} + + beforeShow: (args) -> + logger.debug("RateUserDialog.beforeShow", args.d1) + @firstName = '' + @lastName = '' + @email = '' + + @setState({target: null}) + + rest.getUserDetail({id: args.d1}).done((response) => @userLookupDone(response)).fail((jqXHR) => @userLookupFail(jqXHR)) + afterHide: () -> + + userLookupDone: (response) -> + @setState({target: response}) + + userLookupFail: (jqXHR) -> + @app.ajaxError(jqXHR, null, null) + + onAppInit: (@app) -> + dialogBindings = { + 'beforeShow': @beforeShow, + 'afterHide': @afterHide + }; + + @app.bindDialog('rate-user', dialogBindings); + + + componentDidMount: () -> + @root = $(@getDOMNode()) + + getInitialState: () -> + {inviteErrors: null} + + doCancel: (e) -> + e.preventDefault() + @app.layout.closeDialog('rate-user', true); + + doRating: (e) -> + e.preventDefault() + + rest.createReview({id: target}) + + createDone:(response) -> + context.SchoolActions.addInvitation(@state.teacher, response) + context.JK.Banner.showNotice("invitation sent", "Your invitation has been sent!") + @app.layout.closeDialog('invite-school-user') + + createFail: (jqXHR) -> + + handled = false + + if jqXHR.status == 422 + errors = JSON.parse(jqXHR.responseText) + @setState({inviteErrors: errors}) + handled = true + + if !handled + @app.ajaxError(jqXHR, null, null) + + render: () -> + + if @state.user?.teacher? + title = 'Rate Teacher' + help = `

Please rate this teacher based on your experience with them.

` + else + title = 'Rate Student' + help = `

Please rate this student based on your experience with them.

` + + + `
+
+ + +

{title}

+
+
+ + {help} + +
+ CANCEL + SUBMIT RATING +
+
+
` + +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/TeacherSearchOptionsScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/TeacherSearchOptionsScreen.js.jsx.coffee index 4c20d0e2e..4921f7859 100644 --- a/web/app/assets/javascripts/react-components/TeacherSearchOptionsScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/TeacherSearchOptionsScreen.js.jsx.coffee @@ -11,7 +11,7 @@ LocationActions = @LocationActions LIMIT: 20 getInitialState: () -> - {options: {}} + {options: {onlyMySchool: true}} onAppInit: (@app) -> @app.bindScreen('jamclass/searchOptions', {beforeShow: @beforeShow, afterShow: @afterShow}) @@ -22,6 +22,7 @@ LocationActions = @LocationActions afterShow: (e) -> onUserChanged: (@user) -> + @setState(user: @user?.user) onTeacherSearchChanged: (options) -> @setState({options: options}) @@ -59,12 +60,25 @@ LocationActions = @LocationActions handleYearsTeaching: (e) -> yearsTeaching = $(e.target).val() - logger.debug("years teaching:", yearsTeaching) options = @state.options options['years-teaching'] = yearsTeaching @setState({options: options}) + onlyMySchoolChange: (e) -> + $target = $(e.target) + checked = $target.is(':checked') + options = @state.options + options.onlyMySchool = checked + @setState(options) + render: () -> + if @state.user?.school_id? + onlySchoolOption = + `
+

School Options

+ +
` + selectedAge = null yearsTeaching = [] for yr in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 40, 45, 50] @@ -129,6 +143,7 @@ LocationActions = @LocationActions
+ {onlySchoolOption}
CANCEL 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 b6a5c93f5..af66a945f 100644 --- a/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee @@ -48,6 +48,7 @@ ProfileActions = @ProfileActions @needToSearch = false afterHide: (e) -> + @contentBodyScroller.off('scroll') onTeacherSearchStore: (storeChanged) -> @needToSearch = true @@ -97,16 +98,17 @@ ProfileActions = @ProfileActions )) - if @state.next == null - @contentBodyScroller.off('scroll') - if @state.currentPage == 1 and @state.results.length == 0 - @endOfList.text('No Teachers found matching your search').show() - logger.debug("TeacherSearch: empty search") - else if @state.currentPage > 0 - logger.debug("end of search") - @endOfList.text('No more Teachers').show() - else - @registerInfiniteScroll(@contentBodyScroller) + if @visible + if @state.next == null + @contentBodyScroller.off('scroll') + if @state.currentPage == 1 and @state.results.length == 0 + @endOfList.text('No Teachers found matching your search').show() + logger.debug("TeacherSearch: empty search") + else if @state.currentPage > 0 + logger.debug("end of search") + @endOfList.text('No more Teachers').show() + else + @registerInfiniteScroll(@contentBodyScroller) moreAboutTeacher: (user, e) -> e.preventDefault() @@ -176,6 +178,10 @@ ProfileActions = @ProfileActions searchOptions = TeacherSearchStore.getState() summary = '' + + if searchOptions.onlyMySchool + summary += "my school teachers only" + instruments = searchOptions.instruments if instruments? && instruments.length > 0 if instruments.length == 1 @@ -233,8 +239,12 @@ ProfileActions = @ProfileActions if !bio? bio = 'No bio' - console.log("@state.sur : #{@state.user.remaining_test_drives}, #{@state.user['can_buy_test_drive?']}") - if !@state.user? || @state.user.remaining_test_drives > 0 || @state.user['can_buy_test_drive?'] + school_on_school = user.teacher.school_id? && @state.user?.school_id? && user.teacher.school_id == @state.user.school_id + + bookSingleBtn = null + bookTestDriveBtn = null + + if !school_on_school && (!@state.user? || @state.user.remaining_test_drives > 0 || @state.user['can_buy_test_drive?']) bookTestDriveBtn = `BOOK TESTDRIVE LESSON` else bookSingleBtn = `BOOK LESSON` diff --git a/web/app/assets/javascripts/react-components/TestDriveSelectionScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/TestDriveSelectionScreen.js.jsx.coffee index 8a240d9a9..1875f0715 100644 --- a/web/app/assets/javascripts/react-components/TestDriveSelectionScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/TestDriveSelectionScreen.js.jsx.coffee @@ -20,7 +20,6 @@ UserStore = context.UserStore beforeHide: (e) -> - afterShow: (e) -> logger.debug("TestDriveSelection: afterShow", e.id) @@ -60,7 +59,7 @@ UserStore = context.UserStore packageSelect: (packageType, e) -> e.preventDefault() - console.log("test-drive-#{packageType}") + logger.debug("user selected test-drive-#{packageType}") rest.updateUser({desired_package: "test-drive-#{packageType}"}).done((response) => @packageSelectedDone(response)).fail((jqXHR) => @packageSelectedFail(jqXHR)) @@ -72,7 +71,6 @@ UserStore = context.UserStore url = "/client#/jamclass/book-lesson/test-drive" - console.log("TEACHER", JSON.stringify(@state.teacher)) if @state.teacher? url += '_' + @state.teacher.id else diff --git a/web/app/assets/javascripts/react-components/stores/TeacherSearchResultsStore.js.coffee b/web/app/assets/javascripts/react-components/stores/TeacherSearchResultsStore.js.coffee index 018cf10fc..db6e5e22b 100644 --- a/web/app/assets/javascripts/react-components/stores/TeacherSearchResultsStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/TeacherSearchResultsStore.js.coffee @@ -76,6 +76,7 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions query.years_teaching = searchOptions['years-teaching'] query.country = searchOptions.location?.country query.region = searchOptions.location?.region + query.onlyMySchool = searchOptions.onlyMySchool query query diff --git a/web/app/assets/javascripts/react-components/stores/TeacherSearchStore.js.coffee b/web/app/assets/javascripts/react-components/stores/TeacherSearchStore.js.coffee index bf65b6736..ea67beddb 100644 --- a/web/app/assets/javascripts/react-components/stores/TeacherSearchStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/TeacherSearchStore.js.coffee @@ -8,7 +8,7 @@ TeacherSearchActions = @TeacherSearchActions @TeacherSearchStore = Reflux.createStore( { listenables: TeacherSearchActions - searchOptions: {} + searchOptions: {onlyMySchool: true} viewingTeacher: null init: -> diff --git a/web/app/assets/stylesheets/client/react-components/TeacherSearchOptions.css.scss b/web/app/assets/stylesheets/client/react-components/TeacherSearchOptions.css.scss index c2f808c0e..3738be5f5 100644 --- a/web/app/assets/stylesheets/client/react-components/TeacherSearchOptions.css.scss +++ b/web/app/assets/stylesheets/client/react-components/TeacherSearchOptions.css.scss @@ -86,6 +86,16 @@ margin-top:10px; } } + .school-options { + label { + display:inline-block; + vertical-align:middle; + } + input { + vertical-align: middle; + margin-right:10px; + } + } .years-teaching-header { margin-top:30px; diff --git a/web/app/controllers/api_reviews_controller.rb b/web/app/controllers/api_reviews_controller.rb index 0aaa0b6a6..dcdd8d3b0 100644 --- a/web/app/controllers/api_reviews_controller.rb +++ b/web/app/controllers/api_reviews_controller.rb @@ -16,7 +16,6 @@ class ApiReviewsController < ApiController # Create a review: def create @review = Review.create(params) - respond_with_model(@review) end # List reviews matching targets for given review summary: diff --git a/web/app/helpers/client_helper.rb b/web/app/helpers/client_helper.rb index 25c95128f..f0515cc80 100644 --- a/web/app/helpers/client_helper.rb +++ b/web/app/helpers/client_helper.rb @@ -72,6 +72,7 @@ module ClientHelper gon.session_stat_thresholds = Rails.application.config.session_stat_thresholds gon.midi_enabled = Rails.application.config.midi_enabled gon.chat_blast = Rails.application.config.chat_blast + gon.jamclass_enabled = Rails.application.config.jamclass_enabled # is this the native client or browser? @nativeClient = is_native_client? diff --git a/web/app/views/api_reviews/create.rabl b/web/app/views/api_reviews/create.rabl new file mode 100644 index 000000000..b493d229c --- /dev/null +++ b/web/app/views/api_reviews/create.rabl @@ -0,0 +1,3 @@ +object @review + +extends "api_reviews/show" \ No newline at end of file diff --git a/web/app/views/api_reviews/show.rabl b/web/app/views/api_reviews/show.rabl new file mode 100644 index 000000000..5fc22c1d4 --- /dev/null +++ b/web/app/views/api_reviews/show.rabl @@ -0,0 +1,3 @@ +object @review + +attributes :id, :rating diff --git a/web/app/views/api_teachers/detail.rabl b/web/app/views/api_teachers/detail.rabl index 86c843c39..d0ff46327 100644 --- a/web/app/views/api_teachers/detail.rabl +++ b/web/app/views/api_teachers/detail.rabl @@ -35,7 +35,8 @@ attributes :id, :teaches_test_drive, :test_drives_per_week, :errors, - :profile_pct + :profile_pct, + :school_id child :review_summary => :review_summary do @@ -107,3 +108,9 @@ node :experiences_award do |teacher| } end # collect end + +if current_user + node :has_rated_teacher do |teacher| + current_user.has_rated_teacher(teacher) + end +end \ No newline at end of file diff --git a/web/app/views/api_users/show.rabl b/web/app/views/api_users/show.rabl index 5cf41f03e..b8f2ff2bc 100644 --- a/web/app/views/api_users/show.rabl +++ b/web/app/views/api_users/show.rabl @@ -28,7 +28,7 @@ end # give back more info if the user being fetched is yourself if current_user && @user == current_user - attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :show_whats_next_count, :subscribe_email, :auth_twitter, :new_notifications, :sales_count, :reuse_card, :purchased_jamtracks_count, :first_downloaded_client_at, :created_at, :first_opened_jamtrack_web_player, :gifted_jamtracks, :has_redeemable_jamtrack, :remaining, :has_stored_credit_card?, :remaining_test_drives, :can_buy_test_drive?, :lesson_package_type_id + attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :show_whats_next_count, :subscribe_email, :auth_twitter, :new_notifications, :sales_count, :reuse_card, :purchased_jamtracks_count, :first_downloaded_client_at, :created_at, :first_opened_jamtrack_web_player, :gifted_jamtracks, :has_redeemable_jamtrack, :remaining, :has_stored_credit_card?, :remaining_test_drives, :can_buy_test_drive?, :lesson_package_type_id, :school_id node :owned_school_id do |user| user.owned_school.id if user.owned_school @@ -97,6 +97,9 @@ elsif current_user node :internet_score do |user| current_user.score_info(user) end + node :has_rated_student do |user| + current_user.has_rated_student(user) + end end child :friends => :friends do diff --git a/web/config/application.rb b/web/config/application.rb index add6a3775..2bd476537 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -437,6 +437,6 @@ if defined?(Bundler) config.lesson_wait_time_window_minutes = 10 config.end_of_wait_window_forgiveness_minutes = 1 config.olark_enabled = true - + config.jamclass_enabled = false end end diff --git a/web/spec/features/book_single_lesson_spec.rb b/web/spec/features/book_single_lesson_spec.rb index 608e8afc8..79c33204d 100644 --- a/web/spec/features/book_single_lesson_spec.rb +++ b/web/spec/features/book_single_lesson_spec.rb @@ -8,9 +8,11 @@ describe "Single Lesson", :js => true, :type => :feature, :capybara_feature => t let(:teacher_user) {FactoryGirl.create(:teacher_user, first_name: "Teacher1", ready_for_session_at: Time.now)} let(:teacher_user2) {FactoryGirl.create(:teacher_user, ready_for_session_at: Time.now)} let(:teacher_user3) {FactoryGirl.create(:teacher_user, ready_for_session_at: Time.now)} + let(:school) {FactoryGirl.create(:school)} before(:each) do + LessonPackagePurchase.destroy_all LessonBooking.destroy_all Recording.delete_all Diagnostic.delete_all @@ -19,12 +21,8 @@ describe "Single Lesson", :js => true, :type => :feature, :capybara_feature => t UserMailer.deliveries.clear emulate_client # create an old test drive and fakely use up all the credits so that we can book the lesson - Timecop.travel(Date.new(2016, 03, 01)) - testdrive_lesson(user, teacher_user3) - user.remaining_test_drives = 0 - user.save! - user.reload - sign_in_poltergeist user + + teacher_user.teacher.ready_for_session_at = Time.now teacher_user.teacher.save! teacher_user.teacher.price_per_lesson_60_cents.should eql 3000 @@ -39,6 +37,14 @@ describe "Single Lesson", :js => true, :type => :feature, :capybara_feature => t end it "succeeds" do + Timecop.travel(Date.new(2016, 03, 01)) + testdrive_lesson(user, teacher_user3) + user.remaining_test_drives = 0 + user.save! + user.reload + + sign_in_poltergeist user + visit "/client#/teachers/search" Timecop.travel(Date.new(2016, 04, 01)) @@ -90,6 +96,7 @@ describe "Single Lesson", :js => true, :type => :feature, :capybara_feature => t lesson_booking = LessonBooking.where(teacher_id: teacher_user).order(:created_at).last lesson_booking.should_not be_nil + lesson_booking.same_school.should be_false lesson_session = LessonSession.where(teacher_id: teacher_user).order(:created_at).last lesson_session.teacher.should eql teacher_user @@ -198,7 +205,177 @@ describe "Single Lesson", :js => true, :type => :feature, :capybara_feature => t lesson_session2.reload lesson_session2.analysed.should be_true analysis = JSON.parse(lesson_session2.analysis) - analysis["reason"].should eql LessonSessionAnalyser::NEITHER_SHOW + analysis["reason"].should eql LessonSessionAnalyser::TEACHER_FAULT + lesson_session2.billing_attempts.should eql 0 + lesson_session2.billed.should eql false + lesson_session2.success.should be_false + + lesson_session2.teacher_distribution.should be_nil + end + + + it "succeeds (school-on-school)" do + + teacher_user.teacher.school = school + teacher_user.teacher.save + user.school = school + user.save! + + sign_in_poltergeist user + + visit "/client#/teachers/search" + + Timecop.travel(Date.new(2016, 04, 01)) + + find('.teacher-search-result[data-teacher-id="' + teacher_user.id + '"] .try-normal').trigger(:click) + + # book the lesson + fill_in "slot-1-date", with: "Sun Apr 17 2016" + #find('.slot.slot-1 input.hasDatepicker').trigger(:click) + # click 4-6 + find('td a', text: '17').trigger(:click) + + #find('.slot.slot-2 input.hasDatepicker').trigger(:click) + # click 4-7 + fill_in "slot-2-date", with: "Mon Apr 18 2016" + find('td a', text: '18').trigger(:click) + + fill_in 'user-description', with: 'abc def dog neck' + + select('60 Minute Lesson for $30.00', :from => "booking-options-for-teacher") + sleep 3 + + find('a.book-lesson-btn', text: 'BOOK LESSON').trigger(:click) + + #find('h2', text: 'your lesson has been requested') + + #find('h2', text: 'enter payment info for lesson') + + + #fill_in 'card-number', with: '4111111111111111' + #fill_in 'expiration', with: '11/2016' + #fill_in 'cvv', with: '111' + #fill_in 'zip', with: '78759' + + #find('.purchase-btn').trigger(:click) + + # we tell user they have test drive purchased, and take them to the teacher screen + find('#banner h1', text: 'Lesson Requested') + # dismiss banner + find('a.button-orange', text:'CLOSE').trigger(:click) + + user.student_lesson_bookings.count.should eql 1 # this single one + lesson_booking = user.student_lesson_bookings.order(:created_at).last + lesson_booking.is_requested?.should be_true + lesson_booking.card_presumed_ok.should be_false + lesson_booking.recurring.should be false + + + + lesson_booking = LessonBooking.where(teacher_id: teacher_user).order(:created_at).last + lesson_booking.should_not be_nil + lesson_booking.school.should eql school + lesson_booking.same_school.should be_true + lesson_session = LessonSession.where(teacher_id: teacher_user).order(:created_at).last + lesson_session.teacher.should eql teacher_user + + lesson_package_purchase = LessonPackagePurchase.where(user_id: user.id, teacher_id: teacher_user.id).order(:created_at).first + lesson_package_purchase.should be_nil + user.reload + user.remaining_test_drives.should eql 0 + user.sales.count.should eql 0 + + # jamclass scren + find('h2', text: 'my lessons') + find('tr[data-lesson-session-id="' + lesson_session.id + '"] .displayStatusColumn', text: 'Requested') + find('tr[data-lesson-session-id="' + lesson_session.id + '"] .first_name', text: teacher_user.first_name) + + # open up hover + find('tr[data-lesson-session-id="' + lesson_session.id + '"] .lesson-session-actions-btn').trigger(:click) + + # check out the status + find('li[data-lesson-option="status"] a', text: 'View Status').trigger(:click) + + # and now go to the session status screen + find('h2', text: 'your lesson has been requested') + + # let's make a second request to a different teacher now + + # let's make sure we can ask for another lesson too! + teacher_user2.teacher.ready_for_session_at = Time.now + teacher_user2.teacher.save! + + visit "/client#/teachers/search" + find('span.search-summary', text: 'my school teachers only') + find('a.teacher-search-options').trigger(:click) + find('input.onlyMySchool').trigger(:click) # uncheck + find('a.search-btn').trigger(:click) + find('span.search-summary', text: 'all teachers') + find('.teacher-search-result[data-teacher-id="' + teacher_user2.id + '"] .try-test-drive').trigger(:click) + + select_test_drive(4) + + fill_out_single_lesson + + fill_out_payment + + sleep 5 + + find('h2', text: 'my lessons') + # dismiss banner + find('a.button-orange', text:'CLOSE').trigger(:click) + + user.reload + user.has_stored_credit_card?.should be_true + lesson_session1 = lesson_session + lesson_booking = LessonBooking.where(teacher_id: teacher_user2.id).first + lesson_booking.should_not be_nil + lesson_booking.card_presumed_ok.should be_true + lesson_session = LessonSession.where(teacher_id: teacher_user2.id).first + lesson_session.teacher.should eql teacher_user2 + lesson_session2 = lesson_session + + lesson_package_purchase = LessonPackagePurchase.where(user_id: user.id, teacher_id: teacher_user.id).order(:created_at).first + lesson_package_purchase.should be_nil + user.reload + user.remaining_test_drives.should eql 3 + user.sales.count.should eql 1 + + # approve by teacher: + teacher_approve(lesson_session1) + + successful_lesson(lesson_session1) + + LessonSession.hourly_check + lesson_session1.reload + lesson_session1.analysed.should be_true + analysis = JSON.parse(lesson_session1.analysis) + analysis["reason"].should eql LessonSessionAnalyser::SUCCESS + lesson_session1.success.should be_true + lesson_session1.billing_attempts.should eql nil + lesson_session1.billed.should eql false + + LessonBooking.hourly_check + + lesson_session1.reload + teacher_distribution = lesson_session1.teacher_distribution + teacher_distribution.should be_nil + + # check the second session, which no one went too + + + lesson_session2.reload + lesson_session2.analysed.should be_false + + # approve by teacher: + teacher_approve(lesson_session2) + LessonSession.hourly_check + LessonBooking.hourly_check + + lesson_session2.reload + lesson_session2.analysed.should be_true + analysis = JSON.parse(lesson_session2.analysis) + analysis["reason"].should eql LessonSessionAnalyser::TEACHER_FAULT lesson_session2.billing_attempts.should eql 0 lesson_session2.billed.should eql false lesson_session2.success.should be_false diff --git a/web/spec/features/book_test_drive_spec.rb b/web/spec/features/book_test_drive_spec.rb index 2082a5842..741f7fe7b 100644 --- a/web/spec/features/book_test_drive_spec.rb +++ b/web/spec/features/book_test_drive_spec.rb @@ -58,7 +58,7 @@ describe "Test Drive", :js => true, :type => :feature, :capybara_feature => true lesson_package_purchase = LessonPackagePurchase.where(user_id: user.id).first lesson_package_purchase.should_not be_nil lesson_package_purchase.lesson_package_type.is_test_drive?.should be_true - lesson_package_purchase.lesson_payment_charge.should_not be_nil + lesson_package_purchase.lesson_payment_charge.should be_nil user.reload user.remaining_test_drives.should eql 3 #lesson_package_purchase.amount_charged.should eql 49.99 diff --git a/web/spec/features/school_landing_spec.rb b/web/spec/features/school_landing_spec.rb index 7197a3660..d0ada1e8b 100644 --- a/web/spec/features/school_landing_spec.rb +++ b/web/spec/features/school_landing_spec.rb @@ -46,6 +46,7 @@ describe "School Landing", :js => true, :type => :feature, :capybara_feature => user.is_a_teacher.should be false user.school_interest.should be true user.owned_school.should_not be_nil + user.owned_school.affiliate_partner.should_not be_nil user.musician.should be true end diff --git a/web/spec/features/school_student_register_spec.rb b/web/spec/features/school_student_register_spec.rb index c8967ec0d..d8501c937 100644 --- a/web/spec/features/school_student_register_spec.rb +++ b/web/spec/features/school_student_register_spec.rb @@ -1,4 +1,4 @@ -require 'spec_helper' + require 'spec_helper' describe "Student Landing", :js => true, :type => :feature, :capybara_feature => true do @@ -52,6 +52,7 @@ describe "Student Landing", :js => true, :type => :feature, :capybara_feature => student.musician.should be true student.teacher.should be_nil student.school.should eql school + student.affiliate_referral.should eql school.affiliate_partner find('#user-profile #username', text: student.name) @@ -87,6 +88,7 @@ describe "Student Landing", :js => true, :type => :feature, :capybara_feature => student.musician.should be true student.teacher.should be_nil student.school.should eql school + student.affiliate_referral.should eql school.affiliate_partner school_invitation.reload school_invitation.accepted.should be_true @@ -109,6 +111,7 @@ describe "Student Landing", :js => true, :type => :feature, :capybara_feature => find('#user-profile #username', text: user.name) user.reload + user.affiliate_referral.should be_nil #user.is_a_student.should be true #user.is_a_teacher.should be false #user.musician.should be true diff --git a/web/spec/features/school_teacher_register_spec.rb b/web/spec/features/school_teacher_register_spec.rb index ae0d24e8e..586483c13 100644 --- a/web/spec/features/school_teacher_register_spec.rb +++ b/web/spec/features/school_teacher_register_spec.rb @@ -50,6 +50,7 @@ describe "Teacher Landing", :js => true, :type => :feature, :capybara_feature => teacher.teacher.should_not be_nil teacher.school.should be_nil teacher.teacher.school.should eql school + teacher.affiliate_referral.should eql school.affiliate_partner find('#user-profile #username', text: teacher.name) UserMailer.deliveries.count.should eql 2 @@ -85,6 +86,7 @@ describe "Teacher Landing", :js => true, :type => :feature, :capybara_feature => teacher.teacher.should_not be_nil teacher.school.should be_nil teacher.teacher.school.should eql school + teacher.affiliate_referral.should eql school.affiliate_partner school_invitation.reload school_invitation.accepted.should be_true @@ -107,6 +109,7 @@ describe "Teacher Landing", :js => true, :type => :feature, :capybara_feature => find('#user-profile #username', text: user.name) user.reload + user.affiliate_referral.should be_nil #user.is_a_student.should be true #user.is_a_teacher.should be false #user.musician.should be true diff --git a/web/spec/support/lessons.rb b/web/spec/support/lessons.rb index 40ae0e8e2..cc497ebf9 100644 --- a/web/spec/support/lessons.rb +++ b/web/spec/support/lessons.rb @@ -63,6 +63,9 @@ def fill_out_payment fill_in 'zip', with: '78759' find('.purchase-btn').trigger(:click) + + # it needs to go 'disabled state' to indicate it's updating + find('.purchase-btn.disabled') end def select_test_drive(count = 4)