This commit is contained in:
Seth Call 2016-03-31 16:37:07 -05:00
parent 34067cb61d
commit 0052b054b1
66 changed files with 3912 additions and 1412 deletions

View File

@ -77,6 +77,8 @@ gem 'influxdb', '0.1.8'
gem 'influxdb-rails', '0.1.10'
gem 'recurly'
gem 'sendgrid_toolkit', '>= 1.1.1'
gem 'stripe'
gem 'zip-codes'
group :libv8 do
gem 'libv8', "~> 4.5.95"

View File

@ -0,0 +1,47 @@
ActiveAdmin.register JamRuby::LessonBooking, :as => 'LessonBookings' do
menu :label => 'LessonBooking', :parent => 'JamClass'
config.sort_order = 'created_at desc'
config.batch_actions = false
config.per_page = 100
config.paginate = true
config.filters = false
scope("All", default: true ) { |scope| scope.unscoped.order('created_at desc') }
scope("Requested") { |scope| scope.unscoped.where(status: LessonBooking::STATUS_REQUESTED).order('created_at desc') }
scope("Approved") { |scope| scope.unscoped.approved.order('created_at desc') }
scope("Suspended" ) { |scope| scope.unscoped.suspended.order('created_at desc') }
scope("Canceled" ) { |scope| scope.unscoped.canceled.order('created_at desc') }
index do
column "User Link" do |lesson_booking|
span do
link_to "Web URL", "#{Rails.application.config.external_root_url}/client#/jamclass/lesson-booking/#{lesson_booking.id}"
end
end
column "Type" do |lesson_booking|
lesson_booking.display_type
end
column "Status" do |lesson_booking|
lesson_booking.status
end
column "Teacher" do |lesson_booking|
teacher = lesson_booking.teacher
span do
link_to "#{teacher.name} (#{teacher.email})", "#{Rails.application.config.external_root_url}/client#/profile/teacher/#{teacher.id}"
end
end
column "Student" do |lesson_booking|
student = lesson_booking.student
span do
link_to "#{student.name} (#{student.email})", "#{Rails.application.config.external_root_url}/client#/profile/#{student.id}"
end
end
end
show do
end
end

View File

@ -0,0 +1,59 @@
ActiveAdmin.register JamRuby::LessonSession, :as => 'LessonSessions' do
menu :label => 'LessonSession', :parent => 'JamClass'
config.sort_order = 'created_at desc'
config.batch_actions = false
config.per_page = 100
config.paginate = true
config.filters = false
scope("All", default: true) { |scope| scope.unscoped.order('created_at desc') }
scope("Requested" ) { |scope| scope.unscoped.where(status: LessonBooking::STATUS_REQUESTED).order('created_at desc') }
scope("Approved") { |scope| scope.unscoped.approved.order('created_at desc') }
scope("Suspended" ) { |scope| scope.unscoped.suspended.order('created_at desc') }
scope("Canceled" ) { |scope| scope.unscoped.canceled.order('created_at desc') }
scope("Missed" ) { |scope| scope.unscoped.missed.order('created_at desc') }
scope("Completed" ) { |scope| scope.unscoped.completed.order('created_at desc') }
index do
column "User Link" do |lesson_sesson|
lesson_booking = lesson_sesson.lesson_booking
span do
link_to "Web URL", "#{Rails.application.config.external_root_url}/client#/jamclass/lesson-booking/#{lesson_booking.id}"
end
end
column "Status" do |lesson_session|
lesson_session.status
end
column "Start Time" do |lesson_session|
span do
lesson_session.music_session.pretty_scheduled_start(true)
end
br
span do
lesson_session.music_session.scheduled_start
end
end
column "Duration" do |lesson_session|
lesson_session.duration
end
column "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}"
end
end
column "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}"
end
end
end
show do
end
end

View File

@ -8,7 +8,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Students' do
config.paginate = true
def booked_anything(scope)
scope.joins(:student_lesson_bookings).where('lesson_bookings.status = ?' > LessonBooking::STATUS_APPROVED).uniq
scope.joins(:student_lesson_bookings).where('lesson_bookings.active = true').uniq
end
scope("Default", default: true) { |scope| booked_anything(scope).order('ready_for_session_at IS NULL DESC') }
@ -26,7 +26,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Students' do
column "Session Ready" do |teacher|
div do
if teacher.ready_for_session
if teacher.ready_for_session_at
span do
'YES'
end

View File

