lots of progress on amazon-readiness

This commit is contained in:
Seth Call 2017-07-09 21:21:29 -05:00
parent cd9525ac2b
commit 7ff7327ef8
79 changed files with 1586 additions and 353 deletions

View File

@ -59,7 +59,7 @@ gem 'resque'
gem 'resque-retry'
gem 'resque-failed-job-mailer'
gem 'resque-lonely_job', '~> 1.0.0'
gem 'eventmachine', '1.0.4'
gem 'eventmachine', '1.2.3'
gem 'amqp', '0.9.8'
#gem 'logging-rails', :require => 'logging/rails'
gem 'pg_migrate'
@ -82,9 +82,9 @@ gem 'stripe'
gem 'zip-codes'
gem 'email_validator'
group :libv8 do
gem 'libv8', "~> 4.5.95"
end
#group :libv8 do
# gem 'libv8', "~> 4.5.95"
#end
# To use Jbuilder templates for JSON

View File

@ -0,0 +1,40 @@
ActiveAdmin.register JamRuby::User, :as => 'InactiveJamClassUsers' do
menu :label => 'Inactive JamClass Users w/o Credits', :parent => 'JamClass'
config.sort_order = 'created_at'
config.batch_actions = false
config.per_page = 100
config.paginate = true
config.filters = false
scope("All", default: true) { |scope| scope.includes(:taken_lessons => :music_session).select("distinct(users.id), users.email, users.first_name, users.last_name").joins("inner join posa_cards on posa_cards.user_id = users.id inner join lesson_sessions on lesson_sessions.user_id = users.id left outer join music_sessions on music_sessions.lesson_session_id = music_sessions.id ").where("jamclass_credits = 0") }
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
column "POSA" do |user|
span do
posa = user.posa_cards[0]
if posa.lesson_package_type
posa.lesson_package_type.id
else
posa.card_type
end
end
end
column "Last Session" do |user|
span do
if user.taken_lessons.length == 0
"none yet"
else
most_recent_lesson = user.taken_lessons.order('created_at desc')[0]
link_to most_recent_lesson.scheduled_start, admin_lesson_session_path(most_recent_lesson)
end
end
end
end
end

View File

@ -0,0 +1,45 @@
ActiveAdmin.register JamRuby::User, :as => 'InactiveJamClassPOSAUsers' do
menu :label => 'Inactive JamClass Users w/ Credits', :parent => 'JamClass'
config.sort_order = 'created_at'
config.batch_actions = false
config.per_page = 100
config.paginate = true
config.filters = false
scope("All", default: true) { |scope| scope.includes(:taken_lessons => :music_session).select("distinct(users.id), users.email, users.first_name, users.last_name, users.jamclass_credits").joins("inner join posa_cards on posa_cards.user_id = users.id left outer join lesson_sessions on lesson_sessions.user_id = users.id left outer join music_sessions on music_sessions.lesson_session_id = music_sessions.id ").where("jamclass_credits > 0 AND music_sessions.id IS NULL OR music_sessions.scheduled_start < ?", Time.now - 7.days) }
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
column "POSA" do |user|
span do
posa = user.posa_cards[0]
if posa.lesson_package_type
posa.lesson_package_type.id
else
posa.card_type
end
end
end
column "Credits" do |user|
span do
user.jamclass_credits
end
end
column "Last Session" do |user|
span do
if user.taken_lessons.length == 0
"none yet"
else
most_recent_lesson = user.taken_lessons.order('created_at desc')[0]
link_to most_recent_lesson.scheduled_start, admin_lesson_session_path(most_recent_lesson)
end
end
end
end
end

View File

@ -17,10 +17,16 @@ ActiveAdmin.register JamRuby::LessonSession, :as => 'LessonSessions' do
scope("Completed") { |scope| scope.unscoped.completed.order('created_at desc') }
index do
column "User Link" do |lesson_session|
column "Actions" do |teacher|
links = ''.html_safe
links << link_to("View", resource_path(teacher), :class => "member_link view_link")
links << link_to("Edit", edit_resource_path(teacher), :class => "member_link edit_link")
links
end
column "App Link" do |lesson_session|
lesson_booking = lesson_session.lesson_booking
span do
link_to "Web URL", "#{Rails.application.config.external_root_url}/client#/jamclass/lesson-booking/#{lesson_booking.id}"
link_to "link", "#{Rails.application.config.external_root_url}/client#/jamclass/lesson-booking/#{lesson_booking.id}"
end
end
column "Status" do |lesson_session|
@ -58,10 +64,10 @@ ActiveAdmin.register JamRuby::LessonSession, :as => 'LessonSessions' do
show do
attributes_table do
row "User Link" do |lesson_session|
row "App Link" do |lesson_session|
lesson_booking = lesson_session.lesson_booking
span do
link_to "Web URL", "#{Rails.application.config.external_root_url}/client#/jamclass/lesson-booking/#{lesson_booking.id}"
link_to "link", "#{Rails.application.config.external_root_url}/client#/jamclass/lesson-booking/#{lesson_booking.id}"
end
end
row "Status" do |lesson_session|
@ -82,13 +88,13 @@ ActiveAdmin.register JamRuby::LessonSession, :as => 'LessonSessions' do
row "Teacher" do |lesson_session|
teacher = lesson_session.teacher
span do
link_to "#{teacher.name} (#{teacher.email})", "#{Rails.application.config.external_root_url}/client#/profile/teacher/#{teacher.id}"
link_to teacher.admin_name, "#{Rails.application.config.external_root_url}/client#/profile/teacher/#{teacher.id}"
end
end
row "Student" do |lesson_session|
student = lesson_session.student
span do
link_to "#{student.name} (#{student.email})", "#{Rails.application.config.external_root_url}/client#/profile/#{student.id}"
link_to student.admin_name, "#{Rails.application.config.external_root_url}/client#/profile/#{student.id}"
end
end
row "Followup Emails Sent" do |lesson_session|
@ -111,6 +117,11 @@ ActiveAdmin.register JamRuby::LessonSession, :as => 'LessonSessions' do
lesson_session.timed_description
end
end
row "Session" do |lesson_session|
span do
link_to "Session", lesson_session.music_session.admin_url
end
end
row "Analysis" do |lesson_session|
if lesson_session.analysed
span style: "white-space: pre;" do

View File

@ -60,4 +60,51 @@ ActiveAdmin.register JamRuby::MusicSession, :as => 'Music Session' do
end
end
show do
attributes_table do
row :id
row :name
row :description
row :creator do |session|
link_to(session.creator.admin_name, session.creator.admin_url)
end
row :created_at
row :started_at
row :session_ended_at do |session| session.session_removed_at end
row :genre
row :recurring_mode
row :timezone
row :fan_access
row :music_access
row :approval_required
row :open_rsvps
row :is_unstructured_rsv
row :canceled
row :lesson_session do |session|
lesson_session = session.lesson_session
if lesson_session
link_to("Lesson", lesson_session.admin_url)
else
''
end
end
row 'Session Attendances' do |session|
table_for(msuh = session.music_session_user_histories) do
column :user do |msuh| msuh.user.admin_name end
column :joined do |msuh| msuh.created_at.strftime('%b %d %Y, %H:%M') end
column :duration do |msuh| "#{msuh.duration_minutes.round} minutes" end
column :perf_data do |msuh|
unless (uu = msuh.perf_uri).blank?
link_to('Per Data Link', uu)
else
'No Perf Data'
end
end
end
end
end
end
end

View File

@ -10,15 +10,31 @@ ActiveAdmin.register_page "POSA Card Uploads" do
file = params[:jam_ruby_posa_card][:csv]
array_of_arrays = CSV.read(file.tempfile.path)
array_of_arrays.each do |row|
if row.length != 1
raise "UKNONWN CSV FORMAT! Must be 1 column"
if row.length != 4
raise "UKNONWN CSV FORMAT! Must be 4 columns"
end
code = row[0]
lesson_package_type = row[1]
preactivate = row[2].strip == "true"
requires_purchase = row[3].strip == "true"
posa_card = PosaCard.new
posa_card.code = code
posa_card.lesson_package_type = LessonPackageType.find(lesson_package_type)
posa_card.preactivate = preactivate
posa_card.requires_purchase = requires_purchase
posa_card.purchased = !requires_purchase
posa_card.card_type = params[:jam_ruby_posa_card][:card_type]
if posa_card.card_type == PosaCard::JAM_CLASS_4
posa_card.is_lesson = true
posa_card.credits = 4
elsif posa_card.card_type == PosaCard::JAM_CLASS_2
posa_card.is_lesson = true
posa_card.credits = 2
end
posa_card.origin = file .original_filename
posa_card.save!
end

View File

@ -45,7 +45,7 @@ ActiveAdmin.register JamRuby::Teacher, :as => 'Teachers' do
end
end
=begin
column "Background Check" do |teacher|
div do
if teacher.background_check_at
@ -56,25 +56,25 @@ ActiveAdmin.register JamRuby::Teacher, :as => 'Teachers' do
br
end
span do
link_to(mark_background_check_admin_teacher_path(teacher.id), {confirm: "Mark as background checked?"}) do
"mark as checked"
link_to(edit_admin_teacher_background_check_path(teacher.id)) do
"update background check"
end
end
else
span do
''
'NO'
end
span do
br
end
span do
link_to("mark as checked", mark_background_check_admin_teacher_path(teacher.id), {confirm: "Mark as background checked?"})
link_to("update background check", edit_admin_teacher_background_check_path(teacher.id))
end
end
end
end
=end
column "Session Ready" do |teacher|
div do
if teacher.ready_for_session_at

View File

@ -0,0 +1,31 @@
ActiveAdmin.register JamRuby::Teacher, :as => 'TeacherBackgroundCheck' do
config.filters = false
menu :label => 'Teacher Background Check', :parent => 'JamClass'
form do |f|
f.inputs 'Set Background Check' do
f.input :background_check_at, as: :date_select
end
f.actions
end
index do
column "Actions" do |teacher|
links = ''.html_safe
links << link_to("View", resource_path(teacher), :class => "member_link view_link")
links << link_to("Edit", edit_resource_path(teacher), :class => "member_link edit_link")
links
end
column 'User' do |oo|
oo.user.email
end
end
end

View File

@ -87,7 +87,7 @@ module JamAdmin
config.recurly_root_url = 'https://jamkazam-development.recurly.com'
# where is rabbitmq?
config.rabbitmq_host = "localhost"
config.rabbitmq_host = "127.0.0.1"
config.rabbitmq_port = 5672
# set to false to instead use amazon. You will also need to supply amazon secrets

View File

@ -1,5 +1,5 @@
class JamRuby::Teacher
attr_accessible :short_bio, as: :admin
attr_accessible :short_bio, :background_check_at, as: :admin
end

View File

@ -1,26 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>The page you were looking for doesn't exist (404)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
padding: 0 4em;
margin: 4em auto 0 auto;
border: 1px solid #ccc;
border-right-color: #999;
border-bottom-color: #999;
}
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
</style>
</head>
<body>
<!-- This file lives in public/404.html -->
<div class="dialog">
<h1>The page you were looking for doesn't exist.</h1>
<p>You may have mistyped the address or the page may have moved.</p>
</div>
</body>
</html>

View File

@ -1,26 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>The change you wanted was rejected (422)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
padding: 0 4em;
margin: 4em auto 0 auto;
border: 1px solid #ccc;
border-right-color: #999;
border-bottom-color: #999;
}
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
</style>
</head>
<body>
<!-- This file lives in public/422.html -->
<div class="dialog">
<h1>The change you wanted was rejected.</h1>
<p>Maybe you tried to change something you didn't have access to.</p>
</div>
</body>
</html>

View File

@ -1,25 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>We're sorry, but something went wrong (500)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
padding: 0 4em;
margin: 4em auto 0 auto;
border: 1px solid #ccc;
border-right-color: #999;
border-bottom-color: #999;
}
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
</style>
</head>
<body>
<!-- This file lives in public/500.html -->
<div class="dialog">
<h1>We're sorry, but something went wrong.</h1>
</div>
</body>
</html>

View File

@ -1,5 +0,0 @@
# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
# User-Agent: *
# Disallow: /

View File

@ -376,3 +376,4 @@ jam_track_download_rights.sql
guitar_center_integration_v1.sql
mobile_recording_support.sql
youtube_broadcast.sql
amazon_v1.sql

38
db/up/amazon_v1.sql Normal file
View File

@ -0,0 +1,38 @@
-- create new lesson package types
ALTER TABLE lesson_package_types ADD COLUMN is_test_drive BOOLEAN NOT NULL DEFAULT TRUE;
UPDATE lesson_package_types SET is_test_drive = FALSE WHERE id in('single', 'single-free');
INSERT INTO lesson_package_types (id, name, description, package_type, price, is_test_drive) VALUES ('amazon-test-drive-free-4', 'Test Drive (4)', 'Four free lessons which you can use to find that ideal teacher.', 'test-drive-4', 0.0, TRUE);
INSERT INTO lesson_package_types (id, name, description, package_type, price, is_test_drive) VALUES ('amazon-test-drive-free-2', 'Test Drive (2)', 'Two free lessons which you can use to find that ideal teacher.', 'test-drive-2', 0.0, TRUE);
INSERT INTO lesson_package_types (id, name, description, package_type, price, is_test_drive) VALUES ('amazon-test-drive-paid-4', 'Test Drive (4)', 'Four reduced-price lessons which you can use to find that ideal teacher.', 'test-drive-4', 19.99, TRUE);
-- need new posa card types for Amazon -- should be able to point to lesson package type
ALTER TABLE posa_cards ADD COLUMN lesson_package_type_id VARCHAR(64) REFERENCES lesson_package_types(id) ON DELETE SET NULL;
ALTER TABLE posa_cards ADD COLUMN credits INTEGER NOT NULL DEFAULT 1;
ALTER TABLE posa_cards ADD COLUMN is_lesson BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE posa_cards ADD COLUMN preactivate BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE posa_cards ADD COLUMN requires_purchase BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE posa_cards ADD COLUMN purchased BOOLEAN NOT NULL DEFAULT TRUE;
ALTER TABLE lesson_bookings ADD COLUMN posa_card_purchased BOOLEAN NOT NULL DEFAULT TRUE;
update posa_cards set credits = 5 where card_type = 'jam_tracks_5';
update posa_cards set credits = 10 where card_type = 'jam_tracks_10';
update posa_cards set credits = 4 where card_type = 'jam_class_4';
update posa_cards set is_lesson = TRUE where card_type = 'jam_class_4';
update posa_cards set lesson_package_type_id = 'test-drive' where card_type = 'jam_class_4';
ALTER TABLE lesson_package_purchases ADD COLUMN total_roll_forward_amount_in_cents INTEGER;
ALTER TABLE lesson_package_purchases ADD COLUMN remaining_roll_forward_amount_in_cents INTEGER;
ALTER TABLE lesson_package_purchases ADD COLUMN reduced_roll_forward_amount_in_cents INTEGER NOT NULL DEFAULT 0;
ALTER TABLE lesson_package_purchases ADD COLUMN expected_session_times INTEGER;
ALTER TABLE lesson_package_purchases ADD COLUMN actual_session_times INTEGER;
ALTER TABLE teacher_distributions ADD COLUMN reduced_roll_forward_amount_in_cents INTEGER NOT NULL DEFAULT 0;
ALTER TABLE lesson_bookings ADD COLUMN remaining_roll_forward_amount_in_cents INTEGER NOT NULL DEFAULT 0;
ALTER TABLE users ADD COLUMN lesson_package_needs_purchase_id VARCHAR(64) REFERENCES lesson_package_types(id) ON DELETE SET NULL;

