Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop

This commit is contained in:
Jonathan Kolyer 2016-05-15 04:35:53 +00:00
commit 481bc36133
256 changed files with 8683 additions and 1335 deletions

View File

@ -73,7 +73,7 @@ gem 'iso-639'
gem 'rubyzip'
gem 'sanitize'
gem 'slim'
gem 'influxdb'
#gem 'influxdb'
gem 'cause' # needed by influxdb
gem 'influxdb-rails', '0.1.10'
gem 'recurly'
@ -107,7 +107,6 @@ end
group :development, :test do
gem 'capybara'
gem 'rspec-rails', '2.14.2'
gem 'guard-rspec'
gem 'jasmine', '1.3.1'
gem 'execjs', '1.4.0'
#gem 'therubyracer' #, '0.11.0beta8'

View File

@ -0,0 +1,20 @@
ActiveAdmin.register JamRuby::User, :as => 'SchoolInterest' do
menu :label => 'Interested in Schools', :parent => 'JamClass'
config.sort_order = 'created_at desc'
config.batch_actions = false
config.per_page = 100
config.paginate = true
config.filters = false
scope("All", default: true) { |scope| scope.where(school_interest: true) }
index do
column "Name" do |user|
span do
link_to "#{user.name} (#{user.email})", "#{Rails.application.config.external_root_url}/client#/profile/#{user.id}"
end
end
end
end

View File

@ -6,12 +6,13 @@ ActiveAdmin.register JamRuby::User, :as => 'Students' do
config.batch_actions = false
config.per_page = 100
config.paginate = true
config.filters = false
def booked_anything(scope)
def booked_anything(scope)
scope.joins(:student_lesson_bookings).where('lesson_bookings.active = true').uniq
end
scope("Default", default: true) { |scope| booked_anything(scope).order('ready_for_session_at IS NULL DESC') }
scope("Default", default: true) { |scope| scope.where('is_a_student = true OR ((select count(id) from lesson_bookings where lesson_bookings.user_id = users.id) > 0)').order('users.ready_for_session_at IS NULL DESC') }
index do
column "Name" do |user|
@ -45,8 +46,8 @@ ActiveAdmin.register JamRuby::User, :as => 'Students' do
end
end
column "School" do |user|
if teacher.school
teacher.school.name
if user.school
user.school.name
end
end
end

View File

@ -1,6 +1,6 @@
ActiveAdmin.register JamRuby::Teacher, :as => 'Teachers' do
menu :label => 'Teacher', :parent => 'JamClass'
menu :label => 'Teachers', :parent => 'JamClass'
config.sort_order = 'created_at desc'
config.batch_actions = false

View File

@ -1,18 +1,21 @@
GEM
remote: http://rubygems.org/
specs:
little-plugger (1.1.3)
little-plugger (1.1.4)
logging (1.7.2)
little-plugger (>= 1.1.3)
pg (0.17.1)
pg_migrate (0.1.13)
pg_migrate (0.1.14)
logging (= 1.7.2)
pg (= 0.17.1)
thor
thor (0.18.1)
thor (0.19.1)
PLATFORMS
ruby
DEPENDENCIES
pg_migrate (= 0.1.13)!
pg_migrate (= 0.1.14)!
BUNDLED WITH
1.11.2

View File

@ -341,4 +341,10 @@ email_blacklist.sql
jamblaster_connection.sql
teacher_progression.sql
teacher_complete.sql
lessons.sql
lessons.sql
lessons_unread_messages.sql
track_school_signups.sql
add_test_drive_types.sql
updated_subjects.sql
update_payment_history.sql
lesson_booking_schools.sql

View File

@ -0,0 +1,4 @@
INSERT INTO lesson_package_types (id, name, description, package_type, price) VALUES ('test-drive-2', 'Test Drive (2)', 'Two reduced-price lessons which you can use to find that ideal teacher.', 'test-drive-2', 29.99);
INSERT INTO lesson_package_types (id, name, description, package_type, price) VALUES ('test-drive-1', 'Test Drive (1)', 'One reduced-price lessons which you can use to find that ideal teacher.', 'test-drive-1', 15.99);
UPDATE lesson_package_types set name = 'Test Drive (4)', package_type = 'test-drive-4' WHERE id = 'test-drive';
ALTER TABLE users ADD COLUMN lesson_package_type_id VARCHAR(64) REFERENCES lesson_package_types(id);

View File

@ -0,0 +1,3 @@
ALTER TABLE lesson_bookings ADD COLUMN school_id INTEGER REFERENCES schools(id);
ALTER TABLE teacher_payments ADD COLUMN school_id INTEGER REFERENCES schools(id);
ALTER TABLE teacher_distributions ADD COLUMN school_id INTEGER REFERENCES schools(id);

View File

@ -0,0 +1,34 @@
ALTER TABLE chat_messages DROP COLUMN lesson_booking_id;
ALTER TABLE chat_messages ADD COLUMN lesson_session_id VARCHAR(64) REFERENCES lesson_sessions(id);
ALTER TABLE lesson_sessions ADD COLUMN teacher_unread_messages BOOLEAN DEFAULT FALSE NOT NULL;
ALTER TABLE lesson_sessions ADD COLUMN student_unread_messages BOOLEAN DEFAULT FALSE NOT NULL;
ALTER TABLE chat_messages ADD COLUMN purpose VARCHAR(200);
ALTER TABLE lesson_sessions ADD COLUMN student_short_canceled BOOLEAN DEFAULT FALSE NOT NULL;
ALTER TABLE lesson_sessions ADD COLUMN teacher_short_canceled BOOLEAN DEFAULT FALSE NOT NULL;
ALTER TABLE lesson_sessions ADD COLUMN sent_starting_notice BOOLEAN DEFAULT FALSE NOT NULL;
ALTER TABLE lesson_bookings DROP CONSTRAINT lesson_bookings_counter_slot_id_fkey;
ALTER TABLE lesson_bookings ADD CONSTRAINT lesson_bookings_counter_slot_id_fkey FOREIGN KEY (counter_slot_id) REFERENCES lesson_booking_slots(id) ON DELETE CASCADE;
ALTER TABLE lesson_bookings DROP CONSTRAINT lesson_bookings_default_slot_id_fkey;
ALTER TABLE lesson_bookings ADD CONSTRAINT lesson_bookings_default_slot_id_fkey FOREIGN KEY (default_slot_id) REFERENCES lesson_booking_slots(id) ON DELETE CASCADE;
ALTER TABLE lesson_sessions DROP CONSTRAINT lesson_sessions_slot_id_fkey;
ALTER TABLE lesson_sessions ADD CONSTRAINT lesson_sessions_slot_id_fkey FOREIGN KEY (slot_id) REFERENCES lesson_booking_slots(id) ON DELETE CASCADE;
ALTER TABLE users DROP CONSTRAINT users_teacher_id_fkey;
ALTER TABLE users ADD CONSTRAINT users_teacher_id_fkey FOREIGN KEY (teacher_id) REFERENCES teachers(id) ON DELETE CASCADE;
ALTER TABLE music_sessions DROP CONSTRAINT music_sessions_lesson_session_id_fkey;
ALTER TABLE music_sessions ADD CONSTRAINT music_sessions_lesson_session_id_fkey FOREIGN KEY (lesson_session_id) REFERENCES lesson_sessions(id) ON DELETE SET NULL;
ALTER TABLE notifications DROP CONSTRAINT notifications_lesson_session_id_fkey;
ALTER TABLE notifications ADD CONSTRAINT notifications_lesson_session_id_fkey FOREIGN KEY (lesson_session_id) REFERENCES lesson_sessions(id) ON DELETE CASCADE;
ALTER TABLE chat_messages DROP CONSTRAINT chat_messages_lesson_session_id_fkey;
ALTER TABLE chat_messages ADD CONSTRAINT chat_messages_lesson_session_id_fkey FOREIGN KEY (lesson_session_id) REFERENCES lesson_sessions(id) ON DELETE CASCADE;
ALTER TABLE chat_messages DROP CONSTRAINT chat_messages_target_user_id_fkey;
ALTER TABLE chat_messages ADD CONSTRAINT chat_messages_target_user_id_fkey FOREIGN KEY (lesson_session_id) REFERENCES lesson_sessions(id) ON DELETE SET NULL;

View File

@ -0,0 +1 @@
ALTER TABLE USERS ADD COLUMN school_interest BOOLEAN DEFAULT FALSE;

View File

@ -0,0 +1 @@
ALTER TABLE charges ADD COLUMN user_id VARCHAR(64) REFERENCES users(id);

View File

@ -0,0 +1,27 @@
-- https://jamkazam.atlassian.net/browse/VRFS-3407
UPDATE subjects SET description = 'Composition' WHERE id = 'composing';
UPDATE subjects SET description = 'Recording & Production' WHERE id = 'recording';
UPDATE subjects SET description = 'Sight Reading' WHERE id = 'site-reading';
INSERT INTO subjects(id, description) VALUES ('film-scoring', 'Film Scoring');
INSERT INTO subjects(id, description) VALUES ('video-game-scoring', 'Video Game Scoring');
INSERT INTO subjects(id, description) VALUES ('ear-training', 'Ear Training');
INSERT INTO subjects(id, description) VALUES ('harmony', 'Harmony');
INSERT INTO subjects(id, description) VALUES ('music-therapy', 'Music Therapy');
INSERT INTO subjects(id, description) VALUES ('songwriting', 'Songwriting');
INSERT INTO subjects(id, description) VALUES ('conducting', 'Conducting');
INSERT INTO subjects(id, description) VALUES ('instrument-repair', 'Instrument Repair');
INSERT INTO subjects(id, description) VALUES ('improvisation', 'Improvisation');
INSERT INTO subjects(id, description) VALUES ('pro-tools', 'Pro Tools');
INSERT INTO subjects(id, description) VALUES ('ableton-live', 'Ableton Live');
INSERT INTO subjects(id, description) VALUES ('fl-studio', 'FL Studio');
INSERT INTO subjects(id, description) VALUES ('garageband', 'GarageBand');
INSERT INTO subjects(id, description) VALUES ('apple-logic-pro', 'Apple Logic Pro');
INSERT INTO subjects(id, description) VALUES ('presonus-studio-one', 'PreSonus Studio One');
INSERT INTO subjects(id, description) VALUES ('reaper', 'Reaper');
INSERT INTO subjects(id, description) VALUES ('cubase', 'Cubase');
INSERT INTO subjects(id, description) VALUES ('sonar', 'Sonar');
INSERT INTO subjects(id, description) VALUES ('reason', 'Reason');
INSERT INTO subjects(id, description) VALUES ('amplitube', 'AmpliTube');
INSERT INTO subjects(id, description) VALUES ('line-6-pod', 'Line 6 Pod');
INSERT INTO subjects(id, description) VALUES ('guitar-ring', 'Guitar Rig');

View File

@ -619,6 +619,8 @@ message ChatMessage {
optional string msg_id = 4;
optional string created_at = 5;
optional string channel = 6;
optional string lesson_session_id = 7;
optional string purpose = 8;
}
message SendChatMessage {

View File

@ -49,7 +49,7 @@ gem 'rest-client'
gem 'iso-639'
gem 'rubyzip'
gem 'sanitize'
gem 'influxdb'
#gem 'influxdb'
gem 'recurly'
gem 'sendgrid_toolkit', '>= 1.1.1'
gem 'stripe'

View File

@ -57,6 +57,7 @@ require "jam_ruby/resque/scheduled/unused_music_notation_cleaner"
require "jam_ruby/resque/scheduled/user_progress_emailer"
require "jam_ruby/resque/scheduled/daily_job"
require "jam_ruby/resque/scheduled/hourly_job"
require "jam_ruby/resque/scheduled/minutely_job"
require "jam_ruby/resque/scheduled/daily_session_emailer"
require "jam_ruby/resque/scheduled/new_musician_emailer"
require "jam_ruby/resque/scheduled/music_session_reminder"

View File

@ -36,6 +36,14 @@ module JamRuby
subject: options[:subject])
end
def partner(options)
mail(to: APP_CONFIG.email_partners_alias,
from: APP_CONFIG.email_generic_from,
body: options[:body],
content_type: "text/plain",
subject: options[:subject])
end
def recurly_alerts(user, options)
body = options[:body]

View File

@ -81,6 +81,22 @@ module JamRuby
end
end
def school_owner_welcome_message(user)
@user = user
@subject= "Welcome to JamKazam and JamClass online lessons!"
sendgrid_category "Welcome"
sendgrid_unique_args :type => "welcome_message"
sendgrid_recipients([user.email])
sendgrid_substitute('@USERID', [user.id])
sendgrid_substitute(EmailBatchProgression::VAR_FIRST_NAME, [user.first_name])
mail(:to => user.email, :subject => @subject) do |format|
format.text
format.html
end
end
def password_changed(user)
@user = user
@ -912,7 +928,7 @@ module JamRuby
@lesson_session = lesson_session
email = @student.email
subject = "You have used #{@student.remaining_test_drives} of 4 TestDrive lesson credits"
subject = "You have used #{@student.remaining_test_drives} of #{@student.total_test_drives} TestDrive lesson credits"
unique_args = {:type => "student_test_drive_success"}
sendgrid_category "Notification"
@ -927,6 +943,31 @@ module JamRuby
end
end
def student_test_drive_no_bill(lesson_session)
@student = lesson_session.student
@teacher = lesson_session.teacher
@session_name = lesson_session.music_session.name
@session_description = lesson_session.music_session.description
@session_date = lesson_session.slot.pretty_scheduled_start(true)
@session_url = lesson_session.web_url
@lesson_session = lesson_session
email = @student.email
subject = "Your TestDrive with #{@teacher.name} will not be billed"
unique_args = {:type => "student_test_drive_no_bill"}
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
sendgrid_recipients([email])
sendgrid_substitute('@USERID', [@student.id])
mail(:to => email, :subject => subject) do |format|
format.text
format.html { render :layout => "from_user_mailer" }
end
end
# successfully completed, but no more test drives left
def student_test_drive_lesson_done(lesson_session)
@ -939,7 +980,7 @@ module JamRuby
@lesson_session = lesson_session
email = @student.email
subject = "You have used all 4 TestDrive lesson credits"
subject = "You have used all TestDrive lesson credits"
unique_args = {:type => "student_test_drive_success"}
sendgrid_category "Notification"
@ -1229,12 +1270,39 @@ module JamRuby
end
end
# always goes to the teacher
def teacher_distribution_done(teacher_payment)
@school = teacher_payment.school
@teacher_payment = teacher_payment
@distribution = teacher_payment.teacher_distribution
@teacher = teacher_payment.teacher
@payable_teacher = teacher_payment.payable_teacher
@name = @teacher.first_name || 'Anonymous'
@student = @distribution.student
email = @teacher.email
@subject = "You have received payment for your participation in JamClass"
if @school
if @distribution.is_test_drive?
@subject = "Your TestDrive lesson with #{@student.name}"
elsif @distribution.is_normal?
@subject = "Your lesson with #{@student.name}"
elsif @distribution.is_monthly?
@subject = "Your #{@distribution.month_name} lessons with #{@student.name}"
else
@subject = "Your lesson with #{@student.name}"
end
else
if @distribution.is_test_drive?
@subject = "You have earned #{@distribution.real_distribution_display} for your TestDrive lesson with #{@student.first_name}"
elsif @distribution.is_normal?
@subject = "You have earned #{@distribution.real_distribution_display} for your lesson with #{@student.first_name}"
elsif @distribution.is_monthly?
@subject = "You have earned #{@distribution.real_distribution_display} for your #{@distribution.month_name} lessons with #{@student.first_name}"
else
@subject = "You have earned #{@distribution.real_distribution_display} for your lesson with #{@student.first_name}"
end
end
unique_args = {:type => "teacher_distribution_done"}
sendgrid_category "Notification"
@ -1249,24 +1317,51 @@ module JamRuby
end
end
# if school, goes to school owner; otherwise goes to teacher
def teacher_distribution_fail(teacher_payment)
@school = teacher_payment.school
@teacher_payment = teacher_payment
@distribution = teacher_payment.teacher_distribution
@teacher = teacher_payment.teacher
email = @teacher.email
@payable_teacher = teacher_payment.payable_teacher
@student = @distribution.student
@name = @payable_teacher.first_name || 'Anonymous'
email = @payable_teacher.email
@card_declined = teacher_payment.is_card_declined?
@card_expired = teacher_payment.is_card_expired?
@bill_date = teacher_payment.last_billed_at_date
@subject = "We were unable to pay you today"
if @school
if @distribution.is_test_drive?
@subject = "We had a problem paying #{@distribution.real_distribution_display} for #{@teacher.name}'s TestDrive lesson with #{@student.name}"
elsif @distribution.is_normal?
@subject = "We had a problem paying #{@distribution.real_distribution_display} for #{@teacher.name}'s lesson with #{@student.name}"
elsif @distribution.is_monthly?
@subject = "We had a problem paying #{@distribution.real_distribution_display} for #{@teacher.name}'s #{@distribution.month_name} lessons with #{@student.name}"
else
@subject = "We had a problem paying #{@distribution.real_distribution_display} for #{@teacher.name}'s lesson with #{@student.name}"
end
else
if @distribution.is_test_drive?
@subject = "We had a problem paying you #{@distribution.real_distribution_display} for your TestDrive lesson with #{@student.first_name}"
elsif @distribution.is_normal?
@subject = "We had a problem paying you #{@distribution.real_distribution_display} for your lesson with #{@student.first_name}"
elsif @distribution.is_monthly?
@subject = "We had a problem paying you #{@distribution.real_distribution_display} for your #{@distribution.month_name} lessons with #{@student.first_name}"
else
@subject = "We had a problem paying you #{@distribution.real_distribution_display} for your lesson with #{@student.first_name}"
end
end
unique_args = {:type => "teacher_distribution_fail"}
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
sendgrid_recipients([email])
sendgrid_substitute('@USERID', [@teacher.id])
sendgrid_substitute('@USERID', [@payable_teacher.id])
mail(:to => email, :subject => @subject) do |format|
format.text
@ -1274,6 +1369,42 @@ module JamRuby
end
end
# always goes to the school owner
def school_distribution_done(teacher_payment)
@school = teacher_payment.school
@teacher_payment = teacher_payment
@distribution = teacher_payment.teacher_distribution
@teacher = teacher_payment.teacher
@payable_teacher = @school.owner
@name = @payable_teacher.first_name || 'Anonymous'
@student = @distribution.student
email = @payable_teacher.email
if @distribution.is_test_drive?
@subject = "#{@teacher.name} has earned #{@distribution.real_distribution_display} for TestDrive lesson with #{@student.name}"
elsif @distribution.is_normal?
@subject = "#{@teacher.name} has earned #{@distribution.real_distribution_display} for a lesson with #{@student.name}"
elsif @distribution.is_monthly?
@subject = "#{@teacher.name} has earned #{@distribution.real_distribution_display} for #{@distribution.month_name} lessons with #{@student.name}"
else
@subject = "#{@teacher.name} has earned #{@distribution.real_distribution_display} for a lesson with #{@student.name}"
end
unique_args = {:type => "school_distribution_done"}
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
sendgrid_recipients([email])
sendgrid_substitute('@USERID', [@payable_teacher.id])
mail(:to => email, :subject => @subject) do |format|
format.text
format.html
end
end
def monthly_recurring_done(lesson_session)
@student = lesson_session.student
@teacher = lesson_session.teacher
@ -1485,5 +1616,78 @@ module JamRuby
format.html
end
end
def lesson_chat(chat_msg)
@target = chat_msg.target_user
@sender = chat_msg.user
@message = chat_msg.message
@lesson_session = chat_msg.lesson_session
@session_name = @lesson_session.music_session.name
@session_description = @lesson_session.music_session.description
@session_date = @lesson_session.slot.pretty_scheduled_start(true)
email = @target.email
@subject = "#{@sender.name} has sent you a message about a lesson"
unique_args = {:type => "lesson_chat"}
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
sendgrid_recipients([email])
sendgrid_substitute('@USERID', [@target.id])
mail(:to => email, :subject => @subject) do |format|
format.text
format.html { render :layout => "from_user_mailer" }
end
end
def lesson_starting_soon_teacher(lesson_session)
@lesson_booking = lesson_booking = lesson_session.lesson_booking
@student = lesson_booking.student
@teacher = lesson_booking.teacher
@lesson_session = lesson_booking.next_lesson
@session_name = @lesson_session.music_session.name
@session_description = @lesson_session.music_session.description
@session_date = @lesson_session.slot.pretty_scheduled_start(true)
email = @teacher.email
@subject = "Your lesson with #{@student.first_name} on JamKazam is starting soon"
unique_args = {:type => "send_starting_notice_teacher"}
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
sendgrid_recipients([email])
sendgrid_substitute('@USERID', [@teacher.id])
mail(:to => email, :subject => @subject) do |format|
format.text
format.html { render :layout => "from_user_mailer" }
end
end
def lesson_starting_soon_student(lesson_session)
@lesson_booking = lesson_booking = lesson_session.lesson_booking
@student = lesson_booking.student
@teacher = lesson_booking.teacher
@message = message
@lesson_session = lesson_booking.next_lesson
@session_name = @lesson_session.music_session.name
@session_description = @lesson_session.music_session.description
@session_date = @lesson_session.slot.pretty_scheduled_start(true)
email = @student.email
@subject = "Your lesson with #{@student.first_name} on JamKazam is starting soon"
unique_args = {:type => "send_starting_notice_student"}
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
sendgrid_recipients([email])
sendgrid_substitute('@USERID', [@student.id])
mail(:to => email, :subject => @subject) do |format|
format.text
format.html { render :layout => "from_user_mailer" }
end
end
end
end