@ -12,6 +12,9 @@ CREATE TABLE lesson_package_types (
CREATE TABLE lesson_bookings (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id VARCHAR(64) REFERENCES users(id) NOT NULL,
active BOOLEAN NOT NULL DEFAULT FALSE,
accepter_id VARCHAR(64) REFERENCES users(id),
canceler_id VARCHAR(64) REFERENCES users(id),
lesson_type VARCHAR(64) NOT NULL,
recurring BOOLEAN NOT NULL,
lesson_length INTEGER NOT NULL,
@ -22,6 +25,7 @@ CREATE TABLE lesson_bookings (
card_presumed_ok BOOLEAN NOT NULL DEFAULT FALSE,
sent_notices BOOLEAN NOT NULL DEFAULT FALSE,
status VARCHAR,
cancel_message VARCHAR,
user_decremented BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
@ -88,6 +92,8 @@ CREATE TABLE lesson_sessions (
analysed BOOLEAN NOT NULL DEFAULT FALSE,
analysis JSON,
analysed_at TIMESTAMP,
cancel_message VARCHAR,
canceler_id VARCHAR(64) REFERENCES users(id),
charge_id VARCHAR(64) REFERENCES charges(id),
success BOOLEAN NOT NULL DEFAULT FALSE,
sent_notices BOOLEAN NOT NULL DEFAULT FALSE,
@ -119,6 +125,8 @@ CREATE TABLE lesson_booking_slots (
hour INTEGER,
minute INTEGER,
timezone VARCHAR NOT NULL,
message VARCHAR,
accept_message VARCHAR,
update_all BOOLEAN NOT NULL DEFAULT FALSE,
proposer_id VARCHAR(64) REFERENCES users(id) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
@ -126,6 +134,7 @@ CREATE TABLE lesson_booking_slots (
);
ALTER TABLE lesson_bookings ADD COLUMN default_slot_id VARCHAR(64) REFERENCES lesson_booking_slots(id);
ALTER TABLE lesson_bookings ADD COLUMN counter_slot_id VARCHAR(64) REFERENCES lesson_booking_slots(id);
ALTER TABLE lesson_sessions ADD COLUMN slot_id VARCHAR(64) REFERENCES lesson_booking_slots(id);
ALTER TABLE chat_messages ADD COLUMN target_user_id VARCHAR(64) REFERENCES users(id);

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
You have confirmed a lesson request.
<% end %>
<% if @message %>
<% if @message.present? %>
<br/><br/><%= @sender.name %> says:
<br/><%= @message %>
<br/>

View File

@ -0,0 +1,29 @@
<% provide(:title, @subject) %>
<% provide(:photo_url, @lesson_booking.canceler.resolved_photo_url) %>
<% content_for :note do %>
<p>
<% if @lesson_booking.recurring %>
All lessons that were scheduled for <%= @lesson_booking.dayWeekDesc %> with <%= @teacher.name %> have been canceled.
<% else %>
Your lesson with <%= @teacher.name %> has been canceled.<br/><br/>
Session Name: <%= @session_name %><br/>
Session Description: <%= @session_description %></br>
<%= @session_date %>
<% end %>
<% if @message.present? %>
<br/><br/><%= @lesson_booking.canceler.name %> says:
<br/><%= @message %>
<br/>
<% end %>
<br/><br/>Click the button below to view more info about the canceled session.</p>
<p>
<a href="<%= @lesson_session.web_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">VIEW LESSON DETAILS</a>
</p>
<% end %>

View File

@ -0,0 +1,3 @@
<%= @subject %>
To see this lesson, click here: <%= @lesson_session.web_url %>

View File

@ -0,0 +1,20 @@
<% provide(:title, @subject) %>
<% provide(:photo_url, @teacher.resolved_photo_url) %>
<% content_for :note do %>
<p>
This teacher has declined your lesson request.
<% if @message.present? %>
<br/><br/><%= @teacher.name %> says:
<br/><%= @message %>
<br/>
<% end %>
<br/><br/>Click the button below to view the teacher's response.</p>
<p>
<a href="<%= @lesson_session.web_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">VIEW RESPONSE</a>
</p>
<% end %>

View File

@ -0,0 +1,3 @@
<%= @subject %>
To see this lesson, click here: <%= @lesson_session.web_url %>

View File

@ -0,0 +1,23 @@
<% provide(:title, @subject) %>
<% provide(:photo_url, @lesson_session.canceler.resolved_photo_url) %>
<% content_for :note do %>
<p>
Your lesson with <%= @teacher.name %> has been canceled.<br/><br/>
Session Name: <%= @session_name %><br/>
Session Description: <%= @session_description %></br>
<%= @session_date %>
<% if @message.present? %>
<br/><br/><%= @lesson_session.canceler.name %> says:
<br/><%= @message %>
<br/>
<% end %>
<br/><br/>Click the button below to view more info about the canceled session.</p>
<p>
<a href="<%= @lesson_session.web_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">VIEW RESPONSE</a>
</p>
<% end %>

View File

@ -0,0 +1,3 @@
<%= @subject %>
To see this lesson, click here: <%= @lesson_session.web_url %>

View File

@ -1,10 +1,10 @@
<% provide(:title, "Lesson Request sent to #{@sender.name}") %>
<% provide(:title, "Lesson requested of #{@sender.name}") %>
<% provide(:photo_url, @sender.resolved_photo_url) %>
<% content_for :note do %>
<p>You have requested a <%= @lesson_booking.display_type %> lesson. <br /><br/>Click the button below to see your lesson request. You will receive another email when the teacher accepts or reject the request.</p>
<p>You have requested a <%= @lesson_booking.display_type %> lesson. <br /><br/>Click the button below to see your lesson request. You will receive another email when the teacher accepts or rejects the request.</p>
<p>
<a href="<%= @lesson_booking.home_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">VIEW LESSON REQUEST</a>
<a href="<%= @lesson_booking.web_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">VIEW LESSON REQUEST</a>
</p>
<% end %>

View File

@ -1,3 +1,3 @@
You have requested a lesson from <%= @sender.name %>.
To see this lesson request, click here: <%= @lesson_booking.home_url %>
To see this lesson request, click here: <%= @lesson_booking.web_url %>

View File

@ -5,7 +5,7 @@
<p>
All lessons with <%= @lesson_session.teacher.name %> have been rescheduled.
<% if @message %>
<% if @message.present? %>
<br/><br/><%= @sender.name %> says:
<br/><%= @message %>
<br/>

View File

@ -1,11 +1,11 @@
<% provide(:title, "JamClass Scheduled with #{@teacher.name}") %>
<% provide(:title, @subject) %>
<p><%= @body %></p>
<p>
<%= @session_name %><br/>
Session Name: <%= @session_name %><br/>
<%= @session_description %></br>
<%= @session_date %>
</p>
<p><a style="color: #ffcc00;" href="<%= @session_url %>">View Session Details</a></p>
<p><a style="color: #ffcc00;" href="<%= @session_url %>">VIEW LESSON DETAILS</a></p>

View File

@ -0,0 +1,29 @@
<% provide(:title, @subject) %>
<% provide(:photo_url, @lesson_booking.canceler.resolved_photo_url) %>
<% content_for :note do %>
<p>
<% if @lesson_booking.recurring %>
All lessons that were scheduled for <%= @lesson_booking.dayWeekDesc %> with <%= @student.name %> have been canceled.
<% else %>
Your lesson with <%= @student.name %> has been canceled.<br/><br/>
Session Name: <%= @session_name %><br/>
Session Description: <%= @session_description %></br>
<%= @session_date %>
<% end %>
<% if @message.present? %>
<br/><br/><%= @lesson_booking.canceler.name %> says:
<br/><%= @message %>
<br/>
<% end %>
<br/><br/>Click the button below to view more info about the canceled session.</p>
<p>
<a href="<%= @lesson_session.web_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">VIEW LESSON DETAILS</a>
</p>
<% end %>

View File

@ -0,0 +1,3 @@
<%= @subject %>
To see this lesson, click here: <%= @lesson_session.web_url %>

View File

@ -0,0 +1,24 @@
<% provide(:title, @subject) %>
<% provide(:photo_url, @lesson_session.canceler.resolved_photo_url) %>
<% content_for :note do %>
<p>
Your lesson with <%= @teacher.name %> has been canceled.<br/><br/>
Session Name: <%= @session_name %><br/>
Session Description: <%= @session_description %></br>
<%= @session_date %>
<% if @message.present? %>
<br/><br/><%= @lesson_session.canceler.name %> says:
<br/><%= @message %>
<br/>
<% end %>
<br/><br/>Click the button below to view more info about the canceled session.</p>
<p>
<a href="<%= @lesson_session.web_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">VIEW LESSON DETAILS</a>
</p>
<% end %>

View File

@ -0,0 +1,3 @@
<%= @subject %>
To see this lesson, click here: <%= @lesson_session.web_url %>

View File

@ -4,7 +4,7 @@
<% content_for :note do %>
<p>This student has requested to schedule a <%= @lesson_booking.display_type %> lesson. <br /><br/>Click the button below to get more information and to respond to this lesson request. You must respond to this lesson request promptly, or it will be cancelled, thank you!</p>
<p>
<a href="<%= @lesson_booking.home_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">VIEW LESSON REQUEST</a>
<a href="<%= @lesson_booking.web_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">VIEW LESSON REQUEST</a>
</p>
<% end %>

View File

@ -5,7 +5,7 @@
<p>
All lessons with <%= @lesson_session.student.name %> have been rescheduled.
<% if @message %>
<% if @message.present? %>
<br/><br/><%= @sender.name %> says:
<br/><%= @message %>
<br/>

View File

@ -1,11 +1,11 @@
<% provide(:title, "JamClass Scheduled with #{@student.name}") %>
<% provide(:title, @subject) %>
<p><%= @body %></p>
<p>
<%= @session_name %><br/>
Session Name: <%= @session_name %><br/>
<%= @session_description %></br>
<%= @session_date %>
</p>
<p><a style="color: #ffcc00;" href="<%= @session_url %>">View Session Details</a></p>
<p><a style="color: #ffcc00;" href="<%= @session_url %>">VIEW LESSON DETAILS</a></p>

View File

@ -60,7 +60,7 @@ module JamRuby
query = ChatMessage.where('music_session_id = ?', music_session_id)
end
query = query.offset(start).limit(limit).order('created_at DESC')
query = query.offset(start).limit(limit).order('created_at DESC').includes([:user])
if query.length == 0
[query, nil]

View File

@ -338,6 +338,7 @@ module JamRuby
query = query.select("original_artist, array_agg(jam_tracks.id) AS id, MIN(name) AS name, MIN(description) AS description, MIN(recording_type) AS recording_type, MIN(original_artist) AS original_artist, MIN(songwriter) AS songwriter, MIN(publisher) AS publisher, MIN(sales_region) AS sales_region, MIN(price) AS price, MIN(version) AS version")
query = query.group("original_artist")
query = query.order('jam_tracks.original_artist')
query = query.includes([{ jam_track_tracks: :instrument }, { genres_jam_tracks: :genre }])
else
query = query.group("jam_tracks.id")
if options[:sort_by] == 'jamtrack'

View File

@ -11,8 +11,9 @@ module JamRuby
STATUS_CANCELED = 'canceled'
STATUS_APPROVED = 'approved'
STATUS_SUSPENDED = 'suspended'
STATUS_COUNTERED = 'countered'
STATUS_TYPES = [STATUS_REQUESTED, STATUS_CANCELED, STATUS_APPROVED, STATUS_SUSPENDED]
STATUS_TYPES = [STATUS_REQUESTED, STATUS_CANCELED, STATUS_APPROVED, STATUS_SUSPENDED, STATUS_COUNTERED]
LESSON_TYPE_FREE = 'single-free'
LESSON_TYPE_TEST_DRIVE = 'test-drive'
@ -30,7 +31,10 @@ module JamRuby
belongs_to :user, class_name: "JamRuby::User"
belongs_to :teacher, class_name: "JamRuby::User"
belongs_to :accepter, class_name: "JamRuby::User"
belongs_to :canceler, class_name: "JamRuby::User"
belongs_to :default_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :default_slot_id, inverse_of: :defaulted_booking
belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_booking
has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot"
has_many :lesson_sessions, class_name: "JamRuby::LessonSession"
@ -38,15 +42,16 @@ module JamRuby
validates :user, presence: true
validates :teacher, presence: true
validates :lesson_type, presence: true, inclusion: {in: LESSON_TYPES}
validates :lesson_type, inclusion: {in: LESSON_TYPES}
validates :status, presence: true, inclusion: {in: STATUS_TYPES}
validates :recurring, inclusion: {in: [true, false]}
validates :sent_notices, inclusion: {in: [true, false]}
validates :card_presumed_ok, inclusion: {in: [true, false]}
validates :lesson_length, presence: true, inclusion: {in: [30, 45, 60, 90, 120]}
validates :active, inclusion: {in: [true, false]}
validates :lesson_length, inclusion: {in: [30, 45, 60, 90, 120]}
validates :payment_style, inclusion: {in: PAYMENT_STYLES}
validates :booked_price, presence: true
validates :description, no_profanity: true, length: {minimum: 10, maximum: 20000}, presence: true
validates :description, no_profanity: true, length: {minimum: 10, maximum: 20000}
validate :validate_user, on: :create
validate :validate_recurring
@ -62,6 +67,7 @@ module JamRuby
around_save :around_update
scope :test_drive, -> { where(lesson_type: LESSON_TYPE_TEST_DRIVE) }
scope :active, -> { where(active: true) }
scope :approved, -> { where(status: STATUS_APPROVED) }
scope :requested, -> { where(status: STATUS_REQUESTED) }
scope :canceled, -> { where(status: STATUS_CANCELED) }
@ -107,18 +113,43 @@ module JamRuby
booked_price
end
def alt_slot
found = nil
lesson_booking_slots.each do |slot|
if slot.id != default_slot.id
found = slot
break
end
end
found
end
def student
user
end
def accept(lesson_session, slot)
if !is_approved?
def next_lesson
if recurring
lesson_sessions.joins(:music_session).where("scheduled_start is not null").where("scheduled_start > ?", Time.now).order(:created_at).first
else
lesson_sessions[0]
end
end
def accept(lesson_session, slot, accepter)
if !is_active?
self.accepting = true
end
self.active = true
self.status = STATUS_APPROVED
self.counter_slot = nil
self.default_slot = slot
self.accepter = accepter
if !self.save
puts "unable to accept lesson booking #{errors.inspect}"
raise ActiveRecord::Rollback
end
end
@ -126,6 +157,8 @@ module JamRuby
def counter(lesson_session, proposer, slot)
self.countering = true
self.lesson_booking_slots << slot
self.counter_slot = slot
#self.status = STATUS_COUNTERED
if !self.save
raise ActiveRecord::Rollback
end
@ -244,7 +277,7 @@ module JamRuby
if sessions.count == 0
needed_sessions = 1
end
elsif is_approved?
elsif is_active?
expected_num_sessions = recurring ? 2 : 1
needed_sessions = expected_num_sessions - sessions.count
end
@ -267,7 +300,7 @@ module JamRuby
rsvps = [{instrument_id: 'other', proficiency_level: 0, approve: true}]
music_session = MusicSession.create(student, {
name: "JamClass taught by #{teacher.name}",
name: "#{display_type2} JamClass taught by #{teacher.name}",
description: "This is a #{lesson_length}-minute #{display_type2} with #{teacher.name}.",
musician_access: false,
fan_access: false,
@ -293,8 +326,11 @@ module JamRuby
raise ActiveRecord::Rollback
end
# send out email to student to act as something they can add to their calendar
Notification.send_student_jamclass_invitation(music_session, student)
if lesson_session.is_active?
# send out email to student to act as something they can add to their calendar
Notification.send_student_jamclass_invitation(music_session, student)
end
end
end
@ -318,11 +354,8 @@ module JamRuby
else
raise "unable to determine object type of #{target}"
end
end
def is_requested?
status == STATUS_REQUESTED
end
@ -339,17 +372,22 @@ module JamRuby
status == STATUS_SUSPENDED
end
def is_active?
active
end
def validate_accepted
if !is_requested?
if self.status_was != STATUS_REQUESTED && self.status_was != STATUS_COUNTERED
self.errors.add(:status, "This lesson is already #{self.status}.")
end
self.status = STATUS_APPROVED
self.accepting = false
end
def send_notices
UserMailer.student_lesson_request(self).deliver
UserMailer.teacher_lesson_request(self).deliver
Notification.send_lesson_message('accept', lesson_sessions[0], false) # TODO: this isn't quite an 'accept'
self.sent_notices = true
self.save
end
@ -380,7 +418,12 @@ module JamRuby
elsif is_test_drive?
"TestDrive"
elsif is_normal?
"Lesson Purchase"
if recurring
"recurring"
else
"single"
end
end
end
@ -404,10 +447,8 @@ module JamRuby
elsif is_normal?
booked_price * 100
end
end
def is_single_free?
lesson_type == LESSON_TYPE_FREE
end
@ -420,6 +461,70 @@ module JamRuby
lesson_type == LESSON_TYPE_PAID
end
def dayWeekDesc(slot = default_slot)
day = case slot.day_of_week
when 0 then "Sunday"
when 1 then "Monday"
when 2 then "Tuesday"
when 3 then "Wednesday"
when 4 then "Thursday"
when 5 then "Friday"
when 6 then "Saturday"
end
if slot.hour > 11
hour = slot.hour - 12
if hour == 0
hour = 12
end
am_pm = 'pm'
else
hour = slot.hour
am_pm = 'am'
end
"#{day} at #{hour}:#{slot.minute}#{am_pm}"
end
def approved_before?
!self.accepter_id.nil?
end
def cancel(canceler, other, message)
self.active = false
self.status = STATUS_CANCELED
self.cancel_message = message
self.canceler = canceler
if save
if approved_before?
# just tell both people it's cancelled, to act as confirmation
Notification.send_lesson_message('canceled', next_lesson, false)
Notification.send_lesson_message('canceled', next_lesson, true)
UserMailer.student_lesson_booking_canceled(self, message).deliver
UserMailer.teacher_lesson_booking_canceled(self, message).deliver
chat_message_prefix = "Lesson Canceled"
else
if canceler == student
# if it's the first time acceptance student canceling, we call it a 'cancel'
Notification.send_lesson_message('canceled', next_lesson, false)
UserMailer.teacher_lesson_booking_canceled(self, message).deliver
chat_message_prefix = "Lesson Canceled"
else
# if it's the first time acceptance teacher, it was declined
UserMailer.student_lesson_booking_declined(self, message).deliver
Notification.send_lesson_message('declined', next_lesson, true)
chat_message_prefix = "Lesson Declined"
end
end
chat_message = message.nil? ? chat_message_prefix : "#{chat_message_prefix}: #{message}"
msg = ChatMessage.create(canceler, nil, chat_message, ChatMessage::CHANNEL_LESSON, nil, other, self)
end
end
def card_approved
self.card_presumed_ok = true
if self.save && !sent_notices
@ -437,8 +542,8 @@ module JamRuby
# errors.add(:user, 'has no credit card stored')
#end
elsif is_test_drive?
if user.has_requested_test_drive?
errors.add(:user, "have requested test drives")
if user.has_requested_test_drive?(teacher) && !user.admin
errors.add(:user, "has a requested TestDrive with this teacher")
end
if !user.has_test_drives? && !user.can_buy_test_drive?
errors.add(:user, "have no remaining test drives")
@ -520,6 +625,7 @@ module JamRuby
lesson_booking.lesson_booking_slots = lesson_booking_slots
lesson_booking_slots.each do |slot|
slot.lesson_booking = lesson_booking
slot.message = description
end if lesson_booking_slots
if lesson_booking.save
@ -539,7 +645,7 @@ module JamRuby
def self.find_bookings_needing_sessions(minimum_start_time)
MusicSession.select([:lesson_booking_id]).joins(:lesson_session => :lesson_booking).where("lesson_bookings.status = '#{STATUS_APPROVED}'").where('lesson_bookings.recurring = true').where("scheduled_start is not null").where("scheduled_start > ?", minimum_start_time).group(:lesson_booking_id).having('count(lesson_booking_id) < 2')
MusicSession.select([:lesson_booking_id]).joins(:lesson_session => :lesson_booking).where("lesson_bookings.active = true").where('lesson_bookings.recurring = true').where("scheduled_start is not null").where("scheduled_start > ?", minimum_start_time).group(:lesson_booking_id).having('count(lesson_booking_id) < 2')
end
# 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
@ -577,7 +683,7 @@ module JamRuby
.joins("LEFT JOIN lesson_package_purchases ON (lesson_package_purchases.lesson_booking_id = lesson_bookings.id AND (lesson_package_purchases.year = #{current_month_first_day.year} AND lesson_package_purchases.month = #{current_month_first_day.month}))")
.where("lesson_package_purchases.id IS NULL OR (lesson_package_purchases.id IS NOT NULL AND lesson_package_purchases.post_processed = false)")
.where(payment_style: PAYMENT_STYLE_MONTHLY)
.where(status: STATUS_APPROVED)
.active
.where('music_sessions.scheduled_start >= ?', current_month_first_day)
.where('music_sessions.scheduled_start <= ?', current_month_last_day).uniq
@ -653,6 +759,7 @@ module JamRuby
def suspend!
# when this is called, the calling code sends out a email to let the student and teacher know (it feels unnatural it's not here, though)
self.status = STATUS_SUSPENDED
self.active = false
if self.save
future_sessions.each do |lesson_session|
LessonSession.find(lesson_session.id).suspend!
@ -663,6 +770,7 @@ module JamRuby
def unsuspend!
if self.status == STATUS_SUSPENDED
self.status = STATUS_APPROVED
self.active = true
if self.save
future_sessions.each do |lesson_session|
LessonSession.find(lesson_session.id).unsuspend!
@ -692,7 +800,7 @@ module JamRuby
end
def web_url
APP_CONFIG.external_root_url + "/client#/jamclass/lesson-request/" + id
APP_CONFIG.external_root_url + "/client#/jamclass/lesson-booking/" + id
end
def update_payment_url

View File

@ -8,6 +8,7 @@ module JamRuby
belongs_to :lesson_session, class_name: "JamRuby::LessonSession"
belongs_to :proposer, class_name: "JamRuby::User"
has_one :defaulted_booking, class_name: "JamRuby::LessonBooking", foreign_key: :default_slot_id, inverse_of: :default_slot
has_one :countered_booking, class_name: "JamRuby::LessonBooking", foreign_key: :counter_slot_id, inverse_of: :counter_slot
SLOT_TYPE_SINGLE = 'single'
SLOT_TYPE_RECURRING = 'recurring'
@ -46,6 +47,10 @@ module JamRuby
self.proposer == container.teacher
end
def is_student_created?
!is_teacher_created?
end
def is_teacher_approved?
!is_teacher_created?
end
@ -122,7 +127,7 @@ module JamRuby
found ||= lesson_session.lesson_booking
end
def pretty_scheduled_start(with_timezone)
def pretty_scheduled_start(with_timezone = true)
start_time = scheduled_time(0)
@ -149,8 +154,34 @@ module JamRuby
else
"#{start_time.strftime("%A, %B %e")} - #{start_time.strftime("%l:%M%P").strip}"
end
end
def pretty_start_time(with_timezone = true)
start_time = scheduled_time(0)
begin
tz = TZInfo::Timezone.get(timezone)
rescue Exception => e
@@log.error("unable to find timezone=#{tz_identifier}, e=#{e}")
end
if tz
begin
start_time = tz.utc_to_local(start_time)
rescue Exception => e
@@log.error("unable to convert #{scheduled_start} to #{tz}, e=#{e}")
puts "unable to convert #{e}"
end
end
duration = lesson_length * 60 # convert from minutes to seconds
end_time = start_time + duration
if with_timezone
"#{start_time.strftime("%a, %b %e")} at #{start_time.strftime("%l:%M%P").strip} #{tz.name}"
else
"#{start_time.strftime("%a, %b %e")} at #{start_time.strftime("%l:%M%P").strip}"
end
end
def validate_proposer
@ -183,7 +214,10 @@ module JamRuby
if is_teacher_created?
return # the thinking is that a teacher can propose much tighter to the time; since they only counter; maybe they talked to the student
end
minimum_start_time = create_minimum_booking_time
#
# minimum_start_time = create_minimum_booking_time
minimum_start_time = Time.now
if day_of_week
# this is recurring; it will sort itself out
@ -191,7 +225,8 @@ module JamRuby
time = scheduled_time(0)
if time <= minimum_start_time
errors.add("must be at least #{APP_CONFIG.minimum_lesson_booking_hrs} hours out from now")
#errors.add(:base, "must be at least #{APP_CONFIG.minimum_lesson_booking_hrs} hours in the future")
errors.add(:preferred_day, "can not be in the past")
end
end

View File

@ -3,13 +3,14 @@ module JamRuby
class LessonSession < ActiveRecord::Base
attr_accessor :accepting, :creating, :countering, :countered_slot, :countered_lesson
attr_accessor :accepting, :creating, :countering, :countered_slot, :countered_lesson, :canceling
@@log = Logging.logger[LessonSession]
delegate :sent_billing_notices, :last_billing_attempt_at, :billing_attempts, :billing_should_retry, :billed, :billed_at, :billing_error_detail, :billing_error_reason, :is_card_declined?, :is_card_expired?, :last_billed_at_date, :sent_billing_notices, to: :lesson_payment_charge
delegate :is_test_drive?, :is_single_free?, :is_normal?, to: :lesson_booking
delegate :is_test_drive?, :is_single_free?, :is_normal?, :approved_before?, :is_active?, to: :lesson_booking
delegate :pretty_scheduled_start, to: :music_session
STATUS_REQUESTED = 'requested'
@ -18,8 +19,9 @@ module JamRuby
STATUS_COMPLETED = 'completed'
STATUS_APPROVED = 'approved'
STATUS_SUSPENDED = 'suspended'
STATUS_COUNTERED = 'countered'
STATUS_TYPES = [STATUS_REQUESTED, STATUS_CANCELED, STATUS_MISSED, STATUS_COMPLETED, STATUS_APPROVED, STATUS_SUSPENDED]
STATUS_TYPES = [STATUS_REQUESTED, STATUS_CANCELED, STATUS_MISSED, STATUS_COMPLETED, STATUS_APPROVED, STATUS_SUSPENDED, STATUS_COUNTERED]
LESSON_TYPE_SINGLE = 'paid'
LESSON_TYPE_SINGLE_FREE = 'single-free'
@ -28,6 +30,7 @@ module JamRuby
has_one :music_session, class_name: "JamRuby::MusicSession"
belongs_to :teacher, class_name: "JamRuby::User", foreign_key: :teacher_id, inverse_of: :taught_lessons
belongs_to :canceler, class_name: "JamRuby::User", foreign_key: :canceler_id
belongs_to :lesson_package_purchase, class_name: "JamRuby::LessonPackagePurchase"
belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking"
belongs_to :slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :slot_id
@ -50,10 +53,19 @@ module JamRuby
validate :validate_creating, :if => :creating
validate :validate_accepted, :if => :accepting
validate :validate_canceled, :if => :canceling
after_save :after_counter, :if => :countering
after_save :manage_slot_changes
after_create :create_charge
scope :approved, -> { where(status: STATUS_APPROVED) }
scope :requested, -> { where(status: STATUS_REQUESTED) }
scope :canceled, -> { where(status: STATUS_CANCELED) }
scope :suspended, -> { where(status: STATUS_SUSPENDED) }
scope :completed, -> { where(status: STATUS_COMPLETED) }
scope :missed, -> { where(status: STATUS_MISSED) }
def create_charge
if !is_test_drive?
self.lesson_payment_charge = LessonPaymentCharge.new
@ -310,6 +322,10 @@ module JamRuby
status == STATUS_SUSPENDED
end
def is_countered?
status == STATUS_COUNTERED
end
def validate_creating
if !is_requested? && !is_approved?
self.errors.add(:status, "is not valid for a new lesson session.")
@ -321,16 +337,31 @@ module JamRuby
end
def validate_accepted
if !is_requested?
self.errors.add(:status, "This session is already #{self.status}.")
if self.status_was != STATUS_REQUESTED && self.status_was != STATUS_COUNTERED
self.errors.add(:status, "This session is already #{self.status_was}.")
end
if lesson_booking && lesson_booking.is_test_drive? && lesson_package_purchase.nil?
self.errors.add(:lesson_package_purchase, "must be specified for a test drive purchase")
if approved_before?
# only checking for this on 1st time through acceptance
if lesson_booking && lesson_booking.is_test_drive? && lesson_package_purchase.nil?
self.errors.add(:lesson_package_purchase, "must be specified for a test drive purchase")
end
end
self.accepting = false
self.status = STATUS_APPROVED
end
def validate_canceled
if !is_canceled?
self.errors.add(:status, "This session is already #{self.status}.")
end
# check 24 hour window
if Time.now.to_i - scheduled_start.to_i > 24 * 60 * 60
self.errors.add(:base, "This session is due to start within 24 hours and can not be canceled")
end
self.canceling = false
end
def self.create(booking)
@ -391,7 +422,7 @@ module JamRuby
def update_scheduled_start(week_offset)
music_session.scheduled_start = slot.scheduled_time(week_offset)
music_session.save
music_session.save!
end
# grabs the next available time that's after the present, to avoid times being scheduled in the past
@ -422,16 +453,43 @@ module JamRuby
message = params[:message]
slot = params[:slot]
slot = LessonBookingSlot.find(slot)
self.slot = slot
accepter = params[:accepter]
self.slot = slot = LessonBookingSlot.find(slot)
self.slot.accept_message = message
self.slot.save!
self.accepting = true
self.status = STATUS_APPROVED
if is_approved?
if !approved_before?
# 1st time this has ever been approved; there are other things we need to do
if lesson_package_purchase.nil? && lesson_booking.is_test_drive?
self.lesson_package_purchase = student.most_recent_test_drive_purchase
end
if self.save
# also let the lesson_booking know we got accepted
lesson_booking.accept(self, slot, accepter)
UserMailer.student_lesson_accepted(self, message, slot).deliver
UserMailer.teacher_lesson_accepted(self, message, slot).deliver
chat_message = message ? "Lesson Approved: '#{message}'" : "Lesson Approved"
msg = ChatMessage.create(teacher, nil, chat_message, ChatMessage::CHANNEL_LESSON, nil, student, lesson_booking)
Notification.send_jamclass_invitation_teacher(music_session, teacher)
Notification.send_student_jamclass_invitation(music_session, student)
Notification.send_lesson_message('accept', self, true)
else
@@log.error("unable to accept slot #{slot.id} for lesson #{self.id}")
puts("unable to accept slot #{slot.id} for lesson #{self.id}")
end
else
# this implies a new slot has been countered, and now approved
if self.save
if slot.update_all
lesson_booking.accept(self, slot)
msg = ChatMessage.create(teacher, nil, message, ChatMessage::CHANNEL_LESSON, nil, student, lesson_booking)
lesson_booking.accept(self, slot, accepter)
chat_message = message ? "All Lesson Times Updated: '#{message}'" : "All Lesson Times Updated"
msg = ChatMessage.create(slot.proposer, nil, chat_message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, lesson_booking)
Notification.send_lesson_message('accept', self, true) # TODO: this isn't quite an 'accept'
UserMailer.student_lesson_update_all(self, message, slot).deliver
UserMailer.teacher_lesson_update_all(self, message, slot).deliver
@ -444,33 +502,16 @@ module JamRuby
puts("unable to accept slot #{slot.id} for lesson #{self.id} because it's in the past")
raise ActiveRecord::Rollback
end
chat_message = message ? "Lesson Updated Time Approved: '#{message}'" : "Lesson Updated Time Approved"
msg = ChatMessage.create(slot.proposer, nil, chat_message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, lesson_booking)
UserMailer.student_lesson_accepted(self, message, slot).deliver
UserMailer.teacher_lesson_accepted(self, message, slot).deliver
end
else
@@log.error("unable to accept slot #{slot.id} for lesson #{self.id}")
puts("unable to accept slot #{slot.id} for lesson #{self.id}")
end
else
self.accepting = true
if lesson_package_purchase.nil? && lesson_booking.is_test_drive?
self.lesson_package_purchase = student.most_recent_test_drive_purchase
end
if self.save
lesson_booking.accept(self, slot)
msg = ChatMessage.create(teacher, nil, message, ChatMessage::CHANNEL_LESSON, nil, student, lesson_booking)
Notification.send_lesson_message('accept', self, true)
UserMailer.student_lesson_accepted(self, message, slot).deliver
UserMailer.teacher_lesson_accepted(self, message, slot).deliver
else
@@log.error("unable to accept slot #{slot.id} for lesson #{self.id}")
puts("unable to accept slot #{slot.id} for lesson #{self.id}")
@@log.error("unable to accept slot #{slot.id} for lesson #{self.id} #{errors.inspect}")
puts("unable to accept slot #{slot.id} for lesson #{self.id} #{errors.inspect}")
end
end
end
end
@ -482,9 +523,11 @@ module JamRuby
self.countering = true
slot.proposer = proposer
slot.lesson_session = self
slot.message = message
self.lesson_booking_slots << slot
self.countered_slot = slot
self.countered_lesson = self
self.status = STATUS_COUNTERED
if self.save
if slot.update_all || lesson_booking.is_requested?
lesson_booking.counter(self, proposer, slot)
@ -497,6 +540,41 @@ module JamRuby
Notification.send_lesson_message('counter', self, slot.is_teacher_created?)
end
# teacher accepts the lesson
def cancel(params)
LessonSession.transaction do
canceler = params[:canceler]
other = canceler == teacher ? student : teacher
message = params[:message]
if params[:update_all].present?
update_all = params[:update_all]
else
update_all = !lesson_booking.recurring
end
self.status = STATUS_CANCELED
self.cancel_message = message
self.canceler = canceler
self.canceling = true
if self.save
if update_all
lesson_booking.cancel(canceler, other, message)
else
msg = ChatMessage.create(canceler, nil, message, ChatMessage::CHANNEL_LESSON, nil, other, lesson_booking)
Notification.send_lesson_message('canceled', self, false)
Notification.send_lesson_message('canceled', self, true)
UserMailer.student_lesson_canceled(self, message).deliver
UserMailer.teacher_lesson_canceled(self, message).deliver
end
end
end
end
def description(lesson_booking)
lesson_booking.lesson_package_type.description(lesson_booking)
end
@ -510,7 +588,7 @@ module JamRuby
end
def web_url
APP_CONFIG.external_root_url + "/client#/jamclass/lesson-request/" + id
APP_CONFIG.external_root_url + "/client#/jamclass/lesson-booking/" + id
end
def update_payment_url

View File

@ -423,7 +423,9 @@ module JamRuby
when CREATE_TYPE_RSVP, CREATE_TYPE_SCHEDULE_FUTURE
Notification.send_scheduled_session_invitation(ms, receiver)
when CREATE_TYPE_LESSON
Notification.send_jamclass_invitation(ms, receiver)
if ms.lesson_session.is_active?
Notification.send_jamclass_invitation_teacher(ms, receiver)
end
else
Notification.send_session_invitation(receiver, user, ms.id)
end
@ -942,7 +944,7 @@ SQL
# with_timezone = FALSE
# Thursday, July 10 - 10:00pm
# this should be in a helper
def pretty_scheduled_start(with_timezone)
def pretty_scheduled_start(with_timezone = true)
if scheduled_start && scheduled_duration
start_time = scheduled_start

View File

@ -62,7 +62,7 @@ module JamRuby
end
end
self.class.format_msg(self.description, {:user => source_user, :band => band, :session => session})
self.class.format_msg(self.description, {:user => source_user, target: target_user :band => band, :session => session, purpose: purpose, student_directed: student_directed})
end
# TODO: MAKE ALL METHODS BELOW ASYNC SO THE CLIENT DOESN'T BLOCK ON NOTIFICATION LOGIC
@ -132,6 +132,8 @@ module JamRuby
user = options[:user]
band = options[:band]
session = options[:session]
purpose = options[:purpose]
student_directed = options[:student_directed]
name, band_name = ""
unless user.nil?
@ -249,8 +251,38 @@ module JamRuby
when NotificationTypes::BAND_INVITATION_ACCEPTED
return "#{name} has accepted your band invitation to join #{band_name}."
when NotificationTypes::LESSON_MESSAGE
notification_msg = 'Lesson Changed'
if purpose == 'requested'
notification_msg = 'You have received a lesson request'
elsif purpose == 'accept'
notification_msg = 'Your lesson request is confirmed!'
elsif purpose == 'declined'
notification_msg = "We're sorry your lesson request has been declined."
elsif purpose == 'canceled'
notification_msg = "Your lesson request has been canceled."
elsif purpose == 'counter'
if student_directed
notification_msg = "Instructor has proposed a different time for your lesson."
else
notification_msg = "Student has proposed a different time for your lesson."
end
elsif purpose == 'reschedule'
'A lesson reschedule has been requested'
end
return notification_msg
when NotificationTypes::SCHEDULED_JAMCLASS_INVITATION
if student_directed
"You have been scheduled to take a JamClass with #{user.name}."
else
"You have been scheduled to teach a JamClass to #{user.name}"
end
else
return ""
return description
end
end
@ -389,25 +421,9 @@ module JamRuby
notification.purpose = purpose
notification.session_id = lesson_session.music_session.id
notification_msg = 'Lesson Changed'
notification_msg = format_msg(NotificationTypes::LESSON_MESSAGE, {purpose: purpose})
if purpose == 'requested'
notification_msg = 'You have received a lesson request'
elsif purpose == 'accept'
notification_msg = 'Your lesson request is confirmed!'
elsif purpose == 'declined'
notification_msg = "We're sorry your lesson request has been declined."
elsif purpose == 'counter'
if student_directed
notification_msg = "Instructor has proposed a different time for your lesson."
else
notification_msg = "Student has proposed a different time for your lesson."
end
elsif purpose == 'reschedule'
'A lesson reschedule has been requested'
end
notification.message = notification_msg
#notification.message = notification_msg
notification.save
@ -701,20 +717,23 @@ module JamRuby
end
end
def send_jamclass_invitation(music_session, user)
def send_jamclass_invitation_teacher(music_session, user)
return if music_session.nil? || user.nil?
target_user = user
source_user = music_session.creator
teacher = target_user = user
student = source_user = music_session.creator
notification_msg = format_msg(NotificationTypes::SCHEDULED_JAMCLASS_INVITATION, {user: student, student_directed: false})
notification = Notification.new
notification.description = NotificationTypes::SCHEDULED_JAMCLASS_INVITATION
notification.source_user_id = source_user.id
notification.target_user_id = target_user.id
notification.session_id = music_session.id
#notification.message = notification_msg
notification.save
notification_msg = "You have been scheduled to teach a JamClass with #{name}."
if target_user.online
msg = @@message_factory.scheduled_jamclass_invitation(
@ -734,25 +753,25 @@ module JamRuby
begin
UserMailer.teacher_scheduled_jamclass_invitation(music_session.lesson_session.teacher, notification_msg, music_session).deliver
rescue => e
@@log.error("Unable to send SCHEDULED_JAMCLASS_INVITATION email to user #{target_user.email} #{e}")
@@log.error("Unable to send SCHEDULED_JAMCLASS_INVITATION email to user #{music_session.lesson_session.teacher.email} #{e}")
end
end
def send_student_jamclass_invitation(music_session, user)
return if music_session.nil? || user.nil?
target_user = user
source_user = music_session.lesson_session.teacher
student = target_user = user
teacher = source_user = music_session.lesson_session.teacher
notification_msg = format_msg(NotificationTypes::SCHEDULED_JAMCLASS_INVITATION, {teacher: teacher, student_directed: true})
notification = Notification.new
notification.description = NotificationTypes::SCHEDULED_JAMCLASS_INVITATION
notification.source_user_id = source_user.id
notification.target_user_id = target_user.id
notification.session_id = music_session.id
#notification.message = notification_msg
notification.save
notification_msg = "You have been scheduled to student a JamClass with #{name}."
if target_user.online
msg = @@message_factory.scheduled_jamclass_invitation(
target_user.id,
@ -769,10 +788,11 @@ module JamRuby
end
begin
UserMailer.teacher_scheduled_jamclass_invitation(target_user, notification_msg, music_session).deliver
UserMailer.student_scheduled_jamclass_invitation(student, notification_msg, music_session).deliver
rescue => e
@@log.error("Unable to send SCHEDULED_JAMCLASS_INVITATION email to user #{target_user.email} #{e}")
@@log.error("Unable to send SCHEDULED_JAMCLASS_INVITATION email to user #{student.email} #{e}")
end
end

View File

@ -54,6 +54,9 @@ module JamRuby
query = User.joins(:teacher)
# only show teachers with background check set and ready for session set to true
query = query.where('teachers.ready_for_session_at IS NOT NULL')
instruments = params[:instruments]
if instruments && !instruments.blank? && instruments.length > 0
query = query.joins("inner JOIN teachers_instruments AS tinst ON tinst.teacher_id = teachers.id")
@ -89,7 +92,7 @@ module JamRuby
end
years_teaching = params[:years_teaching].to_i
if years_teaching && years_teaching > 0
if params[:years_teaching] && years_teaching > 0
query = query.where('years_teaching >= ?', years_teaching)
end
@ -97,20 +100,20 @@ module JamRuby
teaches_intermediate = params[:teaches_intermediate]
teaches_advanced = params[:teaches_advanced]
if teaches_beginner || teaches_intermediate || teaches_advanced
if teaches_beginner.present? || teaches_intermediate.present? || teaches_advanced.present?
clause = ''
if teaches_beginner
if teaches_beginner == true
clause << 'teaches_beginner = true'
end
if teaches_intermediate
if teaches_intermediate == true
if clause.length > 0
clause << ' OR '
end
clause << 'teaches_intermediate = true'
end
if teaches_advanced
if teaches_advanced == true
if clause.length > 0
clause << ' OR '
end
@ -120,7 +123,7 @@ module JamRuby
end
student_age = params[:student_age].to_i
if student_age && student_age > 0
if params[:student_age] && student_age > 0
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

View File

@ -1871,8 +1871,8 @@ module JamRuby
!unprocessed_test_drive.nil?
end
def has_requested_test_drive?
!requested_test_drive.nil?
def has_requested_test_drive?(teacher = nil)
!requested_test_drive(teacher).nil?
end
def fetch_stripe_customer
@ -1970,8 +1970,12 @@ module JamRuby
{lesson: booking, test_drive: test_drive, intent:intent, purchase: purchase}
end
def requested_test_drive
LessonBooking.requested(self).where(lesson_type: LessonBooking::LESSON_TYPE_TEST_DRIVE).first
def requested_test_drive(teacher = nil)
query = LessonBooking.requested(self).where(lesson_type: LessonBooking::LESSON_TYPE_TEST_DRIVE)
if teacher
query = query.where(teacher_id: teacher.id)
end
query.first
end
def unprocessed_test_drive

View File

@ -16,11 +16,13 @@ describe LessonBooking do
describe "suspend!" do
it "should set status as well as update status of all associated lesson_sessions" do
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60)
booking.lesson_sessions[0].accept({message: "got it", slot: booking.lesson_booking_slots[0].id})
booking.lesson_sessions[0].accept({accepter: teacher_user, message: "got it", slot: booking.lesson_booking_slots[0].id})
booking.reload
booking.active.should eql true
booking.suspend!
booking.errors.any?.should be false
booking.reload
booking.active.should eql false
booking.status.should eql LessonBooking::STATUS_SUSPENDED
booking.lesson_sessions.count.should eql 2
booking.lesson_sessions.each do |lesson_session|
@ -45,7 +47,7 @@ describe LessonBooking do
time = day.to_time
Timecop.freeze(time)
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60)
booking.accept(booking.lesson_sessions[0], booking.lesson_booking_slots[0])
booking.accept(booking.lesson_sessions[0], booking.lesson_booking_slots[0], teacher_user)
booking.errors.any?.should be false
LessonBooking.count.should eql 1
@ -119,12 +121,6 @@ describe LessonBooking do
LessonBooking.bill_monthlies
LessonPackagePurchase.count.should eql 1
purchase.reload
if purchase.billing_error_detail
# to aid in test failure debugging. shouldn't usually 'puts'
puts purchase.billing_error_reason
puts purchase.billing_error_detail
end
purchase.month.should eql 1
purchase.year.should eql 2016
purchase.lesson_booking.should eql booking
@ -148,7 +144,7 @@ describe LessonBooking do
time = day.to_time
Timecop.freeze(time)
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60)
booking.accept(booking.lesson_sessions[0], booking.lesson_booking_slots[0])
booking.accept(booking.lesson_sessions[0], booking.lesson_booking_slots[0], teacher_user)
booking.errors.any?.should be false
LessonBooking.count.should eql 1
@ -203,7 +199,7 @@ describe LessonBooking do
time = day.to_time
Timecop.freeze(time)
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60)
booking.accept(booking.lesson_sessions[0], booking.lesson_booking_slots[0])
booking.accept(booking.lesson_sessions[0], booking.lesson_booking_slots[0], teacher_user)
booking.errors.any?.should be false
LessonBooking.count.should eql 1
@ -307,7 +303,7 @@ describe LessonBooking do
time = Date.new(2016, 1, 1)
Timecop.freeze(time)
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60)
booking.accept(booking.lesson_sessions[0], booking.lesson_booking_slots[0])
booking.accept(booking.lesson_sessions[0], booking.lesson_booking_slots[0], teacher_user)
booking.errors.any?.should be false
now = Time.now
@ -524,7 +520,7 @@ describe LessonBooking do
booking = LessonBooking.book_test_drive(user, teacher_user, valid_single_slots, "Hey I've heard of you before.")
booking.errors.any?.should be true
booking.errors[:user].should eq ["have requested test drives"]
booking.errors[:user].should eq ["has a requested TestDrive with this teacher"]
ChatMessage.count.should eq 1
end
@ -648,7 +644,7 @@ describe LessonBooking do
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_WEEKLY, 60)
booking.lesson_sessions.length.should eql 1
booking.accept(booking.lesson_sessions[0], booking.lesson_booking_slots[0])
booking.accept(booking.lesson_sessions[0], booking.lesson_booking_slots[0], teacher_user)
booking.errors.any?.should be false
booking.reload
booking.lesson_sessions.length.should eql 2
@ -686,13 +682,116 @@ describe LessonBooking do
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60)
booking.lesson_sessions.length.should eql 1
booking.accept(booking.lesson_sessions[0], booking.lesson_booking_slots[0])
booking.accept(booking.lesson_sessions[0], booking.lesson_booking_slots[0], user)
booking.errors.any?.should be false
booking.reload
booking.lesson_sessions.length.should eql 1
booking.accepter.should eql user
end
end
describe "canceling" do
after do
Timecop.return
end
it "single session gets canceled before accepted" do
booking = LessonBooking.book_normal(user, teacher_user, valid_single_slots, "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60)
lesson_session = booking.lesson_sessions[0]
lesson_session.status.should eql LessonSession::STATUS_REQUESTED
lesson_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
# avoid 24 hour problem
UserMailer.deliveries.clear
Timecop.freeze(7.days.ago)
lesson_session.cancel({canceler: teacher_user, message: 'meh', slot: booking.default_slot.id, update_all: false})
lesson_session.errors.any?.should be_false
lesson_session.status.should eql LessonSession::STATUS_CANCELED
lesson_session.reload
booking.reload
booking.status.should eql LessonSession::STATUS_CANCELED
UserMailer.deliveries.length.should eql 1
end
it "single session gets canceled after accepted" do
booking = LessonBooking.book_normal(user, teacher_user, valid_single_slots, "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60)
lesson_session = booking.lesson_sessions[0]
lesson_session.status.should eql LessonSession::STATUS_REQUESTED
lesson_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
lesson_session.accept({accepter: teacher_user, message: 'Yeah I got this', slot: booking.default_slot.id, update_all: false})
lesson_session.errors.any?.should be_false
lesson_session.status.should eql LessonSession::STATUS_APPROVED
lesson_session.reload
lesson_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
UserMailer.deliveries.clear
Timecop.freeze(7.days.ago)
lesson_session.cancel({canceler: user, message: 'meh', slot: booking.default_slot.id, update_all: false})
lesson_session.errors.any?.should be_false
lesson_session.status.should eql LessonSession::STATUS_CANCELED
lesson_session.reload
booking.reload
booking.status.should eql LessonSession::STATUS_CANCELED
UserMailer.deliveries.length.should eql 2
end
it "recurring session gets canceled after accepted" do
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60)
booking.active.should eql false
lesson_session = booking.lesson_sessions[0]
lesson_session.status.should eql LessonSession::STATUS_REQUESTED
lesson_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
lesson_session.accept({accepter: teacher_user, message: 'Yeah I got this', slot: booking.default_slot.id, update_all: false})
lesson_session.errors.any?.should be_false
lesson_session.status.should eql LessonSession::STATUS_APPROVED
lesson_session.reload
lesson_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
booking.reload
booking.active.should eql true
UserMailer.deliveries.clear
Timecop.freeze(7.days.ago)
lesson_session.cancel({canceler: user, message: 'meh', slot: booking.default_slot.id, update_all: false})
lesson_session.errors.any?.should be_false
lesson_session.status.should eql LessonSession::STATUS_CANCELED
lesson_session.reload
lesson_session.canceler.should eql user
booking.reload
booking.active.should eql true
booking.status.should eql LessonSession::STATUS_APPROVED
booking.canceler.should be_nil
UserMailer.deliveries.length.should eql 2
end
it "recurring booking gets canceled after accepted" do
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60)
lesson_session = booking.lesson_sessions[0]
lesson_session.status.should eql LessonSession::STATUS_REQUESTED
lesson_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
lesson_session.accept({accepter: teacher_user, message: 'Yeah I got this', slot: booking.default_slot.id, update_all: false})
lesson_session.errors.any?.should be_false
lesson_session.status.should eql LessonSession::STATUS_APPROVED
lesson_session.reload
lesson_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
UserMailer.deliveries.clear
Timecop.freeze(7.days.ago)
lesson_session.cancel({canceler: user, message: 'meh', slot: booking.default_slot.id, update_all: true})
lesson_session.errors.any?.should be_false
lesson_session.status.should eql LessonSession::STATUS_CANCELED
lesson_session.reload
booking.reload
booking.status.should eql LessonSession::STATUS_CANCELED
booking.canceler.should eql user
UserMailer.deliveries.length.should eql 2
end
end
describe "rescheduling" do
after do
@ -707,14 +806,19 @@ describe LessonBooking do
counter = FactoryGirl.build(:lesson_booking_slot_single, hour: 16)
lesson_session.counter({proposer: user, slot: counter, message: 'Does this work better?'})
lesson_session.errors.any?.should be false
lesson_session.status.should eql LessonSession::STATUS_REQUESTED
lesson_session.status.should eql LessonSession::STATUS_COUNTERED
lesson_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
booking.reload
booking.counter_slot.should eql counter
lesson_session.accept({message: 'Yeah I got this', slot: counter.id, update_all: false})
lesson_session.accept({accepter: teacher_user, message: 'Yeah I got this', slot: counter.id, update_all: false})
lesson_session.errors.any?.should be_false
lesson_session.status.should eql LessonSession::STATUS_APPROVED
lesson_session.reload
lesson_session.scheduled_start.should eql counter.scheduled_time(0)
booking.reload
booking.accepter.should eql teacher_user
booking.counter_slot.should be_nil
end
it "recurring" do
@ -727,16 +831,17 @@ describe LessonBooking do
counter = FactoryGirl.build(:lesson_booking_slot_recurring, day_of_week: 2)
lesson_session.counter({proposer: user, slot: counter, message: 'Does this work better?'})
lesson_session.errors.any?.should be false
lesson_session.status.should eql LessonSession::STATUS_REQUESTED
lesson_session.status.should eql LessonSession::STATUS_COUNTERED
lesson_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
# to help scoot out the 'created_at' of the lessons
Timecop.freeze(Time.now + 10)
lesson_session.accept({message: 'Yeah I got this', slot: counter.id, update_all: false})
lesson_session.accept({accepter: teacher_user, message: 'Yeah I got this', slot: counter.id, update_all: false})
lesson_session.errors.any?.should be_false
lesson_session.status.should eql LessonSession::STATUS_APPROVED
booking.reload
booking.status.should eql LessonSession::STATUS_APPROVED
lesson_session.reload
lesson_session.scheduled_start.should eql counter.scheduled_time(0)
@ -750,10 +855,10 @@ describe LessonBooking do
counter2 = FactoryGirl.build(:lesson_booking_slot_recurring, day_of_week: 4, update_all: false)
lesson_session.counter({proposer: user, slot: counter2, message: 'ACtually, let\'s do this instead for just this one'})
lesson_session.errors.any?.should be false
lesson_session.status.should eql LessonSession::STATUS_APPROVED
lesson_session.status.should eql LessonSession::STATUS_COUNTERED
lesson_session.scheduled_start.should eql counter.scheduled_time(0)
lesson_session.accept({message: 'OK, lets fix just this one', slot: counter2.id})
lesson_session.accept({accepter: teacher_user, message: 'OK, lets fix just this one', slot: counter2.id})
lesson_session.errors.any?.should be_false
lesson_session.status.should eql LessonSession::STATUS_APPROVED
booking.reload
@ -768,17 +873,20 @@ describe LessonBooking do
counter3 = FactoryGirl.build(:lesson_booking_slot_recurring, day_of_week: 5, update_all: true)
lesson_session.counter({proposer: user, slot: counter3, message: 'ACtually, let\'s do this instead for just this one... again'})
lesson_session.errors.any?.should be false
lesson_session.status.should eql LessonSession::STATUS_APPROVED
lesson_session.status.should eql LessonSession::STATUS_COUNTERED
lesson_session.reload
lesson_session2.reload
lesson_session.scheduled_start.should eql counter2.scheduled_time(0)
lesson_session2.scheduled_start.should eql counter.scheduled_time(1)
booking.reload
booking.counter_slot.should eql counter3
lesson_session.accept({message: 'OK, lets fix all of them', slot: counter3.id})
lesson_session.accept({accepter: teacher_user, message: 'OK, lets fix all of them', slot: counter3.id})
lesson_session.errors.any?.should be_false
lesson_session.status.should eql LessonSession::STATUS_APPROVED
booking.reload
booking.counter_slot.should be_nil
lesson_session.reload
lesson_session2.reload
lesson_session.created_at.should be < lesson_session2.created_at

View File

@ -74,7 +74,7 @@ describe "RenderMailers", :slow => true do
lesson_session = testdrive_lesson(user, teacher)
UserMailer.deliveries.clear
UserMailer.student_lesson_accepted(lesson_session, "custom message").deliver
UserMailer.student_lesson_accepted(lesson_session, "custom message", lesson_session.lesson_booking.default_slot).deliver
end
it "teacher_scheduled_jamclass_invitation" do

View File

@ -2118,6 +2118,19 @@
})
}
function getLessonBooking(options) {
options = options || {}
return $.ajax({
type: "GET",
url: '/api/lesson_bookings/' + options.id,
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(options)
})
}
function getUnprocessedLesson(options) {
options = options || {}
return $.ajax({
@ -2140,6 +2153,36 @@
})
}
function acceptLessonBooking(options) {
return $.ajax({
type: "POST",
url: '/api/lesson_bookings/' + options.id + '/accept',
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(options)
})
}
function counterLessonBooking(options) {
return $.ajax({
type: "POST",
url: '/api/lesson_bookings/' + options.id + '/counter',
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(options)
})
}
function cancelLessonBooking(options) {
return $.ajax({
type: "POST",
url: '/api/lesson_bookings/' + options.id + '/cancel',
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(options)
})
}
function createAlert(subject, data) {
var message = {subject:subject};
@ -2384,8 +2427,12 @@
this.signup = signup;
this.portOverCarts = portOverCarts;
this.bookLesson = bookLesson;
this.getLessonBooking = getLessonBooking;
this.getUnprocessedLesson = getUnprocessedLesson;
this.getUnprocessedLessonOrIntent = getUnprocessedLessonOrIntent;
this.acceptLessonBooking = acceptLessonBooking;
this.cancelLessonBooking = cancelLessonBooking;
this.counterLessonBooking = counterLessonBooking;
this.submitStripe = submitStripe;
this.getLessonSessions = getLessonSessions;
this.getTestDriveStatus = getTestDriveStatus;

View File

@ -506,6 +506,12 @@
$.btOffAll(); // add any prod bubbles if you close a dialog
}
function screenProperty(screen, property) {
if (screen && screen in screenBindings) {
return screenBindings[screen][property]
}
return null;
}
function screenEvent(screen, evtName, data) {
if (screen && screen in screenBindings) {
if (evtName in screenBindings[screen]) {
@ -563,8 +569,7 @@
// reset the hash to where it just was
context.location.hash = currentHash;
}
else {
// not rejected by the screen; let it go
else {(screen)
return postFunction(e);
}
}
@ -583,8 +588,9 @@
logger.debug("layout: changing screen to " + currentScreen);
// notify everyone
$(document).triggerHandler(EVENTS.SCREEN_CHANGED, {previousScreen: previousScreen, newScreen: currentScreen})
window.NavActions.screenChanged(currentScreen, screenProperty(currentScreen, 'navName'))
context.JamTrackPreviewActions.screenChange()
screenEvent(currentScreen, 'beforeShow', data);
@ -731,6 +737,13 @@
}
logger.debug("opening dialog: " + dialog)
var $dialog = $('[layout-id="' + dialog + '"]');
if($dialog.length == 0) {
logger.debug("unknown dialog encountered: " + dialog)
return
}
var $overlay = $('.dialog-overlay')
if (opts.sizeOverlayToContent) {
@ -743,12 +756,12 @@
$overlay.show();
centerDialog(dialog);
var $dialog = $('[layout-id="' + dialog + '"]');
stackDialogs($dialog, $overlay);
addScreenContextToDialog($dialog)
$dialog.show();
// maintain center (un-attach previous sensor if applicable, then re-add always)
window.ResizeSensor.detach($dialog.get(0))
new window.ResizeSensor($dialog, function(){
centerDialog(dialog);

View File

@ -918,7 +918,7 @@
handleNotification(payload, header.type);
app.notify({
"title": "JamClass Invitation",
"title": "JamClass Scheduled",
"text": payload.msg + "<br/><br/>" + payload.session_name + "<br/>" + payload.session_date,
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
}, [{

View File

@ -3,6 +3,7 @@
//= require_directory ./react-components/helpers
//= require_directory ./react-components/actions
//= require ./react-components/stores/AppStore
//= require ./react-components/stores/NavStore
//= require ./react-components/stores/UserStore
//= require ./react-components/stores/UserActivityStore
//= require ./react-components/stores/InstrumentStore

View File

@ -44,6 +44,17 @@ UserStore = context.UserStore
componentDidUpdate:() ->
@iCheckify()
@slot1Date = @root.find('.slot-1 .date-picker')
@slot2Date = @root.find('.slot-2 .date-picker')
@slot1Date.datepicker({
dateFormat: "D M d yy",
onSelect: ((e) => @toggleDate(e))
})
@slot2Date.datepicker({
dateFormat: "D M d yy",
onSelect: ((e) => @toggleDate(e))
})
toggleDate: (e) ->
@ -97,6 +108,7 @@ UserStore = context.UserStore
teacherId: null,
generalErrors: null,
descriptionErrors: null,
bookedPriceErrors: null,
slot1Errors: null,
slot2Errors: null
updating: false,
@ -120,7 +132,6 @@ UserStore = context.UserStore
hour = new Number(hour)
if am_pm == 'PM'
hour += 12
hour -= 1
else
hour = null
@ -140,7 +151,7 @@ UserStore = context.UserStore
{hour: hour, minute: minute, date: date, day_of_week: day_of_week}
resetErrors: () ->
@setState({generalErrors: null, slot1Errors: null, slot2Errors: null, descriptionErrors: null})
@setState({generalErrors: null, slot1Errors: null, slot2Errors: null, descriptionErrors: null, bookedPriceErrors: null})
isRecurring: () ->
@state.recurring == 'recurring'
@ -199,7 +210,8 @@ UserStore = context.UserStore
options.recurring = @isRecurring()
parsed = @bookingOption()
options.lesson_length = parsed? && parsed.lesson_length
if parsed?
options.lesson_length = parsed.lesson_length
else
@ -212,7 +224,7 @@ UserStore = context.UserStore
booked: (response) ->
@setState({updating: false})
if response.user['has_stored_credit_card?']
context.location ="/client#/jamclass/lesson-request/" + response.id
context.location ="/client#/jamclass/lesson-session/" + response.id
else
context.location = '/client#/jamclass/payment'
@ -225,6 +237,10 @@ UserStore = context.UserStore
for errorType, errors of body.errors
if errorType == 'description'
@setState({descriptionErrors: errors})
else if errorType == 'booked_price'
@setState({bookedPriceErrors: errors})
else if errorType == 'lesson_length'
# swallow, because 'booked_price' covers this
else if errorType == 'lesson_booking_slots'
# do nothing. these are handled better by the _children errors
else
@ -263,7 +279,7 @@ UserStore = context.UserStore
if duration_enabled
enabledMinutes.push(minutes)
if !@state.recurring
if !@isRecurring()
for minutes in enabledMinutes
lesson_price = teacher["price_per_lesson_#{minutes}_cents"]
value = "single|#{minutes}"
@ -284,7 +300,7 @@ UserStore = context.UserStore
results.push(`<option value={value}>{display}</option>`)
if results.length == 0
results.push(`<option value=''>This teaher has no pricing options</option>`)
results.push(`<option value=''>This teacher has no pricing options</option>`)
else
results.unshift(`<option value=''>Please choose an option...</option>`)
results
@ -305,7 +321,11 @@ UserStore = context.UserStore
hours = []
for hour in ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']
hours.push(`<option key={hour} value={hour}>{hour}</option>`)
if hour == '12'
key = '00'
else
key = hour
hours.push(`<option key={key} value={key}>{hour}</option>`)
minutes = []
for minute in ['00', '15', '30', '45']
@ -317,17 +337,19 @@ UserStore = context.UserStore
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)
slot1Errors = context.JK.reactErrors(@state.slot1Errors, {preferred_day: 'Date', day_of_week: 'Day'})
slot2Errors = context.JK.reactErrors(@state.slot2Errors, {preferred_day: 'Date', day_of_week: 'Day'})
generalErrors = context.JK.reactErrors(@state.generalErrors, {user: 'You'})
bookedPriceClasses = classNames({booked_price: true, error: bookedPriceErrors?, field: true, 'booking-options': true})
descriptionClasses = classNames({description: true, error: descriptionErrors?})
slot1Classes = classNames({slot: true, 'slot-1': true, error: slot1Errors?})
slot2Classes = classNames({slot: true, 'slot-2': true, error: slot2Errors?})
generalClasses = classNames({general: true, error: generalErrors?})
if !@state.recurring
if !@isRecurring()
slots =
`<div className="slots">
<div className={slot1Classes}>
@ -412,8 +434,8 @@ UserStore = context.UserStore
testDriveLessons = "#{this.state.user?.remaining_test_drives} TestDrive lesson credits"
actions = `<div className="actions left">
<a className={bookLessonClasses} onClick={this.onBookLesson}>BOOK TESTDRIVE LESSON</a>
<a className={cancelClasses} onClick={this.onCancel}>CANCEL</a>
<a className={bookLessonClasses} onClick={this.onBookLesson}>BOOK TESTDRIVE LESSON</a>
</div>`
columnLeft = `<div className="column column-left">
@ -451,10 +473,10 @@ UserStore = context.UserStore
else if @isNormal()
bookingOptionsForTeacher = @constructBookingOptions()
header = `<h2>book a lesson</h2>`
header = `<h2>book a lesson with {teacher_first_name}</h2>`
outActions = `<div className="actions right ">
<a className={bookLessonClasses} onClick={this.onBookLesson}>BOOK LESSON</a>
<a className={cancelClasses} onClick={this.onCancel}>CANCEL</a>
<a className={bookLessonClasses} onClick={this.onBookLesson}>BOOK LESSON</a>
</div>`
columnLeft = `<div className="column column-left">
@ -467,9 +489,10 @@ UserStore = context.UserStore
<input type="radio" name="lesson-frequency" value="recurring" checked={this.isRecurring()}/><label for="lesson-frequency">A series of recurring weekly lessons</label>
</div>
</div>
<div className="field booking-options">
<div className={bookedPriceClasses}>
<label>What lesson length and payment option do you prefer?</label>
<select name="booking-options-for-teacher">{bookingOptionsForTeacher}</select>
<select name="booking-options-for-teacher" className="booking-options-for-teacher">{bookingOptionsForTeacher}</select>
{bookedPriceErrors}
</div>
<div className={descriptionClasses}>
<div className="description-prompt">Tell {teacher_first_name} a little about yourself as a student.</div>

View File

@ -0,0 +1,683 @@
context = window
rest = context.JK.Rest()
logger = context.JK.logger
UserStore = context.UserStore
@LessonBooking = React.createClass({
mixins: [
Reflux.listenTo(AppStore, "onAppInit"),
Reflux.listenTo(UserStore, "onUserChanged")
]
onAppInit: (@app) ->
@app.bindScreen('jamclass/lesson-booking',
{beforeShow: @beforeShow, afterShow: @afterShow, beforeHide: @beforeHide, navName: 'Lesson Booking'})
onUserChanged: (userState) ->
@setState({user: userState?.user})
onSlotDecision: (slot_decision) ->
@setState({slot_decision: slot_decision?.slot_decision})
componentWillMount: () ->
componentDidMount: () ->
@checkboxes = [{selector: 'input.slot-decision', stateKey: 'slot-decision'}]
@root = $(@getDOMNode())
componentDidUpdate: () ->
# add friendly helpers to a slot
processSlot: (slot, booking) ->
if !slot?
return
slot.slotTime = @slotTime(slot, booking)
slot.is_recurring = @isRecurring()
if slot['is_teacher_created?']
slot.creatorRole = 'teacher'
slot.creatorRoleRelative = 'teacher'
else
slot.creatorRole = 'student'
slot.creatorRoleRelative = 'student'
if context.JK.currentUserId == slot.proposer_id
slot.creatorRoleRelative = "your"
slot.mySlot = @mySlot(slot)
console.log("SLOT", slot)
componentWillUpdate: (nextProps, nextState) ->
if nextState.booking?
booking = nextState.booking
if !booking.post_processed
booking.post_processed = true
for slot in booking.slots
@processSlot(slot, booking)
@processSlot(booking.counter_slot, booking)
@processSlot(booking.default_slot, booking)
@processSlot(booking.alt_slot, booking)
getInitialState: () ->
{
user: null,
booking: null,
updating: false,
updatingLesson: false
}
beforeHide: (e) ->
beforeShow: (e) ->
afterShow: (e) ->
@setState({updating: true})
rest.getLessonBooking({
id: e.id,
}).done((response) => @getLessonBookingDone(response)).fail(@app.ajaxError)
getLessonBookingDone: (response) ->
if response.counter_slot?
startSlotDecision = response.counter_slot.id
else
startSlotDecision = response.default_slot.id
@setState({booking: response, updating: false, slot_decision: startSlotDecision})
toJamClassMain: (e) ->
e.preventDefault()
window.location.href = '/client#/jamclass'
onCancel: () ->
# what to do?
window.location.href = '/client#/jamclass'
onAccept: () ->
@setState({updatingLesson: true})
if @state.slot_decision == 'counter'
request = @getSlotData(0)
request.id = this.state.booking.id
request.timezone = window.jstz.determine().name()
request.message = @getMessage()
rest.counterLessonBooking(request).done((response) => @counterLessonBookingDone(response)).fail((response) => @counterLessonBookingFail(response))
else if @state.slot_decision == 'decline'
request = {}
request.message = @getMessage()
request.id = this.state.booking.id
rest.cancelLessonBooking(request).done((response) => @cancelLessonBookingDone(response)).fail((response) => @cancelLessonBookingFail(response))
else
request = {}
request.message = @getMessage()
request.id = this.state.booking.id
request.slot = this.state.slot_decision
rest.acceptLessonBooking(request).done((response) => @acceptLessonBookingDone(response)).fail((response) => @acceptLessonBookingFail(response))
onCancelLesson: (e) ->
@setState({updatingLesson: true})
request = {}
request.message = @getMessage()
request.id = this.state.booking.id
rest.cancelLessonBooking(request).done((response) => @cancelLessonBookingDone(response)).fail(@app.ajaxError)
acceptLessonBookingDone: (response ) ->
logger.debug("accept lesson booking done")
@setState({booking:response, updatingLesson: false})
cancelLessonBookingDone: (response ) ->
logger.debug("cancel lesson booking done")
@setState({booking:response, updatingLesson: false})
counterLessonBookingDone: (response ) ->
logger.debug("counter lesson booking done")
@setState({booking:response, updatingLesson: false})
counterLessonBookingFail: (response ) ->
@setState({updatingLesson: false})
logger.debug("counter lesson booking failed", response)
@app.ajaxError(arguments[0], arguments[1], arguments[2])
acceptLessonBookingFail: (response ) ->
@setState({updatingLesson: false})
logger.debug("accept lesson booking failed", response)
@app.ajaxError(arguments[0], arguments[1], arguments[2])
cancelLessonBookingFail: (response ) ->
@setState({updatingLesson: false})
logger.debug("cancel lesson booking failed", response)
@app.ajaxError(arguments[0], arguments[1], arguments[2])
getMessage: () ->
@root.find('textarea.message').val()
getSlotData: (position) ->
$slot = @root.find('.slot-' + (position + 1))
picker = $slot.find('.date-picker')
hour = $slot.find('.hour').val()
minute = $slot.find('.minute').val()
am_pm = $slot.find('.am_pm').val()
if hour? and hour != ''
hour = new Number(hour)
if am_pm == 'PM'
hour += 12
else
hour = null
if minute? and minute != ''
minute = new Number(minute)
else
minute = null
if !@isRecurring()
date = picker.datepicker("getDate")
if date?
date = context.JK.formatDateYYYYMMDD(date)
else
day_of_week = $slot.find('.day_of_week').val()
{hour: hour, minute: minute, date: date, day_of_week: day_of_week}
student: () ->
@state.booking?.user
teacher: () ->
@state.booking?.teacher
otherRole: () ->
if @teacherViewing()
'student'
else
'teacher'
other: () ->
if @teacherViewing()
@student()
else if @studentViewing()
@teacher()
else
null
myself: () ->
if @teacherViewing()
@teacher()
else if @studentViewing()
@student()
else
null
neverAccepted: () ->
!this.state.booking?.accepter_id?
defaultSlot: () ->
@state.booking?.default_slot
counteredSlot: () ->
@state.booking?.counter_slot
canceler: () ->
if @student().id == this.state.booking?.canceler_id
@student()
else
@teacher()
counterer: () ->
if @counteredSlot()?['is_teacher_created?']
@teacher()
else
@student()
otherCountered: () ->
@myself().id == @counterer().id
studentCanceled: () ->
@canceler().id == @student().id
selfCanceled: () ->
@canceler().id == @myself().id
studentMadeDefaultSlot: () ->
@student()?.id == @defaultSlot()?.proposer_id
teacherViewing: () ->
@state.booking? && @state.booking.teacher_id == context.JK.currentUserId
studentViewing: () ->
@state.booking? && @state.booking.user_id == context.JK.currentUserId
isActive: () ->
@state.booking?.active == true
isRequested: () ->
@state.booking?.status == 'requested' && !@isCounter()
isCounter: () ->
@counteredSlot()? && !@isCanceled() && !@isSuspended()
isInitialCounter: () ->
@isCounter() && !@isActive()
isActiveCounter: () ->
@isCounter() && @isActive()
isApproved: () ->
@state.booking?.status == 'approved'
isCanceled: () ->
@state.booking?.status == 'canceled'
isSuspended: () ->
@state.booking?.status == 'suspended'
isStudentCountered: () ->
@counteredSlot()?['is_student_created?']
isTeacherCountered: () ->
@counteredSlot()?['is_teacher_created?']
isTestDrive: () ->
@state.booking?.lesson_type == 'test-drive'
isRecurring: (booking = this.state.booking) ->
booking?.recurring
lessonLength: () ->
@state.booking?.lesson_length
lessonDesc: () ->
if @isRecurring()
lessonType = "weekly recurring #{this.lessonLength()}-minute lesson"
else
lessonType = "single #{this.lessonLength()}-minute lesson"
bookedPrice: () ->
this.state.booking?.booked_price
lessonPaymentAmt: () ->
if @state.booking?.payment_style == 'elsewhere'
'$10'
else if @state.booking?.payment_style == 'single'
"$#{this.bookedPrice()}"
else if @state.booking?.payment_style == 'weekly'
"at $#{this.bookedPrice()} per lesson"
else if @state.booking?.payment_style == 'monthly'
"monthly at $#{this.bookedPrice()} per month"
else
"$???"
selfLastToAct: () ->
if @isRequested()
@studentViewing()
else if @isCounter()
@counterer().id == @myself().id
else if @isCanceled()
@canceler().id == @myself().id
else if @isSuspended()
@studentViewing()
else
false
mySlot: (slot) ->
slot.proposer_id == context.JK.currentUserId
slotsDescription: (defaultSlot, altSlot) ->
text = "Preferred day/time for lesson is #{this.slotTime(defaultSlot)}. Secondary option is #{this.slotTime(altSlot)}."
slotTime: (slot, booking = this.state.booking) ->
if @isRecurring(booking)
"#{this.dayOfWeek(slot)} at #{this.dayTime(slot)}"
else
slot.pretty_start_time
slotTimePhrase: (slot) ->
if @isRecurring()
"each " + @slotTime(slot)
else
@slotTime(slot)
slotMessage: (slot, qualifier = '') ->
@messageBlock(slot.mySlot,slot[qualifier + 'message'] )
messageBlock: (selfWroteMessage, message) ->
if selfWroteMessage
whoSaid = "You said:"
else
whoSaid = "Message from #{this.other().first_name}:"
if message && message.length > 0
description = `<table>
<tbody>
<td className="description">{whoSaid}</td>
<td className="message">{message}</td>
</tbody>
</table>`
description
dayTime: (slot) ->
if slot.hour > 11
hour = slot.hour - 12
if hour == 0
hour = 12
end
am_pm = 'pm'
else
hour = slot.hour
am_pm = 'am'
"#{hour + 1}:#{slot.minute}#{am_pm}"
isNow: () ->
startTime = new Date(@state.booking.next_lesson.scheduled_start).getTime()
endTime = (startTime + @lessonLength() * 60)
now = new Date().getTime()
now > startTime && now < endTime
isPast: () ->
new Date().getTime() > new Date(@state.booking.next_lesson.scheduled_start).getTime()
sessionLink: () ->
link = "/client#/session/#{this.stateb.booking.next_lesson.music_session_id}"
`<a href={link}>JOIN SESSION</a>`
nextLessonSummaryWithAvatar: () ->
`<div className="row">
{this.userHeader(this.other())}
{this.nextLessonSummary()}
</div>`
nextLessonSummary: () ->
if @isActive()
if @isNow()
`<p>You should join this session immediately: {this.sessionLink()}</p>`
else if @isPast()
`<p>This lesson is over.</p>`
else
`<p>This lesson is scheduled to start at {this.state.booking.next_lesson.pretty_scheduled_start}</p>`
else if @isRequested()
`<p>This lesson is scheduled to start at {this.state.booking.next_lesson.pretty_scheduled_start}</p>`
renderCancelLesson: () ->
`<div>
<h3>Cancel this Lesson</h3>
<p>If you would like to cancel this lesson, you have to do so 24 hours in advance.</p>
<a className="button-orange cancel-lesson" onClick={this.onCancelLesson}>CANCEL LESSON</a>
</div>`
dayOfWeek: (slot) ->
switch slot.day_of_week
when 0 then "Sunday"
when 1 then "Monday"
when 2 then "Tuesday"
when 3 then "Wednesday"
when 4 then "Thursday"
when 5 then "Friday"
when 6 then "Saturday"
userHeader: (user) ->
photo_url = user?.photo_url
if !photo_url?
photo_url = '/assets/shared/avatar_generic.png'
`<div className="user-header">
<div className="avatar">
<img src={photo_url}/>
</div>
<div className="user-name">
{user.name}
</div>
</div>`
render: () ->
if @state.updating
@renderLoading()
else if @teacherViewing()
@renderTeacher()
else if @studentViewing()
@renderStudent()
else
@renderLoading()
renderTeacher: () ->
if @isRequested()
header = 'respond to lesson request'
content = @renderTeacherRequested()
if @isCounter()
if @isTeacherCountered()
header = 'your proposed alternate day/time is still pending'
else
header = 'student has proposed an alternate day/time'
content = @renderTeacherCountered()
else if @isApproved()
if @isNow()
header = 'the lesson is scheduled for right now!'
else if @isPast()
header = 'this lesson is over'
else
header = 'this lesson is coming up soon'
content = @renderTeacherApproved()
else if @isCanceled()
header = 'this lesson is canceled'
content = @renderTeacherCanceled()
else if @isSuspended()
header = 'This lesson has been suspended'
content = @renderTeacherSuspended()
`<div className="content-body-scroller">
<Nav/>
<h2>{header}</h2>
{content}
<br className="clearall"/>
</div>`
renderStudent: () ->
if @isRequested()
header = 'your lesson has been requested'
content = @renderStudentRequested()
if @isCounter()
if @isTeacherCountered()
header = 'teacher has proposed an alternate day/time'
else
header = 'your proposed alternate day/time is still pending'
content = @renderTeacherCountered()
else if @isApproved()
if @isNow()
header = 'the lesson is scheduled for right now!'
else if @isPast()
header = 'this lesson is over'
else
header = 'this lesson is coming up soon'
content = @renderStudentApproved()
else if @isCanceled()
if @neverAccepted() && @studentViewing() && !@studentCanceled()
header = "we're sorry, but your lesson request has been declined"
else
header = 'this lesson is canceled'
content = @renderStudentCanceled()
else if @isSuspended()
header = 'this lesson has been suspended'
content = @renderStudentSuspended()
`<div className="content-body-scroller">
<Nav/>
<h2>{header}</h2>
{content}
<br className="clearall"/>
</div>`
renderLoading: () ->
header = 'Loading...'
`<div className="content-body-scroller">
<Nav/>
<h2>{header}</h2>
<br className="clearall"/>
</div>`
renderStudentRequested: () ->
`<div className="contents">
<div className="row">
{this.userHeader(this.myself())}
Your request has been sent. You will receive an email when {this.teacher().name} responds.
</div>
</div>`
renderStudentApproved: () ->
if @studentMadeDefaultSlot()
message = this.slotMessage(this.state.booking.default_slot, 'accept')
if @isRecurring()
detail = `<p>Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}</p>`
else
detail = `<p>Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}</p>`
`<div className="contents">
<div className="row">
{this.userHeader(this.teacher())}
<p>Has accepted your lesson request.</p>
{detail}
{message}
</div>
<div className="row">
<h3>What Now?</h3>
<p>We strongly recommending adding this lesson to your calendar now so you don't forget it!</p>
<p>You can do this manually today; we will soon add a easier way to do so automatically.</p>
{this.nextLessonSummary()}
</div>
<div className="row">
{this.renderCancelLesson()}
</div>
</div>`
renderStudentCanceled: () ->
@renderCanceled()
renderStudentSuspended: () ->
# <p className="description">Message from {this.other().first_name}:</p>
# <div className="message"></div>
renderStudentCountered: () ->
@renderCountered()
renderTeacherRequested: () ->
if @isTestDrive()
action = `<p className="action">Has requested a TestDrive {this.lessonLength()}-minute lesson, for which you will be paid $10.</p>`
else
action = `<p className="action">Has requested a {this.lessonDesc()} lesson, for which you will be paid {this.lessonPaymentAmt()}.</p>`
`<div className="contents">
<div className="row">
{this.userHeader(this.other())}
{action}
{this.slotMessage(this.state.booking.default_slot)}
</div>
<LessonBookingDecision onSlotDecision={this.onSlotDecision} initial={this.neverAccepted()} counter={this.isCounter()} is_recurring={this.isRecurring()} slot_decision={this.state.slot_decision} slots={[this.state.booking.default_slot, this.state.booking.alt_slot]} otherRole={this.otherRole()} onUserDecision={this.onAccept} onUserCancel={this.onCancel} disabled={this.state.updatingLesson} selfLastToAct={this.selfLastToAct()}/>
</div>`
renderTeacherApproved: () ->
`<div className="contents">
{this.nextLessonSummaryWithAvatar()}
<div className="row">
{this.renderCancelLesson()}
</div>
</div>`
renderTeacherCanceled: () ->
@renderCanceled()
renderTeacherSuspended: () ->
renderTeacherCountered: () ->
@renderCountered()
renderCanceled: () ->
canceler = @canceler()
myself = @myself()
initial = @neverAccepted()
if initial
if @studentViewing()
if @studentCanceled()
action = `<p>You canceled this lesson request.</p>`
else
action = `<p>Has declined your lesson request.</p>`
else
if @studentCanceled()
action = `<p>Has canceled this lesson request.</p>`
else
action = `<p>You declined this lesson request.</p>`
if @studentViewing()
if @studentCanceled()
blurb = `<p>We're sorry this scheduling attempt did not working out for you. Please search our community of instructors to find someone else who looks like a good fit for you, and submit a new lesson request</p>`
else
blurb = `<p>We're sorry this instructor has declined your request. Please search our community of instructors to find someone else who looks like a good fit for you, and submit a new lesson request</p>`
whatNow =
`<div className="row">
<h3>What Now?</h3>
{blurb}
<a href="/client#/jamclass/searchOptions" className="search-for-more-teachers">SEARCH INSTRUCTORS NOW</a>
</div>`
`<div className="contents">
<div className="row">
{this.userHeader(canceler)}
{action}
{this.messageBlock(this.selfCanceled(), this.state.booking.cancel_message)}
</div>
{whatNow}
</div>`
renderCountered: () ->
counterer = @counterer()
myself = @myself()
initial = @neverAccepted()
phrase = this.slotTimePhrase(this.counteredSlot())
action = `<p>Has suggested a different time for your lesson.</p>`
detail = `<p>Proposed alternate day/time is {phrase}</p>`
`<div className="contents">
<div className="row">
{this.userHeader(counterer)}
{action}
{detail}
{this.slotMessage(this.counteredSlot())}
</div>
<LessonBookingDecision onSlotDecision={this.onSlotDecision} initial={initial} counter={this.isCounter()} is_recurring={this.isRecurring()} slot_decision={this.state.slot_decision} slots={[this.state.booking.counter_slot]} otherRole={this.otherRole()} onUserDecision={this.onAccept} onUserCancel={this.onCancel} selfLastToAct={this.selfLastToAct()}/>
</div>`
})

View File

@ -0,0 +1,216 @@
@LessonBookingDecision = React.createClass({
#slot.creatorRole
#slot.slotTime
# props.initial
# props.counter
# props.slots
# props.is_recurring
# props.slot_decision
# props.otherRole
mixins: [
ICheckMixin,
]
propTypes: {
onSlotDecision: React.PropTypes.func.isRequired
onUserCancel: React.PropTypes.func.isRequired
onUserDecision: React.PropTypes.func.isRequired
}
getInitialState: () ->
{
slot_decision: null
}
componentWillMount: () ->
@days = []
@days.push(`<option value=''>Choose a day of the week...</option>`)
@days.push(`<option value="0">Sunday</option>`)
@days.push(`<option value="1">Monday</option>`)
@days.push(`<option value="2">Tuesday</option>`)
@days.push(`<option value="3">Wednesday</option>`)
@days.push(`<option value="4">Thursday</option>`)
@days.push(`<option value="5">Friday</option>`)
@days.push(`<option value="6">Saturday</option>`)
@hours = []
for hour in ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']
if hour == '12'
key = '00'
else
key = hour
@hours.push(`<option key={key} value={key}>{hour}</option>`)
@minutes = []
for minute in ['00', '15', '30', '45']
@minutes.push(`<option key={minute} value={minute}>{minute}</option>`)
@am_pm = [`<option key="AM" value="AM">AM</option>`, `<option key="PM" value="PM">PM</option>`]
componentDidMount: () ->
@checkboxes = [{selector: 'input.slot-decision', stateKey: 'slot-decision'}]
@root = $(@getDOMNode())
@iCheckify()
componentDidUpdate: () ->
@iCheckify()
@slotDate = @root.find('.date-picker')
@slotDate.datepicker({
dateFormat: "D M d yy",
onSelect: ((e) => @toggleDate(e))
})
checkboxChanged: (e) ->
checked = $(e.target).is(':checked')
value = $(e.target).val()
@props.onSlotDecision({slot_decision: value})
onUserDecision: (e) ->
e.preventDefault()
if this.props.disabled
return
this.props.onUserDecision(this.state.slot_decision)
onUserCancel: (e) ->
e.preventDefault()
if this.props.disabled
return
this.props.onUserCancel()
dayOfWeek: (slot) ->
switch slot.day_of_week
when 0 then "Sunday"
when 1 then "Monday"
when 2 then "Tuesday"
when 3 then "Wednesday"
when 4 then "Thursday"
when 5 then "Friday"
when 6 then "Saturday"
slotLabelText: (index, slot) ->
if @props.counter
"Accept #{slot.creatorRoleRelative} proposed day/time"
else
if index == 0
"Accept #{slot.creatorRoleRelative} preferred day/time"
else
"Accept #{slot.creatorRoleRelative} secondary day/time"
showDeclineVerb: () ->
this.props.initial && this.props.otherRole == 'student'
render: () ->
if this.props.selfLastToAct
userPromptHeader = `<h3>Would you like to do something else?</h3>`
else
userPromptHeader = `<h3>How do you want to handle this request?</h3>`
if this.props.slot_decision == 'counter'
actionBtnText = "PROPOSE ALTERNATE TIME"
else if this.props.slot_decision == 'decline'
if @showDeclineVerb()
verb = "DECLINE"
else
verb = "CANCEL"
actionBtnText = "#{verb} LESSON"
else if this.props.initial
actionBtnText = "ACCEPT & SCHEDULE LESSON"
else
actionBtnText = "ACCEPT & UPDATE LESSON"
if this.props.is_recurring
slotAltPrompt = `<div className="slot-alt-prompt">
<span className="alt-date-block">
<span className="alt-date">Day:</span>
<select disabled={this.props.disabled} className="day_of_week" data-slot={i}>{days}</select>
</span>
<span className="alt-time-block">
<span className="alt-time">Time:</span>
<select className="hour">{this.hours}</select> : <select disabled={this.props.disabled} className="minute">{this.minutes}</select>
<select className="am_pm">{this.am_pm}</select>
</span>
</div>`
else
slotAltPrompt = `<div className="slot-alt-prompt">
<span className="alt-date-block">
<span className="alt-date">Date:</span>
<input className="date-picker" type="text" data-slot={i}></input>
</span>
<span className="alt-time-block">
<span className="alt-time">Time:</span>
<select className="hour">{this.hours}</select> : <select disabled={this.props.disabled} className="minute">{this.minutes}</select>
<select disabled={this.props.disabled} className="am_pm">{this.am_pm}</select>
</span>
</div>`
if @showDeclineVerb()
declineVerb = "Decline"
else
declineVerb = "Cancel"
cancelClasses = {cancel: true, "button-grey": true, disabled: this.props.disabled}
scheduleClasses = {schedule: true, "button-orange": true, disabled: this.props.disabled}
slots = []
if !(this.props.counter && this.props.selfLastToAct)
proposeAltLabelText = "Propose alternate day/time"
for slot, i in @props.slots
if this.props.is_recurring
slotDetail = `<div className="slot-detail">Each {slot.slotTime}</div>`
else
slotDetail = `<div className="slot-detail">{slot.slotTime}</div>`
slots.push(`<div key={slot.id} className="field slot-decision-field">
<div className="label-area">
<input disabled={this.props.disabled} className="slot-decision" type="radio" name="slot-decision"
value={slot.id} readyOnly={true} defaultChecked={slot.id == this.props.slot_decision} /><label>{this.slotLabelText(i, slot)}</label>
</div>
{slotDetail}
</div>`)
else
proposeAltLabelText = "Propose new alternate day/time"
# if you have issued a counter, you should be able to withdraw it
# TODO
`<div className="row">
<div className="column column-left">
{userPromptHeader}
<div className="slot-decision slot-1">
{slots}
<div className="field slot-decision-field">
<div className="label-area">
<input disabled={this.props.disabled} className="slot-decision" type="radio" readyOnly={true} name="slot-decision" value="counter" defaultChecked={this.props.slot_decision == 'counter'}/>
<label>{proposeAltLabelText}</label>
</div>
{slotAltPrompt}
</div>
<div className="field slot-decision-field">
<input disabled={this.props.disabled} className="slot-decision" type="radio" name="slot-decision" value="decline" readyOnly={true} defaultChecked={this.props.slot_decision == 'decline'} /><label>{declineVerb} lesson
request</label>
</div>
</div>
</div>
<div className="column column-right">
<h3>Send message to {this.props.otherRole} with your response.</h3>
<textarea className="message" name="message" disabled={this.props.disabled}></textarea>
<div className="actions">
<a className={classNames(cancelClasses)} onClick={this.onUserCancel}>CANCEL</a>
<a className={classNames(scheduleClasses)} onClick={this.onUserDecision}>{actionBtnText}</a>
</div>
</div>
</div>`
})

View File

@ -215,7 +215,7 @@ UserStore = context.UserStore
title: "Lesson Requested",
text: "The teacher has been notified of your lesson request, and should respond soon.<br/><br/>We've taken you automatically to the page for this request, and sent an email to you with a link here as well. All communication with the teacher will show up on this page and in email."
})
window.location = "/client#/jamclass/lesson-request/" + response.lesson.id
window.location = "/client#/jamclass/lesson-session/" + response.lesson.id
else if response.test_drive? || response.intent_book_test_drive?
if response.test_drive?.teacher_id