View File

@ -20,6 +20,14 @@ module JamRuby
subject: options[:subject])
end
def jamclass_alerts(options)
mail(to: APP_CONFIG.email_jamclass_alerts_alias,
from: APP_CONFIG.email_generic_from,
body: options[:body],
content_type: "text/plain",
subject: options[:subject])
end
def crash_alert(options)
mail(to: APP_CONFIG.email_crashes_alias,
from: APP_CONFIG.email_generic_from,

View File

@ -355,7 +355,7 @@ SQL
if active_music_session
music_session = active_music_session.music_session
if music_session.session_controller_id && !active_music_session.users.exists?(music_session.session_controller)
if music_session.session_controller_id && !active_music_session.users.exists?(music_session.session_controller.id)
# find next in line, because the current 'session controller' is not part of the session
next_in_line(music_session, active_music_session)
end

View File

@ -88,13 +88,12 @@ module JamRuby
# If this is a lesson posa card, then put that user into the guitar center school
def self.post_posa_claim(posa)
if posa.is_lesson_posa_card?
# Associate user with guitar center school
if posa.retailer.is_guitar_center?
posa.user.is_a_student = true
# Associate user with guitar center school
if posa.retailer && posa.retailer.is_guitar_center?
if posa.user.school_id.nil?
posa.user.school_id = School.guitar_center.id
end
posa.user.save
end
end

View File

@ -9,7 +9,7 @@ module JamRuby
@@log = Logging.logger[LessonBooking]
attr_accessor :accepting, :countering, :canceling, :autocanceling, :countered_slot, :countered_lesson, :current_purchase, :current_lesson
attr_accessor :accepting, :countering, :canceling, :autocanceling, :countered_slot, :countered_lesson, :current_purchase, :current_lesson, :expected_session_times, :adjustment_in_cents
STATUS_REQUESTED = 'requested'
STATUS_CANCELED = 'canceled'
@ -97,7 +97,7 @@ module JamRuby
end
def after_create
if (posa_card || card_presumed_ok || !payment_if_school_on_school?) && !sent_notices
if ((posa_card && posa_card.purchased) || card_presumed_ok || !payment_if_school_on_school?) && !sent_notices
send_notices
end
end
@ -188,7 +188,7 @@ module JamRuby
success = self.save
if !success
puts "unable to accept lesson booking #{errors.inspect}"
#puts "unable to accept lesson booking #{errors.inspect}"
end
success
end
@ -467,6 +467,11 @@ module JamRuby
def resolved_test_drive_package
result = nil
# posa card is best indicator of lesson package type
if posa_card
return posa_card.lesson_package_type
end
purchase = student.most_recent_test_drive_purchase
if purchase
# for lessons already packaged
@ -528,21 +533,22 @@ module JamRuby
end
end
def distribution_price_in_cents(target, education, split = nil, fee_rate = nil)
distribution = teacher_distribution_price_in_cents(target)
def distribution_price_in_cents(target, education, split = nil)
if split
distribution = teacher_distribution_price_in_cents(target, split)
(distribution * split).round
# when a split is provided, we also pin down the teacher_fee_in_cents, instead of assuming a bunch of stuff
elsif education
distribution = teacher_distribution_price_in_cents(target, 0.0625)
(distribution * 0.0625).round # 0.0625 is 1/4th of 25%
else
distribution = teacher_distribution_price_in_cents(target)
distribution
end
end
def teacher_distribution_price_in_cents(target)
def teacher_distribution_price_in_cents(target, split = nil)
if is_single_free?
0
elsif is_test_drive?
@ -558,13 +564,76 @@ module JamRuby
# 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).round
price, times = LessonSessionMonthlyPrice.price(self, start_date)
price_in_cents = (price * 100).round
# OK, we have a suggested price based on date, but we need to now adjust if previous lessons have been unsuccessful
adjusted_price_in_cents = LessonSessionMonthlyPrice.adjust_for_missed_lessons(self, price_in_cents, split)
self.expected_session_times = times # save for later
self.adjustment_in_cents = price_in_cents - adjusted_price_in_cents
adjusted_price_in_cents
else
booked_price * 100
end
end
end
# find any lesson package purchases for this lesson booking for previous months that have not had adjustments
# we have to base this on 'now', meaning we do not consider months until they are fully closed
# this does mean, because we collect up to a week in advance of a new month starting, that a student will likely not see adjustments until 2 cycles forward
def self.previous_needing_adjustment
now = Time.now.utc
year = now.year
month = now.month
if month == 1
previous_year = year - 1
previous_month = 12
else
previous_year = year
previous_month = month - 1
end
LessonPackagePurchase.where(recurring: true).where('month <= ?', previous_month).where('year <= ?', previous_year).where('actual_session_times is null').where('expected_session_times is not null')
.limit(500)
end
def self.adjust_for_missed_sessions
# Go to previous lesson_package_purchase month, and see if we need to adjust for a roll forward due to missed sessions
previous_purchases = LessonBooking.previous_needing_adjustment
previous_purchases.each do |previous_purchase|
# XXX other monthly code uses session start time. should we be doing that?
successful_lessons = LessonSession.where(lesson_booking: previous_purchase.lesson_booking).where(success: true).where('analysed_at >= ? AND analysed_at < ?', previous_purchase.beginning_of_month_at, previous_purchase.end_of_month_at)
previous_purchase.actual_session_times = successful_lessons.count
# find out how many actual lessons were had, and then we can adjust price of this current distribution (amount_in_cents) accordingly
ratio = previous_purchase.actual_session_times.to_f / previous_purchase.expected_session_times.to_f
if ratio < 1
# discount next month for student
amount_paid_last_month_in_cents = previous_purchase.price_in_cents # this does not include tax. It's just the expected price of the booking
previous_purchase.remaining_roll_forward_amount_in_cents = previous_purchase.total_roll_forward_amount_in_cents = (amount_paid_last_month_in_cents * ratio).round
# if there is a roll forward, add it to the lesson booking
previous_purchase.lesson_booking.remaining_roll_forward_amount_in_cents += previous_purchase.remaining_roll_forward_amount_in_cents
previous_purchase.lesson_booking.save!
else
previous_purchase.total_roll_forward_amount_in_cents = 0
previous_purchase.applied_roll_forward_amount_in_cents = 0
end
previous_purchase.save
end
end
def is_single_free?
lesson_type == LESSON_TYPE_FREE
end
@ -674,6 +743,9 @@ module JamRuby
def card_approved
self.card_presumed_ok = true
if posa_card_id
self.posa_card_purchased = true
end
if self.save && !sent_notices
send_notices
end
@ -792,7 +864,10 @@ module JamRuby
if lesson_type == LESSON_TYPE_TEST_DRIVE
# if the user has any jamclass credits, then we should get their most recent posa purchase
if user.jamclass_credits > 0
lesson_booking.posa_card = user.most_recent_posa_purchase.posa_card
lesson_booking.posa_card = user.most_recent_posa_card
if lesson_booking.posa_card
lesson_booking.posa_card_purchased = lesson_booking.posa_card.purchased
end
else
# otherwise, it's a normal test drive, and we should honor test_drive_package_choice if specified
lesson_booking.test_drive_package_choice = test_drive_package_choice
@ -837,7 +912,7 @@ module JamRuby
end
def self.unprocessed(current_user)
LessonBooking.where(user_id: current_user.id).where(card_presumed_ok: false).where(same_school_free: false).where(posa_card:nil)
LessonBooking.where(user_id: current_user.id).where(card_presumed_ok: false).where(same_school_free: false).where('posa_card_id is null OR (posa_card_id is not null AND posa_card_purchased = false)')
end
def self.requested(current_user)
@ -902,9 +977,15 @@ module JamRuby
# check for any recurring sessions where there are not at least 2 sessions into the future. If not, we need to make sure they get made
def self.hourly_check
schedule_upcoming_lessons
# order matters: bill_monthly code will use the adjustments made in here to correct billing roll forward
adjust_for_missed_sessions
# needs to come after 'adjust_for_missed_sessions'
bill_monthlies
end
def self.bill_monthlies
now = Time.now
billable_monthlies(now).each do |lesson_booking|

View File

@ -29,17 +29,27 @@ module JamRuby
after_create :add_test_drives
after_create :create_charge
def validate_test_drive
if user
# if this is a posa card purchase, we won't stop it from getting created
if posa_card_id
if posa_card
return
end
if lesson_package_type.is_test_drive? && !user.can_buy_test_drive?
if lesson_package_type.is_test_drive?
if user.lesson_package_needs_purchase_id
# if lesson_package_needs_purchase is set, we need to let the purchase go through because the user alrady has the credits; gotta let them pay
return
end
if !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
end
def create_charge
if payment_if_school_on_school? && lesson_booking && lesson_booking.is_monthly_payment?
@ -61,7 +71,7 @@ module JamRuby
end
def add_test_drives
if posa_card_id
if posa_card
#user.jamclass_credits incremented in posa_card.rb
return
end
@ -85,12 +95,28 @@ module JamRuby
lesson_payment_charge.amount_in_cents / 100.0
end
def beginning_of_month_at
Date.new(year, month, 1).to_time.utc.beginning_of_month
end
def end_of_month_at
Date.new(year, month, 1).to_time.utc.end_of_month
end
def period_over?
Time.now.utc > end_of_month_at
end
def self.create(user, lesson_booking, lesson_package_type, year = nil, month = nil, posa_card = nil)
purchase = LessonPackagePurchase.new
purchase.user = user
purchase.lesson_booking = lesson_booking
purchase.teacher = lesson_booking.teacher if lesson_booking
purchase.posa_card = posa_card
if !purchase.posa_card && lesson_booking
# the lesson booking has a posa card? if so, we should track that
purchase.posa_card = lesson_booking.posa_card
end
if year
purchase.year = year
@ -117,6 +143,7 @@ module JamRuby
purchase.teacher_distributions << teacher_dist
# price should always match the teacher_distribution, if there is one
purchase.price = teacher_dist.amount_in_cents / 100
purchase.reduced_roll_forward_amount_in_cents += teacher_dist.reduced_roll_forward_amount_in_cents
end
if retailer_split && retailer_split > 0
@ -124,33 +151,39 @@ module JamRuby
teacher_dist.retailer = teacher.teacher.retailer
teacher_dist.save
purchase.teacher_distributions << teacher_dist
purchase.reduced_roll_forward_amount_in_cents += teacher_dist.reduced_roll_forward_amount_in_cents
end
else
teacher_dist = TeacherDistribution.create_for_lesson_package_purchase(purchase, false)
purchase.teacher_distributions << teacher_dist
purchase.reduced_roll_forward_amount_in_cents += teacher_dist.reduced_roll_forward_amount_in_cents
# price should always match the teacher_distribution, if there is one
purchase.price = teacher_dist.amount_in_cents / 100
if lesson_booking.school_on_school_payment? && lesson_booking.school.education
teacher_dist = TeacherDistribution.create_for_lesson_package_purchase(purchase, true)
purchase.teacher_distributions << teacher_dist
purchase.reduced_roll_forward_amount_in_cents += teacher_dist.reduced_roll_forward_amount_in_cents
end
end
# record expected times for times played in the month
purchase.expected_session_times = lesson_booking.expected_session_times
end
else
purchase.recurring = false
end
if 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 if purchase.price.nil?
end
purchase.save
purchase
end

View File

@ -6,11 +6,16 @@ module JamRuby
PRODUCT_TYPE = 'LessonPackageType'
TEST_DRIVE_4_ID = 'test-drive'
SINGLE_FREE = 'single-free'
TEST_DRIVE_4 = 'test-drive'
TEST_DRIVE_4 = 'test-drive-4'
TEST_DRIVE_2 = 'test-drive-2'
TEST_DRIVE_1 = 'test-drive-1'
SINGLE = 'single'
AMAZON_TEST_DRIVE_4_PAID_ID = 'amazon-test-drive-paid-4'
AMAZON_TEST_DRIVE_FREE_2_ID = 'amazon-test-drive-free-2'
AMAZON_TEST_DRIVE_FREE_4_ID = 'amazon-test-drive-free-4'
LESSON_PACKAGE_TYPES =
[
@ -22,14 +27,18 @@ module JamRuby
]
has_many :user_desired_packages, class_name: "JamRuby::User", :foreign_key => "lesson_package_type_id", inverse_of: :desired_package
has_many :users_needing_purchase, class_name: "JamRuby::User", :foreign_key => "lesson_package_needs_purchase_id", inverse_of: :lesson_package_needs_purchase
has_many :posa_cards, class_name: "JamRuby::PosaCard"
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]
LessonPackageType.select("id").where(is_test_drive: true).map {|i| i.id }
end
def self.monthly
LessonPackageType.find(MONTHLY)
end
@ -38,8 +47,20 @@ module JamRuby
LessonPackageType.find(SINGLE_FREE)
end
def self.amazon_test_drive_free_4
LessonPackageType.find(AMAZON_TEST_DRIVE_FREE_4_ID)
end
def self.amazon_test_drive_free_2
LessonPackageType.find(AMAZON_TEST_DRIVE_FREE_2_ID)
end
def self.amazon_test_drive_paid_4
LessonPackageType.find(AMAZON_TEST_DRIVE_4_PAID_ID)
end
def self.test_drive_4
LessonPackageType.find(TEST_DRIVE_4)
LessonPackageType.find(TEST_DRIVE_4_ID)
end
def self.test_drive_2
@ -54,6 +75,7 @@ module JamRuby
LessonPackageType.find(SINGLE)
end
def booked_price(lesson_booking)
if is_single_free?
0
@ -94,7 +116,7 @@ module JamRuby
end
def is_test_drive?
id.start_with?('test-drive')
is_test_drive
end
def is_normal?
@ -112,7 +134,7 @@ module JamRuby
def plan_code
if package_type == SINGLE_FREE
"lesson-package-single-free"
elsif package_type == 'test-drive-4'
elsif package_type == TEST_DRIVE_4
"lesson-package-test-drive-4"
elsif package_type == TEST_DRIVE_2
"lesson-package-test-drive-2"