View File

@ -0,0 +1,18 @@
<% provide(:title, @subject) %>
<% provide(:photo_url, @sender.resolved_photo_url) %>
<% content_for :note do %>
<p>
<% if @message.present? %>
<br/><br/><%= @sender.name %> says:
<br/><%= @message %>
<br/>
<% end %>
<br/><br/>Click the button below to view the entire lesson conversation.</p>
<p>
<a href="<%= @lesson_session.chat_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">VIEW LESSON CONVERSATION</a>
</p>
<% end %>

View File

@ -0,0 +1,7 @@
<%= @subject %>
<%= @sender.name %> says:
<%= @message %>
To see the full lesson conversation, click here: <%= @lesson_session.chat_url %>

View File

@ -0,0 +1,12 @@
<% provide(:title, @subject) %>
<% provide(:photo_url, @teacher.resolved_photo_url) %>
<% content_for :note do %>
<p>
Your lesson with <%= @teacher.name %> is scheduled to begin on JamKazam in less than 30 minutes.
<p>
<a href="<%= @lesson_session.home_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">JAMCLASS HOME</a>
</p>
<% end %>

View File

@ -0,0 +1,4 @@
Your lesson with <%= @teacher.name %> is scheduled to begin on JamKazam in less than 30 minutes.
JAMCLASS HOME (<%= @lesson_session.home_url %>

View File

@ -0,0 +1,12 @@
<% provide(:title, @subject) %>
<% provide(:photo_url, @student.resolved_photo_url) %>
<% content_for :note do %>
<p>
Your lesson with <%= @student.name %> is scheduled to begin on JamKazam in less than 30 minutes.
<p>
<a href="<%= @lesson_session.home_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">JAMCLASS HOME</a>
</p>
<% end %>

View File

@ -0,0 +1,3 @@
Your lesson with <%= @student.name %> is scheduled to begin on JamKazam in less than 30 minutes.
JAMCLASS HOME (<%= @lesson_session.home_url %>

View File

@ -7,7 +7,7 @@
</p>
<p>
We hope you enjoyed your JamClass lesson today with <%= @teacher.name %>. As just a reminder, you already paid for this lesson in advance.
We hope you enjoyed your JamClass lesson today with <%= @teacher.name %>.
</p>
<p>

View File

@ -0,0 +1,20 @@
<% provide(:title, @subject) %>
<p>
Hello <%= @name %>,
</p>
<% if @distribution.is_test_drive? %>
<p>We have processed a payment to you via your Stripe account for $<%= @distribution.real_distribution_display %> for this lesson.</p>
<% elsif @distribution.is_normal? %>
<p>We have processed a payment to you via your Stripe account for $<%= @distribution.real_distribution_display %> for this lesson.</p>
<% elsif @distribution.is_monthly? %>
<p>We have processed a payment to you via your Stripe account for $<%= @distribution.real_distribution_display %> for <%= @distribution.month_name %> lessons.</p>
<% else %>
Unknown payment type.
<% end %>
<br/>
<br/>
Best Regards,<br/>
JamKazam

View File

@ -0,0 +1,16 @@
<% provide(:title, @subject) %>
Hello <%= @name %>,
<% if @distribution.is_test_drive? %>
We have processed a payment to you via your Stripe account for $<%= @distribution.real_distribution_display %> for this lesson.
<% elsif @distribution.is_normal? %>
We have processed a payment to you via your Stripe account for $<%= @distribution.real_distribution_display %> for this lesson.
<% elsif @distribution.is_monthly? %>
We have processed a payment to you via your Stripe account for $<%= @distribution.real_distribution_display %> for <%= @distribution.month_name %> lessons.
<% else %>
Unknown payment type.
<% end %>
Best Regards,
JamKazam

View File

@ -0,0 +1,39 @@
<% provide(:title, @subject) %>
<% if !@user.anonymous? %>
<p>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
</p>
<% end %>
<p>
Thank you for expressing an interest in exploring our music school partner program! A member of our staff will reach out to you shortly to chat with you and answer any/all questions you may have about our partner program, our technologies, and how we can help you continue to build your music school business.
</p>
<p>
We'd also like to provide links to some help articles that explain how many things work, and will likely answer many of your questions in a well-organized manner:
</p>
<p><a href="https://jamkazam.desk.com/customer/en/portal/topics/935633-jamclass-online-music-lessons---for-music-schools/articles" style="color:#fc0"><b style="color:#fc0">Guide for Music School Owners</b></a><br/>
These help articles explain things from the perspective of the school owner - e.g. how you can schedule and book lessons from our marketplace with your teachers, how billing and payments are handled, and so on.
</p>
<p><a href="https://jamkazam.desk.com/customer/en/portal/topics/926076-jamclass-online-music-lessons---for-teachers/articles" style="color:#fc0"><b style="color: #fc0">Guide for Music Lesson Teachers</b></a><br/>
These help articles explain how teachers use the features of the platform outside of the online lesson sessions.
</p>
<p><a href="https://jamkazam.desk.com/customer/en/portal/topics/673198-key-features-to-use-in-online-sessions/articles" style="color:#fc0"><b style="color: #fc0">Key Features To Use In Online Sessions</b></a><br/>
These help articles explain the key features instructors can use in online sessions to teach effectively.
</p>
<p>
<a href="https://jamkazam.desk.com/customer/en/portal/articles/1288274-computer-internet-audio-and-video-requirements"><b>Gear Requirements</b></a><br/>
This help article explains the requirements for your computer, audio and video gear, and Internet service.
</p>
<p>
Thanks again for connecting with us, and we look forward to speaking with you soon!
</p>
<p>Best Regards,<br/>
Team JamKazam</p>

View File

@ -0,0 +1,24 @@
<% if !@user.anonymous? %>
Hello <%= EmailBatchProgression::VAR_FIRST_NAME %>
<% end %>
Thank you for expressing an interest in exploring our music school partner program! A member of our staff will reach out to you shortly to chat with you and answer any/all questions you may have about our partner program, our technologies, and how we can help you continue to build your music school business.
We'd also like to provide links to some help articles that explain how many things work, and will likely answer many of your questions in a well-organized manner:
-- Guide for Music School Owners (https://jamkazam.desk.com/customer/en/portal/topics/935633-jamclass-online-music-lessons---for-music-schools/articles)
These help articles explain things from the perspective of the school owner - e.g. how you can schedule and book lessons from our marketplace with your teachers, how billing and payments are handled, and so on.
-- Guide for Music Lesson Teachers (https://jamkazam.desk.com/customer/en/portal/topics/926076-jamclass-online-music-lessons---for-teachers/articles)
These help articles explain how teachers use the features of the platform outside of the online lesson sessions.
-- Key Features To Use In Online Sessions (https://jamkazam.desk.com/customer/en/portal/topics/673198-key-features-to-use-in-online-sessions/articles)
These help articles explain the key features instructors can use in online sessions to teach effectively.
-- Gear Requirements (https://jamkazam.desk.com/customer/en/portal/articles/1288274-computer-internet-audio-and-video-requirements)
This help article explains the requirements for your computer, audio and video gear, and Internet service.
Thanks again for connecting with us, and we look forward to speaking with you soon!
Best Regards,
Team JamKazam

View File

@ -14,7 +14,7 @@
<br/><%= @message %>
<br/>
<% end %>
<br/><br/>Click the button below to get more information and to add this lesson to your calendar!
<!--<br/><br/>Click the button below to get more information and to add this lesson to your calendar!-->
<br/><br/>We strongly suggest adding this to your calendar so you don't forget it, or you'll end up paying for a lesson you don't get.</p>
<p>
<a href="<%= @lesson_session.web_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">VIEW LESSON DETAILS</a>

View File

@ -7,8 +7,7 @@
</p>
<p>
We hope you enjoyed your JamClass lesson today with <%= @teacher.name %>. You have been
billed $<%= @lesson_session.amount_charged %> for today's lesson.
We hope you enjoyed your JamClass lesson today with <%= @teacher.name %>. You have been billed $<%= @lesson_session.amount_charged %> for today's lesson.
</p>
<p>

View File

@ -6,7 +6,7 @@
Hello <%= @student.name %>,
</p>
<p>You will not be billed for today's session with <%= @teacher.name %>
<p>You will not be billed for today's session with <%= @teacher.name %>.
<br/>
<br/>
Click the button below to see more information about this session.

View File

@ -1,5 +1,5 @@
Hello <%= @student.name %>,
You will not be billed for today's session with <%= @teacher.name %>
You will not be billed for today's session with <%= @teacher.name %>.
To see this lesson, click here: <%= @lesson_session.web_url %>

View File

@ -8,4 +8,4 @@
<%= @session_date %>
</p>
<p><a style="color: #ffcc00;" href="<%= @session_url %>">VIEW LESSON DETAILS</a></p>
<p><a style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;"href="<%= @session_url %>">VIEW LESSON DETAILS</a></p>

View File

@ -1,4 +1,4 @@
<% provide(:title, "You have used #{@student.remaining_test_drives} of 4 TestDrive lesson credits") %>
<% provide(:title, "You have used #{@student.remaining_test_drives} of #{@student.total_test_drives} TestDrive lesson credits") %>
<% provide(:photo_url, @teacher.resolved_photo_url) %>
<% content_for :note do %>

View File

@ -1,4 +1,4 @@
You have used <%= @student.remaining_test_drives %> of 4 TestDrive lesson credits.
You have used <%= @student.remaining_test_drives %> of <%= @student.total_test_drives %> TestDrive lesson credits.
<% if @student.has_rated_teacher(@teacher) %>
Also, please rate your teacher at <%= @teacher.ratings_url %> now for todays lesson to help other students in the community find the best instructors.

View File

@ -1,11 +1,11 @@
<% provide(:title, "You have used all 4 TestDrive lesson credits") %>
<% provide(:title, "You have used all TestDrive lesson credits") %>
<p>
Hello <%= @student.name %>,
</p>
<p>
We hope you enjoyed your JamClass lesson today with <%= @teacher.name %>. You have now used all 4 TestDrive credits.
We hope you enjoyed your JamClass lesson today with <%= @teacher.name %>. You have now used all your TestDrive credits.
<% if !@student.has_rated_teacher(@teacher) %>
Please <a href="<%= @teacher.ratings_url %>" style="color:#fc0">rate your teacher</a> now for todays lesson to

View File

@ -1,4 +1,4 @@
You have used all of your 4 TestDrive lesson credits.
You have used all of your TestDrive lesson credits.
<% if @student.has_rated_teacher(@teacher) %>
Also, please rate your teacher at <%= @teacher.ratings_url %> now for todays lesson to help other students in the community find the best instructors.

View File

@ -0,0 +1,23 @@
<% provide(:title, "Your TestDrive with #{@teacher.name} will not be billed") %>
<% provide(:photo_url, @teacher.resolved_photo_url) %>
<% content_for :note do %>
<p>
Hello <%= @student.name %>,
</p>
<p>You have not used a credit for today's TestDrive with <%= @teacher.name %>.
<br/>
<br/>
Click the button below to see more information about this session.
</p>
<p>
<a href="<%= @lesson_session.web_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">VIEW
LESSON DETAILS</a>
</p>
<p>
Best Regards,<br>Team JamKazam
</p>
<% end %>

View File

@ -0,0 +1,5 @@
Hello <%= @student.name %>,
You have not used a credit for today's TestDrive with <%= @teacher.name %>.
To see this lesson, click here: <%= @lesson_session.web_url %>

View File

@ -32,7 +32,7 @@
teacher for you. Finding the right teacher is the single most important determinant of success in your lessons. Would
you marry the first person you ever dated? No? Same here. Pick 4 teachers who look great, and then see who you click
with. It's a phenomenal value, and then you can stick with the best teacher for you.
<a href="https://www.jamkazam.com/client#/jamclass/book-lesson/purchase_test-drive" style="color:#fc0">Click this link
<a href="https://www.jamkazam.com/client#/jamclass/lesson-payment/test-drive" style="color:#fc0">Click this link
to sign up now
for TestDrive</a>. Then you can book 4 TestDrive lessons to get rolling.
</p>

View File

@ -18,7 +18,7 @@ TestDrive lets you take 4 full lessons (30 minutes each) from 4 different teache
teacher for you. Finding the right teacher is the single most important determinant of success in your lessons. Would
you marry the first person you ever dated? No? Same here. Pick 4 teachers who look great, and then see who you click
with. It's a phenomenal value, and then you can stick with the best teacher for you.
Click this link to sign up now for TestDrive (https://www.jamkazam.com/client#/jamclass/book-lesson/purchase_test-drive).
Click this link to sign up now for TestDrive (https://www.jamkazam.com/client#/jamclass/test-drive-selection).
Then you can book 4 TestDrive lessons to get rolling.
2. Set Up Your Gear

View File

@ -1,31 +1,42 @@
<% provide(:title, @subject) %>
<p>You were paid a total of $<%= @teacher_payment.amount %> for your participation in JamClass. Below are more details:</p>
<br/>
<p>
Hello <%= @name %>,
</p>
<% @teacher_payment.teacher_distributions.each do |distribution| %>
<% if distribution.is_test_drive? %>
<h3>You have earned $<%= distribution.amount %> for your TestDrive lesson with <%= distribution.student.name %></h3>
<% if @distribution.is_test_drive? %>
<% if @school %>
<h3>We hope you enjoyed your TestDrive lesson today with <%= @distribution.student.name %>.</h3>
<% else %>
<h3>You have earned $<%= @distribution.real_distribution_display %> for your TestDrive lesson with <%= @distribution.student.name %>.</h3>
<% end %>
<p>
<% if !@teacher_payment.teacher.has_rated_student(distribution.student) %>
If you haven't already done so, please <a href="<%= distribution.student.student_ratings_url %>" style="color:#fc0">rate your student</a> now to help us monitor for any issues with students who may cause issues for our instructor community.
<% if !@teacher_payment.teacher.has_rated_student(@distribution.student) %>
If you haven't already done so, please <a href="<%= @distribution.student.student_ratings_url %>" style="color:#fc0">rate your student</a> now to help us monitor for any issues with students who may cause issues for our instructor community.
<% end %>
If you had technical problems during your lesson, or have questions, or would like to make suggestions on how to improve JamClass, please email us at <a href="mailto:support@jamkazam.com" style="color:#fc0">support@jamkazam.com</a>.
</p>
<% elsif distribution.is_normal? %>
<h3>You have earned $<%= distribution.amount %> for your lesson with <%= distribution.student.name %></h3>
<% elsif @distribution.is_normal? %>
<% if @school %>
<h3>we hope you enjoyed your lesson today with <%= @distribution.student.name %>.</h3>
<% else %>
<h3>You have earned $<%= @distribution.real_distribution_display %> for your lesson with <%= @distribution.student.name %>.</h3>
<% end %>
<p>
<% if !@teacher_payment.teacher.has_rated_student(distribution.student) %>
If you haven't already done so, please <a href="<%= distribution.student.student_ratings_url %>" style="color:#fc0">rate your student</a> now to help us monitor for any issues with students who may cause issues for our instructor community.
<% if !@teacher_payment.teacher.has_rated_student(@distribution.student) %>
If you haven't already done so, please <a href="<%= @distribution.student.student_ratings_url %>" style="color:#fc0">rate your student</a> now to help us monitor for any issues with students who may cause issues for our instructor community.
<% end %>
If you had technical problems during your lesson, or have questions, or would like to make suggestions on how to improve JamClass, please email us at <a href="mailto:support@jamkazam.com" style="color:#fc0">support@jamkazam.com</a>.
</p>
<% elsif distribution.is_monthly? %>
<h3>You have earned $<%= distribution.amount %> for your <%= distribution.month_name%> lesson with <%= distribution.student.name %></h3>
<% elsif @distribution.is_monthly? %>
<% if @school %>
<h3>we hope you enjoyed your <%= @distribution.month_name %> lessons with <%= @distribution.student.name %>.</h3>
<% else %>
<h3>You have earned $<%= @distribution.real_distribution_display %> for your <%= @distribution.month_name%> lessons with <%= @distribution.student.name %>.</h3>
<% end %>
<p>
<% if !@teacher_payment.teacher.has_rated_student(distribution.student) %>
If you haven't already done so, please <a href="<%= distribution.student.student_ratings_url %>" style="color:#fc0">rate your student</a> now to help us monitor for any issues with students who may cause issues for our instructor community.
<% if !@teacher_payment.teacher.has_rated_student(@distribution.student) %>
If you haven't already done so, please <a href="<%= @distribution.student.student_ratings_url %>" style="color:#fc0">rate your student</a> now to help us monitor for any issues with students who may cause issues for our instructor community.
<% end %>
If you had technical problems during your lesson, or have questions, or would like to make suggestions on how to improve JamClass, please email us at <a href="mailto:support@jamkazam.com" style="color:#fc0">support@jamkazam.com</a>.
</p>
@ -35,7 +46,5 @@
<br/>
<br/>
<% end %>
Best Regards,<br/>
JamKazam

View File

@ -1,31 +1,40 @@
<% provide(:title, @subject) %>
You were paid a total of $<%= @teacher_payment.amount %> for your participation in JamClass. Below are more details:
Hello <%= @name %>,
<% @teacher_payment.teacher_distributions.each do |distribution| %>
<% if distribution.is_test_drive? %>
You have earned $<%= distribution.amount %> for your TestDrive lesson with <%= distribution.student.name %>.
<% if !@teacher_payment.teacher.has_rated_student(distribution.student) %>
If you haven't already done so, please rate your student now to help us monitor for any issues with students who may cause issues for our instructor community. <%= distribution.student.student_ratings_url %>
<% if @distribution.is_test_drive? %>
<% if @school %>
We hope you enjoyed your TestDrive lesson today with <%= @distribution.student.name %>.
<% else %>
You have earned $<%= @distribution.amount %> for your TestDrive lesson with <%= @distribution.student.name %>.
<% end %>
<% if !@teacher_payment.teacher.has_rated_student(@distribution.student) %>
If you haven't already done so, please rate your student now to help us monitor for any issues with students who may cause issues for our instructor community. <%= @distribution.student.student_ratings_url %>
<% end%>
If you had technical problems during your lesson, or have questions, or would like to make suggestions on how to improve JamClass, please email us at support@jamkazam.com.
<% elsif distribution.is_normal? %>
You have earned $<%= distribution.amount %> for your lesson with <%= distribution.student.name %>.
<% if !@teacher_payment.teacher.has_rated_student(distribution.student) %>
If you haven't already done so, please rate your student now to help us monitor for any issues with students who may cause issues for our instructor community. <%= distribution.student.student_ratings_url %>
<% elsif @distribution.is_normal? %>
<% if @school %>
We hope you enjoyed your lesson today with <%= @distribution.student.name %>.
<% else %>
You have earned $<%= @distribution.amount %> for your lesson with <%= @distribution.student.name %>.
<% end %>
<% if !@teacher_payment.teacher.has_rated_student(@distribution.student) %>
If you haven't already done so, please rate your student now to help us monitor for any issues with students who may cause issues for our instructor community. <%= @distribution.student.student_ratings_url %>
<% end%>
If you had technical problems during your lesson, or have questions, or would like to make suggestions on how to improve JamClass, please email us at support@jamkazam.com.
<% elsif distribution.is_monthly? %>
You have earned $<%= distribution.amount %> for your <%= distribution.month_name%> lesson with <%= distribution.student.name %>.
<% if !@teacher_payment.teacher.has_rated_student(distribution.student) %>
If you haven't already done so, please rate your student now to help us monitor for any issues with students who may cause issues for our instructor community. <%= distribution.student.student_ratings_url %>
<% elsif @distribution.is_monthly? %>
<% if @school %>
We hope you enjoyed your <%= @distribution.month_name%> lessons with <%= @distribution.student.name %>.
<% else %>
You have earned $<%= @distribution.amount %> for your <%= @distribution.month_name%> lessons with <%= @distribution.student.name %>.
<% end %>
<% if !@teacher_payment.teacher.has_rated_student(@distribution.student) %>
If you haven't already done so, please rate your student now to help us monitor for any issues with students who may cause issues for our instructor community. <%= @distribution.student.student_ratings_url %>
<% end%>
If you had technical problems during your lesson, or have questions, or would like to make suggestions on how to improve JamClass, please email us at support@jamkazam.com.
<% else %>
Unknown payment type.
<% end %>
<% end %>
Best Regards,
JamKazam

View File

@ -1,5 +1,12 @@
<% provide(:title, @subject) %>
<p>Hello <%= @name %>,</p>
<% if @school %>
<p>
We attempted to process a payment via your Stripe account for <%= @distribution.real_distribution_display %> for this lesson, but the payment failed. Please sign into your Stripe account, and verify that everything there is working properly. Well try again to process this payment in about 24 hours.
</p>
<% else %>
<p>
<% if @card_declined %>
When we tried to distribute a payment to you on <%= @bill_date %>, the charge was declined by stripe. Can you please check your stripe account status? Thank you!
@ -9,6 +16,7 @@
For some reason, when we tried to distribute a payment to you on <%= @bill_date %>, the charge failed. Can you please check your stripe account status? Thank you!
<% end %>
</p>
<% end %>
<br/>
Best Regards,<br/>

View File

@ -1,5 +1,9 @@
<% provide(:title, @subject) %>
Hello <%= @name %>,
<% if @school %>
We attempted to process a payment via your Stripe account for <%= @distribution.real_distribution_display %> for this lesson, but the payment failed. Please sign into your Stripe account, and verify that everything there is working properly. Well try again to process this payment in about 24 hours.
<% else %>
<% if @card_declined %>
When we tried to distribute a payment to you on <%= @bill_date %>, the charge was declined by stripe. Can you please check your stripe account status? Thank you!
<% elsif @card_expired %>
@ -7,6 +11,7 @@ When we tried to distribute a payment to you on <%= @bill_date %>, the charge wa
<% else %>
For some reason, when we tried to distribute a payment to you on <%= @bill_date %>, the charge failed. Can you please check your stripe account status? Thank you!
<% end %>
<% end %>
Best Regards,
JamKazam

View File

@ -8,7 +8,7 @@
<% else %>
This student has accepted your lesson request!
<% end %>
<br/><br/>Click the button below to get more information and to add this lesson to your calendar!
<!--<br/><br/>Click the button below to get more information and to add this lesson to your calendar!-->
<br/><br/>We strongly suggest adding this to your calendar so you don't forget it.</p>
<p>
<a href="<%= @lesson_session.web_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">VIEW LESSON DETAILS</a>

View File

@ -8,4 +8,4 @@
<%= @session_date %>
</p>
<p><a style="color: #ffcc00;" href="<%= @session_url %>">VIEW LESSON DETAILS</a></p>
<p><a style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;"href="<%= @session_url %>">VIEW LESSON DETAILS</a></p>

View File

@ -419,7 +419,7 @@ SQL
end
connection.join_the_session(music_session, as_musician, tracks, user, audio_latency, video_sources)
JamRuby::MusicSessionUserHistory.join_music_session(user.id, music_session.id)
JamRuby::MusicSessionUserHistory.join_music_session(user.id, music_session.id, client_id)
# connection.music_session_id = music_session.id
# connection.as_musician = as_musician
# connection.joining_session = true

View File

@ -1,5 +1,5 @@
require 'influxdb'
#require 'influxdb'
# monkey patch InfluxDB client to clear the queue when asked to stop
=begin

View File

@ -993,14 +993,16 @@ module JamRuby
end
# creates the chat message
def chat_message(session_id, sender_name, sender_id, msg, msg_id, created_at, channel)
def chat_message(session_id, sender_name, sender_id, msg, msg_id, created_at, channel, lesson_session_id, purpose)
chat_message = Jampb::ChatMessage.new(
:sender_id => sender_id,
:sender_name => sender_name,
:msg => msg,
:msg_id => msg_id,
:created_at => created_at,
:channel => channel
:channel => channel,
:lesson_session_id => lesson_session_id,
:purpose => purpose
)
if session_id

View File

@ -843,5 +843,9 @@ module JamRuby
stats
end
def lesson_session
music_session.lesson_session
end
end
end

View File

@ -357,7 +357,7 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base
UPDATE affiliate_quarterly_payments
SET
closed = TRUE, closed_at = NOW()
WHERE year < #{year} OR quarter < #{quarter}
WHERE year < #{year} OR (year = #{year} AND quarter < #{quarter})
}
ActiveRecord::Base.connection.execute(sql)

View File

@ -1,7 +1,7 @@
module JamRuby
class AffiliatePaymentCharge < Charge
has_one :teacher_payment, class_name: "JamRuby::TeacherPayment", foreign_key: :affiliate_charge_id
#has_one :teacher_payment, class_name: "JamRuby::TeacherPayment", foreign_key: :affiliate_charge_id
def distribution
@distribution ||= teacher_payment.teacher_distribution
@ -36,15 +36,15 @@ module JamRuby
end
def do_send_notices
UserMailer.teacher_distribution_done(teacher_payment)
#UserMailer.teacher_distribution_done(teacher_payment)
end
def do_send_unable_charge
UserMailer.teacher_distribution_fail(teacher_payment)
#UserMailer.teacher_distribution_fail(teacher_payment)
end
def construct_description
teacher_payment.teacher_distribution.description
#teacher_payment.teacher_distribution.description
end
end

View File

@ -4,7 +4,7 @@ module JamRuby
DEFAULT_ENVIRONMENT = 'public'
CLIENT_PREFIX = 'JamClient'
PRODUCTS = ["#{CLIENT_PREFIX}/Win32", "#{CLIENT_PREFIX}/MacOSX"]
PRODUCTS = ["#{CLIENT_PREFIX}/Win32", "#{CLIENT_PREFIX}/MacOSX", "#{CLIENT_PREFIX}/JamBlaster"]
self.primary_key = 'id'
attr_accessible :version, :uri, :sha1, :environment, :product, as: :admin

View File

@ -1,6 +1,8 @@
module JamRuby
class Charge < ActiveRecord::Base
belongs_to :user, class_name: "JamRuby::User"
validates :sent_billing_notices, inclusion: {in: [true, false]}
def max_retries
@ -96,6 +98,7 @@ module JamRuby
else
self.billing_error_detail = e.to_s + "\n" + e.backtrace.join("\n\t") if e.backtrace
end
puts "Charge: unhandled exception #{billing_error_reason}, #{billing_error_detail}"
self.save(validate: false)
end

View File

@ -16,26 +16,44 @@ module JamRuby
belongs_to :user
belongs_to :music_session
belongs_to :target_user, class_name: "JamRuby::User"
belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking"
belongs_to :lesson_session, class_name: "JamRuby::LessonSession"
validates :user, presence: true
validates :message, length: {minimum: 1, maximum: 255}, no_profanity: true, unless: :ignore_message_checks
def self.create(user, music_session, message, channel, client_id, target_user = nil, lesson_booking = nil)
def self.create(user, music_session, message, channel, client_id, target_user = nil, lesson_session = nil, purpose = nil)
chat_msg = ChatMessage.new
chat_msg.user_id = user.id
chat_msg.music_session_id = music_session.id if music_session
chat_msg.message = message
chat_msg.channel = channel
chat_msg.target_user = target_user
chat_msg.lesson_booking = lesson_booking
chat_msg.lesson_session = lesson_session
chat_msg.purpose = purpose
if lesson_booking
if lesson_session
chat_msg.ignore_message_checks = true
if user.id == lesson_session.student.id
lesson_session.teacher_unread_messages = true
Notification.send_lesson_message('chat', lesson_session, false, message)
else
lesson_session.student_unread_messages = true
Notification.send_lesson_message('chat', lesson_session, true, message)
end
lesson_session.save(validate: false)
# a nil purpose means 'normal chat', which is the only time we should send an email
if !target_user.online? && purpose.nil? && message.present?
UserMailer.lesson_chat(chat_msg).deliver!
end
end
if chat_msg.save
ChatMessage.send_chat_msg music_session, chat_msg, user, client_id, channel
ChatMessage.send_chat_msg music_session, chat_msg, user, client_id, channel, lesson_session, purpose, target_user
end
chat_msg
end
@ -60,6 +78,11 @@ module JamRuby
query = ChatMessage.where('music_session_id = ?', music_session_id)
end
if params.has_key? (:lesson_session)
lesson_session_id = params[:lesson_session]
query = ChatMessage.where('lesson_session_id = ?', lesson_session_id)
end
query = query.offset(start).limit(limit).order('created_at DESC').includes([:user])
if query.length == 0
@ -71,8 +94,9 @@ module JamRuby
end
end
def send_chat_msg(music_session, chat_msg, user, client_id, channel)
def send_chat_msg(music_session, chat_msg, user, client_id, channel, lesson_session, purpose, target_user)
music_session_id = music_session.id if music_session
lesson_session_id = lesson_session.id if lesson_session
msg = @@message_factory.chat_message(
music_session_id,
@ -81,14 +105,19 @@ module JamRuby
chat_msg.message,
chat_msg.id,
chat_msg.created_at.utc.iso8601,
channel
channel,
lesson_session_id,
purpose
)
if channel == 'session'
@@mq_router.server_publish_to_session(music_session, msg, sender = {:client_id => client_id})
elsif channel == 'global'
@@mq_router.publish_to_active_clients(msg)
elsif channel == 'lesson'
@@mq_router.publish_to_user(target_user.id, msg, sender = {:client_id => client_id})
end
end
end
end

View File

@ -47,7 +47,7 @@ module JamRuby
has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_instruments"
def self.standard_list
return Instrument.where('instruments.popularity > 0').order('instruments.popularity DESC, instruments.description ASC')
return Instrument.where('instruments.popularity > 0').order('instruments.description ASC')
end
def self.jam_track_list

View File

@ -3,5 +3,10 @@ module JamRuby
include HtmlSanitize
html_sanitize strict: [:name, :description]
has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_languages"
def self.english_sort
languages = Language.order(:description)
languages.sort_by { |l| [ l.id == 'EN' ? 0 : 1, l.description] }
end
end
end

View File

@ -9,7 +9,7 @@ module JamRuby
@@log = Logging.logger[LessonBooking]
attr_accessor :accepting, :countering, :countered_slot, :countered_lesson
attr_accessor :accepting, :countering, :canceling, :countered_slot, :countered_lesson
STATUS_REQUESTED = 'requested'
STATUS_CANCELED = 'canceled'
@ -37,12 +37,12 @@ module JamRuby
belongs_to :teacher, class_name: "JamRuby::User"
belongs_to :accepter, class_name: "JamRuby::User"
belongs_to :canceler, class_name: "JamRuby::User"
belongs_to :default_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :default_slot_id, inverse_of: :defaulted_booking
belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_booking
has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot"
has_many :lesson_sessions, class_name: "JamRuby::LessonSession"
has_many :lesson_package_purchases, class_name: "JamRuby::LessonPackagePurchase"
belongs_to :default_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :default_slot_id, inverse_of: :defaulted_booking, :dependent => :destroy
belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_booking, :dependent => :destroy
belongs_to :school, class_name: "JamRuby::School"
has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot", :dependent => :destroy
has_many :lesson_sessions, class_name: "JamRuby::LessonSession", :dependent => :destroy
has_many :lesson_package_purchases, class_name: "JamRuby::LessonPackagePurchase", :dependent => :destroy
validates :user, presence: true
validates :teacher, presence: true
@ -62,7 +62,9 @@ module JamRuby
validate :validate_lesson_booking_slots
validate :validate_lesson_length
validate :validate_payment_style
validate :validate_uncollectables, on: :create
validate :validate_accepted, :if => :accepting
validate :validate_canceled, :if => :canceling
before_save :before_save
@ -85,7 +87,7 @@ module JamRuby
end
def after_create
if card_presumed_ok && !sent_notices
if (card_presumed_ok || school_on_school?) && !sent_notices
send_notices
end
end
@ -136,6 +138,9 @@ module JamRuby
def next_lesson
if recurring
session = lesson_sessions.joins(:music_session).where("scheduled_start is not null").where("scheduled_start > ?", Time.now).order(:created_at).first
if session.nil?
session = lesson_sessions[0]
end
LessonSession.find(session.id) if session
else
lesson_sessions[0]
@ -272,7 +277,7 @@ module JamRuby
times << time
end
end
times
{ times: times, session: sessions.first }
end
def determine_needed_sessions(sessions)
@ -390,6 +395,14 @@ module JamRuby
self.accepting = false
end
def validate_canceled
if !is_canceled?
self.errors.add(:status, "This session is already #{self.status}.")
end
self.canceling = false
end
def send_notices
UserMailer.student_lesson_request(self).deliver
UserMailer.teacher_lesson_request(self).deliver
@ -398,11 +411,26 @@ module JamRuby
self.save
end
def resolved_test_drive_package
result = nil
purchase = student.most_recent_test_drive_purchase
if purchase
# for lessons already packaged
result = purchase.lesson_package_type
else
# for unbooked lessons
result = student.desired_package
end
if result.nil?
result = LessonPackageType.test_drive_4
end
result
end
def lesson_package_type
if is_single_free?
LessonPackageType.single_free
elsif is_test_drive?
LessonPackageType.test_drive
resolved_test_drive_package
elsif is_normal?
LessonPackageType.single
end
@ -439,19 +467,32 @@ module JamRuby
if is_single_free?
0
elsif is_test_drive?
LessonPackageType.test_drive.price
resolved_test_drive_package.price
elsif is_normal?
teacher.teacher.booking_price(lesson_length, payment_style != PAYMENT_STYLE_MONTHLY)
end
end
def distribution_price_in_cents
def distribution_price_in_cents(target)
if is_single_free?
0
elsif is_test_drive?
10 * 100
elsif is_normal?
booked_price * 100
if is_monthly_payment?
raise "not a LessonPackagePurchase: #{target.inspect}" if !target.is_a?(LessonPackagePurchase)
today = Date.today
start_date = Date.new(target.year, target.month, 1)
if today.year == target.year && today.month == target.month
# we are in the month being billed. we should set the start date based on today
start_date = today
end
LessonSessionMonthlyPrice.price(self, start_date) * 100
else
booked_price * 100
end
end
end
@ -500,41 +541,49 @@ module JamRuby
def approved_before?
!self.accepter_id.nil?
end
def cancel(canceler, other, message)
self.canceling = true
self.active = false
self.status = STATUS_CANCELED
self.cancel_message = message
self.canceler = canceler
success = save
if success
lesson_sessions.past_cancel_window.each do |lesson_session|
lesson_session = LessonSession.find(lesson_session.id) # because .upcoming creates ReadOnly records
lesson_session.cancel_lesson(canceler, message)
if !lesson_session.save
return lesson_session
end
end
if approved_before?
# just tell both people it's cancelled, to act as confirmation
Notification.send_lesson_message('canceled', next_lesson, false)
Notification.send_lesson_message('canceled', next_lesson, true)
UserMailer.student_lesson_booking_canceled(self, message).deliver
UserMailer.teacher_lesson_booking_canceled(self, message).deliver
chat_message_prefix = "Lesson Canceled"
purpose = "Lesson Canceled"
else
if canceler == student
# if it's the first time acceptance student canceling, we call it a 'cancel'
Notification.send_lesson_message('canceled', next_lesson, false)
UserMailer.teacher_lesson_booking_canceled(self, message).deliver
chat_message_prefix = "Lesson Canceled"
purpose = "Lesson Canceled"
else
# if it's the first time acceptance teacher, it was declined
UserMailer.student_lesson_booking_declined(self, message).deliver
Notification.send_lesson_message('declined', next_lesson, true)
chat_message_prefix = "Lesson Declined"
purpose = "Lesson Declined"
end
end
chat_message = message.nil? ? chat_message_prefix : "#{chat_message_prefix}: #{message}"
msg = ChatMessage.create(canceler, nil, chat_message, ChatMessage::CHANNEL_LESSON, nil, other, self)
message = '' if message.nil?
msg = ChatMessage.create(canceler, nil, message, ChatMessage::CHANNEL_LESSON, nil, other, next_lesson, purpose)
end
success
self
end
def card_approved
@ -555,7 +604,7 @@ module JamRuby
#end
elsif is_test_drive?
if user.has_requested_test_drive?(teacher) && !user.admin
errors.add(:user, "has a requested TestDrive with this teacher")
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")
@ -603,6 +652,15 @@ module JamRuby
end
end
def validate_uncollectables
if user.uncollectables.count > 0
errors.add(:user, 'have unpaid lessons.')
end
end
def school_owned?
!!school
end
def self.book_free(user, teacher, lesson_booking_slots, description)
self.book(user, teacher, LessonBooking::LESSON_TYPE_FREE, lesson_booking_slots, false, 30, PAYMENT_STYLE_ELSEWHERE, description)
@ -632,6 +690,9 @@ module JamRuby
lesson_booking.payment_style = payment_style
lesson_booking.description = description
lesson_booking.status = STATUS_REQUESTED
if lesson_booking.teacher && lesson_booking.teacher.teacher.school
lesson_booking.school = lesson_booking.teacher.teacher.school
end
# two-way association slots, for before_validation loic in slot to work
lesson_booking.lesson_booking_slots = lesson_booking_slots
@ -641,20 +702,24 @@ module JamRuby
end if lesson_booking_slots
if lesson_booking.save
msg = ChatMessage.create(user, lesson_booking.lesson_sessions[0], description, ChatMessage::CHANNEL_LESSON, nil, teacher, lesson_booking)
description = '' if description.nil?
msg = ChatMessage.create(user, lesson_booking.lesson_sessions[0], description, ChatMessage::CHANNEL_LESSON, nil, teacher, lesson_booking.lesson_sessions[0], 'Lesson Requested')
end
end
lesson_booking
end
def self.unprocessed(current_user)
LessonBooking.where(user_id: current_user.id).where(card_presumed_ok: false)
LessonBooking.where(user_id: current_user.id).where(card_presumed_ok: false).where('school_id IS NULL')
end
def self.requested(current_user)
LessonBooking.where(user_id: current_user.id).where(status: STATUS_REQUESTED)
end
def school_on_school?
teacher.teacher.school && student.school && (teacher.teacher.school.id == student.school.id)
end
def self.find_bookings_needing_sessions(minimum_start_time)
MusicSession.select([:lesson_booking_id]).joins(:lesson_session => :lesson_booking).where("lesson_bookings.active = true").where('lesson_bookings.recurring = true').where("scheduled_start is not null").where("scheduled_start > ?", minimum_start_time).group(:lesson_booking_id).having('count(lesson_booking_id) < 2')
@ -806,7 +871,6 @@ module JamRuby
end
end
def home_url
APP_CONFIG.external_root_url + "/client#/jamclass"
end

View File

@ -73,25 +73,44 @@ module JamRuby
def scheduled_times(needed_sessions, minimum_start_time)
#puts "NEEDED SESSIONS #{needed_sessions} #{minimum_start_time}"
times = []
week_offset = 0
needed_sessions.times do |i|
candidate = scheduled_time(i + week_offset)
#puts "#{i}: candidate #{candidate} week_offset:#{week_offset}"
if day_of_week && candidate <= minimum_start_time
# move it up a week
week_offset += 1
candidate = scheduled_time(i + week_offset)
#puts "retry #1 #{candidate}"
# sanity check
if candidate <= minimum_start_time
week_offset += 1
candidate = scheduled_time(i + week_offset)
#puts "retry #2 #{candidate}"
if candidate <= minimum_start_time
raise "candidate time less than minimum start time even after scoot: #{lesson_booking.id} #{self.id}"
week_offset += 1
candidate = scheduled_time(i + week_offset)
#puts "retry #3 #{candidate}"
if candidate <= minimum_start_time
week_offset += 1
candidate = scheduled_time(i + week_offset)
#puts "retry #4 #{candidate}"
if candidate <= minimum_start_time
raise "candidate time less than minimum start time even after scoot: #{lesson_booking.id} #{self.id}"
end
end
end
end
end
times << candidate

View File

@ -26,23 +26,26 @@ module JamRuby
def validate_test_drive
if user
if !user.can_buy_test_drive?
if lesson_package_type.is_test_drive? && !user.can_buy_test_drive?
errors.add(:user, "can not buy test drive right now because you have already purchased it within the last year")
end
end
end
def create_charge
self.lesson_payment_charge = LessonPaymentCharge.new
lesson_payment_charge.amount_in_cents = 0
lesson_payment_charge.fee_in_cents = 0
lesson_payment_charge.lesson_package_purchase = self
lesson_payment_charge.save!
if !school_on_school? && lesson_booking && lesson_booking.is_monthly_payment?
self.lesson_payment_charge = LessonPaymentCharge.new
lesson_payment_charge.user = user
lesson_payment_charge.amount_in_cents = 0
lesson_payment_charge.fee_in_cents = 0
lesson_payment_charge.lesson_package_purchase = self
lesson_payment_charge.save!
end
end
def add_test_drives
if self.lesson_package_type.is_test_drive?
new_test_drives = user.remaining_test_drives + 4
new_test_drives = user.remaining_test_drives + lesson_package_type.test_drive_count
User.where(id: user.id).update_all(remaining_test_drives: new_test_drives)
user.remaining_test_drives = new_test_drives
end
@ -71,17 +74,19 @@ module JamRuby
if lesson_booking && lesson_booking.requires_teacher_distribution?(purchase)
purchase.teacher_distribution = TeacherDistribution.create_for_lesson_package_purchase(purchase)
# price should always match the teacher_distribution, if there is one
purchase.price = purchase.teacher_distribution.amount_in_cents / 100
end
else
purchase.recurring = false
end
if lesson_booking
purchase.lesson_package_type = lesson_booking.lesson_package_type
purchase.price = lesson_booking.booked_price # lesson_package_type.booked_price(lesson_booking)
purchase.lesson_package_type = lesson_package_type ? lesson_package_type : lesson_booking.lesson_package_type
purchase.price = lesson_booking.booked_price if purchase.price.nil?
else
purchase.lesson_package_type = lesson_package_type
purchase.price = lesson_package_type.price
purchase.price = lesson_package_type.price if purchase.price.nil?
end
purchase.save
@ -100,6 +105,9 @@ module JamRuby
description(lesson_booking)
end
def timed_description
"Lessons for the month of #{self.month_name} with #{self.lesson_booking.student.name}"
end
def month_name
if recurring
@ -113,11 +121,21 @@ module JamRuby
user
end
def school_on_school?
teacher.teacher.school && student.school && (teacher.teacher.school.id == student.school.id)
end
def bill_monthly(force = false)
lesson_payment_charge.charge(force)
if lesson_payment_charge.billed
if school_on_school?
success = true
else
lesson_payment_charge.charge(force)
success = lesson_payment_charge.billed
end
if success
self.sent_notices = true
self.sent_notices_at = Time.now
self.post_processed = true

View File

@ -7,21 +7,29 @@ module JamRuby
PRODUCT_TYPE = 'LessonPackageType'
SINGLE_FREE = 'single-free'
TEST_DRIVE = 'test-drive'
TEST_DRIVE_4 = 'test-drive'
TEST_DRIVE_2 = 'test-drive-2'
TEST_DRIVE_1 = 'test-drive-1'
SINGLE = 'single'
LESSON_PACKAGE_TYPES =
[
SINGLE_FREE,
TEST_DRIVE,
TEST_DRIVE_4,
TEST_DRIVE_2,
TEST_DRIVE_1,
SINGLE
]
has_many :user_desired_packages, class_name: "JamRuby::User", :foreign_key => "lesson_package_type_id", inverse_of: :desired_package
validates :name, presence: true
validates :description, presence: true
validates :price, presence: true
validates :package_type, presence: true, inclusion: {in: LESSON_PACKAGE_TYPES}
def self.test_drive_package_ids
[TEST_DRIVE_4, TEST_DRIVE_2, TEST_DRIVE_1]
end
def self.monthly
LessonPackageType.find(MONTHLY)
end
@ -30,8 +38,16 @@ module JamRuby
LessonPackageType.find(SINGLE_FREE)
end
def self.test_drive
LessonPackageType.find(TEST_DRIVE)
def self.test_drive_4
LessonPackageType.find(TEST_DRIVE_4)
end
def self.test_drive_2
LessonPackageType.find(TEST_DRIVE_2)
end
def self.test_drive_1
LessonPackageType.find(TEST_DRIVE_1)
end
def self.single
@ -42,17 +58,21 @@ module JamRuby
if is_single_free?
0
elsif is_test_drive?
LessonPackageType.test_drive.price
10.00
elsif is_normal?
lesson_booking.booked_price #teacher.teacher.booking_price(lesson_booking.lesson_length, lesson_booking.payment_style == LessonBooking::PAYMENT_STYLE_SINGLE)
end
end
def test_drive_count
package_type["test-drive-".length, 1].to_i
end
def description(lesson_booking)
if is_single_free?
"Single Free Lesson"
elsif is_test_drive?
"Test Drive"
"Test Drive (#{test_drive_count})"
elsif is_normal?
if lesson_booking.recurring
"Recurring #{lesson_booking.payment_style == LessonBooking::PAYMENT_STYLE_WEEKLY ? "Weekly" : "Monthly"} #{lesson_booking.lesson_length}m"
@ -71,7 +91,7 @@ module JamRuby
end
def is_test_drive?
id == TEST_DRIVE
id.start_with?('test-drive')
end
def is_normal?
@ -86,8 +106,12 @@ module JamRuby
def plan_code
if package_type == SINGLE_FREE
"lesson-package-single-free"
elsif package_type == TEST_DRIVE
"lesson-package-test-drive"
elsif package_type == 'test-drive-4'
"lesson-package-test-drive-4"
elsif package_type == TEST_DRIVE_2
"lesson-package-test-drive-2"
elsif package_type == TEST_DRIVE_1
"lesson-package-test-drive-1"
elsif package_type == SINGLE
"lesson-package-single"
else

View File

@ -9,7 +9,7 @@ module JamRuby
end
def charged_user
@charged_user ||= target.student
user
end
def resolve_target
@ -31,6 +31,10 @@ module JamRuby
charged_user
end
def teacher
target.teacher
end
def is_lesson?
!lesson_session.nil?
end
@ -85,5 +89,13 @@ module JamRuby
end
end
end
def description
target.timed_description
end
def expected_price_in_cents
target.lesson_booking.distribution_price_in_cents(target)
end
end
end

View File

@ -5,13 +5,13 @@ module JamRuby
include HtmlSanitize
html_sanitize strict: [:cancel_message]
attr_accessor :accepting, :creating, :countering, :countered_slot, :countered_lesson, :canceling
attr_accessor :accepting, :creating, :countering, :countered_slot, :countered_lesson, :canceling, :assigned_student
@@log = Logging.logger[LessonSession]
delegate :sent_billing_notices, :last_billing_attempt_at, :billing_attempts, :billing_should_retry, :billed, :billed_at, :billing_error_detail, :billing_error_reason, :is_card_declined?, :is_card_expired?, :last_billed_at_date, :sent_billing_notices, to: :lesson_payment_charge
delegate :is_test_drive?, :is_single_free?, :is_normal?, :approved_before?, :is_active?, to: :lesson_booking
delegate :sent_billing_notices, :last_billing_attempt_at, :billing_attempts, :billing_should_retry, :billed_at, :billing_error_detail, :billing_error_reason, :is_card_declined?, :is_card_expired?, :last_billed_at_date, :sent_billing_notices, to: :lesson_payment_charge, allow_nil: true
delegate :is_test_drive?, :is_single_free?, :is_normal?, :approved_before?, :is_active?, :recurring, :is_monthly_payment?, to: :lesson_booking
delegate :pretty_scheduled_start, to: :music_session
@ -30,16 +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"
has_one :music_session, class_name: "JamRuby::MusicSession", :dependent => :destroy
belongs_to :teacher, class_name: "JamRuby::User", foreign_key: :teacher_id, inverse_of: :taught_lessons
belongs_to :canceler, class_name: "JamRuby::User", foreign_key: :canceler_id
belongs_to :lesson_package_purchase, class_name: "JamRuby::LessonPackagePurchase"
belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking"
belongs_to :slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :slot_id
belongs_to :slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :slot_id, :dependent => :destroy
belongs_to :lesson_payment_charge, class_name: "JamRuby::LessonPaymentCharge", foreign_key: :charge_id
belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_lesson
belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_lesson, :dependent => :destroy
has_one :teacher_distribution, class_name: "JamRuby::TeacherDistribution"
has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot"
has_many :notifications, :class_name => "JamRuby::Notification", :foreign_key => "lesson_session_id"
has_many :chat_messages, :class_name => "JamRuby::ChatMessage", :foreign_key => "lesson_session_id"
validates :duration, presence: true, numericality: {only_integer: true}
@ -69,10 +71,13 @@ module JamRuby
scope :suspended, -> { where(status: STATUS_SUSPENDED) }
scope :completed, -> { where(status: STATUS_COMPLETED) }
scope :missed, -> { where(status: STATUS_MISSED) }
scope :upcoming, -> { joins(:music_session).where('music_sessions.scheduled_start > ?', Time.now) }
scope :past_cancel_window, -> { joins(:music_session).where('music_sessions.scheduled_start > ?', 24.hours.from_now) }
def create_charge
if !is_test_drive?
if !school_on_school? && !is_test_drive? && !is_monthly_payment?
self.lesson_payment_charge = LessonPaymentCharge.new
lesson_payment_charge.user = @assigned_student
lesson_payment_charge.amount_in_cents = 0
lesson_payment_charge.fee_in_cents = 0
lesson_payment_charge.lesson_session = self
@ -96,23 +101,52 @@ module JamRuby
self.save
end
def music_session_id
music_session.id
end
def self.hourly_check
analyse_sessions
complete_sessions
end
def self.minutely_check
upcoming_sessions_reminder
end
def self.analyse_sessions
MusicSession.joins(lesson_session: :lesson_booking).where('session_removed_at IS NOT NULL').where('analysed = false').each do |music_session|
MusicSession.joins(lesson_session: :lesson_booking).where('lesson_sessions.status = ?', LessonSession::STATUS_APPROVED).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = false').each do |music_session|
lession_session = music_session.lesson_session
lession_session.analyse
end
end
def self.complete_sessions
MusicSession.joins(lesson_session: [:lesson_booking, :lesson_payment_charge]).where('session_removed_at IS NOT NULL').where('analysed = true').where('lesson_sessions.post_processed = false').where('billing_should_retry = true').each do |music_session|
# this will find any paid session (recurring monthly paid, recurring single paid, single paid)
MusicSession.joins(lesson_session: [:lesson_booking, :lesson_payment_charge]).where('lesson_sessions.status = ?', LessonSession::STATUS_COMPLETED).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = true').where('lesson_sessions.post_processed = false').where('billing_should_retry = true').each do |music_session|
lession_session = music_session.lesson_session
lession_session.session_completed
end
# test drives don't have a lesson_payment_charge, so we don't join against them
MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.status = ?', LessonSession::STATUS_COMPLETED).where('lesson_sessions.lesson_type = ?', LESSON_TYPE_TEST_DRIVE).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = true').where('lesson_sessions.post_processed = false').each do |music_session|
lession_session = music_session.lesson_session
lession_session.session_completed
end
end
def self.upcoming_sessions_reminder
now = Time.now
half_hour_from_now = 30.minutes.from_now
if Time.zone
now = Time.zone.local_to_utc(now)
half_hour_from_now = Time.zone.local_to_utc(half_hour_from_now)
end
MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.status = ?', LessonSession::STATUS_APPROVED).where('sent_starting_notice = false').where('(scheduled_start > ? and scheduled_start < ?)', now, half_hour_from_now).each do |music_session|
lession_session = music_session.lesson_session
lession_session.send_starting_notice
end
end
def analyse
@ -122,12 +156,14 @@ module JamRuby
analysis = LessonSessionAnalyser.analyse(self)
self.analysis = analysis_to_json(analysis)
self.analysis = LessonSession.analysis_to_json(analysis)
self.success = analysis[:bill]
self.analysed_at = Time.now
self.analysed = true
if lesson_booking.requires_teacher_distribution?(self)
self.status = STATUS_COMPLETED
if success && lesson_booking.requires_teacher_distribution?(self)
self.teacher_distribution = TeacherDistribution.create_for_lesson(self)
end
@ -137,12 +173,19 @@ module JamRuby
end
end
def billed
if lesson_booking.is_test_drive?
false
else
lesson_payment_charge.billed
end
end
def amount_charged
lesson_payment_charge.amount_in_cents / 100.0
end
def analysis_to_json(analysis)
def self.analysis_to_json(analysis, preserve_object = false)
json = {}
analysis.each do |k, v|
@ -160,7 +203,19 @@ module JamRuby
json[k] = v
end
end
json.to_json
if preserve_object
json
else
json.to_json
end
end
def send_starting_notice
UserMailer.lesson_starting_soon_student(self).deliver!
UserMailer.lesson_starting_soon_teacher(self).deliver!
self.sent_starting_notice = true
self.save(validate: false)
end
def session_completed
@ -186,10 +241,14 @@ module JamRuby
end
def bill_lesson
if school_on_school?
success = true
else
lesson_payment_charge.charge
success = lesson_payment_charge.billed
end
lesson_payment_charge.charge
if lesson_payment_charge.billed
if success
self.sent_notices = true
self.sent_notices_at = Time.now
self.post_processed = true
@ -202,13 +261,13 @@ module JamRuby
def test_drive_completed
distribution = teacher_distribution
if distribution # not all lessons/payment charges have a distribution
distribution.ready = true
distribution.save(validate: false)
end
if !sent_notices
if success
if distribution # not all lessons/payment charges have a distribution
distribution.ready = true
distribution.save(validate: false)
end
student.test_drive_succeeded(self)
else
student.test_drive_failed(self)
@ -242,9 +301,12 @@ module JamRuby
end
else
if lesson_booking.is_monthly_payment?
# bad session; just poke user
if !sent_notices
UserMailer.monthly_recurring_no_bill(self).deliver
if !school_on_school?
# bad session; just poke user
UserMailer.monthly_recurring_no_bill(self).deliver
end
self.sent_notices = true
self.sent_notices_at = Time.now
self.post_processed = true
@ -254,8 +316,11 @@ module JamRuby
else
if !sent_notices
# bad session; just poke user
UserMailer.student_weekly_recurring_no_bill(student, self).deliver
if !school_on_school?
# bad session; just poke user
UserMailer.student_lesson_normal_no_bill(self).deliver
end
self.sent_notices = true
self.sent_notices_at = Time.now
self.post_processed = true
@ -272,8 +337,11 @@ module JamRuby
bill_lesson
else
if !sent_notices
UserMailer.student_lesson_normal_no_bill(self).deliver
UserMailer.teacher_lesson_no_bill(self).deliver
if !school_on_school?
UserMailer.student_lesson_normal_no_bill(self).deliver
UserMailer.teacher_lesson_normal_no_bill(self).deliver
end
self.sent_notices = true
self.sent_notices_at = Time.now
self.post_processed = true
@ -314,10 +382,6 @@ module JamRuby
status == STATUS_COMPLETED
end
def is_missed?
status == STATUS_MISSED
end
def is_approved?
status == STATUS_APPROVED
end
@ -330,6 +394,14 @@ module JamRuby
status == STATUS_COUNTERED
end
def analysis_json
@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.")
@ -378,6 +450,7 @@ module JamRuby
lesson_session.teacher = booking.teacher
lesson_session.status = booking.status
lesson_session.slot = booking.default_slot
lesson_session.assigned_student = booking.student
if booking.is_test_drive?
lesson_session.lesson_package_purchase = booking.student.most_recent_test_drive_purchase
end
@ -393,21 +466,31 @@ module JamRuby
music_session.creator
end
def student_id
music_session.creator.id
end
def self.index(user, params = {})
limit = params[:per_page]
limit ||= 100
limit = limit.to_i
query = LessonSession.joins(:music_session).joins(music_session: :creator)
query = query.includes([:teacher, :music_session])
query = LessonSession.unscoped.joins([:music_session, :lesson_booking]).joins(music_session: :creator)
#query = query.includes([:teacher, :music_session])
query = query.includes([:music_session])
query = query.order('music_sessions.scheduled_start DESC')
if params[:as_teacher]
query = query.where('lesson_sessions.teacher_id = ?', user.id)
if params[:as_teacher].present?
if params[:as_teacher]
query = query.where('lesson_sessions.teacher_id = ?', user.id)
else
query = query.where('music_sessions.user_id = ?', user.id)
end
else
query = query.where('music_sessions.user_id = ?', user.id)
query = query.where('(lesson_sessions.teacher_id = ? or music_sessions.user_id = ?)', user.id, user.id)
end
query = query.where('lesson_bookings.card_presumed_ok = true OR (music_sessions.user_id = ?)', user.id)
current_page = params[:page].nil? ? 1 : params[:page].to_i
next_page = current_page + 1
@ -451,6 +534,17 @@ module JamRuby
time.nil? ? nil : attempt
end
def school_owner_id
school = teacher.teacher.school
if school
school.owner.id
end
end
def access?(user)
user.id == music_session.user_id || user.id == teacher.id || user.id == school_owner_id
end
# teacher accepts the lesson
def accept(params)
response = self
@ -480,8 +574,8 @@ module JamRuby
end
UserMailer.student_lesson_accepted(self, message, slot).deliver
UserMailer.teacher_lesson_accepted(self, message, slot).deliver
chat_message = message ? "Lesson Approved: '#{message}'" : "Lesson Approved"
msg = ChatMessage.create(teacher, nil, chat_message, ChatMessage::CHANNEL_LESSON, nil, student, lesson_booking)
message = '' if message.nil?
msg = ChatMessage.create(teacher, nil, message, ChatMessage::CHANNEL_LESSON, nil, student, self, "Lesson Approved")
Notification.send_jamclass_invitation_teacher(music_session, teacher)
Notification.send_student_jamclass_invitation(music_session, student)
Notification.send_lesson_message('accept', self, true)
@ -501,8 +595,8 @@ module JamRuby
response = lesson_booking
raise ActiveRecord::Rollback
end
chat_message = message ? "All Lesson Times Updated: '#{message}'" : "All Lesson Times Updated"
msg = ChatMessage.create(slot.proposer, nil, chat_message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, lesson_booking)
message = '' if message.nil?
msg = ChatMessage.create(slot.proposer, nil, message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, self, "All Lesson Times Updated")
Notification.send_lesson_message('accept', self, true) # TODO: this isn't quite an 'accept'
UserMailer.student_lesson_update_all(self, message, slot).deliver
UserMailer.teacher_lesson_update_all(self, message, slot).deliver
@ -515,8 +609,8 @@ module JamRuby
puts("unable to accept slot #{slot.id} for lesson #{self.id} because it's in the past")
raise ActiveRecord::Rollback
end
chat_message = message ? "Lesson Updated Time Approved: '#{message}'" : "Lesson Updated Time Approved"
msg = ChatMessage.create(slot.proposer, nil, chat_message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, lesson_booking)
message = '' if message.nil?
msg = ChatMessage.create(slot.proposer, nil, message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, self, "Lesson Updated Time Approved")
UserMailer.student_lesson_accepted(self, message, slot).deliver
UserMailer.teacher_lesson_accepted(self, message, slot).deliver
end
@ -551,25 +645,42 @@ module JamRuby
self.counter_slot = slot
end
if self.save
if update_all
if !lesson_booking.counter(self, proposer, slot)
response = lesson_booking
raise ActiveRecord::Rollback
end
if update_all && !lesson_booking.counter(self, proposer, slot)
response = lesson_booking
raise ActiveRecord::Rollback
end
else
response = self
raise ActiveRecord::Rollback
end
msg = ChatMessage.create(slot.proposer, music_session, message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, lesson_booking)
message = '' if message.nil?
msg = ChatMessage.create(slot.proposer, music_session, message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, self, "New Time Proposed")
Notification.send_lesson_message('counter', self, slot.is_teacher_created?)
end
response
end
def cancel_lesson(canceler, message)
canceled_by_student = canceler == student
self.status = STATUS_CANCELED
self.cancel_message = message
self.canceler = canceler
self.canceling = true
if canceled_by_student
self.student_canceled = true
self.student_canceled_at = Time.now
self.student_canceled_reason = message
self.student_short_canceled = 24.hours.from_now > scheduled_start
else
self.teacher_canceled = true
self.teacher_canceled_at = Time.now
self.teacher_canceled_reason = message
self.teacher_short_canceled = 24.hours.from_now > scheduled_start
end
end
# teacher accepts the lesson
def cancel(params)
@ -577,39 +688,39 @@ module JamRuby
LessonSession.transaction do
canceler = params[:canceler]
other = canceler == teacher ? student : teacher
canceled_by_student = canceler == student
other = canceled_by_student ? teacher : student
message = params[:message]
message = '' if message.nil?
if params[:update_all].present?
if lesson_booking.recurring
update_all = params[:update_all]
else
update_all = !lesson_booking.recurring
update_all = true
end
if lesson_booking.is_test_drive?
student.test_drive_declined(self)
end
self.status = STATUS_CANCELED
self.cancel_message = message
self.canceler = canceler
self.canceling = true
if self.save
if update_all
if !lesson_booking.cancel(canceler, other, message)
response = lesson_booking
raise ActiveRecord::Rollback
end
else
msg = ChatMessage.create(canceler, nil, message, ChatMessage::CHANNEL_LESSON, nil, other, lesson_booking)
Notification.send_lesson_message('canceled', self, false)
Notification.send_lesson_message('canceled', self, true)
UserMailer.student_lesson_canceled(self, message).deliver
UserMailer.teacher_lesson_canceled(self, message).deliver
if update_all
response = lesson_booking.cancel(canceler, other, message)
if response.errors.any?
raise ActiveRecord::Rollback
end
else
response = self
raise ActiveRecord::Rollback
end
cancel_lesson(canceler, message)
if !save
response = self
raise ActiveRecord::Rollback
end
msg = ChatMessage.create(canceler, nil, message, ChatMessage::CHANNEL_LESSON, nil, other, self, "Lesson Canceled")
Notification.send_lesson_message('canceled', self, false)
Notification.send_lesson_message('canceled', self, true)
UserMailer.student_lesson_canceled(self, message).deliver
UserMailer.teacher_lesson_canceled(self, message).deliver
end
end
response
@ -619,6 +730,19 @@ module JamRuby
lesson_booking.lesson_package_type.description(lesson_booking)
end
def timed_description
if is_test_drive?
"TestDrive session with #{self.lesson_booking.student.name} on #{self.scheduled_start.to_date.strftime('%B %d, %Y')}"
else
if self.lesson_booking.is_monthly_payment?
"Monthly Lesson with #{self.lesson_booking.student.name} on #{self.scheduled_start.to_date.strftime('%B %d, %Y')}"
else
"Lesson with #{self.lesson_booking.student.name} on #{self.scheduled_start.to_date.strftime('%B %d, %Y')}"
end
end
end
def stripe_description(lesson_booking)
description(lesson_booking)
end
@ -638,5 +762,9 @@ module JamRuby
def admin_url
APP_CONFIG.admin_root_url + "/admin/lesson_sessions/" + id
end
def chat_url
APP_CONFIG.external_root_url + "/client#/jamclass/chat-dialog/d1=lesson_" + id
end
end
end

View File

@ -1,7 +1,7 @@
module JamRuby
class LessonSessionAnalyser
SUCCESS = 'success'
SUCCESS = 'success'
SESSION_ONGOING = 'session_ongoing'
THRESHOLD_MET = 'threshold_met'
WAITED_CORRECTLY = 'waited_correctly'
@ -16,6 +16,7 @@ module JamRuby
STUDENT_NOT_THERE_WHEN_JOINED = 'student_not_there_when_joined'
JOINED_LATE = 'did_not_join_on_time'
NO_SHOW = 'no_show'
NEITHER_SHOW = 'neither_show'
# what are the potential results?
@ -42,7 +43,7 @@ module JamRuby
# reason: 'both_fault'
def self.analyse(lesson_session)
def self.analyse(lesson_session, force = false)
reason = nil
teacher = nil
student = nil
@ -62,26 +63,25 @@ module JamRuby
teacher_ranges = merge_overlapping_ranges(all_teacher_ranges)
intersecting = intersecting_ranges(student_ranges, teacher_ranges)
student_analysis = analyse_intersection(lesson_session, student_ranges)
teacher_analysis = analyse_intersection(lesson_session, teacher_ranges)
together_analysis = analyse_intersection(lesson_session, intersecting)
# spec: https://jamkazam.atlassian.net/wiki/display/PS/Product+Specification+-+JamClass#ProductSpecification-JamClass-TeacherReceives&RespondstoLessonBookingRequest
if music_session.session_removed_at.nil?
if !force && !((music_session.scheduled_start + (lesson_session.duration * 60)) < Time.now)
reason = SESSION_ONGOING
bill = false
else
if lesson_session.is_canceled? && lesson_session.canceled_by_teacher? && lesson_session.canceled_late?
# If the lesson was cancelled less than 24 hours before the start time by the teacher, then we do not bill the student.
teacher = LATE_CANCELLATION
bill = false
elsif lesson_session.is_canceled? && lesson_session.canceled_by_student? && lesson_session.canceled_late?
# If the lesson was cancelled less than 24 hours before the start time by the student (if that is even possible, I cant remember now), then we do bill the student.
student = LATE_CANCELLATION
bill = true
elsif together_analysis[:session_time] / 60 > APP_CONFIG.lesson_together_threshold_minutes
#if lesson_session.is_canceled? && lesson_session.canceled_by_teacher? && lesson_session.canceled_late?
# # If the lesson was cancelled less than 24 hours before the start time by the teacher, then we do not bill the student.
# teacher = LATE_CANCELLATION
# bill = false
#elsif lesson_session.is_canceled? && lesson_session.canceled_by_student? && lesson_session.canceled_late?
# # If the lesson was cancelled less than 24 hours before the start time by the student (if that is even possible, I cant remember now), then we do bill the student.
# student = LATE_CANCELLATION
# bill = true
if together_analysis[:session_time] / 60 > APP_CONFIG.lesson_together_threshold_minutes
bill = true
reason = SUCCESS
elsif teacher_analysis[:joined_on_time] && teacher_analysis[:waited_correctly]
@ -98,8 +98,17 @@ module JamRuby
student = JOINED_LATE
bill = true
end
else
if teacher_analysis[:no_show]
teacher = NO_SHOW
elsif !teacher_analysis[:joined_on_time]
teacher = JOINED_LATE
elsif !teacher_analysis[:waited_correctly]
teacher = MINIMUM_TIME_NOT_MET
end
end
end
if reason.nil?
@ -107,6 +116,8 @@ module JamRuby
reason = STUDENT_FAULT
elsif teacher
reason = TEACHER_FAULT
else
reason = NEITHER_SHOW
end
end
@ -150,7 +161,8 @@ module JamRuby
def self.analyse_intersection(lesson_session, ranges)
start = lesson_session.scheduled_start
# be sure to call .to_time on any ActiveRecord time, because we get a ton of deprecation warninsg about Time#succ if you use ActiveSupport:: TimeZone
start = lesson_session.scheduled_start.to_time
planned_duration_seconds = lesson_session.duration * 60
end_time = start + planned_duration_seconds
@ -286,7 +298,7 @@ module JamRuby
end
def self.ranges_overlap?(a, b)
a.include?(b.begin) || b.include?(a.begin)
a.cover?(b.begin) || b.cover?(a.begin)
end
def self.merge_ranges(a, b)

View File

@ -6,7 +6,19 @@ module JamRuby
raise "lesson_booking is not monthly paid #{lesson_booking.admin_url}" if !lesson_booking.is_monthly_payment?
times = lesson_booking.predicted_times_for_month(start_day.year, start_day.month)
data = lesson_booking.predicted_times_for_month(start_day.year, start_day.month)
times = data[:times]
session = data[:session]
true_start = start_day
if session
# if there is already a session for the month, that is the real star
true_start = session.scheduled_start.to_date
end
# filter out anything before the start day
times.select! { |time| time.to_date >= true_start }
result = nil
if times.length == 0

View File

@ -22,6 +22,7 @@ module JamRuby
CREATE_TYPE_IMMEDIATE = 'immediately'
CREATE_TYPE_QUICK_START = 'quick-start'
CREATE_TYPE_LESSON = 'lesson'
CREATE_TYPE_QUICK_PUBLIC = 'quick-public'
attr_accessor :legal_terms, :language_description, :access_description, :scheduling_info_changed
@ -323,11 +324,29 @@ module JamRuby
# let session be restarted for up to 2 hours after finishing
session_finished = "(music_sessions.session_removed_at > NOW() - '2 hour'::INTERVAL)"
query = MusicSession.where("music_sessions.canceled = FALSE")
query = MusicSession.joins(
%Q{
LEFT OUTER JOIN
invitations
ON
music_sessions.id = invitations.music_session_id AND invitations.receiver_id = '#{user.id}'
}
)
query = query.where("music_sessions.canceled = FALSE")
query = query.where('music_sessions.fan_access = TRUE or music_sessions.musician_access = TRUE') if only_public
query = query.where("music_sessions.user_id = '#{user.id}'")
#query = query.where("music_sessions.user_id = '#{user.id}' OR invitations.id IS NOT NULL")
query = query.where("music_sessions.id in (
select distinct(rs.music_session_id)
from rsvp_slots rs
where rs.id in (
select rrrs.rsvp_slot_id
from rsvp_requests rr
inner join rsvp_requests_rsvp_slots rrrs on rr.id = rrrs.rsvp_request_id
where rr.user_id = '#{user.id}'AND rrrs.chosen = true
)
) OR invitations.id IS NOT NULL OR music_sessions.user_id = '#{user.id}'")
query = query.where("music_sessions.scheduled_start IS NULL OR #{session_not_started} OR #{session_finished} OR #{session_started_not_finished}")
query = query.where("music_sessions.create_type IS NULL OR music_sessions.create_type != '#{CREATE_TYPE_QUICK_START}'")
query = query.where("music_sessions.create_type IS NULL OR (music_sessions.create_type != '#{CREATE_TYPE_QUICK_START}' AND music_sessions.create_type != '#{CREATE_TYPE_QUICK_PUBLIC}')")
query = query.order("music_sessions.scheduled_start ASC")
query
@ -339,7 +358,7 @@ module JamRuby
filter_approved = only_approved ? 'AND rrrs.chosen = true' : ''
MusicSession.where(%Q{music_sessions.canceled = FALSE AND
(music_sessions.create_type is NULL OR music_sessions.create_type != '#{CREATE_TYPE_QUICK_START}') AND
(music_sessions.create_type is NULL OR (music_sessions.create_type != '#{CREATE_TYPE_QUICK_START}' AND music_sessions.create_type != '#{CREATE_TYPE_QUICK_PUBLIC}')) AND
(music_sessions.scheduled_start is NULL OR music_sessions.scheduled_start > NOW() - '4 hour'::INTERVAL) AND
music_sessions.id in (
select distinct(rs.music_session_id)
@ -760,7 +779,7 @@ module JamRuby
query = query.offset(offset)
query = query.limit(limit)
query = query.where("music_sessions.create_type IS NULL OR (music_sessions.create_type != ? AND music_sessions.create_type != ?)", MusicSession::CREATE_TYPE_QUICK_START, MusicSession::CREATE_TYPE_IMMEDIATE)
query = query.where("music_sessions.create_type IS NULL OR (music_sessions.create_type != ? AND music_sessions.create_type != ? AND music_sessions.create_type != ?)", MusicSession::CREATE_TYPE_QUICK_START, MusicSession::CREATE_TYPE_IMMEDIATE, MusicSession::CREATE_TYPE_QUICK_PUBLIC)
query = query.where("music_sessions.genre_id = ?", genre) unless genre.blank?
query = query.where('music_sessions.language = ?', lang) unless lang.blank?
query = query.where("(description_tsv @@ to_tsquery('jamenglish', ?))", ActiveRecord::Base.connection.quote(keyword) + ':*') unless keyword.blank?
@ -875,7 +894,7 @@ SQL
result
end
def scheduled_start_date
def scheduled_start_date
if self.scheduled_start_time.blank?
""
else
@ -936,20 +955,16 @@ SQL
end
duration
end
# should create a timestamp like:
#
# with_timezone = TRUE
# Tuesday, April 29, 8:00-9:00 PM TIMEZONE (where TIMEZONE is the TIMEZONE defined in the MusicSession when it was created)
#
# with_timezone = FALSE
# Thursday, July 10 - 10:00pm
# this should be in a helper
def pretty_scheduled_start(with_timezone = true)
def pretty_scheduled_start(with_timezone = true, shorter = false)
if scheduled_start && scheduled_duration
start_time = scheduled_start
timezone_display = 'UTC'
utc_offset_display = '00:00'
tz_identifier, tz_display = MusicSession.split_timezone(timezone)
short_tz = 'GMT'
begin
tz = TZInfo::Timezone.get(tz_identifier)
rescue Exception => e
@ -960,6 +975,15 @@ SQL
begin
start_time = tz.utc_to_local(scheduled_start.utc)
timezone_display = tz_display
utc_offset_hours = tz.current_period.utc_total_offset / (60*60)
hour = sprintf '%02d', utc_offset_hours.abs
minutes = sprintf '%02d', ((tz.current_period.utc_total_offset.abs % 3600) / 3600) * 60
utc_offset_display = "#{utc_offset_hours < 0 ? '-' : ' '}#{hour}:#{minutes}"
short_tz = start_time.strftime("%Z")
if short_tz == 'UTC'
short_tz = 'GMT'
end
rescue Exception => e
@@log.error("unable to convert #{scheduled_start} to #{tz}, e=#{e}")
puts "unable to convert #{e}"
@ -969,7 +993,11 @@ SQL
duration = safe_scheduled_duration
end_time = start_time + duration
if with_timezone
"#{start_time.strftime("%A, %B %e")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} #{timezone_display}"
if shorter
"#{start_time.strftime("%a, %b %e %Y")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} (#{short_tz}#{utc_offset_display})"
else
"#{start_time.strftime("%A, %B %e")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} #{timezone_display}"
end
else
"#{start_time.strftime("%A, %B %e")} - #{start_time.strftime("%l:%M%P").strip}"
end

View File

@ -26,7 +26,9 @@ module JamRuby
end
def range
Range.new(created_at, session_removed_at || Time.now)
# to_time to not use ActiveSupport::TimeWithZone
session_removed_at_time = session_removed_at.to_time if session_removed_at
Range.new(created_at.to_time, session_removed_at_time || Time.now.to_time)
end
def music_session
@ -38,9 +40,9 @@ module JamRuby
end
def self.save(music_session_id, user_id, client_id, tracks)
return true if 0 < self.where(:music_session_id => music_session_id,
:user_id => user_id,
:client_id => client_id).count
return true if 0 < self.where(:music_session_id => music_session_id,
:user_id => user_id,
:client_id => client_id).where('session_removed_at is NULL').count
session_user_history = MusicSessionUserHistory.new
session_user_history.music_session_id = music_session_id
session_user_history.user_id = user_id
@ -58,10 +60,12 @@ module JamRuby
(end_time - self.created_at) / 60.0
end
def self.join_music_session(user_id, session_id)
def self.join_music_session(user_id, session_id, client_id)
hist = self
.where(:user_id => user_id)
.where(:music_session_id => session_id)
.where(:client_id => client_id)
.where('session_removed_at IS NULL')
.limit(1)
.first
hist.start_history if hist

View File

@ -63,7 +63,7 @@ module JamRuby
end
end
self.class.format_msg(self.description, {:user => source_user, target: target_user, :band => band, :session => session, purpose: purpose, student_directed: student_directed})
self.class.format_msg(self.description, {:user => source_user, target: target_user, :band => band, :session => session, purpose: purpose, student_directed: student_directed, msg: message})
end
# TODO: MAKE ALL METHODS BELOW ASYNC SO THE CLIENT DOESN'T BLOCK ON NOTIFICATION LOGIC
@ -135,6 +135,7 @@ module JamRuby
session = options[:session]
purpose = options[:purpose]
student_directed = options[:student_directed]
msg = options[:msg]
name, band_name = ""
unless user.nil?
@ -271,6 +272,8 @@ module JamRuby
end
elsif purpose == 'reschedule'
'A lesson reschedule has been requested'
elsif purpose == 'chat'
notification_msg = "Lesson Message: #{msg}"
end
return notification_msg
@ -404,7 +407,7 @@ module JamRuby
end
end
def send_lesson_message(purpose, lesson_session, student_directed)
def send_lesson_message(purpose, lesson_session, student_directed, msg = nil)
notification = Notification.new
notification.description = NotificationTypes::LESSON_MESSAGE
@ -422,9 +425,11 @@ module JamRuby
notification.session_id = lesson_session.music_session.id
notification.lesson_session_id = lesson_session.id
notification_msg = format_msg(NotificationTypes::LESSON_MESSAGE, {purpose: purpose})
notification_msg = format_msg(NotificationTypes::LESSON_MESSAGE, {purpose: purpose, msg: msg})
#notification.message = notification_msg
if purpose == 'chat'
notification.message = msg
end
notification.save
@ -756,7 +761,7 @@ module JamRuby
end
begin
UserMailer.teacher_scheduled_jamclass_invitation(music_session.lesson_session.teacher, notification_msg, music_session).deliver
#UserMailer.teacher_scheduled_jamclass_invitation(music_session.lesson_session.teacher, notification_msg, music_session).deliver
rescue => e
@@log.error("Unable to send SCHEDULED_JAMCLASS_INVITATION email to user #{music_session.lesson_session.teacher.email} #{e}")
end
@ -796,7 +801,7 @@ module JamRuby
end
begin
UserMailer.student_scheduled_jamclass_invitation(student, notification_msg, music_session).deliver
#UserMailer.student_scheduled_jamclass_invitation(student, notification_msg, music_session).deliver
rescue => e
@@log.error("Unable to send SCHEDULED_JAMCLASS_INVITATION email to user #{student.email} #{e}")
end
@ -1094,7 +1099,7 @@ module JamRuby
# start in less than 24 hours, and haven't been
# notified for a particular interval yet:
def send_session_reminders
MusicSession.where("scheduled_start > NOW() AND scheduled_start <= (NOW()+INTERVAL '1 DAYS')").each do |candidate_session|
MusicSession.where("scheduled_start > NOW() AND scheduled_start <= (NOW()+INTERVAL '1 DAYS') AND lesson_session_id IS NULL").each do |candidate_session|
tm = candidate_session.scheduled_start
if (tm>(12.hours.from_now) && !notified?(candidate_session, NotificationTypes::SCHEDULED_SESSION_REMINDER_DAY))
# Send 24 hour reminders:

View File

@ -5,6 +5,7 @@ module JamRuby
belongs_to :sale
belongs_to :recurly_transaction_web_hook
belongs_to :charge
def self.index(user, params = {})
@ -14,7 +15,7 @@ module JamRuby
limit = limit.to_i
query = PaymentHistory.limit(limit)
.includes(sale: [:sale_line_items], recurly_transaction_web_hook:[])
.includes(sale: [:sale_line_items], recurly_transaction_web_hook:[], charge:[])
.where(user_id: user.id)
.where("transaction_type = 'sale' OR transaction_type = 'refund' OR transaction_type = 'void'")
.order('created_at DESC')

View File

@ -208,8 +208,8 @@ module JamRuby
free && non_free
end
def self.purchase_test_drive(current_user, booking = nil)
self.purchase_lesson(current_user, booking, LessonPackageType.test_drive)
def self.purchase_test_drive(current_user, lesson_package_type, booking = nil)
self.purchase_lesson(current_user, booking, lesson_package_type)
end
def self.purchase_normal(current_user, booking)
@ -273,6 +273,7 @@ module JamRuby
purchase = LessonPackagePurchase.create(current_user, lesson_booking, lesson_package_type) if purchase.nil?
if purchase.errors.any?
puts "purchase errors #{purchase.errors.inspect}"
price_info = {}
price_info[:purchase] = purchase
return price_info

View File

@ -43,14 +43,17 @@ module JamRuby
JamTrack.find_by_id(product_id)
elsif product_type == GIFTCARD
GiftCardType.find_by_id(product_id)
elsif product_type == LESSON
lesson_package_purchase
else
raise 'unsupported product type'
end
end
def product_info
item = product
{ name: product.name } if item
{ name: product.name, product_type: product_type } if item
end
def state

View File

@ -18,6 +18,8 @@ module JamRuby
has_many :students, class_name: ::JamRuby::User
has_many :teachers, class_name: ::JamRuby::Teacher
has_many :school_invitations, class_name: 'JamRuby::SchoolInvitation'
has_many :teacher_payments, class_name: 'JamRuby::TeacherPayment'
has_many :teacher_distributions, class_name: 'JamRuby::TeacherDistribution'
validates :user, presence: true
validates :enabled, inclusion: {in: [true, false]}

View File

@ -44,10 +44,25 @@ module JamRuby
SignupHint.where("created_at < :week", {:week => 1.week.ago}).delete_all
end
def self.most_recent_redirect(user, default)
def self.most_recent_redirect(user, default, queryParams=nil)
puts "jquery params"
hint = SignupHint.where(user_id: user.id).order('created_at desc').first
if hint
hint.redirect_location
redirect = hint.redirect_location
puts "redirect #{redirect}"
uri = URI.parse(redirect)
bits = uri.query ? URI.decode_www_form(uri.query) : []
if queryParams
queryParams.each do |k, v|
bits << [k, v]
end
end
puts "bits #{bits}"
uri.query = URI.encode_www_form(bits)
puts "oh yeah #{uri.to_s}"
return uri.to_s
else
default
end

View File

@ -55,7 +55,7 @@ module JamRuby
query = User.joins(:teacher)
# only show teachers with background check set and ready for session set to true
# only show teachers with ready for session set to true
query = query.where('teachers.ready_for_session_at IS NOT NULL')
instruments = params[:instruments]
@ -145,7 +145,11 @@ module JamRuby
def self.save_teacher(user, params)
teacher = build_teacher(user, params)
teacher.save
if teacher.save
# flag the user as a teacher
teacher.user.is_a_teacher = true
teacher.user.save(validate: false)
end
teacher
end
@ -190,8 +194,10 @@ module JamRuby
teacher.price_per_month_120_cents = params[:price_per_month_120_cents] if params.key?(:price_per_month_120_cents)
teacher.teaches_test_drive = params[:teaches_test_drive] if params.key?(:teaches_test_drive)
teacher.test_drives_per_week = params[:test_drives_per_week] if params.key?(:test_drives_per_week)
teacher.test_drives_per_week ||= 10 # default to 10 in absence of others
teacher.school_id = params[:school_id] if params.key?(:school_id)
# Many-to-many relations:
if params.key?(:genres)
genres = params[:genres]
@ -317,7 +323,7 @@ module JamRuby
end
def has_stripe_billing?
false
user.has_stripe_connect?
end
def has_instruments_or_subject?

View File

@ -5,10 +5,43 @@ module JamRuby
belongs_to :teacher_payment, class_name: "JamRuby::TeacherPayment"
belongs_to :lesson_session, class_name: "JamRuby::LessonSession"
belongs_to :lesson_package_purchase, class_name: "JamRuby::LessonPackagePurchase"
belongs_to :school, class_name: "JamRuby::School"
validates :teacher, presence: true
validates :amount_in_cents, presence: true
def self.index(current_user, params)
limit = params[:per_page]
limit ||= 100
limit = limit.to_i
query = TeacherDistribution.where(teacher_id: current_user.id).where('school_id IS NULL').order('created_at desc')
current_page = params[:page].nil? ? 1 : params[:page].to_i
next_page = current_page + 1
# will_paginate gem
query = query.paginate(:page => current_page, :per_page => limit)
if query.length == 0 # no more results
{query: query, next_page: nil}
elsif query.length < limit # no more results
{query: query, next_page: nil}
else
{query: query, next_page: next_page}
end
end
def not_collectable
if is_test_drive?
false
elsif is_normal?
!lesson_session.billing_should_retry
else
! lesson_package_purchase.billing_should_retry
end
end
def self.create_for_lesson(lesson_session)
distribution = create(lesson_session)
distribution.lesson_session = lesson_session
@ -26,13 +59,35 @@ module JamRuby
distribution.teacher = target.teacher
distribution.ready = false
distribution.distributed = false
distribution.amount_in_cents = target.lesson_booking.distribution_price_in_cents
distribution.amount_in_cents = target.lesson_booking.distribution_price_in_cents(target)
distribution.school = target.lesson_booking.school
distribution
end
def amount
amount_in_cents / 100.0
end
def real_distribution_in_cents
amount_in_cents - calculate_teacher_fee
end
def real_distribution
(real_distribution_in_cents / 100.0)
end
def real_distribution_display
'$%.2f' % real_distribution
end
def calculate_teacher_fee
if is_test_drive?
0
else
(amount_in_cents * (teacher.teacher.jamkazam_rate + 0.03)).round
end
end
def student
if lesson_session
lesson_session.student
@ -59,17 +114,9 @@ module JamRuby
def description
if lesson_session
if lesson_session.lesson_booking.is_test_drive?
"Test Drive session with #{lesson_session.lesson_booking.student.name} on #{lesson_session.scheduled_start.to_date}"
elsif lesson_session.lesson_booking.is_normal?
if lesson_session.lesson_booking.is_weekly_payment? || lesson_session.lesson_booking.is_monthly_payment?
raise "Should not be here"
else
"A session with #{lesson_session.lesson_booking.student.name} on #{lesson_session.scheduled_start.to_date}"
end
end
lesson_session.timed_description
else
"Monthly session for the month of #{lesson_package_purchase.month_name} with #{lesson_package_purchase.lesson_booking.student.name}"
lesson_package_purchase.description
end
end
end

View File

@ -4,12 +4,22 @@ module JamRuby
belongs_to :teacher, class_name: "JamRuby::User", foreign_key: :teacher_id
belongs_to :teacher_payment_charge, class_name: "JamRuby::TeacherPaymentCharge", foreign_key: :charge_id
has_one :teacher_distribution, class_name: "JamRuby::TeacherDistribution"
belongs_to :school, class_name: "JamRuby::School"
def self.hourly_check
teacher_payments
end
# pay the school if the payment owns the school; otherwise default to the teacher
def payable_teacher
if school
school.owner
else
teacher
end
end
def teacher_distributions
[teacher_distribution]
end
@ -51,15 +61,10 @@ module JamRuby
24
end
def calculate_teacher_fee
if teacher_distribution.is_test_drive?
0
else
(amount_in_cents * 0.28).round
end
def real_distribution_in_cents
amount_in_cents - fee_in_cents
end
# will find, for a given teacher, an outstading unsuccessful payment or make a new one.
# it will then associate a charge with it, and then execute the charge.
def self.charge(teacher)
@ -79,12 +84,13 @@ module JamRuby
payment.teacher_distribution = teacher_distribution
end
payment.school = payment.teacher_distribution.school
payment.amount_in_cents = payment.teacher_distribution.amount_in_cents
payment.fee_in_cents = payment.calculate_teacher_fee
payment.fee_in_cents = payment.teacher_distribution.calculate_teacher_fee
if payment.teacher_payment_charge.nil?
charge = TeacherPaymentCharge.new
charge.user = payment.payable_teacher
charge.amount_in_cents = payment.amount_in_cents
charge.fee_in_cents = payment.fee_in_cents
charge.teacher_payment = payment

View File

@ -12,15 +12,13 @@ module JamRuby
end
def teacher
@teacher ||= teacher_payment.teacher
@teacher ||= teacher_payment.payable_teacher
end
def charged_user
teacher
end
def do_charge(force)
# source will let you supply a token. But... how to get a token in this case?
@ -38,11 +36,17 @@ module JamRuby
end
def do_send_notices
UserMailer.teacher_distribution_done(teacher_payment)
unless teacher_payment.school && distribution.is_monthly?
# we don't send monthly success notices to the teacher if they are in a school, otherwise they get an email
UserMailer.teacher_distribution_done(teacher_payment).deliver
end
if teacher_payment.school
UserMailer.school_distribution_done(teacher_payment).deliver
end
end
def do_send_unable_charge
UserMailer.teacher_distribution_fail(teacher_payment)
UserMailer.teacher_distribution_fail(teacher_payment).deliver
end
def construct_description

View File

@ -51,7 +51,7 @@ module JamRuby
has_many :user_authorizations, :class_name => "JamRuby::UserAuthorization"
has_many :reviews, :class_name => "JamRuby::Review"
has_one :review_summary, :class_name => "JamRuby::ReviewSummary"
has_one :review_summary, :class_name => "JamRuby::ReviewSummary"
# calendars (for scheduling NOT in music_session)
has_many :calendars, :class_name => "JamRuby::Calendar"
@ -71,7 +71,7 @@ module JamRuby
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"
# genres
has_many :genre_players, as: :player, class_name: "JamRuby::GenrePlayer", dependent: :destroy
has_many :genres, through: :genre_players, class_name: "JamRuby::Genre"
@ -101,7 +101,7 @@ module JamRuby
has_many :followers, :as => :followable, :class_name => "JamRuby::Follow", :dependent => :destroy
# text messages
has_many :text_messages, :class_name => "JamRuby:TextMessage", :foreign_key => "target_user_id"
has_many :text_messages, :class_name => "JamRuby::TextMessage", :foreign_key => "target_user_id"
# notifications
has_many :notifications, :class_name => "JamRuby::Notification", :foreign_key => "target_user_id"
@ -176,6 +176,7 @@ module JamRuby
has_many :teacher_lesson_bookings, :class_name => "JamRuby::LessonBooking", :foreign_key => "teacher_id", inverse_of: :teacher
has_many :teacher_distributions, :class_name => "JamRuby::TeacherDistribution", :foreign_key => "teacher_id", inverse_of: :teacher
has_many :teacher_payments, :class_name => "JamRuby::TeacherPayment", :foreign_key => "teacher_id", inverse_of: :teacher
belongs_to :desired_package, :class_name => "JamRuby::LessonPackageType", :foreign_key => "lesson_package_type_id", inverse_of: :user_desired_packages # used to hold whether user last wanted test drive 4/2/1
# Shopping carts
has_many :shopping_carts, :class_name => "JamRuby::ShoppingCart"
@ -209,6 +210,8 @@ module JamRuby
before_save :create_remember_token, :if => :should_validate_password?
before_save :stringify_avatar_info, :if => :updating_avatar
after_save :after_save
validates :first_name, length: {maximum: 50}, no_profanity: true
validates :last_name, length: {maximum: 50}, no_profanity: true
validates :biography, length: {maximum: 4000}, no_profanity: true
@ -255,11 +258,22 @@ module JamRuby
scope :musicians_geocoded, musicians.geocoded_users
scope :email_opt_in, where(:subscribe_email => true)
def after_save
if school_interest && !school_interest_was
AdminMailer.partner({body: "#{email} signed up via the https://www.jamkazam.com/landing/jamclass/schools page.\n\nFull list is here: https://www.jamkazam.com/admin/admin/school_interests", subject: "#{email} is interested in schools"}).deliver
if owned_school.nil?
school = School.new
school.user = self
school.save!
end
end
end
def update_teacher_pct
if teacher
teacher.update_profile_pct
end
end
def user_progression_fields
@user_progression_fields ||= Set.new ["first_downloaded_client_at", "first_ran_client_at", "first_music_session_at", "first_real_music_session_at", "first_good_music_session_at", "first_certified_gear_at", "first_invited_at", "first_friended_at", "first_recording_at", "first_social_promoted_at", "first_played_jamtrack_at"]
end
@ -1090,6 +1104,7 @@ module JamRuby
teacher = options[:teacher]
school_invitation_code = options[:school_invitation_code]
school_id = options[:school_id]
school_interest = options[:school_interest]
user = User.new
user.validate_instruments = true
@ -1115,6 +1130,7 @@ module JamRuby
user.has_redeemable_jamtrack = true
user.is_a_student = !!student
user.is_a_teacher = !!teacher
user.school_interest = !!school_interest
if user.is_a_student || user.is_a_teacher
musician = true
end
@ -1125,7 +1141,7 @@ module JamRuby
user.school_id = school_id
elsif user.is_a_teacher
school = School.find_by_id(school_id)
school_name = school ? school.name : 'a music school'
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)
end
else
@ -1318,16 +1334,13 @@ module JamRuby
end if affiliate_referral_id.present?
if user.is_a_student
UserMailer.student_welcome_message(user).deliver
end
if user.is_a_teacher
elsif user.is_a_teacher
UserMailer.teacher_welcome_message(user).deliver
end
if !user.is_a_teacher && !user.is_a_student
elsif user.school_interest
UserMailer.school_owner_welcome_message(user).deliver
else
UserMailer.welcome_message(user).deliver
end
@ -1891,7 +1904,7 @@ module JamRuby
end
def can_buy_test_drive?
lesson_purchases.where(lesson_package_type_id: LessonPackageType.test_drive.id).where('created_at > ?', APP_CONFIG.test_drive_wait_period_year.years.ago).count == 0
lesson_purchases.where('lesson_package_type_id in (?)', LessonPackageType.test_drive_package_ids).where('created_at > ?', APP_CONFIG.test_drive_wait_period_year.years.ago).count == 0
end
def has_test_drives?
@ -1906,6 +1919,15 @@ module JamRuby
!requested_test_drive(teacher).nil?
end
def stripe_auth
user_authorizations.where(provider: "stripe_connect").first
end
def has_stripe_connect?
auth = stripe_auth
auth && (!auth.token_expiration || auth.token_expiration > Time.now)
end
def fetch_stripe_customer
Stripe::Customer.retrieve(stripe_customer_id)
end
@ -1931,9 +1953,11 @@ module JamRuby
customer
end
def card_approved(token, zip)
def card_approved(token, zip, booking_id)
approved_booking = nil
found_uncollectables = nil
User.transaction do
self.stripe_token = token if token
self.stripe_zip_code = zip if zip
@ -1941,14 +1965,22 @@ module JamRuby
self.stripe_customer_id = customer.id
self.stored_credit_card = true
if self.save
# we can also 'unlock' any booked sessions that still need to be done so
LessonBooking.unprocessed(self).each do |booking|
booking.card_approved
approved_booking = booking
if booking_id
approved_booking = LessonBooking.find_by_id(booking_id)
if approved_booking
approved_booking.card_approved
end
end
if uncollectables.count > 0
found_uncollectables = uncollectables
uncollectables.update_all(billing_should_retry: true)
else
found_uncollectables = nil
end
end
end
approved_booking
[approved_booking, found_uncollectables]
end
def update_name(name)
@ -1977,6 +2009,8 @@ module JamRuby
normal = nil
intent = nil
purchase = nil
lesson_package_type = nil
uncollectables = nil
User.transaction do
if params[:name].present?
@ -1985,20 +2019,32 @@ module JamRuby
end
end
booking = card_approved(params[:token], params[:zip])
booking, uncollectables = card_approved(params[:token], params[:zip], params[:booking_id])
if params[:test_drive]
self.reload
result = Sale.purchase_test_drive(self, booking)
if booking
lesson_package_type = booking.resolved_test_drive_package
end
if lesson_package_type.nil?
lesson_package_type = LessonPackageType.test_drive_4
end
result = Sale.purchase_test_drive(self, lesson_package_type, booking)
test_drive = result[:sale]
purchase = result[:purchase]
if booking && !purchase.errors.any?
# the booking would not have a lesson_package_purchase associated yet, so let's associate it
booking.lesson_sessions.update_all(lesson_package_purchase_id: purchase.id)
end
elsif params[:normal]
self.reload
end
intent = TeacherIntent.recent_test_drive(self)
end
{lesson: booking, test_drive: test_drive, intent:intent, purchase: purchase}
{lesson: booking, test_drive: test_drive, purchase: purchase, lesson_package_type: lesson_package_type, uncollectables: uncollectables}
end
def requested_test_drive(teacher = nil)
@ -2018,7 +2064,16 @@ module JamRuby
end
def most_recent_test_drive_purchase
lesson_purchases.where(lesson_package_type_id: LessonPackageType.test_drive.id).order('created_at desc').first
lesson_purchases.where('lesson_package_type_id in (?)', [LessonPackageType.test_drive_package_ids]).order('created_at desc').first
end
def total_test_drives
purchase = most_recent_test_drive_purchase
if purchase
purchase.test_drive_count
else
0
end
end
def test_drive_succeeded(lesson_session)
@ -2031,16 +2086,26 @@ module JamRuby
end
end
def test_drive_declined(lesson_session)
# because we decrement test_drive credits as soon as you book, we need to bring it back now
self.remaining_test_drives = self.remaining_test_drives + 1
self.save(validate: false)
end
def test_drive_failed(lesson_session)
# because we decrement test_drive credits as soon as you book, we need to bring it back now
self.remaining_test_drives = self.remaining_test_drives + 1
self.save(validate: false)
UserMailer.test_drive_no_bill(self, lesson_session).deliver
UserMailer.student_test_drive_no_bill(lesson_session).deliver
end
def used_test_drives
4 - remaining_test_drives
total_test_drives - remaining_test_drives
end
def uncollectables(limit = 10)
LessonPaymentCharge.where(user_id:self.id).order(:created_at).where('billing_attempts > 0').where(billed: false).limit(limit)
end
def has_rated_teacher(teacher)

View File

@ -0,0 +1,16 @@
module JamRuby
class MinutelyJob
extend Resque::Plugins::JamLonelyJob
@queue = :scheduled_minutely_job
@@log = Logging.logger[MinutelyJob]
def self.perform
@@log.debug("waking up")
LessonSession.minutely_check
@@log.debug("done")
end
end
end

View File

@ -980,7 +980,7 @@ FactoryGirl.define do
price 30.00
factory :test_drive_purchase do
lesson_package_type { JamRuby::LessonPackageType.test_drive }
lesson_package_type { JamRuby::LessonPackageType.test_drive_4 }
association :lesson_booking, factory: :lesson_booking
price 49.99
end
@ -1010,6 +1010,13 @@ FactoryGirl.define do
factory :teacher_payment_charge, parent: :charge, class: 'JamRuby::TeacherPaymentCharge' do
type 'JamRuby::TeacherPaymentCharge'
association :user, factory: :user
end
factory :lesson_payment_charge, parent: :charge, class: 'JamRuby::LessonPaymentCharge' do
type 'JamRuby::LessonPaymentCharge'
association :user, factory: :user
end

View File

@ -38,11 +38,11 @@ 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})
booking = result[:lesson]
lesson = booking.lesson_sessions[0]
booking.card_presumed_ok.should be_true
booking.errors.any?.should be_false
lesson.errors.any?.should be_false
booking.card_presumed_ok.should be_true
booking = result[:lesson]
lesson = booking.lesson_sessions[0]
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
@ -116,7 +116,7 @@ describe "Monthly Recurring Lesson Flow" do
# puts del.inspect
end
# get acceptance emails, as well as 'your stuff is accepted'
UserMailer.deliveries.length.should eql 6
UserMailer.deliveries.length.should eql 2
lesson_session.errors.any?.should be_false
lesson_session.reload
lesson_session.slot.should eql student_counter
@ -126,9 +126,10 @@ describe "Monthly Recurring Lesson Flow" do
lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
booking.status.should eql LessonBooking::STATUS_APPROVED
UserMailer.deliveries.length.should eql 6
UserMailer.deliveries.length.should eql 2
chat = ChatMessage.unscoped.order(:created_at).last
chat.message.should eql "Lesson Approved: 'Yeah I got this'"
chat.message.should eql 'Yeah I got this'
chat.purpose.should eql 'Lesson Approved'
chat.channel.should eql ChatMessage::CHANNEL_LESSON
chat.user.should eql teacher_user
chat.target_user.should eql user
@ -185,7 +186,7 @@ describe "Monthly Recurring Lesson Flow" do
lesson_purchase.sale_line_item.should eql line_item
TeacherPayment.count.should eql 0
TeacherPayment.daily_check
TeacherPayment.hourly_check
teacher_distribution.reload
teacher_distribution.distributed.should be_true
TeacherPayment.count.should eql 1

View File

@ -12,9 +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(:school) {FactoryGirl.create(:school)}
describe "stripe mocked" do
before { StripeMock.start
before {
StripeMock.clear_errors
StripeMock.start
teacher.stripe_account_id = stripe_account1_id
teacher.save!
}
@ -35,7 +38,7 @@ describe "Normal 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})
booking = result[:lesson]
lesson = booking.lesson_sessions[0]
booking.errors.any?.should be_false
@ -71,9 +74,10 @@ describe "Normal Lesson Flow" do
lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
booking.status.should eql LessonBooking::STATUS_APPROVED
UserMailer.deliveries.length.should eql 4
UserMailer.deliveries.length.should eql 2
chat = ChatMessage.unscoped.order(:created_at).last
chat.message.should eql "Lesson Approved: 'Yeah I got this'"
chat.message.should eql 'Yeah I got this'
chat.purpose.should eql 'Lesson Approved'
chat.channel.should eql ChatMessage::CHANNEL_LESSON
chat.user.should eql teacher_user
chat.target_user.should eql user
@ -91,6 +95,7 @@ describe "Normal Lesson Flow" do
lesson_session.music_session.session_removed_at = end_time
lesson_session.music_session.save!
Timecop.travel(end_time + 1)
UserMailer.deliveries.clear
# background code comes around and analyses the session
@ -177,12 +182,12 @@ describe "Normal Lesson Flow" do
lesson_session.reload
payment = lesson_session.lesson_payment_charge
payment.amount_in_cents.should eql 3248
payment.fee_in_cents.should eql 0
lesson_session.billing_attempts.should eql 4
lesson_session.post_processed.should be_true
LessonPaymentCharge.count.should eql 2
LessonPaymentCharge.count.should eql 1
lesson_session.reload
lesson_session.analysed.should be_true
@ -266,7 +271,7 @@ describe "Normal 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})
booking = result[:lesson]
lesson = booking.lesson_sessions[0]
booking.errors.any?.should be_false
@ -351,9 +356,10 @@ describe "Normal Lesson Flow" do
lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
booking.status.should eql LessonBooking::STATUS_APPROVED
UserMailer.deliveries.length.should eql 4
UserMailer.deliveries.length.should eql 2
chat = ChatMessage.unscoped.order(:created_at).last
chat.message.should eql "Lesson Approved: 'Yeah I got this'"
chat.message.should eql 'Yeah I got this'
chat.purpose.should eql 'Lesson Approved'
chat.channel.should eql ChatMessage::CHANNEL_LESSON
chat.user.should eql teacher_user
chat.target_user.should eql user
@ -371,6 +377,7 @@ describe "Normal Lesson Flow" do
lesson_session.music_session.session_removed_at = end_time
lesson_session.music_session.save!
Timecop.travel(end_time + 1)
UserMailer.deliveries.clear
# background code comes around and analyses the session
@ -411,4 +418,167 @@ describe "Normal Lesson Flow" do
user.remaining_test_drives.should eql 0
UserMailer.deliveries.length.should eql 2 # one for student, one for teacher
end
it "works (school on school)" do
# get user and teacher into same school
user.school = school
user.save!
teacher.school = school
teacher.save!
# user has no test drives, no credit card on file, but attempts to book a lesson
booking = LessonBooking.book_normal(user, teacher_user, valid_single_slots, "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60)
booking.errors.any?.should be_false
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
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)
LessonPaymentCharge.count.should eql 0
user.reload
user.stripe_customer_id.should_not 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
booking.status.should eql LessonBooking::STATUS_REQUESTED
######### Teacher counters with new slot
teacher_countered_slot = FactoryGirl.build(:lesson_booking_slot_single, hour: 14)
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)
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})
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)
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 "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
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
lesson_session.billing_error_reason.should be_nil
lesson_session.sent_billing_notices.should be true
user.reload
user.remaining_test_drives.should eql 0
UserMailer.deliveries.length.should eql 2 # one for student, one for teacher
LessonPaymentCharge.count.should eql 0
TeacherDistribution.count.should eql 0
end
end

View File

@ -13,6 +13,9 @@ 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
Timecop.return
end
it "works" do
# user has no test drives, no credit card on file, but attempts to book a lesson
@ -27,7 +30,7 @@ describe "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})
booking = result[:lesson]
lesson = booking.lesson_sessions[0]
booking.errors.any?.should be_false
@ -107,7 +110,7 @@ describe "Recurring Lesson Flow" do
# puts del.inspect
end
# get acceptance emails, as well as 'your stuff is accepted'
UserMailer.deliveries.length.should eql 6
UserMailer.deliveries.length.should eql 2
lesson_session.errors.any?.should be_false
lesson_session.reload
lesson_session.slot.should eql student_counter
@ -117,9 +120,10 @@ describe "Recurring Lesson Flow" do
lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
booking.status.should eql LessonBooking::STATUS_APPROVED
UserMailer.deliveries.length.should eql 6
UserMailer.deliveries.length.should eql 2
chat = ChatMessage.unscoped.order(:created_at).last
chat.message.should eql "Lesson Approved: 'Yeah I got this'"
chat.message.should eql "Yeah I got this"
chat.purpose.should eql 'Lesson Approved'
chat.channel.should eql ChatMessage::CHANNEL_LESSON
chat.user.should eql teacher_user
chat.target_user.should eql user
@ -138,6 +142,7 @@ describe "Recurring Lesson Flow" do
lesson_session.music_session.session_removed_at = end_time
lesson_session.music_session.save!
Timecop.travel(end_time + 1)
UserMailer.deliveries.clear
# background code comes around and analyses the session

View File

@ -14,12 +14,16 @@ describe "TestDrive Lesson Flow" do
let(:valid_recurring_slots) { [lesson_booking_slot_recurring1, lesson_booking_slot_recurring2] }
before {
Timecop.return
teacher.stripe_account_id = stripe_account1_id
teacher.save!
}
it "works" do
user.desired_package = LessonPackageType.test_drive_2
user.save!
# user has no test drives, no credit card on file, but attempts to book a lesson
booking = LessonBooking.book_test_drive(user, teacher_user, valid_single_slots, "Hey I've heard of you before.")
booking.errors.any?.should be_false
@ -28,20 +32,24 @@ 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
user.reload
user.remaining_test_drives.should eql 0
########## Need validate their credit card
token = create_stripe_token
result = user.payment_update({token: token, zip: '78759', test_drive: true})
user.reload
user.remaining_test_drives.should eql 3
result = user.payment_update({token: token, zip: '78759', test_drive: true, booking_id: booking.id})
booking = result[:lesson]
lesson = booking.lesson_sessions[0]
test_drive = result[:test_drive]
booking.errors.any?.should be_false
lesson = booking.lesson_sessions[0]
lesson.errors.any?.should be_false
test_drive = result[:test_drive]
test_drive.errors.any?.should be_false
user.reload
user.remaining_test_drives.should eql 1
booking.card_presumed_ok.should be_true
booking.sent_notices.should be_true
lesson.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
@ -49,20 +57,20 @@ describe "TestDrive Lesson Flow" do
test_drive.stripe_charge_id.should_not be_nil
test_drive.recurly_tax_in_cents.should be 412
test_drive.recurly_total_in_cents.should eql 4999 + 412
test_drive.recurly_subtotal_in_cents.should eql 4999
test_drive.recurly_tax_in_cents.should be 247
test_drive.recurly_total_in_cents.should eql 2999 + 247
test_drive.recurly_subtotal_in_cents.should eql 2999
test_drive.recurly_currency.should eql 'USD'
line_item = test_drive.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.test_drive.id
line_item.product_id.should eq LessonPackageType.test_drive_2.id
user.reload
user.stripe_customer_id.should_not be nil
user.lesson_purchases.length.should eql 1
user.remaining_test_drives.should eql 3
user.remaining_test_drives.should eql 1
lesson_purchase = user.lesson_purchases[0]
lesson_purchase.price.should eql 49.99
lesson_purchase.price.should eql 29.99
lesson_purchase.lesson_package_type.is_test_drive?.should eql true
customer = Stripe::Customer.retrieve(user.stripe_customer_id)
@ -134,7 +142,7 @@ describe "TestDrive Lesson Flow" do
lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
booking.status.should eql LessonBooking::STATUS_APPROVED
UserMailer.deliveries.length.should eql 4
UserMailer.deliveries.length.should eql 2
chat = ChatMessage.unscoped.order(:created_at).last
chat.message.should eql 'Yeah I got this'
chat.channel.should eql ChatMessage::CHANNEL_LESSON
@ -145,7 +153,7 @@ describe "TestDrive Lesson Flow" do
notification.student_directed.should eql true
notification.purpose.should eql 'accept'
notification.description.should eql NotificationTypes::LESSON_MESSAGE
notification.message.should eql "Your lesson request is confirmed!"
notification.message.should be_nil
# teacher & student get into session
@ -156,6 +164,7 @@ describe "TestDrive Lesson Flow" do
lesson_session.music_session.session_removed_at = end_time
lesson_session.music_session.save!
Timecop.travel(end_time + 1)
UserMailer.deliveries.clear
@ -173,11 +182,11 @@ describe "TestDrive Lesson Flow" do
lesson_session.billing_error_reason.should be_nil
lesson_session.sent_notices.should be true
purchase = lesson_session.lesson_package_purchase
purchase.should_not be nil
purchase.price_in_cents.should eql 4999
purchase.should_not be_nil
purchase.price_in_cents.should eql 2999
purchase.lesson_package_type.is_test_drive?.should be true
user.reload
user.remaining_test_drives.should eql 3
user.remaining_test_drives.should eql 1
UserMailer.deliveries.length.should eql 2 # one for student, one for teacher
teacher_distribution = lesson_session.teacher_distribution
@ -194,7 +203,7 @@ describe "TestDrive Lesson Flow" do
teacher_distribution.distributed.should be_false
TeacherPayment.count.should eql 0
TeacherPayment.daily_check
TeacherPayment.hourly_check
TeacherPayment.count.should eql 1
lesson_session.reload

View File

@ -783,6 +783,22 @@ describe AffiliatePartner do
end
end
describe "edge case" do
it "year change" do
partner.touch
last_day_of_year = Date.new(2015, 12, 31)
first_day_of_next_year = Date.new(2016, 01, 01)
AffiliatePartner.tally_up(last_day_of_year)
AffiliatePartner.tally_up(first_day_of_next_year)
quarterly_payment = AffiliateQuarterlyPayment.where(year: 2016, quarter: 0, affiliate_partner_id: partner.id).first
quarterly_payment.closed.should be_false
AffiliatePartner.tally_up(Date.new(2016, 01, 02))
quarterly_payment.reload
quarterly_payment.closed.should be_false
end
end
describe "boundary_dates_for_month" do
it "invalid month" do
expect{AffiliatePartner.boundary_dates_for_month(2015, 0)}.to raise_error

View File

@ -0,0 +1,11 @@
require 'spec_helper'
describe Language do
it "english_sort" do
sorted= Language.english_sort
sorted[0].id.should eql 'EN'
sorted[1].id.should eql 'AF'
sorted[-1].id.should eql 'XH'
end
end

View File

@ -112,7 +112,7 @@ describe LessonBooking do
purchase.last_billing_attempt_at.should eql time
purchase.sent_billing_notices.should eql false
user.card_approved(create_stripe_token, '78759')
user.card_approved(create_stripe_token, '78759', booking.id)
user.save!
day = day + 1
@ -137,7 +137,7 @@ describe LessonBooking do
end
it "advances to next month" do
user.card_approved(create_stripe_token, '78759')
user.card_approved(create_stripe_token, '78759', nil)
user.save!
day = Date.new(2016, 1, 20)
@ -258,7 +258,7 @@ describe LessonBooking do
purchase.billed.should be false
# now that it's suspended, let's unsuspend it
user.card_approved(create_stripe_token, '78759')
user.card_approved(create_stripe_token, '78759', booking.id)
user.save!
day = day + 1
@ -386,38 +386,6 @@ describe LessonBooking do
end
describe "book_free" do
it "works" do
pending "free not supported"
booking = LessonBooking.book_free(user, teacher_user, valid_single_slots, "Hey I've heard of you before.")
booking.errors.any?.should be false
booking.user.should eq user
booking.teacher.should eq teacher_user
booking.description.should eq ("Hey I've heard of you before.")
booking.payment_style.should eq LessonBooking::PAYMENT_STYLE_ELSEWHERE
booking.recurring.should eq false
booking.lesson_length.should eq 30
booking.lesson_type.should eq LessonBooking::LESSON_TYPE_FREE
booking.lesson_booking_slots.length.should eq 2
chat_message = ChatMessage.where(lesson_booking_id: booking.id).first
chat_message.should_not be_nil
chat_message.message.should eq booking.description
user.reload
user.remaining_free_lessons.should eq 0
user.remaining_test_drives.should eq 1
booking.card_presumed_ok.should eq false
booking.sent_notices.should eq false
user.card_approved(create_stripe_token, '78759')
user.save!
booking.reload
booking.sent_notices.should eq true
booking.card_presumed_ok.should eq true
end
it "allows long message to flow through chat" do
@ -426,7 +394,7 @@ describe LessonBooking do
booking.errors.any?.should be false
chat_message = ChatMessage.where(lesson_booking_id: booking.id).first
chat_message = ChatMessage.where(lesson_session_id: booking.next_lesson.id).first
chat_message.should_not be_nil
chat_message.message.should eq booking.description
end
@ -446,16 +414,6 @@ describe LessonBooking do
ChatMessage.count.should eq 1
end
it "prevents user without stored credit card" do
pending "free not supported"
user.stored_credit_card = false
user.save!
booking = LessonBooking.book_free(user, teacher_user, valid_single_slots, "Hey I've heard of you before.")
booking.errors.any?.should be false
end
it "must have 2 lesson booking slots" do
booking = LessonBooking.book_test_drive(user, teacher_user, [], "Hey I've heard of you before.")
@ -488,7 +446,7 @@ describe LessonBooking do
booking.lesson_type.should eq LessonBooking::LESSON_TYPE_TEST_DRIVE
booking.lesson_booking_slots.length.should eq 2
chat_message = ChatMessage.where(lesson_booking_id: booking.id).first
chat_message = ChatMessage.where(lesson_session_id: booking.next_lesson.id).first
chat_message.should_not be_nil
chat_message.message.should eq booking.description
@ -502,7 +460,7 @@ describe LessonBooking do
booking.errors.any?.should be false
chat_message = ChatMessage.where(lesson_booking_id: booking.id).first
chat_message = ChatMessage.where(lesson_session_id: booking.next_lesson.id).first
chat_message.should_not be_nil
chat_message.message.should eq booking.description
end
@ -520,7 +478,7 @@ describe LessonBooking do
booking = LessonBooking.book_test_drive(user, teacher_user, valid_single_slots, "Hey I've heard of you before.")
booking.errors.any?.should be true
booking.errors[:user].should eq ["has a requested TestDrive with this teacher"]
booking.errors[:user].should eq ["have a requested TestDrive with this teacher"]
ChatMessage.count.should eq 1
end
@ -549,7 +507,7 @@ describe LessonBooking do
booking.lesson_type.should eq LessonBooking::LESSON_TYPE_PAID
booking.lesson_booking_slots.length.should eq 2
chat_message = ChatMessage.where(lesson_booking_id: booking.id).first
chat_message = ChatMessage.where(lesson_session_id: booking.next_lesson.id).first
chat_message.should_not be_nil
chat_message.message.should eq booking.description
@ -570,7 +528,7 @@ describe LessonBooking do
booking.lesson_type.should eq LessonBooking::LESSON_TYPE_PAID
booking.lesson_booking_slots.length.should eq 2
chat_message = ChatMessage.where(lesson_booking_id: booking.id).first
chat_message = ChatMessage.where(lesson_session_id: booking.next_lesson.id).first
chat_message.should_not be_nil
chat_message.message.should eq booking.description
@ -579,12 +537,12 @@ describe LessonBooking do
user.remaining_test_drives.should eq 1
end
it "allows long message to flow through chat" do
it "allows long message to flow through chat" do
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, Faker::Lorem.characters(10000), true, LessonBooking::PAYMENT_STYLE_WEEKLY, 60)
booking.errors.any?.should be false
chat_message = ChatMessage.where(lesson_booking_id: booking.id).first
chat_message = ChatMessage.where(lesson_session_id: booking.next_lesson.id).first
chat_message.should_not be_nil
chat_message.message.should eq booking.description
end
@ -709,6 +667,7 @@ describe LessonBooking do
Timecop.freeze(7.days.ago)
lesson_session.cancel({canceler: teacher_user, message: 'meh', slot: booking.default_slot.id, update_all: false})
lesson_session.errors.any?.should be_false
lesson_session.reload
lesson_session.status.should eql LessonSession::STATUS_CANCELED
lesson_session.reload
booking.reload
@ -732,8 +691,8 @@ describe LessonBooking do
Timecop.freeze(7.days.ago)
lesson_session.cancel({canceler: user, message: 'meh', slot: booking.default_slot.id, update_all: false})
lesson_session.errors.any?.should be_false
lesson_session.status.should eql LessonSession::STATUS_CANCELED
lesson_session.reload
lesson_session.status.should eql LessonSession::STATUS_CANCELED
booking.reload
booking.status.should eql LessonSession::STATUS_CANCELED
UserMailer.deliveries.length.should eql 2
@ -784,8 +743,8 @@ describe LessonBooking do
Timecop.freeze(7.days.ago)
lesson_session.cancel({canceler: user, message: 'meh', slot: booking.default_slot.id, update_all: true})
lesson_session.errors.any?.should be_false
lesson_session.status.should eql LessonSession::STATUS_CANCELED
lesson_session.reload
lesson_session.status.should eql LessonSession::STATUS_CANCELED
booking.reload
booking.status.should eql LessonSession::STATUS_CANCELED
booking.canceler.should eql user

View File

@ -13,6 +13,32 @@ describe LessonSessionAnalyser do
let(:valid_recurring_slots) { [lesson_booking_slot_recurring1, lesson_booking_slot_recurring2] }
describe "analyse" do
after{
Timecop.return
}
it "neither show" do
lesson = testdrive_lesson(user, teacher)
music_session = lesson.music_session
Timecop.freeze((lesson.music_session.scheduled_start + lesson.duration * 60) + 1)
analysis = LessonSessionAnalyser.analyse(lesson)
analysis[:reason].should eql LessonSessionAnalyser::TEACHER_FAULT
analysis[:student].should eql nil
analysis[:bill].should be false
student = analysis[:student_analysis]
student[:joined_on_time].should be false
student[:joined_late].should be false
student[:waited_correctly].should be false
student[:initial_waiting_pct].should eql nil
student[:potential_waiting_time].should eql nil
together = analysis[:together_analysis]
together[:session_time].should eql 0
end
it "teacher joined on time, waited, student no show" do
lesson = testdrive_lesson(user, teacher)
music_session = lesson.music_session
@ -21,8 +47,7 @@ describe LessonSessionAnalyser do
start = lesson.scheduled_start
end_time = lesson.scheduled_start + (60 * lesson.duration)
uh2 = FactoryGirl.create(:music_session_user_history, user: teacher, history: music_session, created_at: start, session_removed_at: end_time)
lesson.music_session.session_removed_at = end_time
lesson.music_session.save!
Timecop.travel(end_time + 1)
analysis = LessonSessionAnalyser.analyse(lesson)
@ -41,6 +66,36 @@ describe LessonSessionAnalyser do
together[:session_time].should eql 0
end
it "student joined 1 min before start time, is waiting for 12 minutes" do
lesson = testdrive_lesson(user, teacher)
music_session = lesson.music_session
# create some bogus, super-perfect teacher/student times
start = lesson.scheduled_start
end_time = lesson.scheduled_start + (60 * lesson.duration)
uh2 = FactoryGirl.create(:music_session_user_history, user: user, history: music_session, created_at: start - 60, session_removed_at: nil)
Timecop.freeze(start + 11 * 60)
analysis = LessonSessionAnalyser.analyse(lesson, true)
analysis[:reason].should eql LessonSessionAnalyser::TEACHER_FAULT
analysis[:teacher].should eql LessonSessionAnalyser::NO_SHOW
analysis[:student].should be_nil
analysis[:bill].should be false
student = analysis[:student_analysis]
student[:joined_on_time].should be true
student[:joined_late].should be false
student[:waited_correctly].should be true
student[:initial_waiting_pct].should eql 1.0
student[:potential_waiting_time].should eql 600.0
student[:session_time].should eql (11 * 60).to_f
together = analysis[:together_analysis]
together[:session_time].should eql 0
end
it "teacher joined on time, waited, student joined late" do
lesson = testdrive_lesson(user, teacher)
music_session = lesson.music_session
@ -52,12 +107,10 @@ describe LessonSessionAnalyser do
uh1 = FactoryGirl.create(:music_session_user_history, user: user, history: music_session, created_at: late_start, session_removed_at: late_start + 4 * 60)
uh2 = FactoryGirl.create(:music_session_user_history, user: teacher, history: music_session, created_at: start, session_removed_at: end_time)
lesson.music_session.session_removed_at = end_time
lesson.music_session.save!
Timecop.travel(end_time + 1)
analysis = LessonSessionAnalyser.analyse(lesson)
puts analysis
analysis[:reason].should eql LessonSessionAnalyser::STUDENT_FAULT
analysis[:student].should eql LessonSessionAnalyser::JOINED_LATE
analysis[:bill].should be true
@ -92,8 +145,7 @@ describe LessonSessionAnalyser do
analysis = LessonSessionAnalyser.analyse(lesson)
analysis[:reason].should eql LessonSessionAnalyser::SESSION_ONGOING
lesson.music_session.session_removed_at = end_time
lesson.music_session.save!
Timecop.travel(end_time + 1)
analysis = LessonSessionAnalyser.analyse(lesson)
analysis[:reason].should eql LessonSessionAnalyser::SUCCESS

View File

@ -2,12 +2,12 @@ require 'spec_helper'
describe LessonSession do
let(:user) {FactoryGirl.create(:user, stored_credit_card: false, remaining_free_lessons: 1, remaining_test_drives: 1)}
let(:user) {FactoryGirl.create(:user, stored_credit_card: true, remaining_free_lessons: 1, remaining_test_drives: 1)}
let(:teacher) {FactoryGirl.create(:teacher_user)}
let(:slot1) { FactoryGirl.build(:lesson_booking_slot_single) }
let(:slot2) { FactoryGirl.build(:lesson_booking_slot_single) }
let(:lesson_booking) {LessonBooking.book_normal(user, teacher, [slot1, slot2], "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60)}
let(:lesson_booking) {b = LessonBooking.book_normal(user, teacher, [slot1, slot2], "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60); b.card_presumed_ok = true; b.save!; b}
let(:lesson_session) {lesson_booking.lesson_sessions[0]}
describe "accept" do
@ -16,10 +16,28 @@ describe LessonSession do
end
end
describe "upcoming_sessions_reminder" do
it "succeeds" do
UserMailer.deliveries.clear
LessonSession.upcoming_sessions_reminder
lesson_session.touch
lesson_session.sent_starting_notice.should be_false
lesson_session.is_requested?.should be_true
lesson_session.music_session.scheduled_start = 15.minutes.from_now
lesson_session.music_session.save!
LessonSession.upcoming_sessions_reminder
UserMailer.deliveries.count.should eql 2
UserMailer.deliveries.clear
lesson_session.reload
lesson_session.sent_starting_notice.should be_true
LessonSession.upcoming_sessions_reminder
UserMailer.deliveries.count.should eql 0
end
end
describe "index" do
it "finds single lesson as student" do
lesson_booking.touch
lesson_session.touch
lesson_session.music_session.creator.should eql lesson_session.lesson_booking.user
lesson_session.lesson_booking.teacher.should eql teacher

View File

@ -362,6 +362,31 @@ describe MusicSession do
end
describe "scheduled" do
it "includes any RSVP'ed" do
rsvp_request = FactoryGirl.create(:rsvp_request_for_multiple_slots, user: some_user, music_session: music_session1, number: 2, chosen:true)
approved_rsvps = music_session1.approved_rsvps
approved_rsvps.length.should == 2
sessions = MusicSession.scheduled(approved_rsvps[0])
sessions.length.should == 1
sessions = MusicSession.scheduled(approved_rsvps[1])
sessions.length.should == 1
end
it "includes invited" do
invitee = FactoryGirl.create(:user, last_jam_audio_latency: 30, last_jam_locidispid: 3)
FactoryGirl.create(:friendship, user: creator, friend: invitee)
FactoryGirl.create(:friendship, user: invitee, friend: creator)
music_session = FactoryGirl.create(:music_session, creator: creator)
FactoryGirl.create(:invitation, receiver:invitee, sender:creator, music_session: music_session)
sessions = MusicSession.scheduled(invitee)
sessions.length.should == 1
end
it "excludes based on time-range" do
session = FactoryGirl.create(:music_session, scheduled_start: Time.now)

View File

@ -9,6 +9,9 @@ describe Sale do
let(:jam_track3) { FactoryGirl.create(:jam_track) }
let(:gift_card) { GiftCardType.jam_track_5 }
before(:each) {
Timecop.return
}
def assert_free_line_item(sale_line_item, jamtrack)
sale_line_item.recurly_tax_in_cents.should be_nil
sale_line_item.recurly_total_in_cents.should be_nil
@ -596,6 +599,8 @@ describe Sale do
lesson_session.music_session.session_removed_at = end_time
lesson_session.music_session.save!
Timecop.travel(end_time + 1)
# bill the user
LessonSession.hourly_check
@ -649,6 +654,8 @@ describe Sale do
lesson_session.music_session.session_removed_at = end_time
lesson_session.music_session.save!
Timecop.travel(end_time + 1)
# bill the user
LessonSession.hourly_check
@ -715,7 +722,7 @@ describe Sale do
booking.should eql user.unprocessed_test_drive
token = create_stripe_token
result = user.payment_update({token: token, zip: '72205', test_drive: true})
result = user.payment_update({token: token, zip: '72205', test_drive: true, booking_id: booking.id})
booking.reload
booking.card_presumed_ok.should be_true
@ -731,7 +738,7 @@ describe Sale do
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.test_drive.id
line_item.product_id.should eq LessonPackageType.test_drive_4.id
user.reload
user.stripe_customer_id.should_not be nil
@ -762,7 +769,7 @@ describe Sale do
user.remaining_test_drives.should eql 0
token = create_stripe_token
result = user.payment_update({token: token, zip: '78759', test_drive: true})
result = user.payment_update({token: token, zip: '78759', test_drive: true, booking_id: booking.id})
booking.reload
booking.card_presumed_ok.should be_true
@ -778,7 +785,7 @@ describe Sale do
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.test_drive.id
line_item.product_id.should eq LessonPackageType.test_drive_4.id
user.reload
@ -819,7 +826,7 @@ describe Sale do
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.test_drive.id
line_item.product_id.should eq LessonPackageType.test_drive_4.id
end
it "will reject second test drive purchase" do

View File

@ -0,0 +1,23 @@
require 'spec_helper'
describe TeacherDistribution do
let(:teacher) {FactoryGirl.create(:teacher_user)}
describe "index" do
it "empty" do
TeacherDistribution.index(teacher, {})[:query].count.should eql 0
end
it "returns single" do
distribution = FactoryGirl.create(:teacher_distribution, teacher: teacher)
TeacherDistribution.index(teacher, {})[:query].count.should eql 1
distribution = FactoryGirl.create(:teacher_distribution) # some random teacher
TeacherDistribution.index(teacher, {})[:query].count.should eql 1
end
end
end

View File

@ -6,14 +6,17 @@ describe TeacherPayment do
let(:user2) { FactoryGirl.create(:user) }
let(:teacher_obj) {FactoryGirl.create(:teacher, stripe_account_id: stripe_account1_id)}
let(:teacher_obj2) {FactoryGirl.create(:teacher, stripe_account_id: stripe_account2_id)}
let(:school_owner_teacher) {FactoryGirl.create(:teacher, stripe_account_id: stripe_account2_id)}
let(:teacher) { FactoryGirl.create(:user, teacher: teacher_obj) }
let(:teacher2) { FactoryGirl.create(:user, teacher: teacher_obj2) }
let(:school_teacher) { FactoryGirl.create(:user, teacher: school_owner_teacher)}
let(:test_drive_lesson) {testdrive_lesson(user, teacher)}
let(:test_drive_lesson2) {testdrive_lesson(user2, teacher2)}
let(:test_drive_distribution) {FactoryGirl.create(:teacher_distribution, lesson_session: test_drive_lesson, teacher: teacher, teacher_payment: nil, ready:false)}
let(:test_drive_distribution2) {FactoryGirl.create(:teacher_distribution, lesson_session: test_drive_lesson2, teacher: teacher2, teacher_payment: nil, ready:false)}
let(:normal_lesson_session) {normal_lesson(user, teacher)}
let(:normal_distribution) {FactoryGirl.create(:teacher_distribution, lesson_session: normal_lesson_session, teacher: teacher, teacher_payment: nil, ready:false)}
let(:school) {FactoryGirl.create(:school, user: school_teacher)}
describe "pending_teacher_payments" do
@ -35,6 +38,21 @@ describe TeacherPayment do
payments[0]['id'].should eql teacher.id
end
it "school distribution" do
test_drive_distribution.school = school
test_drive_distribution.save!
payments = TeacherPayment.pending_teacher_payments
payments.count.should eql 0
test_drive_distribution.ready = true
test_drive_distribution.save!
payments = TeacherPayment.pending_teacher_payments
payments.count.should eql 1
payments[0]['id'].should eql teacher.id
end
it "multiple teachers" do
test_drive_distribution.touch
test_drive_distribution2.touch
@ -95,6 +113,7 @@ describe TeacherPayment do
normal_distribution.ready = true
normal_distribution.save!
UserMailer.deliveries.clear
TeacherPayment.teacher_payments
@ -108,9 +127,13 @@ describe TeacherPayment do
puts payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_detail
end
# only one confirm email to teacher
UserMailer.deliveries.length.should eql 1
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.fee_in_cents.should eql 280
payment.teacher_payment_charge.teacher.should eql teacher
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
@ -118,6 +141,39 @@ describe TeacherPayment do
charge.application_fee.should include("fee_")
end
it "charges school" do
normal_distribution.school = school
normal_distribution.ready = true
normal_distribution.save!
UserMailer.deliveries.clear
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
normal_distribution.teacher_payment.school.should eql school
TeacherPayment.count.should eql 1
payment = normal_distribution.teacher_payment
if payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_detail
end
# one to school owner, one to teacher
UserMailer.deliveries.length.should eql 2
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.fee_in_cents.should eql 280
payment.teacher_payment_charge.user.should eql school.owner
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.destination.should eql school.owner.teacher.stripe_account_id
charge.amount.should eql 1000
charge.application_fee.should include("fee_")
end
it "charges multiple" do
test_drive_distribution.touch
test_drive_distribution.ready = true
@ -240,6 +296,76 @@ describe TeacherPayment do
charge.amount.should eql 1000
end
it "failed payment, then success (school)" do
StripeMock.prepare_card_error(:card_declined)
normal_distribution.school = school
normal_distribution.ready = true
normal_distribution.save!
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
payment = normal_distribution.teacher_payment
payment.teacher_payment_charge.billing_error_reason.should eql("card_declined")
payment.teacher_payment_charge.billing_error_detail.should include("declined")
payment.teacher_payment_charge.billed.should eql false
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
payment.teacher_payment_charge.stripe_charge_id.should be_nil
StripeMock.clear_errors
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
# make sure the teacher_payment is reused, and charge is reused
normal_distribution.teacher_payment.should eql(payment)
normal_distribution.teacher_payment.teacher_payment_charge.should eql(payment.teacher_payment_charge)
# no attempt should be made because a day hasn't gone by
payment = normal_distribution.teacher_payment
payment.teacher_payment_charge.billed.should eql false
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
# advance one day so that a charge is attempted again
Timecop.freeze(Date.today + 2)
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
# make sure the teacher_payment is reused, and charge is reused
normal_distribution.teacher_payment.should eql(payment)
normal_distribution.teacher_payment.teacher_payment_charge.should eql(payment.teacher_payment_charge)
# no attempt should be made because a day hasn't gone by
payment = normal_distribution.teacher_payment
payment.reload
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.amount.should eql 1000
end
it "charges multiple (with initial failure)" do
StripeMock.prepare_card_error(:card_declined)

View File

@ -842,6 +842,45 @@ describe User do
end
end
describe "uncollectables" do
let(:user) {FactoryGirl.create(:user)}
let(:teacher) {FactoryGirl.create(:teacher_user)}
it "empty" do
user.uncollectables.count.should eql 0
end
it "one" do
lesson_session = normal_lesson(user, teacher)
lesson_session.lesson_payment_charge.user.should eql user
lesson_session.lesson_payment_charge.billing_attempts = 1
lesson_session.lesson_payment_charge.save!
uncollectables = user.uncollectables
uncollectables.count.should eql 1
uncollectable = uncollectables[0]
uncollectable.description.should_not be_nil
uncollectable.expected_price_in_cents.should eql 3000
uncollectable.is_card_declined?.should be_false
end
it "for monthly" do
lesson_session = monthly_lesson(user, teacher)
lesson_session.booked_price.should eql 30.00
LessonBooking.hourly_check
lesson_session.lesson_payment_charge.should be_nil
purchases=LessonPackagePurchase.where(user_id: user.id)
purchases.count.should eql 1
purchases[0].lesson_payment_charge.billing_attempts = 1
purchases[0].lesson_payment_charge.save!
uncollectables = user.uncollectables
uncollectables.count.should eql 1
uncollectable = uncollectables[0]
uncollectable.description.should_not be_nil
uncollectable.expected_price_in_cents.should eql 3000
uncollectable.is_card_declined?.should be_false
end
end
=begin
describe "update avatar" do

View File

@ -27,6 +27,7 @@ describe "RenderMailers", :slow => true do
it { @filename="welcome_message"; UserMailer.welcome_message(user).deliver }
it { @filename="student_welcome_message"; UserMailer.student_welcome_message(user).deliver }
it { @filename="school_owner_welcome_message"; UserMailer.school_owner_welcome_message(user).deliver }
it { @filename="confirm_email"; UserMailer.confirm_email(user, "/signup").deliver }
it { @filename="password_reset"; UserMailer.password_reset(user, '/reset_password').deliver }
it { @filename="password_changed"; UserMailer.password_changed(user).deliver }
@ -49,6 +50,7 @@ describe "RenderMailers", :slow => true do
@filename = "teacher_welcome_message"
UserMailer.teacher_welcome_message(teacher).deliver
end
it "teacher_lesson_request" do
@filename = "teacher_lesson_request"
@ -120,6 +122,22 @@ describe "RenderMailers", :slow => true do
UserMailer.deliveries.clear
UserMailer.teacher_lesson_completed(lesson).deliver
end
it "lesson_starting_soon_teacher" do
@filename = "lesson_starting_soon_teacher"
lesson = testdrive_lesson(user, teacher)
UserMailer.deliveries.clear
UserMailer.lesson_starting_soon_teacher(lesson).deliver
end
it "lesson_starting_soon_student" do
@filename = "lesson_starting_soon_student"
lesson = testdrive_lesson(user, teacher)
UserMailer.deliveries.clear
UserMailer.lesson_starting_soon_student(lesson).deliver
end
end
end

View File

@ -33,7 +33,7 @@ def testdrive_lesson(user, teacher, slots = nil)
booking.card_presumed_ok.should be_true
if user.most_recent_test_drive_purchase.nil?
LessonPackagePurchase.create(user, booking, LessonPackageType.test_drive)
LessonPackagePurchase.create(user, booking, LessonPackageType.test_drive_4)
end
lesson.accept({message: 'Yeah I got this', slot: slots[0]})
@ -66,7 +66,7 @@ def normal_lesson(user, teacher, slots = nil)
booking.card_presumed_ok.should be_true
#if user.most_recent_test_drive_purchase.nil?
# LessonPackagePurchase.create(user, booking, LessonPackageType.test_drive)
# LessonPackagePurchase.create(user, booking, LessonPackageType.test_drive_4)
#end
lesson.accept({message: 'Yeah I got this', slot: slots[0]})
@ -74,6 +74,41 @@ def normal_lesson(user, teacher, slots = nil)
lesson.reload
lesson.slot.should eql slots[0]
lesson.status.should eql LessonSession::STATUS_APPROVED
lesson.music_session.should_not be_nil
lesson
end
def monthly_lesson(user, teacher, slots = nil)
if slots.nil?
slots = []
slots << FactoryGirl.build(:lesson_booking_slot_recurring)
slots << FactoryGirl.build(:lesson_booking_slot_recurring)
end
if user.stored_credit_card == false
user.stored_credit_card = true
user.save!
end
booking = LessonBooking.book_normal(user, teacher, slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60)
# puts "NORMAL BOOKING #{booking.errors.inspect}"
booking.errors.any?.should be_false
lesson = booking.lesson_sessions[0]
booking.card_presumed_ok.should be_true
#if user.most_recent_test_drive_purchase.nil?
# LessonPackagePurchase.create(user, booking, LessonPackageType.test_drive_4)
#end
lesson.accept({message: 'Yeah I got this', slot: slots[0]})
lesson.errors.any?.should be_false
lesson.reload
lesson.slot.should eql slots[0]
lesson.status.should eql LessonSession::STATUS_APPROVED
lesson.music_session.should_not be_nil
lesson
end

View File

@ -7,6 +7,10 @@ def app_config
'http://localhost:3333'
end
def email_partners_alias
'partners@jamkazam.com'
end
def email_social_alias
'social@jamkazam.com'
end

View File

@ -19,6 +19,7 @@ else
end
#gem 'license_finder'
gem 'pg_migrate', '0.1.14'
gem 'kickbox'
gem 'oj', '2.10.2'
gem 'builder'
@ -89,7 +90,7 @@ gem 'htmlentities'
gem 'sanitize'
gem 'recurly'
#gem 'guard', '2.7.3'
gem 'influxdb' #, '0.1.8'
#gem 'influxdb' #, '0.1.8'
gem 'cause' # needed by influxdb
gem 'influxdb-rails'# , '0.1.10'
gem 'sitemap_generator'
@ -142,6 +143,7 @@ group :test, :cucumber do
gem 'simplecov', '~> 0.7.1'
gem 'simplecov-rcov'
gem 'capybara', '2.4.4'
gem 'rails-assets-sinon', source: 'https://rails-assets.org'
#if ENV['JAMWEB_QT5'] == '1'
# # necessary on platforms such as arch linux, where pacman -S qt5-webkit is your easiet option
# gem "capybara-webkit", :git => 'git://github.com/thoughtbot/capybara-webkit.git'
@ -159,6 +161,7 @@ group :test, :cucumber do
# gem 'growl', '1.0.3'
gem 'poltergeist'
gem 'resque_spec'
gem 'timecop'
#gem 'thin'
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Some files were not shown because too many files have changed in this diff Show More