View File

@ -0,0 +1,58 @@
context = window
rest = context.JK.Rest()
logger = context.JK.logger
UserStore = context.UserStore
@LessonSession = React.createClass({
mixins: [
Reflux.listenTo(AppStore, "onAppInit"),
Reflux.listenTo(UserStore, "onUserChanged")
]
onAppInit: (@app) ->
@app.bindScreen('jamclass/lesson-session',
{beforeShow: @beforeShow, afterShow: @afterShow, beforeHide: @beforeHide})
onUserChanged: (userState) ->
@setState({user: userState?.user})
componentDidMount: () ->
@root = $(@getDOMNode())
getInitialState: () ->
{
user: null,
lesson: null,
updating: false,
}
beforeHide: (e) ->
@resetErrors()
beforeShow: (e) ->
afterShow: (e) ->
@setState({updating: true})
render: () ->
header = "header"
`<div className="content-body-scroller">
<div className="column left-column">
{header}
</div>
<div className="column right-column">
</div>
<br className="clearall"/>
</div>`
})

View File

@ -0,0 +1,34 @@
context = window
teacherActions = window.JK.Actions.Teacher
@Nav = React.createClass({
mixins: [
Reflux.listenTo(AppStore, "onAppInit"),
Reflux.listenTo(NavStore, "onNavChanged")
]
onAppInit: (@app) ->
onNavChanged: (nav) ->
@setState({nav: nav})
render: () ->
navs = []
if this.state?.nav?
nav = this.state.nav
if nav.currentSection?
navs.push(`<span>&nbsp;:&nbsp;</span>`)
navs.push(`<a href={nav.currentSection.url} >{nav.currentSection.name}</a>`)
if nav.optionalParent?
navs.push(`<span>&nbsp;:&nbsp;</span>`)
navs.push(`<a href={nav.optionalParent.url} >{nav.optionalParent.name}</a>`)
if nav.currentScreenName?
navs.push(`<span>&nbsp;:&nbsp;</span>`)
navs.push(`<span styles={{color:'#ccc'}}>{nav.currentScreenName}</span>`)
`<div className="site-nav">
<a href="/client#/home" >JamKazam Home</a>
{navs}
</div>`
})