View File

@ -11,7 +11,7 @@ module JamRuby
@@log = Logging.logger[LessonSession]
delegate :sent_billing_notices, :last_billing_attempt_at, :billing_attempts, :billing_should_retry, :billed_at, :billing_error_detail, :billing_error_reason, :is_card_declined?, :is_card_expired?, :last_billed_at_date, :sent_billing_notices, to: :lesson_payment_charge, allow_nil: true
delegate :is_test_drive?, :is_single_free?, :is_normal?, :approved_before?, :is_active?, :recurring, :is_monthly_payment?, :school_on_school?, :school_on_school_payment?, :no_school_on_school_payment?, :payment_if_school_on_school?, :scheduling_email, :teacher_school_emails, :school_and_teacher, :school_over_teacher, :school_and_teacher_ids, :school_over_teacher_ids, :posa_card, to: :lesson_booking
delegate :is_test_drive?, :is_single_free?, :is_normal?, :approved_before?, :is_active?, :recurring, :is_monthly_payment?, :school_on_school?, :school_on_school_payment?, :no_school_on_school_payment?, :payment_if_school_on_school?, :scheduling_email, :teacher_school_emails, :school_and_teacher, :school_over_teacher, :school_and_teacher_ids, :school_over_teacher_ids, :posa_card, :remaining_roll_forward_amount_in_cents, to: :lesson_booking
delegate :pretty_scheduled_start, to: :music_session
@ -712,7 +712,7 @@ module JamRuby
end
# only show 'fully booked lessons'; not those they can not possibly be paid for
query = query.where('lesson_bookings.posa_card_id IS NOT NULL OR lesson_bookings.card_presumed_ok = true OR (music_sessions.user_id = ?) ' + school_extra, user.id)
query = query.where('(lesson_bookings.posa_card_id IS NOT NULL AND lesson_bookings.posa_card_purchased) OR lesson_bookings.card_presumed_ok = true OR (music_sessions.user_id = ?) ' + school_extra, user.id)
current_page = params[:page].nil? ? 1 : params[:page].to_i
next_page = current_page + 1

View File

@ -6,6 +6,7 @@ module JamRuby
raise "lesson_booking is not monthly paid #{lesson_booking.admin_url}" if !lesson_booking.is_monthly_payment?
data = lesson_booking.predicted_times_for_month(start_day.year, start_day.month)
times = data[:times]
@ -33,7 +34,53 @@ module JamRuby
result = lesson_booking.booked_price
end
result
[result, times.length]
end
def self.adjust_for_missed_lessons(lesson_booking, price_in_cents, split)
if !split
split = 1.0
end
adjusted_price_in_cents = price_in_cents
lesson_package_purchases = LessonPackagePurchase.where(lesson_booking: lesson_booking).where('remaining_roll_forward_amount_in_cents > 0').order(:created_at)
remaining_roll_forward = 0
skip_crediting = false
reduced_amount = 0
lesson_package_purchases.each do |lesson_package_purchase|
if !skip_crediting
take_off = (lesson_package_purchase.remaining_roll_forward_amount_in_cents * split).round
amount_remaining_after_adjustment = price_in_cents - take_off
if amount_remaining_after_adjustment <= 0
# there isn't enough 'price_in_cents' to eat up the needed credit, so we say this teacher_distribution has no due price, and break out of the loo
adjusted_price_in_cents = 0
reduced_amount += price_in_cents
lesson_package_purchase.remaining_roll_forward_amount_in_cents -= price_in_cents
skip_crediting = true
else
adjusted_price_in_cents = amount_remaining_after_adjustment
reduced_amount += take_off
# this lesson_package_purchase is now cleared out - totally credited!
lesson_package_purchase.remaining_roll_forward_amount_in_cents = 0
end
end
remaining_roll_forward = remaining_roll_forward + lesson_package_purchase.remaining_roll_forward_amount_in_cents
lesson_package_purchase.save!
end
lesson_booking.remaining_roll_forward_amount_in_cents = remaining_roll_forward
lesson_booking.save!
adjusted_price_in_cents
end
end
end

View File

@ -126,7 +126,6 @@ module JamRuby
def refresh_stream(user, broadcast)
stream_data = get_livestream(user)
puts "REFRESH STREAM #{stream_data}"
broadcast.stream_id = stream_data["id"]
broadcast.stream_status = stream_data["status"]["streamStatus"]
broadcast.stream_name = stream_data["cdn"]["ingestionInfo"]["streamName"]
@ -185,7 +184,6 @@ module JamRuby
if livestream
# if livestream["status"]["streamStatus"] == "active"
puts "LI EVESATREMA STATATUESNUHOENTUHENOSTHU #{livestream["status"]["streamStatus"]}"
transition_broadcast(user, broadcast, 'live', google_client)
# end
else
@ -1254,6 +1252,10 @@ SQL
sessions
end
def admin_url
APP_CONFIG.admin_root_url + "/admin/music_sessions/" + id
end
private
def generate_share_token

View File

@ -7,20 +7,25 @@ module JamRuby
JAM_TRACKS_5 = 'jam_tracks_5'
JAM_TRACKS_10 = 'jam_tracks_10'
JAM_CLASS_4 = 'jam_class_4'
JAM_CLASS_2 = 'jam_class_2'
CARD_TYPES =
[
JAM_TRACKS_5,
JAM_TRACKS_10,
JAM_CLASS_4
JAM_CLASS_4,
JAM_CLASS_2
]
belongs_to :user, class_name: "JamRuby::User"
belongs_to :retailer, class_name: "JamRuby::Retailer"
belongs_to :lesson_package_type, class_name: "JamRuby::LessonPackageType"
has_many :posa_card_purchases, class_name: 'JamRuby::PosaCardPurchase'
has_one :lesson_package_purchase, class_name: 'JamRuby::LessonPackagePurchase'
has_one :jam_track_right, class_name: "JamRuby::JamTrackRight"
validates :card_type, presence: true, inclusion: {in: CARD_TYPES}
validates :code, presence: true, uniqueness: true
@ -34,20 +39,9 @@ module JamRuby
validate :within_one_year
def is_lesson_posa_card?
card_type == JAM_CLASS_4
self.is_lesson
end
def credits
if card_type == JAM_TRACKS_5
5
elsif card_type == JAM_TRACKS_10
10
elsif card_type == JAM_CLASS_4
4
else
raise "unknown card type #{card_type}"
end
end
def already_activated
if activated_at && activated_at_was && activated_at_changed?
@ -60,7 +54,7 @@ module JamRuby
end
def within_one_year
if user && claimed_at && claimed_at_was && claimed_at_changed?
if user && claimed_at && !claimed_at_was && claimed_at_changed?
if !user.can_claim_posa_card
self.errors.add(:claimed_at, 'was within 1 year')
end
@ -86,7 +80,7 @@ module JamRuby
end
def must_be_activated
if claimed_at && !activated_at
if claimed_at && !preactivate && !activated_at
self.errors.add(:activated_at, 'must already be set')
end
end
@ -94,11 +88,11 @@ module JamRuby
def check_attributed
if user && user_id_changed?
if card_type == JAM_TRACKS_5
user.gifted_jamtracks += 5
user.gifted_jamtracks += credits
elsif card_type == JAM_TRACKS_10
user.gifted_jamtracks += 10
elsif card_type == JAM_CLASS_4
user.jamclass_credits += 4
user.gifted_jamtracks += credits
elsif is_lesson_posa_card?
user.jamclass_credits += credits
else
raise "unknown card type #{card_type}"
end
@ -106,18 +100,6 @@ module JamRuby
end
end
def lesson_package_type
if card_type == JAM_TRACKS_5
raise 'not a lesson package: ' + card_type
elsif card_type == JAM_TRACKS_10
raise 'not a lesson package: ' + card_type
elsif card_type == JAM_CLASS_4
LessonPackageType.test_drive_4
else
raise "unknown card type #{card_type}"
end
end
def product_info
price = nil
plan_code = nil
@ -128,9 +110,9 @@ module JamRuby
elsif card_type == JAM_TRACKS_10
price = 19.99
plan_code = 'posa-jatracks-10'
elsif card_type == JAM_CLASS_4
price = 49.99
plan_code = 'posa-jamclass-4'
elsif is_lesson_posa_card?
price = lesson_package_type.price
plan_code = "posa-jamclass-#{credits}"
else
raise "unknown card type #{card_type}"
end
@ -148,20 +130,53 @@ module JamRuby
self.save
end
def has_been_purchased(create_purchase)
if !purchased
#release flag on user account indicating they need to buy this card
user.lesson_package_needs_purchase = nil
user.save
# indicate this has been purchased
self.purchased = true
self.save
if is_lesson_posa_card? && create_purchase
LessonPackagePurchase.create(user, nil, lesson_package_type, nil, nil, self)
end
end
end
def claim(user)
self.user = user
self.claimed_at = Time.now
if self.save
if user.errors.any?
# happens on signup if bad email etc
return
end
UserWhitelist.card_create(user, 'posa')
if !preactivate
SaleLineItem.associate_user_for_posa(self, user)
end
if requires_purchase
# this is a flag on the account that makes it so the user can't buy any TD's on the payment page until they clear this up, because we've given them credits
user.lesson_package_needs_purchase = self.lesson_package_type
user.save
end
# when you claim a POSA card, you are also making a LessonPackagePurchase
if is_lesson_posa_card?
GuitarCenter.post_posa_claim(self)
purchase = LessonPackagePurchase.create(user, nil, lesson_package_type, nil, nil, self) if purchase.nil?
if purchased
LessonPackagePurchase.create(user, nil, lesson_package_type, nil, nil, self)
end
end
end
end
@ -171,8 +186,8 @@ module JamRuby
'JT-5'
elsif card_type == JAM_TRACKS_10
'JT-10'
elsif card_type == JAM_CLASS_4
'JC-4'
elsif is_lesson_posa_card?
"JC-#{credits}"
else
raise "unknown card type #{card_type}"
end

View File

@ -233,8 +233,8 @@ module JamRuby
free && non_free
end
def self.purchase_test_drive(current_user, lesson_package_type, booking = nil)
self.purchase_lesson(nil, current_user, booking, lesson_package_type)
def self.purchase_test_drive(current_user, lesson_package_type, booking = nil, posa_card = nil)
self.purchase_lesson(nil, current_user, booking, lesson_package_type, nil, nil, false, posa_card)
end
def self.post_sale_test_failure
@ -261,7 +261,7 @@ module JamRuby
end
# this is easy to make generic, but right now, it just purchases lessons
def self.purchase_lesson(charge, current_user, lesson_booking, lesson_package_type, lesson_session = nil, lesson_package_purchase = nil, force = false)
def self.purchase_lesson(charge, current_user, lesson_booking, lesson_package_type, lesson_session = nil, lesson_package_purchase = nil, force = false, posa_card = nil)
stripe_charge = nil
sale = nil
purchase = nil
@ -279,7 +279,7 @@ module JamRuby
sale_line_item = SaleLineItem.create_from_lesson_package(current_user, sale, lesson_package_type, lesson_booking)
price_info = charge_stripe_for_lesson(charge, current_user, lesson_booking, lesson_package_type, sale_line_item, lesson_session, lesson_package_purchase, force)
price_info = charge_stripe_for_lesson(charge, current_user, lesson_booking, lesson_package_type, sale_line_item, lesson_session, lesson_package_purchase, force, posa_card)
post_sale_test_failure
@ -310,7 +310,7 @@ module JamRuby
{sale: sale, stripe_charge: stripe_charge, purchase: purchase}
end
def self.charge_stripe_for_lesson(charge, current_user, lesson_booking, lesson_package_type, sale_line_item, lesson_session = nil, lesson_package_purchase = nil, force = false)
def self.charge_stripe_for_lesson(charge, current_user, lesson_booking, lesson_package_type, sale_line_item, lesson_session = nil, lesson_package_purchase = nil, force = false, posa_card = nil)
if lesson_package_purchase
target = lesson_package_purchase
elsif lesson_session
@ -322,10 +322,9 @@ module JamRuby
current_user.sync_stripe_customer
purchase = lesson_package_purchase
purchase = LessonPackagePurchase.create(current_user, lesson_booking, lesson_package_type) if purchase.nil?
purchase = LessonPackagePurchase.create(current_user, lesson_booking, lesson_package_type, nil, nil, posa_card) if purchase.nil?
if purchase.errors.any?
puts "purchase errors #{purchase.errors.inspect}"
price_info = {}
price_info[:purchase] = purchase
return price_info

View File

@ -141,7 +141,7 @@ module JamRuby
query = query.where("teaches_age_lower <= ? AND (CASE WHEN teaches_age_upper = 0 THEN true ELSE teaches_age_upper >= ? END)", student_age, student_age)
end
# don't show phantom teachers that teach 'bass guitar', 'acoustic guitar', 'electric_guitar'
# don't show phantom teachers that teach 'bass guitar', 'acoustic guitar', 'electric guitar'
query = query.where("((select count(checkgt.instrument_id) from teachers_instruments checkgt where checkgt.teacher_id = teachers.id AND checkgt.instrument_id IN ('bass guitar', 'acoustic guitar', 'electric guitar') ) = 0 AND phantom = true) OR phantom = false")
# order in this way: https://jamkazam.atlassian.net/browse/VRFS-4058
@ -349,8 +349,8 @@ module JamRuby
reviews.order('created_at desc').limit(20)
end
def mark_background_checked
self.background_check_at = Time.now
def mark_background_checked(time)
self.background_check_at = time
self.save!
end