View File

@ -121,7 +121,7 @@ ProfileActions = @ProfileActions
logger.debug("TeacherSearchScreen: user offered test drive")
@app.layout.showDialog('try-test-drive', {d1: user.teacher.id})
else if response.remaining_test_drives > 0
if response.booked_with_teacher
if response.booked_with_teacher && !context.JK.currentUserAdmin
logger.debug("TeacherSearchScreen: teacher already test-drived")
context.JK.Banner.showAlert('TestDrive', "You have already take a TestDrive lesson from this teacher. With TestDrive, you need to use your lessons on 4 different teachers to find one who is best for you. We're sorry, but you cannot take multiple TestDrive lessons from a single teacher.")
@ -241,10 +241,11 @@ ProfileActions = @ProfileActions
`<div className="content-body-scroller">
<div className="screen-content">
<div className="header">
<a href="" onClick={alert.bind('not yet')}>LESSONS HOME</a>&nbsp;:&nbsp;
<a href="/client#/jamclass/searchOptions" >TEACHERS SEARCH</a>&nbsp;:&nbsp;
<a href="/client#/home">JamKazam Home</a>&nbsp;:&nbsp;
<a href="/client#/jamclass">JamClass Home</a>&nbsp;:&nbsp;
<a href="/client#/jamclass/searchOptions" >Teachers Search</a>&nbsp;:&nbsp;
<span className="search-results-options">
<span className="results-text">SEARCH RESULTS / </span>
<span className="results-text">Search Results / </span>
<span className="search-summary">{searchDesc}</span>
</span>
</div>

View File

@ -0,0 +1,7 @@
context = window
@NavActions = Reflux.createActions({
setScreenInfo: {}
screenChanged: {}
})

View File

@ -14,13 +14,20 @@ teacherActions = window.JK.Actions.Teacher
for checkbox in @checkboxes
selector = checkbox.selector
stateKey = checkbox.stateKey
enabled = @state[stateKey]
choice = @state[stateKey]
$candidate = @root.find(selector)
@iCheckIgnore = true
if enabled
@root.find(selector).iCheck('check').attr('checked', true);
if $candidate.attr('type') == 'radio'
$found = @root.find(selector + '[value="' + choice + '"]')
$found.iCheck('check').attr('checked', true)
else
@root.find(selector).iCheck('uncheck').attr('checked', false);
if choice
$candidate.iCheck('check').attr('checked', true);
else
$candidate.iCheck('uncheck').attr('checked', false);
@iCheckIgnore = false
enableICheck: (e) ->

View File

@ -0,0 +1,46 @@
$ = jQuery
context = window
logger = context.JK.logger
rest = new context.JK.Rest()
@NavStore = Reflux.createStore(
{
screenPath: null
currentSection: null
currentScreenName: null
sections: {jamclass: {name: 'JamClass Home', url: '/client#/jamclass'}, jamtrack: {name: 'JamTrack Home', url: '/client#/jamtrack'}}
listenables: @NavActions
init: ->
this.listenTo(context.AppStore, this.onAppInit)
onAppInit: (@app) ->
onScreenChanged: (screenPath, screenName) ->
if screenPath == @screenPath
return
@screenPath = screenPath
@currentScreenName = screenName
@currentSection = null
if screenPath?
index = screenPath.indexOf('/')
if index > -1
rootPath = screenPath.substring(0, index)
else
rootPath = screenPath
@currentSection = @sections[rootPath]
@changed()
onSetScreenInfo: (currentScreenName) ->
@currentScreenName = currentScreenName
@changed()
changed:() ->
@trigger({currentScreen: @currentScreen, currentSection: @currentSection, currentScreenName: @currentScreenName})
}
)

View File

@ -722,3 +722,7 @@ $ReactSelectVerticalPadding: 3px;
opacity: 0.01;
}
}
#ui-datepicker-div {
z-index:10 !important;
}

View File

@ -81,7 +81,7 @@
margin:0 0 20px 0 !important;
}
.avatar {
display:inline-block;
display:inline-block;
padding:1px;
width:48px;
height:48px;
@ -146,4 +146,7 @@
width:80%;
}
}
select.hour {
margin-left:20px;
}
}

View File

@ -0,0 +1,231 @@
@import "client/common";
#lesson-booking {
div[data-react-class="LessonBooking"] {
height:100%;
}
.content-body-scroller {
height:100%;
padding:30px;
@include border_box_sizing;
}
h2 {
font-size: 24px;
font-weight:700;
margin-bottom: 20px !important;
display:inline-block;
}
.row {
margin-bottom:40px;
}
h3 {
font-size: 16px;
font-weight:700;
color:white;
margin-bottom: 10px !important;
display:inline-block;
}
.contents {
padding-left:60px;
position:relative;
}
.no-charge {
float:right;
}
.column {
@include border_box_sizing;
width:50%;
}
.column-left {
float:left;
padding-right:20px;
&.stored {
display:none;
}
}
.column-right {
float:right;
padding-left:20px;
&.stored {
float:left;
}
}
label {
display:inline-block;
color: $ColorTextTypical;
}
select {
display:inline-block;
}
.slot-decision {
vertical-align: top;
}
.slot-alt-prompt {
color: $ColorTextTypical;
display: inline-block;
margin-top: 4px;
padding-left:22px;
label {
margin-bottom:10px;
}
}
.slot-detail {
color: $ColorTextTypical;
display: inline-block;
margin-top: 4px;
padding-left:22px;
}
.alt-date-block {
white-space: nowrap;
float:left;
display:block;
margin-bottom:10px;
vertical-align: middle;
line-height:31px;
}
.alt-time-block {
white-space: nowrap;
float:left;
display:block;
margin-bottom:10px;
vertical-align: middle;
line-height:31px;
}
td.description {
color: $ColorTextTypical;
vertical-align: top;
white-space: nowrap;
}
td.message {
color: $ColorTextTypical;
padding-left: 10px;
vertical-align: top;
}
.date-picker {
margin-right:20px;
}
.alt-date {
margin-right:10px;
}
.alt-time {
margin-right:10px;
}
input {
display:inline-block;
//width: calc(100% - 150px);
width:auto;
@include border_box_sizing;
}
textarea {
width:100%;
@include border_box_sizing;
height:125px;
}
.field {
position:relative;
display:block;
margin-bottom:15px;
label {
width:auto;
}
}
p {
line-height:125% !important;
font-size:14px !important;
margin:0 0 10px 0 !important;
color:$ColorTextTypical !important;
}
.user-header {
position:relative;
height:42px;
}
.user-name {
line-height:48px;
vertical-align:middle;
}
.avatar {
position:absolute;
left:-60px;
display:inline-block;
padding:1px;
width:48px;
height:48px;
background-color:#ed4818;
margin:0 20px 0 0;
-webkit-border-radius:24px;
-moz-border-radius:24px;
border-radius:24px;
float:none;
}
.avatar img {
width: 48px;
height: 48px;
-webkit-border-radius:24px;
-moz-border-radius:24px;
border-radius:24px;
}
.teacher-name {
font-size:16px;
display:inline-block;
height:48px;
vertical-align:middle;
}
.actions {
margin-left:-3px;
margin-bottom:20px;
}
.error-text {
display:block;
}
.actions {
clear:both;
a.cancel {
float:left;
}
a.schedule {
float:right;
}
a {
margin-bottom:20px; // in case the floating wraps
}
}
.site-nav {
margin-bottom:10px;
}
.iradio_minimal {
display:inline-block;
top: -2px;
margin-right: 5px;
}
textarea.message {
width:100%;
height:150px;
margin-bottom:20px;
}
a.cancel-lesson {
margin-left:2px;
}
a.search-for-more-teachers {
margin-top:40px;
text-align:center;
display:block;
}
}

View File

@ -0,0 +1,113 @@
@import "client/common";
#lesson-session {
div[data-react-class="LessonSession"] {
height:100%;
}
.content-body-scroller {
height:100%;
padding:30px;
@include border_box_sizing;
}
h2 {
font-size: 20px;
font-weight:700;
margin-bottom: 20px !important;
display:inline-block;
}
.no-charge {
float:right;
}
.column {
@include border_box_sizing;
width:50%;
}
.column-left {
float:left;
padding-right:20px;
&.stored {
display:none;
}
}
.column-right {
float:right;
padding-left:20px;
&.stored {
float:left;
}
}
label {
display:inline-block;
}
select {
display:inline-block;
}
input {
display:inline-block;
width: calc(100% - 150px);
@include border_box_sizing;
}
textarea {
width:100%;
@include border_box_sizing;
height:125px;
}
.field {
position:relative;
display:block;
margin-top:15px;
margin-bottom:25px;
label {
width:150px;
}
}
p {
line-height:125% !important;
font-size:14px !important;
margin:0 0 20px 0 !important;
}
.avatar {
display:inline-block;
padding:1px;
width:48px;
height:48px;
background-color:#ed4818;
margin:10px 20px 0 0;
-webkit-border-radius:24px;
-moz-border-radius:24px;
border-radius:24px;
float:none;
}
.avatar img {
width: 48px;
height: 48px;
-webkit-border-radius:24px;
-moz-border-radius:24px;
border-radius:24px;
}
.teacher-name {
font-size:16px;
display:inline-block;
height:48px;
vertical-align:middle;
}
.actions {
margin-left:-3px;
margin-bottom:20px;
}
.error-text {
display:block;
}
.actions {
float:left;
clear:both;
}
}

View File

@ -1,6 +1,8 @@
class ApiLessonBookingsController < ApiController
before_filter :api_signed_in_user
before_filter :lookup_lesson_booking, :only => [:accept, :counter, :cancel, :show]
before_filter :auth_lesson_booking, :only => [:accept, :counter, :cancel, :show]
respond_to :json
def index
@ -12,6 +14,10 @@ class ApiLessonBookingsController < ApiController
render "api_lesson_bookings/index", :layout => nil
end
def show
end
def create
if params[:lesson_type] == LessonBooking::LESSON_TYPE_FREE
@ -43,7 +49,7 @@ class ApiLessonBookingsController < ApiController
specified_slot.preferred_day = day
specified_slot.hour = slot[:hour]
specified_slot.minute = slot[:minute]
specified_slot.timezone = slot[:timezone]
specified_slot.timezone = params[:timezone]
slots << specified_slot
end
@ -63,6 +69,7 @@ class ApiLessonBookingsController < ApiController
slots = []
for slot in specified_slots
specified_slot = LessonBookingSlot.new
specified_slot.slot_type = LessonBookingSlot::SLOT_TYPE_SINGLE
if slot[:date].present?
day = slot[:date]
@ -71,7 +78,7 @@ class ApiLessonBookingsController < ApiController
end
specified_slot.hour = slot[:hour]
specified_slot.minute = slot[:minute]
specified_slot.timezone = slot[:timezone]
specified_slot.timezone = params[:timezone]
slots << specified_slot
end
@ -103,9 +110,10 @@ class ApiLessonBookingsController < ApiController
end
end
specified_slot.hour = slot[:hour]
specified_slot.minute = slot[:minute]
specified_slot.timezone = slot[:timezone]
specified_slot.timezone = params[:timezone]
slots << specified_slot
end
@ -118,6 +126,70 @@ class ApiLessonBookingsController < ApiController
end
end
def accept
next_lesson = @lesson_booking.next_lesson
next_lesson.accept({
message: params[:message],
slot: params[:slot],
accepter: current_user
})
if next_lesson.errors.any?
respond_with_model next_lesson
return
end
@lesson_booking.reload
end
def counter
next_lesson = @lesson_booking.next_lesson
slot = LessonBookingSlot.new
if @lesson_booking.recurring
slot.slot_type = LessonBookingSlot::SLOT_TYPE_RECURRING
slot.day_of_week = slot[:day_of_week]
else
slot.slot_type = LessonBookingSlot::SLOT_TYPE_SINGLE
if params[:date].present?
day = params[:date]
day = Date.parse(day) if day && !day.include?('NaN')
slot.preferred_day = day
end
end
slot.hour = params[:hour]
slot.minute = params[:minute]
slot.timezone = params[:timezone]
next_lesson.counter({
proposer: current_user,
message: params[:message],
slot: slot
})
if next_lesson.errors.any?
respond_with_model next_lesson
return
end
@lesson_booking.reload
end
def cancel
next_lesson = @lesson_booking.next_lesson
next_lesson.cancel({
canceler: current_user,
message: params[:message],
update_all: true
})
if next_lesson.errors.any?
respond_with_model next_lesson
return
end
@lesson_booking.reload
end
def unprocessed
@show_teacher = true
@lesson_booking = LessonBooking.unprocessed(current_user).first
@ -129,4 +201,24 @@ class ApiLessonBookingsController < ApiController
@lesson_booking = LessonBooking.unprocessed(current_user).first
@intent = TeacherIntent.recent_test_drive(current_user)
end
private
def lookup_lesson_booking
@lesson_booking = LessonBooking.find_by_id(params[:id])
if @lesson_booking.nil?
# try with lesson session
lesson_session = LessonSession.find_by_id(params[:id])
if lesson_session
@lesson_booking = lesson_session.lesson_booking
end
end
raise ActiveRecord::RecordNotFound, "Can't find lesson booking" if @lesson_booking.nil?
end
def auth_lesson_booking
if current_user.id != @lesson_booking.teacher.id && current_user.id != @lesson_booking.student.id
raise JamPermissionError, "You do not have access to this lesson booking"
end
end
end

View File

@ -39,8 +39,3 @@ node :mixdowns do |jam_track|
end
items
end
child(:jam_track_tap_ins => :tap_ins) {
attributes :offset_time, :bpm, :tap_in_count
}

View File

@ -0,0 +1,3 @@
object @lesson_booking
extends "api_lesson_bookings/show"

View File

@ -0,0 +1,3 @@
object @lesson_booking
extends "api_lesson_bookings/show"

View File

@ -0,0 +1,3 @@
object @lesson_booking
extends "api_lesson_bookings/show"

View File

@ -1,15 +1,32 @@
object @lesson_booking
attributes :id, :lesson_type, :payment_style, :recurring, :teacher_id, :description, :lesson_length, :created_at
attributes :id, :status, :lesson_type, :payment_style, :recurring, :teacher_id, :description, :lesson_length, :created_at, :user_id, :active, :accepter_id, :canceler_id, :cancel_message
child(:lesson_booking_slots => :slots) {
attributes :id, :preferred_day, :day_of_week, :hour, :minute, :slot_type
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?
}
child(:default_slot => :default_slot) {
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?
}
child(:alt_slot => :alt_slot) {
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?
}
child(:counter_slot => :counter_slot) {
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?
}
child(:user => :user) {
attributes :id, :has_stored_credit_card?
attributes :id, :has_stored_credit_card?, :first_name, :last_name, :photo_url, :name
}
child(:teacher => :teacher) do |teacher|
partial "api_users/show", object: teacher
node :teacher do |lesson_booking|
partial "api_users/show", object: lesson_booking.teacher
end
child(:next_lesson => :next_lesson) do |next_lesson|
attributes :id, :scheduled_start, :status, :music_session_id, :pretty_scheduled_start
end

View File

@ -47,6 +47,8 @@
<%= render "clients/teachers/search/search_results" %>
<%= render "clients/jamclass/book_lesson_free" %>
<%= render "clients/jamclass/lesson_payment" %>
<%= render "clients/jamclass/lesson_session" %>
<%= render "clients/jamclass/lesson_booking" %>
<%= render "clients/jamclass/jamclass_student" %>
<%= render "users/feed_music_session_ajax" %>
<%= render "users/feed_recording_ajax" %>

View File

@ -1,4 +1,4 @@
#lesson-book-free.screen.secondary layout="screen" layout-id="jamclass/book-lesson" layout-arg="id"
#lesson-book.screen.secondary layout="screen" layout-id="jamclass/book-lesson" layout-arg="id"
.content-head
.content-icon
= image_tag "content/icon_account.png", :size => "27x20"

View File

@ -0,0 +1,10 @@
#lesson-booking.screen.secondary layout="screen" layout-id="jamclass/lesson-booking" layout-arg="id"
.content-head
.content-icon
= image_tag "content/icon_account.png", :size => "27x20"
h1
| jamclass
= render "screen_navigation"
.content-body
= react_component 'LessonBooking', {}

View File

@ -0,0 +1,10 @@
#lesson-payment.screen.secondary layout="screen" layout-id="jamclass/lesson-session"
.content-head
.content-icon
= image_tag "content/icon_account.png", :size => "27x20"
h1
| jamclass
= render "screen_navigation"
.content-body
= react_component 'LessonSession', {}

View File

@ -684,6 +684,10 @@ SampleApp::Application.routes.draw do
match '/lesson_sessions' => 'api_lesson_sessions#index', :via => :get
match '/lesson_bookings' => 'api_lesson_bookings#create', :via => :post
match '/lesson_bookings/:id/accept' => 'api_lesson_bookings#accept', :via => :post
match '/lesson_bookings/:id/counter' => 'api_lesson_bookings#counter', :via => :post
match '/lesson_bookings/:id/cancel' => 'api_lesson_bookings#cancel', :via => :post
match '/lesson_bookings/:id' => 'api_lesson_bookings#show', :via => :get
match '/lesson_bookings/unprocessed' => 'api_lesson_bookings#unprocessed', :via => :get
match '/lesson_bookings/unprocessed_or_intent' => 'api_lesson_bookings#unprocessed_or_intent', :via => :get

46
web/lib/tasks/lesson.rake Normal file
View File

@ -0,0 +1,46 @@
require 'factory_girl'
namespace :lessons do
task book_test_drive: :environment do |task, args|
user = User.find_by_email(ENV['STUDENT_EMAIL'])
teacher = User.find_by_email(ENV['TEACHER_EMAIL'])
recurring = ENV['RECURRING'] == '1'
slots = []
if recurring
slots << FactoryGirl.build(:lesson_booking_slot_recurring, timezone: 'America/Chicago')
slots << FactoryGirl.build(:lesson_booking_slot_recurring, timezone: 'America/Chicago')
else
slots << FactoryGirl.build(:lesson_booking_slot_single, timezone: 'America/Chicago')
slots << FactoryGirl.build(:lesson_booking_slot_single, timezone: 'America/Chicago')
end
if user.stored_credit_card == false
user.stored_credit_card = true
user.save!
end
booking = LessonBooking.book_test_drive(user, teacher, slots, "Hey I've heard of you before.")
if booking.errors.any?
puts booking.errors.inspect
raise "booking failed"
end
lesson = booking.lesson_sessions[0]
if user.most_recent_test_drive_purchase.nil?
LessonPackagePurchase.create(user, booking, LessonPackageType.test_drive)
end
#lesson.accept({message: 'Yeah I got this', slot: slots[0]})
#lesson.errors.any?.should be_false
#lesson.reload
#lesson.slot.should eql slots[0]
#lesson.status.should eql LessonSession::STATUS_APPROVED
puts "http://localhost:3000/client#/jamclass/lesson-booking/#{booking.id}"
end
end

View File

@ -874,4 +874,97 @@ FactoryGirl.define do
sequence(:jamblaster_client_id ) { |n| "jamblaster_client_id#{n}" }
sequence(:sibling_key ) { |n| "sibling_key#{n}" }
end
factory :lesson_booking_slot, class: 'JamRuby::LessonBookingSlot' do
factory :lesson_booking_slot_single do
slot_type 'single'
preferred_day Date.today + 3
day_of_week nil
hour 12
minute 30
timezone 'UTC'
end
factory :lesson_booking_slot_recurring do
slot_type 'recurring'
preferred_day nil
day_of_week 0
hour 12
minute 30
timezone 'UTC'
end
end
factory :lesson_booking, class: 'JamRuby::LessonBooking' do
association :user, factory: :user
association :teacher, factory: :teacher_user
card_presumed_ok false
sent_notices false
recurring false
lesson_length 30
lesson_type JamRuby::LessonBooking::LESSON_TYPE_FREE
payment_style JamRuby::LessonBooking::PAYMENT_STYLE_ELSEWHERE
description "Oh my goodness!"
status JamRuby::LessonBooking::STATUS_REQUESTED
before(:create) do |lesson_booking, evaluator|
lesson_booking.lesson_booking_slots = [FactoryGirl.build(:lesson_booking_slot_single, lesson_booking: lesson_booking),
FactoryGirl.build(:lesson_booking_slot_single, lesson_booking: lesson_booking)]
end
#lesson_booking_slots [FactoryGirl.build(:lesson_booking_slot_single), FactoryGirl.build(:lesson_booking_slot_single)]
end
factory :lesson_package_purchase, class: "JamRuby::LessonPackagePurchase" do
lesson_package_type { JamRuby::LessonPackageType.single }
association :user, factory: :user
association :teacher, factory: :teacher_user
price 30.00
factory :test_drive_purchase do
lesson_package_type { JamRuby::LessonPackageType.test_drive }
association :lesson_booking, factory: :lesson_booking
price 49.99
end
end
factory :lesson_session, class: 'JamRuby::LessonSession' do
ignore do
student nil
end
music_session {FactoryGirl.create(:music_session, creator: student)}
lesson_booking {FactoryGirl.create(:lesson_booking, user: student, teacher: teacher)}
association :teacher, factory: :teacher_user
lesson_type JamRuby::LessonSession::LESSON_TYPE_SINGLE
duration 30
booked_price 49.99
status JamRuby::LessonSession::STATUS_REQUESTED
#teacher_complete true
#student_complete true
end
factory :charge, class: 'JamRuby::Charge' do
type 'JamRuby::Charge'
amount_in_cents 1000
end
factory :teacher_payment_charge, parent: :charge, class: 'JamRuby::TeacherPaymentCharge' do
type 'JamRuby::TeacherPaymentCharge'
end
factory :teacher_payment, class: 'JamRuby::TeacherPayment' do
association :teacher, factory: :teacher_user
association :teacher_payment_charge, factory: :teacher_payment_charge
amount_in_cents 1000
end
# you gotta pass either lesson_session or lesson_package_purchase for this to make sense
factory :teacher_distribution, class: 'JamRuby::TeacherDistribution' do
association :teacher, factory: :teacher_user
association :teacher_payment, factory: :teacher_payment
ready false
amount_in_cents 1000
end
end