View File

@ -55,14 +55,14 @@ module JamRuby
end
def self.create_for_lesson_package_purchase(lesson_package_purchase, for_education, split = nil, fee_rate = nil)
distribution = create(lesson_package_purchase, for_education, split, fee_rate)
distribution.lesson_package_purchase = lesson_package_purchase
distribution.education = for_education
# lock down the teacher_fee_in_cents
distribution.teacher_fee_in_cents = distribution.calculate_teacher_fee(split, fee_rate)
distribution.reduced_roll_forward_amount_in_cents = lesson_package_purchase.lesson_booking.adjustment_in_cents
distribution
end
@ -71,7 +71,7 @@ module JamRuby
distribution.teacher = target.teacher
distribution.ready = false
distribution.distributed = false
distribution.amount_in_cents = target.lesson_booking.distribution_price_in_cents(target, education, split, fee_rate)
distribution.amount_in_cents = target.lesson_booking.distribution_price_in_cents(target, education, split)
distribution.school = target.lesson_booking.school
distribution
end

View File

@ -37,7 +37,7 @@ module JamRuby
pending_teacher_payments.each do |row|
teacher = User.find(row['id'])
TeacherDistribution.where(teacher_id: teacher.id).where(ready:true).where(distributed: false).each do |distribution|
TeacherDistribution.where(teacher_id: teacher.id).where(ready: true).where(distributed: false).each do |distribution|
payment = TeacherPayment.charge(teacher)
if payment.nil? || !payment.teacher_payment_charge.billed
break
@ -62,6 +62,7 @@ module JamRuby
def last_billed_at_date
teacher_payment_charge.last_billed_at_date
end
def charge_retry_hours
24
end
@ -82,7 +83,7 @@ module JamRuby
end
if payment.teacher_distribution.nil?
teacher_distribution = TeacherDistribution.where(teacher_id: teacher.id).where(ready:true).where(distributed: false).order(:created_at).first
teacher_distribution = TeacherDistribution.where(teacher_id: teacher.id).where(ready: true).where(distributed: false).order(:created_at).first
if teacher_distribution.nil?
return
end
@ -93,6 +94,8 @@ module JamRuby
payment.amount_in_cents = payment.teacher_distribution.amount_in_cents
payment.fee_in_cents = payment.teacher_distribution.calculate_teacher_fee
if payment.teacher_distribution.amount_in_cents > 0
effective_in_cents = payment.real_distribution_in_cents
if payment.teacher_payment_charge.nil?
@ -118,6 +121,15 @@ module JamRuby
payment.teacher_distribution.distributed = true
payment.teacher_distribution.save!
end
else
# 0 amount distribution; these occur in 100% roll forward scenarios (previous month was completely missed)
payment.save!
payment.teacher_distribution.distributed = true
payment.teacher_distribution.save!
end
payment
end
end

View File

@ -41,7 +41,7 @@ module JamRuby
attr_accessible :first_name, :last_name, :email, :city, :password, :password_confirmation, :state, :country, :birth_date, :subscribe_email, :terms_of_service, :original_fpfile, :cropped_fpfile, :cropped_large_fpfile, :cropped_s3_path, :cropped_large_s3_path, :photo_url, :large_photo_url, :crop_selection
# updating_password corresponds to a lost_password
attr_accessor :test_drive_packaging, :validate_instruments, :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field, :mods_json, :expecting_gift_card
attr_accessor :test_drive_packaging, :validate_instruments, :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field, :mods_json, :expecting_gift_card, :purchase_required
belongs_to :icecast_server_group, class_name: "JamRuby::IcecastServerGroup", inverse_of: :users, foreign_key: 'icecast_server_group_id'
has_many :controlled_sessions, :class_name => "JamRuby::MusicSession", inverse_of: :session_controller, foreign_key: :session_controller_id
@ -178,7 +178,7 @@ module JamRuby
has_many :test_drive_package_choice_teachers, :class_name => "JamRuby::TestDrivePackageChoiceTeacher", :foreign_key => "teacher_id"
has_many :test_drive_package_choices, :class_name => "JamRuby::TestDrivePackageChoice", :foreign_key => "user_id", inverse_of: :user
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
belongs_to :lesson_package_needs_purchase, :class_name => "JamRuby::LessonPackageType", :foreign_key => "lesson_package_needs_purchase_id", inverse_of: :users_needing_purchase
# Shopping carts
has_many :shopping_carts, :class_name => "JamRuby::ShoppingCart"
@ -477,7 +477,7 @@ module JamRuby
def age
now = Time.now.utc.to_date
self.birth_date.nil? ? "" : now.year - self.birth_date.year - (self.birth_date.to_date.change(:year => now.year) > now ? 1 : 0)
self.birth_date.nil? ? nil : now.year - self.birth_date.year - (self.birth_date.to_date.change(:year => now.year) > now ? 1 : 0)
end
def session_count
@ -685,6 +685,8 @@ module JamRuby
id
end
def set_password(old_password, new_password, new_password_confirmation)
# so that UserObserver knows to send a confirmation email on success
@ -1358,17 +1360,21 @@ module JamRuby
# if a gift card value was passed in, then try to find that gift card and apply it to user
if gift_card
# first try posa card
posa_card = PosaCard.where(code: gift_card).first
if posa_card
posa_card.claim(user)
user.posa_cards << posa_card
user.purchase_required = posa_card.requires_purchase # temporary; just so the signup page knows to send them to payment place
else
user.expecting_gift_card = true
found_gift_card = GiftCard.where(code: gift_card).where(user_id: nil).first
user.gift_cards << found_gift_card if found_gift_card
if found_gift_card
user.gift_cards << found_gift_card
end
end
end
@ -1448,6 +1454,17 @@ module JamRuby
#if school && school.education
# UserMailer.student_education_welcome_message(user).deliver_now
#else
body = "Name: #{user.name}\n"
body << "Email: #{user.email}\n"
body << "Admin: #{user.admin_student_url}\n"
if posa_card
body << "Package Details: \n"
body << " Package: #{posa_card.lesson_package_type.id}\n"
body << " Credits: #{posa_card.credits}\n"
body << " Code: #{posa_card.code}\n"
end
AdminMailer.jamclass_alerts({subject: "#{user.name} just signed up as a student", body: body}).deliver_now
UserMailer.student_welcome_message(user).deliver_now
#end
elsif user.is_a_teacher
@ -1975,6 +1992,10 @@ module JamRuby
APP_CONFIG.admin_root_url + "/admin/users/" + id
end
def admin_student_url
APP_CONFIG.admin_root_url + "/admin/students" # should add id; not yet supported
end
def jam_track_rights_admin_url
APP_CONFIG.admin_root_url + "/admin/jam_track_rights?q[user_id_equals]=#{id}&commit=Filter&order=created_at DESC"
end
@ -2042,7 +2063,7 @@ module JamRuby
# validate if within waiting period
def can_claim_posa_card
posa_cards.where('card_type = ?', PosaCard::JAM_CLASS_4).where('claimed_at > ?', APP_CONFIG.jam_class_card_wait_period_year.years.ago).count == 0
posa_cards.where('is_lesson = ?', true).where('claimed_at > ?', APP_CONFIG.jam_class_card_wait_period_year.years.ago).count == 0
end
def lessons_with_teacher(teacher)
@ -2197,6 +2218,7 @@ module JamRuby
lesson_package_type = nil
uncollectables = nil
choice = nil
posa_card = nil
User.transaction do
if params[:name].present?
@ -2209,20 +2231,29 @@ module JamRuby
if params[:test_drive]
self.reload
if booking
# bookin will indicate test package
lesson_package_type = booking.resolved_test_drive_package
posa_card = booking.posa_card
elsif choice
# packages also indicate lesson package
lesson_package_type = choice.lesson_package_type
elsif self.lesson_package_needs_purchase
# user has a POSA card requiring purchase, so this takes preference over the 'desired_package' (a user could have both set, but we force user to pay for POSA_CARD requiring purchase before picking up a random TD purchase)
lesson_package_type = self.lesson_package_needs_purchase
# also update POSA cards indicating they have been bought. This below code is a little bit
posa_card = self.posa_cards.where(requires_purchase: true).where(purchased:false).order(:created_at).first
else
# the user has at some point b4 indicated interest in a package; so in absence of above indicators, this is what they are buying
lesson_package_type = self.desired_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)
result = Sale.purchase_test_drive(self, lesson_package_type, booking, posa_card)
test_drive = result[:sale]
purchase = result[:purchase]
if posa_card && !purchase.errors.any?
posa_card.has_been_purchased(false)
end
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)
@ -2233,6 +2264,7 @@ module JamRuby
end
{lesson: booking, test_drive: test_drive, purchase: purchase, lesson_package_type: lesson_package_type, uncollectables: uncollectables, package: choice}
end
@ -2256,6 +2288,10 @@ module JamRuby
lesson_purchases.where('lesson_package_type_id in (?)', LessonPackageType.test_drive_package_ids).where('posa_card_id is not null').order('created_at desc').first
end
def most_recent_posa_card
posa_cards.where('lesson_package_type_id in (?)', LessonPackageType.test_drive_package_ids).order('created_at desc').first
end
def most_recent_test_drive_purchase
lesson_purchases.where('lesson_package_type_id in (?)', LessonPackageType.test_drive_package_ids).order('created_at desc').first
end

View File

@ -12,6 +12,7 @@ module JamRuby
LessonSession.hourly_check
TeacherPayment.hourly_check
@@log.debug("done")
end
end

View File

@ -901,7 +901,24 @@ FactoryGirl.define do
factory :posa_card, class: 'JamRuby::PosaCard' do
sequence(:code) { |n| n.to_s }
card_type JamRuby::PosaCardType::JAM_TRACKS_5
card_type JamRuby::PosaCard::JAM_TRACKS_5
credits 5
requires_purchase false
purchased true
factory :posa_card_lesson_2 do
card_type JamRuby::PosaCard::JAM_CLASS_2
credits 2
lesson_package_type { JamRuby::LessonPackageType.test_drive_2 }
is_lesson true
end
factory :posa_card_lesson_4 do
card_type JamRuby::PosaCard::JAM_CLASS_4
credits 4
lesson_package_type { JamRuby::LessonPackageType.test_drive_4 }
is_lesson true
end
end
factory :posa_card_type, class: 'JamRuby::PosaCardType' do

View File

@ -241,6 +241,47 @@ describe "Monthly Recurring Lesson Flow" do
user.reload
user.remaining_test_drives.should eql 0
UserMailer.deliveries.length.should eql 0 # one for student
booking.remaining_roll_forward_amount_in_cents.should eql 0
lesson_purchase.remaining_roll_forward_amount_in_cents.should be_nil
lesson_purchase.expected_session_times.should eql 2
lesson_purchase.actual_session_times.should be_nil
Timecop.travel(Date.new(2016, 4, 1))
LessonBooking.adjust_for_missed_sessions
# we should find remaining/totals in lesson purcahse as well as booking rolled up
booking.reload
lesson_purchase.reload
lesson_purchase.actual_session_times.should eql 1
lesson_purchase.expected_session_times.should eql 2
booking.remaining_roll_forward_amount_in_cents.should eql (lesson_purchase.price * 100 * (1.0/2.0)).round
lesson_purchase.remaining_roll_forward_amount_in_cents.should eql (lesson_purchase.price * 100 * (1.0/2.0)).round
lesson_purchase.total_roll_forward_amount_in_cents.should eql (lesson_purchase.price * 100 * (1.0/2.0)).round
# but once we bill out, we'll not credit as heavily
LessonBooking.bill_monthlies
booking.reload
lesson_purchase.reload
lesson_purchase.actual_session_times.should eql 1
lesson_purchase.expected_session_times.should eql 2
booking.remaining_roll_forward_amount_in_cents.should eql 0
lesson_purchase.remaining_roll_forward_amount_in_cents.should eql 0
lesson_purchase.total_roll_forward_amount_in_cents.should eql (lesson_purchase.price * 100 * (1.0/2.0)).round
booking.lesson_package_purchases.count.should eql 2
next_purchase = booking.lesson_package_purchases.order(:created_at)[1]
next_purchase.remaining_roll_forward_amount_in_cents.should be nil
next_purchase.total_roll_forward_amount_in_cents.should be nil
next_purchase.expected_session_times.should eql 4
next_purchase.actual_session_times.should be_nil
next_purchase.teacher_distributions.count.should eql 1
distribution = next_purchase.teacher_distributions[0]
distribution.amount_in_cents.should eql (3000 - 750) # booked price is 30. one lesson is 7.50
distribution.teacher_fee_in_cents.should eql ((3000 - 750) * 0.28).round # booked price is 30. one lesson is 7.50. take out .28
distribution.reduced_roll_forward_amount_in_cents.should eql 750
next_purchase.reduced_roll_forward_amount_in_cents.should eql 750
end
it "works (school on school education)" do

View File

@ -16,7 +16,7 @@ describe "TestDrive Lesson Flow" do
let(:affiliate_partner) { FactoryGirl.create(:affiliate_partner) }
let(:affiliate_partner2) { FactoryGirl.create(:affiliate_partner, lesson_rate: 0.30) }
let(:school) { FactoryGirl.create(:school) }
let(:card_lessons) {FactoryGirl.create(:posa_card, card_type: JamRuby::PosaCardType::JAM_CLASS_4)}
let(:card_lessons) {FactoryGirl.create(:posa_card_lesson_4)}
let(:retailer) {FactoryGirl.create(:retailer)}
before {
@ -271,6 +271,7 @@ describe "TestDrive Lesson Flow" do
booking.user.should eql user
booking.sent_notices.should be_true
booking.posa_card.should eql card_lessons
booking.posa_card_purchased.should be_true
user.unprocessed_test_drive.should be_nil
teacher_user.has_booked_test_drive_with_student?(user).should be_true

View File

@ -0,0 +1,24 @@
require 'spec_helper'
describe LessonPackageType do
it "test drive packages" do
# 3 builtt-in, 3 amazon
LessonPackageType.test_drive_package_ids.count.should eql 6
end
describe "amazon price" do
it "4-count" do
LessonPackageType.amazon_test_drive_free_4.price.should eql 0
end
it "2-count" do
LessonPackageType.amazon_test_drive_free_2.price.should eql 0
end
it "4-count reduced" do
LessonPackageType.amazon_test_drive_paid_4.price.should eql 19.99
end
end
end

View File

@ -26,7 +26,9 @@ describe LessonSessionMonthlyPrice do
booking.booked_price.should eql 30.00
booking.booked_price.should eql(LessonSessionMonthlyPrice.price(booking, jan1))
price, times = LessonSessionMonthlyPrice.price(booking, jan1)
booking.booked_price.should eql(price)
times.should eql(5)
end
it "middle of the month" do
@ -38,7 +40,10 @@ describe LessonSessionMonthlyPrice do
booking.booked_price.should eql 30.00
((booking.booked_price * 0.75).round(2)).should eql(LessonSessionMonthlyPrice.price(booking, jan17))
price, times = LessonSessionMonthlyPrice.price(booking, jan17)
((booking.booked_price * 0.75).round(2)).should eql(price)
times.should eql(3)
end
it "end of the month which has a last billable day based on slot" do
@ -50,7 +55,9 @@ describe LessonSessionMonthlyPrice do
booking.booked_price.should eql 30.00
((booking.booked_price * 0.25).round(2)).should eql(LessonSessionMonthlyPrice.price(booking, jan17))
price, times = LessonSessionMonthlyPrice.price(booking, jan17)
((booking.booked_price * 0.25).round(2)).should eql(price)
times.should eql(1)
end
it "end of the month which is not a last billable days" do
@ -62,7 +69,9 @@ describe LessonSessionMonthlyPrice do
booking.booked_price.should eql 30.00
((booking.booked_price * 0.0).round(2)).should eql(LessonSessionMonthlyPrice.price(booking, feb29))
price, times = LessonSessionMonthlyPrice.price(booking, feb29)
((booking.booked_price * 0.0).round(2)).should eql(price)
times.should eql(0)
end
end
end

View File

@ -5,7 +5,8 @@ describe PosaCard do
let(:user) {FactoryGirl.create(:user)}
let(:card) {FactoryGirl.create(:posa_card)}
let(:card2) {FactoryGirl.create(:posa_card)}
let(:card_lessons) {FactoryGirl.create(:posa_card, card_type: JamRuby::PosaCardType::JAM_CLASS_4)}
let(:card_lessons) {FactoryGirl.create(:posa_card_lesson_4)}
let(:card_lessons_2) {FactoryGirl.create(:posa_card_lesson_2)}
let(:retailer) {FactoryGirl.create(:retailer)}
it "created by factory" do
card.touch
@ -42,10 +43,12 @@ describe PosaCard do
end
describe "claim" do
it "succeeds" do
it "succeeds simple" do
PosaCard.activate(card, retailer)
card.reload
card.claim(user)
user.reload
user.jamclass_credits.should be 0
card.errors.any?.should be false
card.claimed_at.should_not be_nil
@ -56,16 +59,33 @@ describe PosaCard do
PosaCard.activate(card_lessons, retailer)
card_lessons.reload
card_lessons.claim(user)
user.jamclass_credits.should be 4
card_lessons.errors.any?.should be false
card_lessons.claimed_at.should_not be_nil
card_lessons.user.should eql user
card_lessons.reload
card_lessons.lesson_package_purchase.should_not be_nil
card_lessons.lesson_package_purchase.lesson_package_type.should eql LessonPackageType.test_drive_4
card_lessons.lesson_package_purchase.lesson_package_type.should eql card_lessons.lesson_package_type
card_lessons.lesson_package_purchase.posa_card.should eql card_lessons
end
it "succeeds with jamclass type 2-count" do
PosaCard.activate(card_lessons_2, retailer)
card_lessons_2.reload
card_lessons_2.claim(user)
user.reload
user.jamclass_credits.should be 2
card_lessons_2.errors.any?.should be false
card_lessons_2.claimed_at.should_not be_nil
card_lessons_2.user.should eql user
card_lessons_2.reload
card_lessons_2.lesson_package_purchase.should_not be_nil
card_lessons_2.lesson_package_purchase.lesson_package_type.should eql card_lessons_2.lesson_package_type
card_lessons_2.lesson_package_purchase.posa_card.should eql card_lessons_2
end
it "associates student automatically for GC" do
gc = GuitarCenter.init
gc_owner = gc[:user]
@ -83,7 +103,7 @@ describe PosaCard do
card_lessons.user.should eql user
card_lessons.reload
card_lessons.lesson_package_purchase.should_not be_nil
card_lessons.lesson_package_purchase.lesson_package_type.should eql LessonPackageType.test_drive_4
card_lessons.lesson_package_purchase.lesson_package_type.should eql card_lessons.lesson_package_type
card_lessons.lesson_package_purchase.posa_card.should eql card_lessons
end
@ -120,16 +140,16 @@ describe PosaCard do
end
it "can't be within one year" do
PosaCard.activate(card, retailer)
card.reload
card.claim(user)
PosaCard.activate(card_lessons, retailer)
card_lessons.reload
card_lessons.claim(user)
PosaCard.activate(card2, retailer)
card2.reload
card2.claim(user)
PosaCard.activate(card_lessons_2, retailer)
card_lessons_2.reload
card_lessons_2.claim(user)
card2.errors.any?.should be true
card2.errors[:user].should eql ['was within 1 year']
card_lessons_2.errors.any?.should be true
card_lessons_2.errors[:claimed_at].should eql ['was within 1 year']
end
end
end

View File

@ -889,6 +889,7 @@ describe Sale do
end
it "can succeed with no booking; just intent" do
user.desired_package = LessonPackageType.test_drive_4
intent = TeacherIntent.create(user, teacher, 'book-test-drive')
token = create_stripe_token
result = user.payment_update({token: token, zip: '78759', test_drive: true})
@ -912,6 +913,7 @@ describe Sale do
end
it "will reject second test drive purchase" do
user.desired_package = LessonPackageType.test_drive_4
intent = TeacherIntent.create(user, teacher, 'book-test-drive')
token = create_stripe_token
result = user.payment_update({token: token, zip: '78759', test_drive: true})

View File

@ -710,7 +710,7 @@ describe User do
user.age.should == 9
user.birth_date = nil
user.age.should == ""
user.age.should == nil
end
end
@ -730,7 +730,7 @@ describe User do
end
it "allow no_show aggregation" do
it "allow no_show aggr egation" do
user.mod_merge({"no_show" => {"some_screen1" => true}})
user.save!
user.reload

View File

@ -19,6 +19,10 @@ def app_config
'alerts@jamkazam.com'
end
def email_jamclass_alerts_alias
'jamclass-alerts@jamkazam.com'
end
def email_crashes_alias
'clientcrash@jamkazam.com'
end

View File

@ -11,6 +11,10 @@
context.JK.Banner.showAlert(
{ title: "Close JamKazam Application",
buttons: [
{name: 'CANCEL SHUTDOWN', click: function() {
logger.debug("'CANCEL SHUTDOWN' selected")
context.JK.Banner.hide();
}},
{name: 'COMPLETELY SHUT DOWN THE APP', click: function() {
logger.debug("'COMPLETELY SHUT DOWN THE APP' selected")
context.jamClient.ShutdownApplication()

View File

@ -100,7 +100,7 @@ UserStore = context.UserStore
userDetailDone: (response) ->
if response.id == @state.teacherId
#school_on_school = response.teacher.school_id? && @state.user?.school_id? && response.teacher.school_id == @state.user.school_id && !response.teacher.school.education
@setState({teacher: response, isSelf: response.id == context.JK.currentUserId, school_on_school: school_on_school})
@setState({teacher: response, isSelf: response.id == context.JK.currentUserId})
else
logger.debug("BookLesson: ignoring teacher details", response.id, @state.teacherId)
@ -235,7 +235,7 @@ UserStore = context.UserStore
@setState({updating: false})
UserActions.refresh()
#if response.user['has_stored_credit_card?'] || @state.school_on_school || response.posa_card_id?
if response.user['has_stored_credit_card?'] || response.posa_card_id?
if response.user['has_stored_credit_card?'] || (response.posa_card_id? && response.posa_card_purchased)
context.JK.Banner.showNotice("Lesson Requested","The teacher has been notified of your lesson request, and should respond soon.<br/><br/>We've taken you back to the JamClass home page, where you can check the status of this lesson, as well as any other past and future lessons.")
url = "/client#/jamclass/lesson-booking/#{response.id}"
url = "/client#/jamclass"
@ -302,13 +302,14 @@ UserStore = context.UserStore
else
for minutes in enabledMinutes
lesson_price = teacher["price_per_lesson_#{minutes}_cents"]
if lesson_price
value = "single|#{minutes}"
display = "#{minutes} Minute Lesson Each Week - $#{(lesson_price / 100).toFixed(2)} Per Week"
results.push(`<option key={value} value={value}>{display}</option>`)
for minutes in enabledMinutes
monthly_price = teacher["price_per_month_#{minutes}_cents"]
if monthly_price
value = "monthly|#{minutes}"
display = "#{minutes} Minute Lesson Each Week - $#{(monthly_price / 100).toFixed(2)} Per Month"
results.push(`<option value={value}>{display}</option>`)
@ -348,8 +349,8 @@ UserStore = context.UserStore
am_pm = [`<option key="AM" value="AM">AM</option>`, `<option key="PM" value="PM">PM</option>`]
bookLessonClasses = classNames({"button-orange": true, 'book-lesson-btn': true, disabled: !this.state.teacher? && !@state.updating})
cancelClasses = classNames({"button-grey": true, disabled: !this.state.teacher? && !@state.updating})
bookLessonClasses = classNames({"button-orange": true, 'book-lesson-btn': true, disabled: !this.state.teacher? || @state.updating})
cancelClasses = classNames({"button-grey": true, disabled: !this.state.teacher? || @state.updating})
descriptionErrors = context.JK.reactSingleFieldErrors('description', @state.descriptionErrors)
bookedPriceErrors = context.JK.reactSingleFieldErrors('booked_price', @state.bookedPriceErrors)

View File

@ -233,6 +233,11 @@ LessonTimerActions = context.LessonTimerActions
rescheduleLesson: (lesson) ->
if lesson.recurring
buttons = []
buttons.push({
name: 'CANCEL',
buttonStyle: 'button-grey',
click: (() => (logger.debug("cancelling out of reschedule dialog")))
})
buttons.push({
name: 'THIS LESSON',
buttonStyle: 'button-orange',

View File

@ -39,7 +39,7 @@ UserStore = context.UserStore
return
slot.slotTime = @slotTime(slot, booking)
slot.is_recurring = @isRecurring()
slot.is_recurring = slot.slot_type == 'recurring'
if slot['is_teacher_created?']
slot.creatorRole = 'teacher'
slot.creatorRoleRelative = 'teacher'
@ -169,6 +169,7 @@ UserStore = context.UserStore
@focusedLesson()?
focusedLesson: () ->
console.log("focusedlesos", this.state?.booking?.focused_lesson)
this.state?.booking?.focused_lesson
updateBookingState: (booking) ->
@ -425,10 +426,22 @@ UserStore = context.UserStore
false
isApproved: () ->
if @hasFocusedLesson()
@focusedLesson().status == 'approved'
else
@state.booking?.status == 'approved'
isCanceled: () ->
@state.booking?.status == 'canceled'
cancelled = @state.booking?.status == 'canceled'
if cancelled
# if the booking is canceelled, lessons are done too. No need to check the focused lesson
return true
else
if @hasFocusedLesson()
return @focusedLesson().status == 'canceled'
else
cancelled
isSuspended: () ->
@state.booking?.status == 'suspended'
@ -498,13 +511,15 @@ UserStore = context.UserStore
text = "Preferred day/time for lesson is #{this.slotTime(defaultSlot)}. Secondary option is #{this.slotTime(altSlot)}."
slotTime: (slot, booking = this.state.booking) ->
if @hasFocusedLesson() || !@isRecurring(booking)
if @hasFocusedLesson() || slot.slot_type == 'single' #!@isRecurring(booking)
slot.pretty_start_time
else
"#{this.dayOfWeek(slot)} at #{this.dayTime(slot)}"
slotTimePhrase: (slot) ->
if @isRecurring()
console.log("SLOT", slot)
if slot.slot_type == 'recurring'
# if @isRecurring()
"each " + @slotTime(slot)
else
@slotTime(slot)
@ -709,6 +724,10 @@ UserStore = context.UserStore
header = 'respond to lesson request'
content = @renderTeacherRequested()
else if @isApproved()
header = @approvedHeader()
content = @renderTeacherApproved()
else if @isCounter()
if @isTeacherCountered()
header = 'your proposed alternate day/time is still pending'
@ -716,10 +735,6 @@ UserStore = context.UserStore
header = 'student has proposed an alternate day/time'
content = @renderTeacherCountered()
else if @isApproved()
header = @approvedHeader()
content = @renderTeacherApproved()
else if @isCompleted()
header = @completedHeader()
content = @renderTeacherComplete()
@ -750,6 +765,11 @@ UserStore = context.UserStore
header = 'your lesson has been requested'
content = @renderStudentRequested()
else if @isApproved()
header = @approvedHeader()
content = @renderStudentApproved()
else if @isCounter()
if @isTeacherCountered()
header = 'teacher has proposed an alternate day/time'
@ -757,10 +777,6 @@ UserStore = context.UserStore
header = 'your proposed alternate day/time is still pending'
content = @renderTeacherCountered()
else if @isApproved()
header = @approvedHeader()
content = @renderStudentApproved()
else if @isCompleted()
header = @completedHeader()
content = @renderStudentComplete()

View File

@ -251,7 +251,7 @@
proposeAltLabelText = "Propose alternate day/time"
for slot, i in @props.slots
if this.props.is_recurring
if slot.is_recurring
slotDetail = `<div className="slot-detail">Each {slot.slotTime}</div>`
else
slotDetail = `<div className="slot-detail">{slot.slotTime}</div>`

View File

@ -307,33 +307,31 @@ UserStore = context.UserStore
if response.test_drive?
# ok, they bought a package
if response.lesson_package_type?
# always of form test-drive-#
prefixLength = "test-drive-".length
packageLength = response.lesson_package_type.package_type.length
testDriveCount = response.lesson_package_type.package_type.substring(prefixLength, packageLength)
testDriveCount = response.lesson_package_type.credits
logger.debug("testDriveCount: " + testDriveCount)
testDriveCountInt = parseInt(testDriveCount);
if context._.isNaN(testDriveCountInt)
testDriveCountInt = 3
context.JK.GA.trackTestDrivePurchase(testDriveCount);
context.JK.GA.trackTestDrivePurchase(testDriveCountInt);
if response.test_drive?.teacher_id
teacher_id = response.test_drive.teacher_id
if testDriveCount == '1'
if response.package?
logger.debug("packaged TD purchase...")
# the user bought a package that already includes the 1, 2, or 4 teachers as part of it
if testDriveCount == 1
text = "You have purchased 1 TestDrive credit and have used it to request a JamClass with #{@state.package.teachers[0].user.name}. The teacher has received your request and should respond shortly."
else if response.package?
text = "Each teacher has received your request and should respond shortly."
else
text = "You have purchased #{testDriveCount} TestDrive credits and have used 1 credit to request a JamClass with #{@state.teacher.name}. The teacher has received your request and should respond shortly."
location = "/client#/jamclass"
else
if @state.teacher?.id
# the user bought the testdrive, and there is a teacher of interest in context (but no booking)
text = "Each teacher has received your request and should respond shortly."
location = "/client#/jamclass"
else if response.lesson?
logger.debug("TD with a lesson booking...")
# there is a lesson booking in context, so the user has gotten a credit + gotten a lesson request going forward
text = "You have purchased #{testDriveCount} TestDrive credits and have used 1 credit to request a JamClass with #{@state.teacher?.name}. The teacher has received your request and should respond shortly."
location = "/client#/jamclass"
else if response.teacher?.id
logger.debug("straight TD package + teacher interest")
# the user bought a test drive straight up with no lesson-booking, but previously showed interest in a teacher
teacher_id = response.teacher.id
if testDriveCount == '1'
text = "You now have 1 TestDrive credit.<br/><br/>We've taken you to the lesson booking screen for the teacher you initially showed interest in."
location = "/client#/jamclass/book-lesson/test-drive_" + teacher_id
@ -341,8 +339,9 @@ UserStore = context.UserStore
text = "You now have #{testDriveCount} TestDrive credits that you can take with #{testDriveCount} different teachers.<br/><br/>We've taken you to the lesson booking screen for the teacher you initially showed interest in."
location = "/client#/jamclass/book-lesson/test-drive_" + teacher_id
else
# the user bought test drive, but 'cold' , i.e., no teacher in context
if testDriveCount == '1'
logger.debug("straight TD package")
# the user bought test drive, but 'cold' , and, no teacher in context
if testDriveCount == 1
text = "You now have 1 TestDrive credit.<br/><br/>We've taken you to the Teacher Search screen, so you can search for teachers right for you."
location = "/client#/teachers/search"
else
@ -495,14 +494,24 @@ UserStore = context.UserStore
else
alert("unknown package type")
else
if this.state.user.lesson_package_type_id == 'test-drive'
# the UI is currenty the same whether the user is browsing around indicating what type of TD they want, or
# if they bought a POSA card required puchase, and have the this.state.user.lesson_package_needs_purchase_id
# we just need to figure out which is set, giving preference to the POSA requiring purchase
lesson_package_type_id = this.state.user.lesson_package_needs_purchase_id
if !lesson_package_type_id?
lesson_package_type_id = this.state.user.lesson_package_type_id
if lesson_package_type_id == 'test-drive'
explanation = `<span>You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entitles you to take 4 private online music lessons - 1 each from 4 different instructors in the JamClass instructor community. The price of this TestDrive package is $49.99.</span>`
else if this.state.user.lesson_package_type_id == 'test-drive-1'
else if lesson_package_type_id == 'test-drive-1'
explanation =`<span>You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entitles you to take 1 private online music lesson from an instructor in the JamClass instructor community. The price of this TestDrive package is $14.99.</span>`
else if this.state.user.lesson_package_type_id == 'test-drive-2'
else if lesson_package_type_id == 'test-drive-2'
explanation =`<span>You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entitles you to take 2 private online music lessons - 1 each from 2 different instructors in the JamClass instructor community. The price of this TestDrive package is $29.99.</span>`
else if lesson_package_type_id == 'amazon-test-drive-paid-4'
explanation = `<span>You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entitles you to take 4 private online music lessons - 1 each from 4 different instructors in the JamClass instructor community. The price of this TestDrive package is $19.99.</span>`
else
alert("You do not have a test drive package selected: " + this.state.user.lesson_package_type_id )
explanation = `<span>Loading your TestDrive packaging info...</span>`
#alert("You do not have a test drive package selected: " + this.state.user.lesson_package_type_id )
bookingDetail = `<p>{explanation}
@ -531,7 +540,7 @@ UserStore = context.UserStore
</p>`
else if this.state.lesson.payment_style == 'weekly'
bookingInfo = `<p>You are booking a weekly recurring series of {lesson_length}-minute
lessons, to be paid individually as each lesson is taken, until cancelled.</p>`
lessons for ${this.bookedPrice()}, to be paid individually as each lesson is taken, until cancelled.</p>`
bookingDetail = `<p>
Your card will be charged on the day of each lesson. If you need to cancel a lesson, you must do so at
least 24 hours before the lesson is scheduled, or you will be charged for the lesson in full.
@ -542,7 +551,7 @@ UserStore = context.UserStore
</p>`
else if this.state.lesson.payment_style == 'monthly'
bookingInfo = `<p>You are booking a weekly recurring series of {lesson_length}-minute
lessons, to be paid for monthly until cancelled.</p>`
lessons for ${this.bookedPrice()}, to be paid for monthly until cancelled.</p>`
bookingDetail = `<p>
Your card will be charged on the first day of each month. Canceling individual lessons does not earn a
refund when buying monthly. To cancel, you must cancel at least 24 hours before the beginning of the

View File

@ -81,7 +81,7 @@ proficiencyDescriptionMap = {
# mount it
@profileClipboard = new Clipboard($profileLink.get(0), {
text: =>
return context.JK.makeAbsolute('/client#/teacher/profile/' + @state.user.teacher?.id)
return context.JK.makeAbsolute('/client#/teacher/profile/' + @state.user.id)
})
else if $profileLink.length == 0 && @profileClipboard?
@profileClipboard.destroy()
@ -245,10 +245,15 @@ proficiencyDescriptionMap = {
biography = biography.replace(/\n/g, "<br/>")
`<div className="section bio">
<a className="copy-profile-link button-orange" href='#' onClick={this.copyProfileLink}>
# make button available only if this is the current user viewing
if @state.userId == @user?.id
copyUrlToClipboard = `<a className="copy-profile-link button-orange" href='#' onClick={this.copyProfileLink}>
COPY PROFILE URL TO CLIPBOARD
</a><h3>Teacher Profile {this.editProfileLink('edit profile', 'introduction')}</h3>
</a>`
`<div className="section bio">
{copyUrlToClipboard}<h3>Teacher Profile {this.editProfileLink('edit profile', 'introduction')}</h3>
<div className="section-content">
<div dangerouslySetInnerHTML={{__html: biography}}></div>
</div>
@ -696,6 +701,17 @@ proficiencyDescriptionMap = {
else
age = null
backgroundCheck = null
backgroundCheckAt = @state.user?.teacher?.background_check_at
if backgroundCheckAt
backgroundCheck =
`<div className="backgroundCheck">
<div>Background Check:</div>
<div className="background-check-time">{context.JK.formatDateShort(new Date(backgroundCheckAt))} <img src="/assets/content/background-check.png" /></div>
</div>`
`<div className="profile-about-left">
<div className="left-content">
<div className="location">
@ -709,6 +725,8 @@ proficiencyDescriptionMap = {
<div className="last-signed-in">Last Signed In:</div>
<div>{"very recently"}</div>
</div>
{backgroundCheck}
</div>
</div>`

View File

@ -137,10 +137,10 @@ ProfileActions = @ProfileActions
if @state.next == null
@contentBodyScroller.off('scroll')
if @state.currentPage == 1 and @state.results.length == 0
if @state.currentPage == 1 and @state.results.length == 0 && !@state.searching
@endOfList.text('No Teachers found matching your search').show()
logger.debug("TeacherSearch: empty search")
else if @state.currentPage > 0
else if @state.currentPage > 0 && !@state.searching
logger.debug("end of search")
@endOfList.text('No more Teachers').show()
else
@ -238,13 +238,16 @@ ProfileActions = @ProfileActions
bookSingleBtn = null
bookTestDriveBtn = null
backgroundCheck = null
if user.teacher.background_check_at
backgroundCheck = `<img className="background-check" src="/assets/content/background-check.png" />`
if !school_on_school && (!@state.user? || @state.user.jamclass_credits > 0 || @state.user.remaining_test_drives > 0 || @state.user['can_buy_test_drive?'])
bookTestDriveBtn = `<a className="button-orange try-test-drive" onClick={this.bookTestDrive.bind(this, user)}>BOOK TESTDRIVE LESSON</a>`
else
bookSingleBtn = `<a className="button-orange try-normal" onClick={this.bookNormalLesson.bind(this, user)}>BOOK LESSON</a>`
resultsJsx.push(`<div key={user.id} className="teacher-search-result" data-teacher-id={user.id}>
<div className="user-avatar">
{backgroundCheck}
<div className="avatar small">
<img src={photo_url} />
</div>

View File

@ -0,0 +1,200 @@
context = window
rest = context.JK.Rest()
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
badCode = 'This is not a valid code. Please carefully re-enter the code and try again. If it still does not work, please email us at support@jamkazam.com to report this problem.'
@AccountActivatePage = React.createClass({
render: () ->
if this.state.formErrors?
for key, value of this.state.formErrors
break
errorText = context.JK.getFullFirstError(key, @state.formErrors, {email: 'Email', password: 'Password', gift_card: 'Coupon Code', posa_cards: 'Coupon Code', 'terms_of_service' : 'The terms of service', claimed_at: "Last coupon code used"})
if errorText? && errorText.indexOf('does not exist') > -1
errorText = 'This is not a valid code. Please carefully re-enter the code and try again. If it still does not work, please email us at support@jamkazam.com to report this problem.'
if errorText? && errorText.indexOf('must already be set') > -1
errorText = 'This card has not been activated by a retailer and cannot currently be used. If you purchased this card from a store, please return to the store and have the store activate the card. Only the store where you purchased this card can activate it.'
if errorText? && errorText.indexOf('already claimed') > -1
errorText = 'This card has already been claimed. If you believe this is in error, please email us at support@jamkazam.com to report this problem.'
buttonClassnames = classNames({'redeem-giftcard': true, 'button-orange': true, disabled: @state.processing || @state.done })
if @state.done
if this.state.gifted_jamtracks
button =
`<div key="done" className="done-action">
<div>You now have {this.state.gifted_jamtracks} JamTracks credits on your account!</div>
<div><a className="go-browse" href="/client#/jamtrack">go to JamTracks home page</a></div>
</div>`
else
if this.state.purchase_required
button =
`<div key="done" className="done-action jam-class purchase-needed">
<div>You have claimed <span className="amount-gifted">{this.state.jamclass_credits}</span> 30-minute JamClass credits, but still need to enter your credit card info!</div>
<div><a className="go-browse" href="/client#/jamclass/lesson-payment/test-drive">go to the lesson payment page</a></div>
</div>`
else
button =
`<div key="done" className="done-action jam-class all-done">
<div>You now have <span className="amount-gifted">{this.state.jamclass_credits}</span> 30-minute JamClass credits on your account!</div>
<div><a className="go-browse" href="/client#/jamclass/searchOptions">go to JamClass teacher search page</a></div>
</div>`
else
if context.JK.currentUserId?
if @state.processing
buttonText = 'ACTIVATING COUPON CODE...'
else
buttonText = 'ACTIVATE COUPON CODE'
else
if @state.processing
buttonText = 'ACTIVATING ACCOUNT...'
else
buttonText = 'ACTIVATE ACCOUNT'
button = `<button key="button" className={buttonClassnames} onClick={this.action}>{buttonText}</button>`
action = `<ReactCSSTransitionGroup transitionName="session-track-list">
{button}
</ReactCSSTransitionGroup>`
title = 'Activate Account'
if context.JK.currentUserId?
form =
`<form onSubmit={this.submit}>
<label>Coupon Code:</label><input type="text" name="code"/>
{action}
</form>`
instruments = `<p className="instructions">Enter the 10-digit code you received in your email and click the Activate button below.</p>`
else
form =
`<form onSubmit={this.submit}>
<label>Coupon Code:</label><input type="text" name="code"/>
<label>Email:</label><input type="text" name="email"/>
<label>Password:</label><input type="password" name="password"/>
<div className="clearall"/>
<input className="terms-checkbox" type="checkbox" name="terms" /><label className="terms-help">I have read and agree to the JamKazam <a href="/corp/terms" onClick={this.termsClicked}>terms of service</a></label>
<div className="clearall"/>
{action}
</form>`
instruments = `<p className="instructions">Enter the 10-digit code you received in your email and click the Activate button below.</p>`
classes = classNames({'redeem-container': true, 'not-logged-in': !context.JK.currentUserId?, 'logged-in': context.JK.currentUserId? })
`<div className={classes}>
<div className="redeem-content">
<h2>{title}</h2>
{instruments}
{form}
<div className={classNames({'errors': true, 'active': this.state.formErrors})}>
{errorText}
</div>
</div>
</div>`
getInitialState: () ->
{formErrors: null, processing:false, gifted_jamtracks: null, jamclass_credits: null, purchase_required: null}
privacyPolicy: (e) ->
e.preventDefault()
context.JK.popExternalLink('/corp/privacy')
termsClicked: (e) ->
e.preventDefault()
context.JK.popExternalLink('/corp/terms')
componentDidMount:() ->
$root = $(this.getDOMNode())
$checkbox = $root.find('.terms-checkbox')
console.log("$checkbox", $checkbox)
context.JK.checkbox($checkbox)
submit: (e) ->
@action(e)
action: (e) ->
if @state.done || @state.processing
e.preventDefault()
return
if context.JK.currentUserId?
@redeem(e)
else
@signup(e)
redeem: (e) ->
e.preventDefault()
return if @state.done || @state.processing
$root = $(@getDOMNode())
$code = $root.find('input[name="code"]')
code = $code.val()
@setState({processing:true})
rest.redeemGiftCard({gift_card: code})
.done((response) =>
@setState({formErrors: null, processing:false, done: true, gifted_jamtracks: response.gifted_jamtracks, jamclass_credits: response.jamclass_credits, purchase_required: response.purchase_required})
).fail((jqXHR) =>
@setState({processing:false})
if jqXHR.status == 422
response = JSON.parse(jqXHR.responseText)
if response.errors
@setState({formErrors: response.errors})
else
context.JK.app.notify({title: 'Unknown Error', text: jqXHR.responseText})
else
context.JK.app.notifyServerError(jqXHR, "Unable to Apply Coupon")
)
signup: (e) ->
e.preventDefault()
return if @state.done || @state.processing
$root = $(@getDOMNode())
$email = $root.find('input[name="email"]')
$code = $root.find('input[name="code"]')
$password = $root.find('input[name="password"]')
terms = $root.find('input[name="terms"]').is(':checked')
@setState({processing:true})
email = $email.val()
password = $password.val()
code = $code.val()
if !code
# must pass up non-null value to indicate user is trying to redeem giftcard while creating account
code = ''
rest.signup({email: email, password: password, gift_card: code, terms: terms})
.done((response) =>
@setState({formErrors: null, processing:false, done: true, gifted_jamtracks: response.gifted_jamtracks, jamclass_credits: response.jamclass_credits, purchase_required: response.purchase_required})
).fail((jqXHR) =>
@setState({processing:false})
if jqXHR.status == 422
response = JSON.parse(jqXHR.responseText)
if response.errors
@setState({formErrors: response.errors})
else
context.JK.app.notify({title: 'Unknown Signup Error', text: jqXHR.responseText})
else
context.JK.app.notifyServerError(jqXHR, "Unable to Sign Up")
)
})

View File

@ -34,6 +34,13 @@ badCode = 'This is not a valid code. Please carefully re-enter the code and try
<div>You now have {this.state.gifted_jamtracks} JamTracks credits on your account!</div>
<div><a className="go-browse" href="/client#/jamtrack">go to JamTracks home page</a></div>
</div>`
else
if this.state.purchase_required
button =
`<div key="done" className="done-action">
<div>You have claimed {this.state.gifted_jamclass} 30-minute JamClass credits, but still need to enter your credit card info!</div>
<div><a className="go-browse" href="/client#/jamclass/lesson-payment/test-drive">go to the lesson payment page</a></div>
</div>`
else
button =
`<div key="done" className="done-action">
@ -66,7 +73,7 @@ badCode = 'This is not a valid code. Please carefully re-enter the code and try
<div className="clearall"/>
{action}
</form>`
instruments = `<p className="instructions">Enter the 10-digit code from the back of your gift card and click the Redeem button below.</p>`
instruments = `<p className="instructions">Enter the 10-digit code from the back of your gift card and click the Redeem button below.</p>`
classes = classNames({'redeem-container': true, 'not-logged-in': !context.JK.currentUserId?, 'logged-in': context.JK.currentUserId? })
@ -121,10 +128,13 @@ badCode = 'This is not a valid code. Please carefully re-enter the code and try
$code = $root.find('input[name="code"]')
code = $code.val()
@setState({processing:true})
rest.redeemGiftCard({gift_card: code})
.done((response) =>
@setState({formErrors: null, processing:false, done: true, gifted_jamtracks: response.gifted_jamtracks, gifted_jamclass: response.gifted_jamclass})
@setState({formErrors: null, processing:false, done: true, gifted_jamtracks: response.gifted_jamtracks, gifted_jamclass: response.gifted_jamclass, purchase_required: response.purchase_required})
).fail((jqXHR) =>
@setState({processing:false})
@ -161,7 +171,7 @@ badCode = 'This is not a valid code. Please carefully re-enter the code and try
rest.signup({email: email, password: password, gift_card: code, terms: terms})
.done((response) =>
@setState({formErrors: null, processing:false, done: true, gifted_jamtracks: response.gifted_jamtracks, gifted_jamclass: response.gifted_jamclass})
@setState({formErrors: null, processing:false, done: true, gifted_jamtracks: response.gifted_jamtracks, gifted_jamclass: response.gifted_jamclass, purchase_required: response.purchase_required})
).fail((jqXHR) =>
@setState({processing:false})

View File

@ -41,9 +41,9 @@ teacherActions = window.JK.Actions.Teacher
else if lesson.status == 'canceled'
lesson.displayStatus = 'Canceled'
if lesson.student_canceled
lesson.displayStatus = 'Canceled (Student)'
lesson.displayStatus = 'Canceled (by Student)'
else if lesson.teacher_canceled
lesson.displayStatus = 'Canceled (Teacher)'
lesson.displayStatus = 'Canceled (by Teacher)'
else if lesson.status == 'suspended'
lesson.displayStatus = 'Suspended'
@ -60,15 +60,15 @@ teacherActions = window.JK.Actions.Teacher
if lesson.analysis?.teacher_analysis?.missed && lesson.analysis?.student_analysis?.missed
lesson.missedRole = 'both student and teacher'
lesson.missedUser = lesson.teacher
lesson.displayStatus = 'Missed (Both)'
lesson.displayStatus = 'Missed (by Both)'
else if lesson.analysis?.teacher_analysis?.missed
lesson.missedRole = 'the teacher'
lesson.missedUser = lesson.teacher
lesson.displayStatus = 'Missed (Teacher)'
lesson.displayStatus = 'Missed (by Teacher)'
else if lesson.analysis?.student_analysis?.missed
lesson.missedRole = 'the student'
lesson.missedUser = lesson.student
lesson.displayStatus = 'Missed (Student)'
lesson.displayStatus = 'Missed (by Student)'
else
lesson.displayStatus = 'Missed'

View File

@ -62,6 +62,14 @@
.backgroundCheck {
margin-top:20px;
img {
margin-left: 10px;
position: relative;
top: 4px;
}
.background-check-time {
margin-top:10px;
}
}
.introductory-video {

View File

@ -98,6 +98,11 @@
}
}
.background-check {
position: absolute;
top: 34px;
left: 24px;
}
.user-avatar {
text-align:center;
float:left;

View File

@ -0,0 +1,90 @@
@import "client/common";
body.web.account_activate {
h2 {
margin-bottom:20px;
}
label{
margin-bottom:4px;
color:$ColorTextTypical;
}
input{
margin-bottom:20px;
width:200px;
}
.redeem-container {
margin-left:350px;
width:400px;
padding-top:20px;
&.logged-in {
button {
margin-top:10px !important;
}
}
&.not-logged-in {
}
}
.redeem-content {
}
p.instructions {
line-height:125%;
color:$ColorTextTypical;
margin-bottom:20px;
}
button {
display:block !important;
height: 29px !important;
margin-bottom: 10px;
margin-right: 0px;
font-size: 16px !important;
padding: 7px 3px !important;
line-height:inherit !important;
margin-left:2px !important;
margin-top:15px;
}
.icheckbox_minimal {
float: left;
top: -2px;
margin-left: 0;
margin-right:10px;
}
.errors {
font-size:14px;
height:20px;
margin:0;
visibility: hidden;
color: red;
font-weight: bold;
&.active {
visibility: visible;
}
}
form {
margin-bottom:20px;
}
.terms-help {
float:left;
margin-top:-5px;
font-size:12px;
width:178px;
}
.done-action {
margin-top: 20px;
line-height: 125%;
}
}

View File

@ -343,6 +343,35 @@ class ApiMusicSessionsController < ApiController
end
render :json => {}, :status => :ok
end
begin
lesson_link = nil
session_link = @history.music_session.admin_url
if @history.music_session.lesson_session
session_type = "Lesson"
lesson_link = @history.music_session.lesson_session.admin_url
else
session_type = "Session"
end
subject = "#{current_user.name} Rated Their #{session_type}!"
body = "Session Type: #{session_type}\n"
body << "Session Rating: #{@history.good_rating? ? "Good" : "Bad"}\n"
body << "User: #{current_user.email}\n"
body << "Music Session URL: #{session_link}\n"
if lesson_link
body << "Lesson URL: #{lesson_link}\n"
end
body << "Session Comments: #{@history.rating_comment}\n"
AdminMailer.jamclass_alerts({subject: subject, body: body}).deliver_now
rescue Exception => e
logger.error("Exception sending out ratings email. Boo #{e}")
end
elsif request.get?
render :json => { :should_rate_session => @history.should_rate_session? }, :status => :ok
end

View File

@ -25,9 +25,10 @@ class ApiUsersController < ApiController
end
def calendar
@user=lookup_user
ics = CalendarManager.new.create_ics_feed(@user)
send_data ics, :filename => 'JamKazam', :disposition => 'inline', :type => "text/calendar"
#@user=lookup_user
#ics = CalendarManager.new.create_ics_feed(@user)
#send_data ics, :filename => 'JamKazam', :disposition => 'inline', :type => "text/calendar"
render :json => {}, :status => 200
end
def show
@ -1015,12 +1016,12 @@ class ApiUsersController < ApiController
if @posa_card.errors.any?
respond_with_model(@posa_card)
else
if @posa_card.card_type == PosaCard::JAM_CLASS_4
render json: {gifted_jamclass: 4}, status: 200
if @posa_card.is_lesson_posa_card?
render json: {gifted_jamclass: @posa_card.credits}, status: 200
elsif @posa_card.card_type == PosaCard::JAM_TRACKS_10
render json: {gifted_jamtracks: 10}, status: 200
render json: {gifted_jamtracks: @posa_card.credits}, status: 200
elsif @posa_card.card_type == PosaCard::JAM_TRACKS_5
render json: {gifted_jamtracks: 5}, status: 200
render json: {gifted_jamtracks: @posa_card.credits}, status: 200
else
raise 'unknown card_type ' + @posa_card.card_type
end

View File

@ -310,6 +310,12 @@ class LandingsController < ApplicationController
render 'redeem_giftcard', layout: 'web'
end
def account_activate
@no_landing_tag = true
@landing_tag_play_learn_earn = true
render 'account_activate', layout: 'web'
end
def buy_gift_card
@no_landing_tag = true
@landing_tag_play_learn_earn = true

View File

@ -1,6 +1,6 @@
object @lesson_booking
attributes :id, :status, :lesson_type, :payment_style, :recurring, :teacher_id, :description, :lesson_length, :created_at, :user_id, :active, :accepter_id, :canceler_id, :cancel_message, :booked_price, :card_presumed_ok, :no_slots, :posa_card_id
attributes :id, :status, :lesson_type, :payment_style, :recurring, :teacher_id, :description, :lesson_length, :created_at, :user_id, :active, :accepter_id, :canceler_id, :cancel_message, :booked_price, :card_presumed_ok, :no_slots, :posa_card_id, :posa_card_purchased, :remaining_roll_forward_amount_in_cents
child(:lesson_booking_slots => :slots) {
attributes :id, :preferred_day, :day_of_week, :hour, :minute, :slot_type, :pretty_scheduled_start, :message, :pretty_start_time, :proposer_id, :is_student_created?, :is_teacher_created?, :timezone, :pretty_timezone, :from_package

View File

@ -4,7 +4,7 @@ object @lesson_session
:status, :student_canceled, :teacher_canceled, :student_canceled_at, :teacher_canceled_at, :student_canceled_reason,
:teacher_canceled_reason, :status, :success, :teacher_unread_messages, :student_unread_messages, :is_active?, :recurring,
:analysed, :school_on_school?, :no_school_on_school_payment?, :payment_if_school_on_school?, :teacher_id, :student_id, :pretty_scheduled_start, :scheduled_start, :teacher_short_canceled,
:best_display_time
:best_display_time, :remaining_roll_forward_amount_in_cents
node do |lesson_session|
{

View File

@ -1,15 +1,18 @@
object @lesson
if @lesson
if @lesson # is a LessonBooking
node :lesson do |lesson|
{id: @lesson.id }
{
id: @lesson.id,
teacher_id: @lesson.teacher_id
}
end
end
if @test_drive
if @test_drive # is a Sale object
node :test_drive do |lesson|
{teacher_id: @test_drive.id}
{id: @test_drive.id}
end
end
@ -29,7 +32,7 @@ end
if @lesson_package_type
node :lesson_package_type do |lesson_package_type|
{package_type: @lesson_package_type.package_type}
{package_type: @lesson_package_type.package_type, credits: @lesson_package_type.test_drive_count}
end
end

View File

@ -36,7 +36,8 @@ attributes :id,
:test_drives_per_week,
:errors,
:profile_pct,
:school_id
:school_id,
:background_check_at
child :review_summary => :review_summary do

View File

@ -34,7 +34,7 @@ end
# give back more info if the user being fetched is yourself
if current_user && @user == current_user
attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :show_whats_next_count, :subscribe_email, :auth_twitter, :new_notifications, :sales_count, :reuse_card, :purchased_jamtracks_count, :first_downloaded_client_at, :created_at, :first_opened_jamtrack_web_player, :gifted_jamtracks, :has_redeemable_jamtrack, :remaining, :has_stored_credit_card?, :remaining_test_drives, :jamclass_credits, :can_buy_test_drive?, :lesson_package_type_id, :school_id, :is_guitar_center_student?
attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :show_whats_next_count, :subscribe_email, :auth_twitter, :new_notifications, :sales_count, :reuse_card, :purchased_jamtracks_count, :first_downloaded_client_at, :created_at, :first_opened_jamtrack_web_player, :gifted_jamtracks, :has_redeemable_jamtrack, :remaining, :has_stored_credit_card?, :remaining_test_drives, :jamclass_credits, :can_buy_test_drive?, :lesson_package_type_id, :school_id, :is_guitar_center_student?, :purchase_required, :lesson_package_needs_purchase_id
node :owned_school_id do |user|
user.owned_school.id if user.owned_school

View File

@ -4,7 +4,7 @@ object @user
attributes :id, :first_name, :last_name, :name, :photo_url
child :teacher do |teacher|
attributes :id, :biography, :school_id
attributes :id, :biography, :school_id, :background_check_at
child :school do |school|
attributes :id, :education

View File

@ -0,0 +1,5 @@
- provide(:page_name, 'landing_page full account_activate')
- provide(:description, 'Here you can redeem a code and associate it with your JamKazam account.')
- provide(:title, 'Activate Account')
= react_component 'AccountActivatePage'

View File

@ -251,6 +251,7 @@ if defined?(Bundler)
config.email_social_alias = 'social@jamkazam.com'
config.email_crashes_alias = 'clientcrash@jamkazam.com'
config.email_alerts_alias = 'alerts@jamkazam.com' # should be used for 'oh no' server down/service down sorts of emails
config.email_jamclass_alerts_alias= 'jamclass-alerts@jamkazam.com'
config.email_generic_from = 'nobody@jamkazam.com'
config.email_recurly_notice = 'recurly-alerts@jamkazam.com'
config.email_smtp_address = 'smtp.sendgrid.net'

View File

@ -90,6 +90,7 @@ SampleApp::Application.configure do
config.minimal_curtain = true
config.video_available= ENV['VIDEO_AVAILABILITY'] || "full"
config.email_generic_from = 'nobody-dev@jamkazam.com'
config.email_jamclass_alerts_alias= ENV['ALERT_EMAIL'] || 'jamclass-alerts-dev@jamkazam.com'
config.email_alerts_alias = ENV['ALERT_EMAIL'] || 'alerts-dev@jamkazam.com'
config.email_crashes_alias = ENV['ALERT_EMAIL'] || 'clientcrash-dev@jamkazam.com'
config.email_social_alias = ENV['ALERT_EMAIL'] || 'social-dev@jamkazam.com'

View File

@ -53,7 +53,7 @@ SampleApp::Application.configure do
config.websocket_gateway_enable = false
config.websocket_gateway_port = 6759
config.websocket_gateway_uri = "ws://localhost:#{config.websocket_gateway_port}/websocket"
config.websocket_gateway_uri = "ws://127.0.0.1:#{config.websocket_gateway_port}/websocket"
#config.websocket_gateway_connect_time_stale_client = 4
#config.websocket_gateway_connect_time_expire_client = 6

View File

@ -22,6 +22,7 @@ Rails.application.routes.draw do
delete '/signout', to: 'sessions#destroy'
match '/redeem_giftcard', to: 'landings#redeem_giftcard', via: :get
match '/account/activate/code', to: 'landings#account_activate', via: :get
# landing pageslanding
get '/jamtracks', to: 'landings#simple_jamtracks', as: 'landing_simple_jamtracks'

View File

@ -13,8 +13,70 @@ rescue TypeError
puts "for production, we ignore type error"
end
def generate
[*('a'..'z'),*('0'..'9')].shuffle[0,10].join.upcase
end
def gc_10
CSV.open("gift-card-10.csv", "wb") do |csv|
for i in 1..150
csv << [generate()]
end
end
end
def gc_20
CSV.open("gift-card-20.csv", "wb") do |csv|
for i in 1..100
csv << [generate()]
end
end
end
# round to. we make
#One set of 200 codes that when redeemed translate into 5 (not 10) JamTracks each.
#One set of 200 codes that when redeemed translate into 4 JamClass lessons each.
def gc_5jt_2
CSV.open("posa-cards-jt-5.csv", "wb") do |csv|
for i in 1..250
csv << [generate()]
end
end
end
def gc_4jc_2
CSV.open("posa-cards-jc-4.csv", "wb") do |csv|
for i in 1..250
csv << [generate()]
end
end
end
namespace :lessons do
task amazon_gift_cards: :environment do|task, args|
CSV.open("posa-cards-amazon-test-drive-paid-4.csv", "wb") do |csv|
for i in 1..250
csv << [generate(), 'amazon-test-drive-paid-4', true, true]
end
end
CSV.open("posa-cards-amazon-test-drive-free-4.csv", "wb") do |csv|
for i in 1..250
csv << [generate(), 'amazon-test-drive-free-4',true, false]
end
end
CSV.open("posa-cards-amazon-test-drive-free-2.csv", "wb") do |csv|
for i in 1..250
csv << [generate(), 'amazon-test-drive-free-2',true, false]
end
end
end
task book_completed: :environment do |task, args|
user = User.find_by_email(ENV['STUDENT'])
@ -68,7 +130,7 @@ namespace :lessons do
end
booking = LessonBooking.book_normal(user, teacher, slots, "Hey I've heard of you before.", recurring, payment_style, 60)
booking = LessonBooking.book_normal(user, teacher, slots, "Hey I've heard of you before.", recurring, payment_style, 30)
if booking.errors.any?
puts booking.errors.inspect
raise "booking failed"

66
web/posa_cards.rb Normal file
View File

@ -0,0 +1,66 @@
def generate
[*('a'..'z'),*('0'..'9')].shuffle[0,10].join.upcase
end
def gc_10
CSV.open("gift-card-10.csv", "wb") do |csv|
for i in 1..150
csv << [generate()]
end
end
end
def gc_20
CSV.open("gift-card-20.csv", "wb") do |csv|
for i in 1..100
csv << [generate()]
end
end
end
# round to. we make
#One set of 200 codes that when redeemed translate into 5 (not 10) JamTracks each.
#One set of 200 codes that when redeemed translate into 4 JamClass lessons each.
def gc_5jt_2
CSV.open("posa-cards-jt-5.csv", "wb") do |csv|
for i in 1..250
csv << [generate()]
end
end
end
def gc_4jc_2
CSV.open("posa-cards-jc-4.csv", "wb") do |csv|
for i in 1..250
csv << [generate()]
end
end
end
def amazon_gift_cards
CSV.open("posa-cards-amazon-test-drive-paid-4.csv", "wb") do |csv|
for i in 1..250
csv << [generate(), 'amazon-test-drive-paid-4', true, true]
end
end
CSV.open("posa-cards-amazon-test-drive-free-4.csv", "wb") do |csv|
for i in 1..250
csv << [generate(), 'amazon-test-drive-free-4',true, false]
end
end
CSV.open("posa-cards-amazon-test-drive-free-2.csv", "wb") do |csv|
for i in 1..250
csv << [generate(), 'amazon-test-drive-free-2',true, false]
end
end
end

View File

@ -34,11 +34,12 @@ describe ApiJamblastersController, type: :controller do
response.status.should == 200
end
it "disallows different user" do
user2 = FactoryGirl.create(:user)
get :is_allowed, {:format => 'json', jbid: jamblaster.client_id, user_id: user2.id}
response.status.should == 403
end
#it "disallows different user" do
# user2 = FactoryGirl.create(:user)
# get :is_allowed, {:format => 'json', jbid: jamblaster.client_id, user_id: user2.id}
# response.status.should == 403
#end
end
describe "not already associated" do

View File

@ -870,7 +870,41 @@ FactoryGirl.define do
factory :posa_card, class: 'JamRuby::PosaCard' do
sequence(:code) { |n| n.to_s }
card_type JamRuby::PosaCardType::JAM_TRACKS_5
card_type JamRuby::PosaCard::JAM_TRACKS_5
requires_purchase false
purchased true
factory :posa_card_lesson_2 do
card_type JamRuby::PosaCard::JAM_CLASS_2
credits 2
lesson_package_type { JamRuby::LessonPackageType.test_drive_2 }
is_lesson true
end
factory :posa_card_lesson_4 do
card_type JamRuby::PosaCard::JAM_CLASS_4
credits 4
lesson_package_type { JamRuby::LessonPackageType.test_drive_4 }
is_lesson true
end
factory :amazon_2_free do
card_type JamRuby::PosaCard::JAM_CLASS_2
credits 2
lesson_package_type { JamRuby::LessonPackageType.amazon_test_drive_free_2 }
is_lesson true
preactivate true
end
factory :amazon_4_paid do
card_type JamRuby::PosaCard::JAM_CLASS_4
credits 2
lesson_package_type { JamRuby::LessonPackageType.amazon_test_drive_paid_4 }
is_lesson true
requires_purchase true
purchased false
preactivate true
end
end
factory :posa_card_type, class: 'JamRuby::PosaCardType' do

View File

@ -0,0 +1,126 @@
require 'spec_helper'
# tests what happens when the websocket connection goes away
describe "Activate Account Card", :js => true, :type => :feature, :capybara_feature => true do
subject { page }
let(:user1) { FactoryGirl.create(:user) }
let(:amazon_2_free_card) { FactoryGirl.create(:amazon_2_free) }
before(:all) do
User.delete_all
end
describe "not logged in" do
describe "amazon_2_free_card" do
it "succeeds" do
visit '/account/activate/code'
amazon_2_free_card.credits.should eql 2
find('h2', text: 'Activate Account')
fill_in "code", with: amazon_2_free_card.code
fill_in "email", with: "amzposa1@jamkazam.com"
fill_in "password", with: "jam123"
find('.redeem-container ins', visible: false).trigger(:click)
find('button.redeem-giftcard', text: 'ACTIVATE ACCOUNT').trigger(:click)
find('.jam-class.all-done span.amount-gifted', text: amazon_2_free_card.credits)
find('.done-action a.go-browse').trigger(:click)
find('h2', text: 'search teachers')
user = User.find_by_email("amzposa1@jamkazam.com")
amazon_2_free_card.reload
amazon_2_free_card.user.should eq(user)
amazon_2_free_card.requires_purchase.should be false
amazon_2_free_card.purchased.should be true
user.reload
user.jamclass_credits.should eq(amazon_2_free_card.credits)
end
it "validates correctly" do
visit '/account/activate/code'
find('h2', text: 'Activate Account')
find('button.redeem-giftcard', text: 'ACTIVATE ACCOUNT').trigger(:click)
find('.errors.active', text: "Email can't be blank")
find('h2', text: 'Activate Account')
fill_in "code", with: amazon_2_free_card.code
fill_in "email", with: "amzpos2@jamkazam.com"
fill_in "password", with: "jam123"
find('.redeem-container ins', visible: false).trigger(:click)
find('button.redeem-giftcard', text: 'ACTIVATE ACCOUNT').trigger(:click)
find('.done-action a.go-browse').trigger(:click)
find('h2', text: 'search teachers')
user = User.find_by_email("amzpos2@jamkazam.com")
amazon_2_free_card.reload
amazon_2_free_card.user.should eq(user)
amazon_2_free_card.requires_purchase.should be false
amazon_2_free_card.purchased.should be true
user.reload
user.jamclass_credits.should eq(amazon_2_free_card.credits)
end
end
end
describe "logged in" do
it "succeeds" do
fast_signin(user1, '/account/activate/code')
find('h2', text: 'Activate Account')
fill_in "code", with: amazon_2_free_card.code
find('button.redeem-giftcard', text: 'ACTIVATE COUPON CODE').trigger(:click)
find('.done-action a.go-browse').trigger(:click)
find('h2', text: 'search teachers')
user1.reload
amazon_2_free_card.reload
amazon_2_free_card.user.should eq(user)
amazon_2_free_card.requires_purchase.should be false
amazon_2_free_card.purchased.should be true
user.jamclass_credits.should eq(amazon_2_free_card.credits)
end
end
describe "logged in" do
it "validates" do
fast_signin(user1, '/account/activate/code')
find('h2', text: 'Activate Account')
find('button.redeem-giftcard').trigger(:click)
find('.errors.active', text: "Coupon Code does not exist")
fill_in "code", with: amazon_2_free_card.code
find('button.redeem-giftcard').trigger(:click)
find('.done-action a.go-browse').trigger(:click)
find('h2', text: 'search teachers')
user1.reload
amazon_2_free_card.reload
amazon_2_free_card.user.should eq(user)
amazon_2_free_card.requires_purchase.should be false
amazon_2_free_card.purchased.should be true
user.jamclass_credits.should eq(amazon_2_free_card.credits)
end
end
end

View File

@ -7,7 +7,7 @@ describe "Test Drive", :js => true, :type => :feature, :capybara_feature => true
let(:user) { FactoryGirl.create(:user, traditional_band: true,paid_sessions: true, paid_sessions_hourly_rate: 1, paid_sessions_daily_rate:1 ) }
let(:teacher_user) {FactoryGirl.create(:teacher_user, ready_for_session_at: Time.now)}
let(:teacher_user2) {FactoryGirl.create(:teacher_user, ready_for_session_at: Time.now)}
let(:card_lessons) {FactoryGirl.create(:posa_card, card_type: JamRuby::PosaCardType::JAM_CLASS_4)}
let(:card_lessons) {FactoryGirl.create(:posa_card_lesson_4)}
let(:retailer) {FactoryGirl.create(:retailer)}
before(:each) do

View File

@ -12,6 +12,13 @@ describe UsersController, :type => :api do
#login(authenticated_user.email, authenticated_user.password, 200, true)
}
def login(user)
# login as fan
post '/api/auth_session.json', { :email => user.email, :password => user.password }.to_json, "CONTENT_TYPE" => 'application/json'
last_response.status.should == 200
JSON.parse(last_response.body).should == { "success" => true }
end
it "unsubscribe" do
user.subscribe_email.should eql true
get '/unsubscribe/' + user.unsubscribe_token
@ -73,13 +80,13 @@ describe UsersController, :type => :api do
describe "logged in" do
it "should not set origin with no referrer info" do
controller.current_user = user
login(user)
get :home
response.cookies["origin"].should be_nil
end
it "should not set origin with referrer info" do
controller.current_user = user
login(user)
get :home, utm_valid_cookie
response.cookies["origin"].should be_nil
end

View File

@ -92,7 +92,7 @@ Thread.new do
:connect_time_stale_browser => 4,
:connect_time_expire_browser => 6,
:max_connections_per_user => 20,
:rabbitmq_host => 'localhost',
:rabbitmq_host => '127.0.0.1',
:rabbitmq_port => 5672,
:calling_thread => current,
:cidr => ['0.0.0.0/0'],

View File

@ -25,13 +25,13 @@ development:
test:
port: 6759
verbose: true
rabbitmq_host: localhost
rabbitmq_host: 127.0.0.1
rabbitmq_port: 5672
<<: *defaults
production:
port: 6767
verbose: false
rabbitmq_host: localhost
rabbitmq_host: 127.0.0.1
rabbitmq_port: 5672
<<: *defaults