* wip
This commit is contained in:
parent
298c6e5bc5
commit
0e1c070c89
|
|
@ -60,6 +60,8 @@ CREATE TABLE lesson_sessions (
|
|||
|
||||
ALTER TABLE music_sessions ADD COLUMN lesson_session_id VARCHAR(64) REFERENCES lesson_sessions(id);
|
||||
ALTER TABLE notifications ADD COLUMN lesson_session_id VARCHAR(64) REFERENCES lesson_sessions(id);
|
||||
ALTER TABLE notifications ADD COLUMN purpose VARCHAR(200);
|
||||
ALTER TABLE notifications ADD COLUMN student_directed BOOLEAN;
|
||||
|
||||
INSERT INTO lesson_package_types (id, name, description, package_type, price) VALUES ('single', 'Single Lesson', 'A single lesson purchased at the teacher''s price.', 'single', 0.00);
|
||||
INSERT INTO lesson_package_types (id, name, description, package_type, price) VALUES ('single-free', 'Free Lesson', 'A free, single lesson.', 'single-free', 0.00);
|
||||
|
|
@ -68,19 +70,22 @@ INSERT INTO lesson_package_types (id, name, description, package_type, price) VA
|
|||
|
||||
CREATE TABLE lesson_booking_slots (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
lesson_booking_id VARCHAR(64) REFERENCES lesson_bookings(id) NOT NULL,
|
||||
lesson_booking_id VARCHAR(64) REFERENCES lesson_bookings(id),
|
||||
lesson_session_id VARCHAR(64) REFERENCES lesson_sessions(id),
|
||||
slot_type VARCHAR(64) NOT NULL,
|
||||
preferred_day DATE,
|
||||
day_of_week INTEGER,
|
||||
hour INTEGER,
|
||||
minute INTEGER,
|
||||
timezone VARCHAR NOT NULL,
|
||||
update_all BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
proposer_id VARCHAR(64) REFERENCES users(id) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
ALTER TABLE lesson_bookings ADD COLUMN default_slot_id VARCHAR(64) REFERENCES lesson_booking_slots(id);
|
||||
ALTER TABLE lesson_sessions ADD COLUMN slot 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);
|
||||
ALTER TABLE chat_messages ADD COLUMN lesson_booking_id VARCHAR(64) REFERENCES lesson_bookings(id);
|
||||
|
|
|
|||
|
|
@ -652,13 +652,13 @@ message MixdownSignFailed {
|
|||
}
|
||||
|
||||
message LessonMessage {
|
||||
optional string lesson_session_id = 1;
|
||||
optional string music_session_id = 1;
|
||||
optional string photo_url = 2;
|
||||
optional string msg = 3;
|
||||
optional string notification_id = 4;
|
||||
optional string created_at = 5;
|
||||
optional string sender_id = 6;
|
||||
optional string received_id = 7;
|
||||
optional string receiver_id = 7;
|
||||
optional bool student_directed = 8;
|
||||
optional string purpose = 9;
|
||||
optional string sender_name = 10;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ gem 'sendgrid_toolkit', '>= 1.1.1'
|
|||
gem 'stripe'
|
||||
gem 'zip-codes'
|
||||
gem 'icalendar'
|
||||
gem 'timespan'
|
||||
|
||||
group :test do
|
||||
gem 'simplecov', '~> 0.7.1'
|
||||
|
|
|
|||
|
|
@ -278,6 +278,7 @@ require "jam_ruby/models/jamblaster"
|
|||
require "jam_ruby/models/jamblaster_user"
|
||||
require "jam_ruby/models/jamblaster_pairing_request"
|
||||
require "jam_ruby/models/sale_receipt_ios"
|
||||
require "jam_ruby/models/lesson_session_analyser"
|
||||
|
||||
include Jampb
|
||||
|
||||
|
|
|
|||
|
|
@ -751,5 +751,54 @@
|
|||
format.html
|
||||
end
|
||||
end
|
||||
|
||||
# teacher proposed counter time; so send msg to the student
|
||||
def student_lesson_counter(lesson_session, slot)
|
||||
|
||||
email = lesson_session.student.email
|
||||
subject = "Instructor has proposed a different time for your lesson"
|
||||
unique_args = {:type => "student_lesson_counter"}
|
||||
@student = lesson_session.student
|
||||
@teacher = lesson_session.teacher
|
||||
@session_name = lesson_session.music_session.name
|
||||
@session_description = lesson_session.music_session.description
|
||||
@session_date = slot.pretty_scheduled_start(true)
|
||||
@session_url = lesson_session.web_url
|
||||
@lesson_session = lesson_session
|
||||
sendgrid_category "Notification"
|
||||
sendgrid_unique_args :type => unique_args[:type]
|
||||
|
||||
sendgrid_recipients([email])
|
||||
sendgrid_substitute('@USERID', [@student.id])
|
||||
|
||||
mail(:to => email, :subject => subject) do |format|
|
||||
format.text
|
||||
format.html
|
||||
end
|
||||
end
|
||||
# student proposed counter time; so send msg to the teacher
|
||||
def teacher_lesson_counter(lesson_session, slot)
|
||||
|
||||
email = lesson_session.teacher.email
|
||||
subject = "Student has proposed a different time for their lesson"
|
||||
unique_args = {:type => "teacher_lesson_counter"}
|
||||
@student = lesson_session.student
|
||||
@teacher = lesson_session.teacher
|
||||
@session_name = lesson_session.music_session.name
|
||||
@session_description = lesson_session.music_session.description
|
||||
@session_date = slot.pretty_scheduled_start(true)
|
||||
@session_url = lesson_session.web_url
|
||||
@lesson_session = lesson_session
|
||||
sendgrid_category "Notification"
|
||||
sendgrid_unique_args :type => unique_args[:type]
|
||||
|
||||
sendgrid_recipients([email])
|
||||
sendgrid_substitute('@USERID', [@teacher.id])
|
||||
|
||||
mail(:to => email, :subject => subject) do |format|
|
||||
format.text
|
||||
format.html
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
<% provide(:title, "#{@teacher.name} has proposed a different time for your lesson") %>
|
||||
<% provide(:photo_url, @teacher.resolved_photo_url) %>
|
||||
|
||||
<% content_for :note do %>
|
||||
<p>
|
||||
<%= @teacher.name %> has proposed a different time for your lesson request.
|
||||
<br/>
|
||||
<br/>
|
||||
Click the button below to get more information and respond.
|
||||
</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 %>
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<%= @teacher.name %> has proposed a different time for your lesson request.
|
||||
|
||||
To see this lesson, click here: <%= @lesson_session.web_url %>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<% provide(:title, "#{@student.name} has proposed a different time for their lesson") %>
|
||||
<% provide(:photo_url, @student.resolved_photo_url) %>
|
||||
|
||||
<% content_for :note do %>
|
||||
<p>
|
||||
<%= @student.name %> has proposed a different time for their lesson request.
|
||||
<br/>
|
||||
<br/>
|
||||
Click the button below to get more information and respond.
|
||||
</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 %>
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<%= @student.name %> has proposed a different time for their lesson request.
|
||||
|
||||
To see this lesson, click here: <%= @lesson_session.web_url %>
|
||||
|
|
@ -917,7 +917,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
# creates the general purpose text message
|
||||
def lesson_message(receiver_id, sender_photo_url, sender_name, sender_id, msg, notification_id, lesson_session_id, created_at)
|
||||
def lesson_message(receiver_id, sender_photo_url, sender_name, sender_id, msg, notification_id, music_session_id, created_at, student_directed, purpose)
|
||||
lesson_message = Jampb::LessonMessage.new(
|
||||
:photo_url => sender_photo_url,
|
||||
:sender_name => sender_name,
|
||||
|
|
@ -925,8 +925,10 @@ module JamRuby
|
|||
:receiver_id => receiver_id,
|
||||
:msg => msg,
|
||||
:notification_id => notification_id,
|
||||
:lesson_session_id => lesson_session_id,
|
||||
:created_at => created_at
|
||||
:music_session_id => music_session_id,
|
||||
:created_at => created_at,
|
||||
:student_directed => student_directed,
|
||||
:purpose => purpose
|
||||
)
|
||||
|
||||
Jampb::ClientMessage.new(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ module JamRuby
|
|||
|
||||
@@log = Logging.logger[LessonBooking]
|
||||
|
||||
attr_accessor :accepting
|
||||
attr_accessor :accepting, :countering, :countered_slot, :countered_lesson
|
||||
|
||||
STATUS_REQUESTED = 'requested'
|
||||
STATUS_CANCELED = 'canceled'
|
||||
|
|
@ -51,6 +51,7 @@ module JamRuby
|
|||
validate :validate_payment_style
|
||||
validate :validate_accepted, :if => :accepting
|
||||
|
||||
|
||||
before_save :before_save
|
||||
before_validation :before_validation
|
||||
after_create :after_create
|
||||
|
|
@ -70,23 +71,31 @@ module JamRuby
|
|||
|
||||
def before_save
|
||||
automatically_default_slot
|
||||
sync_remaining_test_drives
|
||||
end
|
||||
|
||||
def after_save
|
||||
sync_lessons
|
||||
sync_remaining_test_drives
|
||||
end
|
||||
|
||||
def student
|
||||
user
|
||||
end
|
||||
|
||||
def accept(lesson_session, slot, update_all)
|
||||
def accept(lesson_session, slot)
|
||||
self.accepting = true
|
||||
self.default_slot = slot
|
||||
|
||||
if self.default_slot.nil? || update_all
|
||||
self.accepting = true
|
||||
self.default_slot = slot
|
||||
self.save!
|
||||
if !self.save
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
def counter(lesson_session, proposer, slot)
|
||||
self.countering = true
|
||||
self.lesson_booking_slots << slot
|
||||
if !self.save
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -100,7 +109,6 @@ module JamRuby
|
|||
|
||||
def sync_remaining_test_drives
|
||||
if is_test_drive? || is_single_free?
|
||||
puts "CARD PRESUMED OK card_presumed_ok: #{card_presumed_ok}, user_decremented: #{user_decremented}"
|
||||
if card_presumed_ok && !user_decremented
|
||||
self.user_decremented = true
|
||||
|
||||
|
|
@ -110,7 +118,6 @@ module JamRuby
|
|||
user.remaining_free_lessons = remaining_free_lessons
|
||||
|
||||
elsif is_test_drive?
|
||||
puts "TEST DRIVE DECREMENT"
|
||||
remaining_test_drives = user.remaining_test_drives - 1
|
||||
User.where(id: user.id).update_all(remaining_test_drives: remaining_test_drives)
|
||||
user.remaining_test_drives = remaining_test_drives
|
||||
|
|
@ -120,6 +127,10 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def create_minimum_booking_time
|
||||
(Time.now + APP_CONFIG.minimum_lesson_booking_hrs * 60*60)
|
||||
end
|
||||
|
||||
def sync_lessons
|
||||
|
||||
if is_canceled?
|
||||
|
|
@ -129,14 +140,20 @@ module JamRuby
|
|||
|
||||
|
||||
if !recurring && lesson_sessions.count == 1
|
||||
# if not recurring, and there is already one lession created, we are good
|
||||
# if not recurring, and there is already one lesson created, then at most we may have to deal with a changed slot
|
||||
if default_slot_changed?
|
||||
if !lesson_sessions[0].update_scheduled_start(0)
|
||||
puts "unable to update scheduled lesson"
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
# Here we go; let's create a lesson(s) as needed
|
||||
|
||||
# we need to make lessons into the future a bit, to give time for everyone involved
|
||||
minimum_start_time = (Time.now + APP_CONFIG.minimum_lesson_booking_hrs * 60*60)
|
||||
minimum_start_time = create_minimum_booking_time
|
||||
|
||||
# get all sessions that are already scheduled for this booking ahead of the minimum time
|
||||
sessions = MusicSession.joins(:lesson_session).where("lesson_sessions.lesson_booking_id = ?", id).where("scheduled_start is not null").where("scheduled_start > ?", minimum_start_time).order(:created_at)
|
||||
|
|
@ -225,11 +242,11 @@ module JamRuby
|
|||
def send_notices
|
||||
UserMailer.student_lesson_request(self).deliver
|
||||
UserMailer.teacher_lesson_request(self).deliver
|
||||
LessonBooking.where(id: id).update_all(sent_notices: true)
|
||||
self.sent_notices = true
|
||||
self.save
|
||||
end
|
||||
|
||||
def lesson_package_type
|
||||
|
||||
if is_single_free?
|
||||
LessonPackageType.single_free
|
||||
elsif is_test_drive?
|
||||
|
|
@ -371,15 +388,20 @@ module JamRuby
|
|||
lesson_booking.sent_notices = false
|
||||
lesson_booking.teacher = teacher
|
||||
lesson_booking.lesson_type = lesson_type
|
||||
lesson_booking.lesson_booking_slots = lesson_booking_slots
|
||||
lesson_booking.recurring = recurring
|
||||
lesson_booking.lesson_length = lesson_length
|
||||
lesson_booking.payment_style = payment_style
|
||||
lesson_booking.description = description
|
||||
lesson_booking.status = STATUS_REQUESTED
|
||||
|
||||
# two-way association slots, for before_validation loic in slot to work
|
||||
lesson_booking.lesson_booking_slots = lesson_booking_slots
|
||||
lesson_booking_slots.each do |slot|
|
||||
slot.lesson_booking = lesson_booking
|
||||
end if lesson_booking_slots
|
||||
|
||||
if lesson_booking.save
|
||||
msg = ChatMessage.create(user, nil, description, ChatMessage::CHANNEL_LESSON, nil, teacher, lesson_booking)
|
||||
msg = ChatMessage.create(user, lesson_booking.lesson_sessions[0], description, ChatMessage::CHANNEL_LESSON, nil, teacher, lesson_booking)
|
||||
end
|
||||
end
|
||||
lesson_booking
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ module JamRuby
|
|||
@@log = Logging.logger[LessonBookingSlot]
|
||||
|
||||
belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking"
|
||||
|
||||
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
|
||||
|
||||
SLOT_TYPE_SINGLE = 'single'
|
||||
|
|
@ -13,14 +14,49 @@ module JamRuby
|
|||
|
||||
SLOT_TYPES = [SLOT_TYPE_SINGLE, SLOT_TYPE_RECURRING]
|
||||
|
||||
validates :proposer, presence: true
|
||||
validates :slot_type, inclusion: {in: SLOT_TYPES}
|
||||
#validates :preferred_day
|
||||
validates :day_of_week, numericality: {only_integer: true}, allow_blank: true # 0 = sunday - 6 = saturday
|
||||
validates :hour, numericality: {only_integer: true}
|
||||
validates :minute, numericality: {only_integer: true}
|
||||
validates :timezone, presence: true # example: 'America/New_York'
|
||||
validates :update_all, inclusion: {in: [true, false]}
|
||||
|
||||
validate :validate_slot_type
|
||||
validate :validate_slot_minimum_time, on: :create
|
||||
validate :validate_proposer
|
||||
before_validation :before_validation
|
||||
|
||||
def before_validation
|
||||
if proposer.nil?
|
||||
self.proposer = container.student
|
||||
end
|
||||
end
|
||||
|
||||
def container
|
||||
if lesson_booking
|
||||
lesson_booking
|
||||
else
|
||||
lesson_session
|
||||
end
|
||||
end
|
||||
|
||||
def is_teacher_created?
|
||||
self.proposer == container.teacher
|
||||
end
|
||||
|
||||
def recipient
|
||||
if is_teacher_created?
|
||||
container.student
|
||||
else
|
||||
container.teacher
|
||||
end
|
||||
end
|
||||
|
||||
def create_minimum_booking_time
|
||||
(Time.now + APP_CONFIG.minimum_lesson_booking_hrs * 60 * 60)
|
||||
end
|
||||
|
||||
def scheduled_times(needed_sessions, minimum_start_time)
|
||||
|
||||
|
|
@ -73,6 +109,52 @@ module JamRuby
|
|||
time
|
||||
end
|
||||
|
||||
def lesson_length
|
||||
safe_lesson_booking.lesson_length
|
||||
end
|
||||
|
||||
def safe_lesson_booking
|
||||
found = lesson_booking
|
||||
found ||= lesson_session.lesson_booking
|
||||
end
|
||||
|
||||
def pretty_scheduled_start(with_timezone)
|
||||
|
||||
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")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} #{tz}"
|
||||
else
|
||||
"#{start_time.strftime("%A, %B %e")} - #{start_time.strftime("%l:%M%P").strip}"
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
def validate_proposer
|
||||
if proposer && (proposer != container.student && proposer != container.teacher)
|
||||
errors.add(:proposer, "must be either the student or teacher")
|
||||
end
|
||||
end
|
||||
|
||||
def validate_slot_type
|
||||
if slot_type == SLOT_TYPE_SINGLE
|
||||
if preferred_day.nil?
|
||||
|
|
@ -86,5 +168,23 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def validate_slot_minimum_time
|
||||
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
|
||||
|
||||
if day_of_week
|
||||
# this is recurring; it will sort itself out
|
||||
else
|
||||
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")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ module JamRuby
|
|||
if self.lesson_package_type.is_test_drive?
|
||||
new_test_drives = user.remaining_test_drives + 4
|
||||
User.where(id:user.id).update_all(remaining_test_drives: new_test_drives)
|
||||
user.remaining_test_drives = user.remaining_test_drives + 4
|
||||
user.remaining_test_drives = new_test_drives
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ module JamRuby
|
|||
class LessonSession < ActiveRecord::Base
|
||||
|
||||
|
||||
attr_accessor :accepting, :creating
|
||||
attr_accessor :accepting, :creating, :countering, :countered_slot, :countered_lesson
|
||||
|
||||
@@log = Logging.logger[LessonSession]
|
||||
|
||||
|
|
@ -24,6 +24,8 @@ module JamRuby
|
|||
belongs_to :teacher, class_name: "JamRuby::User"
|
||||
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
|
||||
has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot"
|
||||
|
||||
validates :duration, presence: true, numericality: {only_integer: true}
|
||||
validates :lesson_booking, presence: true
|
||||
|
|
@ -37,6 +39,26 @@ module JamRuby
|
|||
|
||||
validate :validate_creating, :if => :creating
|
||||
validate :validate_accepted, :if => :accepting
|
||||
after_save :after_counter, :if => :countering
|
||||
after_save :manage_slot_changes, :if => :slot_changed?
|
||||
|
||||
def manage_slot_changes
|
||||
# if this slot changed, we need to update the time. But LessonBooking does this for us, for requested/accepted .
|
||||
# TODO: what to do, what to do.
|
||||
end
|
||||
|
||||
def after_counter
|
||||
send_counter(@countered_lesson, @countered_slot)
|
||||
end
|
||||
|
||||
def send_counter(countered_lesson, countered_slot)
|
||||
if countered_slot.is_teacher_created?
|
||||
UserMailer.student_lesson_counter(countered_lesson, countered_slot).deliver
|
||||
else
|
||||
UserMailer.teacher_lesson_counter(countered_lesson, countered_slot).deliver
|
||||
end
|
||||
self.countering = false
|
||||
end
|
||||
|
||||
default_scope { order(:created_at) }
|
||||
|
||||
|
|
@ -56,6 +78,7 @@ module JamRuby
|
|||
status == STATUS_APPROVED
|
||||
end
|
||||
|
||||
|
||||
def validate_creating
|
||||
if !is_requested? && !is_approved?
|
||||
self.errors.add(:status, "is not valid for a new lesson session.")
|
||||
|
|
@ -67,6 +90,7 @@ module JamRuby
|
|||
self.errors.add(:status, "This session is already #{self.status}.")
|
||||
end
|
||||
|
||||
self.accepting = false
|
||||
self.status = STATUS_APPROVED
|
||||
end
|
||||
|
||||
|
|
@ -124,30 +148,57 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def update_scheduled_start(week_offset)
|
||||
music_session.scheduled_start = default_slot.scheduled_time(week_offset)
|
||||
music_session.save
|
||||
end
|
||||
|
||||
# teacher accepts the lesson
|
||||
def accept(params)
|
||||
LessonSession.transaction do
|
||||
|
||||
message = params[:message]
|
||||
slot = params[:slot]
|
||||
update_all = params[:update_all]
|
||||
|
||||
self.accepting = true
|
||||
slot = LessonBookingSlot.find(slot)
|
||||
|
||||
lesson_booking.accept(self, slot, update_all)
|
||||
lesson_booking.accept(self, slot)
|
||||
|
||||
self.slot = slot
|
||||
|
||||
if self.save
|
||||
msg = ChatMessage.create(teacher, nil, message, ChatMessage::CHANNEL_LESSON, nil, user, self)
|
||||
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)
|
||||
UserMailer.teacher_lesson_accepted(self, message)
|
||||
UserMailer.student_lesson_accepted(self, message).deliver
|
||||
UserMailer.teacher_lesson_accepted(self, message).deliver
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def counter(params)
|
||||
proposer = params[:proposer]
|
||||
slot = params[:slot]
|
||||
message = params[:message]
|
||||
|
||||
self.countering = true
|
||||
slot.proposer = proposer
|
||||
slot.lesson_session = self
|
||||
self.lesson_booking_slots << slot
|
||||
self.countered_slot = slot
|
||||
self.countered_lesson = self
|
||||
if self.save
|
||||
if slot.update_all || lesson_booking.is_requested?
|
||||
lesson_booking.counter(self, proposer, slot)
|
||||
end
|
||||
else
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
msg = ChatMessage.create(slot.proposer, music_session, message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, lesson_booking)
|
||||
Notification.send_lesson_message('counter', self, slot.is_teacher_created?)
|
||||
end
|
||||
|
||||
def home_url
|
||||
APP_CONFIG.external_root_url + "/client#/jamclass"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,248 @@
|
|||
require 'timespan'
|
||||
module JamRuby
|
||||
class LessonSessionAnalyser
|
||||
|
||||
SUCCESS = 'success'
|
||||
SESSION_ONGOING = 'session_ongoing'
|
||||
THRESHOLD_MET = 'threshold_met'
|
||||
WAITED_CORRECTLY = 'waited_correctly'
|
||||
MINIMUM_TIME_MET = 'minimum_time_met' # for a teacher primarily; they waited around for the student sufficiently
|
||||
LATE_CANCELLATION = 'late_cancellation'
|
||||
|
||||
TEACHER_FAULT = 'teacher_fault'
|
||||
STUDENT_FAULT = 'student_fault'
|
||||
BOTH_FAULT = 'both_fault'
|
||||
|
||||
|
||||
# what are the potential results?
|
||||
|
||||
# bill: true/false
|
||||
|
||||
# teacher: 'no_show'
|
||||
# teacher: 'late'
|
||||
# teacher: 'early_leave'
|
||||
# teacher: 'waited_correctly'
|
||||
# teacher: 'late_cancellation'
|
||||
|
||||
# student: 'no_show'
|
||||
# student: 'late'
|
||||
# student: 'early_leave'
|
||||
# student: 'minimum_time_not_met'
|
||||
# student: 'threshold_met'
|
||||
|
||||
|
||||
# reason: 'session_ongoing'
|
||||
# reason: 'success'
|
||||
# reason: 'student_fault'
|
||||
# reason: 'teacher_fault'
|
||||
# reason: 'both_fault'
|
||||
|
||||
|
||||
def self.analyse(lesson_session)
|
||||
reason = nil
|
||||
teacher = nil
|
||||
student = nil
|
||||
bill = false
|
||||
|
||||
music_session = lesson_session.music_session
|
||||
|
||||
student_histories = MusicSessionUserHistory.where(music_session: music_session, user: lesson_session.student)
|
||||
teacher_histories = MusicSessionUserHistory.where(music_session: music_session, user: lesson_session.teacher)
|
||||
|
||||
# create ranges from music session user history
|
||||
all_student_ranges = time_ranges(student_histories)
|
||||
all_teacher_ranges = time_ranges(teacher_histories)
|
||||
|
||||
# flatten ranges into non-overlapping ranges to simplifly logic
|
||||
student_ranges = merge_overlapping_ranges(all_student_ranges)
|
||||
teacher_ranges = merge_overlapping_ranges(all_teacher_ranges)
|
||||
|
||||
intersecting = intersecting_ranges(student_ranges, teacher_ranges)
|
||||
|
||||
student_analysis = analyse_intersection(lesson_session, student_ranges)
|
||||
teacher_analysis = analyse_intersection(lesson_session, teacher_ranges)
|
||||
together_analysis = analyse_intersection(lesson_session, intersecting)
|
||||
|
||||
# spec: https://jamkazam.atlassian.net/wiki/display/PS/Product+Specification+-+JamClass#ProductSpecification-JamClass-TeacherReceives&RespondstoLessonBookingRequest
|
||||
|
||||
if music_session.session_removed_at.nil?
|
||||
reason = SESSION_ONGOING
|
||||
teacher = SESSION_ONGOING
|
||||
student = SESSION_ONGOING
|
||||
bill = false
|
||||
else
|
||||
|
||||
if lesson_session.is_canceled? && lesson_session.canceled_by_teacher? && lesson_session.canceled_late?
|
||||
# If the lesson was cancelled less than 24 hours before the start time by the teacher, then we do not bill the student.
|
||||
reason = TEACHER_FAULT
|
||||
teacher = LATE_CANCELLATION
|
||||
student = nil
|
||||
bill = false
|
||||
elsif lesson_session.is_canceled? && lesson_session.canceled_by_student? && lesson_session.canceled_late?
|
||||
# If the lesson was cancelled less than 24 hours before the start time by the student (if that is even possible, I can’t remember now), then we do bill the student.
|
||||
reason = STUDENT_FAULT
|
||||
teacher = nil
|
||||
student = LATE_CANCELLATION
|
||||
bill = true
|
||||
elsif together_analysis[:pct] >= APP_CONFIG.lesson_together_threshold_pct
|
||||
# If the teacher and the student are together in the lesson session for at least 80% of the scheduled lesson duration, regardless of who joined/left when, then we will bill the student.
|
||||
bill = true
|
||||
reason = SUCCESS
|
||||
teacher = THRESHOLD_MET
|
||||
student = THRESHOLD_MET
|
||||
else
|
||||
if teacher_analysis[:joined_on_time] && teacher_analysis[:waited_correctly]
|
||||
# if the teacher was present in the session within the first 5 minutes of the scheduled start time and stayed in the session for 10 minutes;
|
||||
# and if either:
|
||||
|
||||
if student_analysis[:no_show]
|
||||
# the student no-showed entirely, then we bill the student.
|
||||
|
||||
elsif student_analysis[:joined_late]
|
||||
# the student joined the lesson more than 10 minutes after the teacher did, regardless of whether the teacher was still in the lesson session at that point; then we bill the student
|
||||
|
||||
elsif student_analysis[:joined_on_time]
|
||||
# the student joined the session within 10 minutes after the teacher did, and the teacher was still there, and the teacher stays until the scheduled end time of the lesson; then we bill the student.
|
||||
if teacher_analysis[:minimum_time_met] && teacher_analysis[:there_when_other_joined]
|
||||
|
||||
else
|
||||
|
||||
end
|
||||
else
|
||||
raise "unknown condition for lesson session: #{lesson_session.id}"
|
||||
end
|
||||
|
||||
elsif student_analysis[:joined_on_time]
|
||||
if student_analysis[:waited_correctly]
|
||||
if teacher_analysis[:other_there_when_joined]
|
||||
# bill the student
|
||||
else
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# if the teacher meets the time threshold, then the user is getting billed
|
||||
|
||||
teacher = MINIMUM_TIME_MET
|
||||
bill = true
|
||||
|
||||
# What happened with the student?
|
||||
|
||||
if student_analysis[:waited_correctly]
|
||||
student = WAITED_CORRECTLY
|
||||
reason = STUDENT_FAULT
|
||||
elsif student_analysis[:time] == 0
|
||||
student = NO_SHOW
|
||||
reason = STUDENT_FAULT
|
||||
elsif student_analysis
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
{
|
||||
reason: reason,
|
||||
teacher: teacher,
|
||||
student: student,
|
||||
bill: bill,
|
||||
student_ranges: student_ranges,
|
||||
teacher_ranges: teacher_ranges,
|
||||
intersecting: intersecting,
|
||||
student_analysis: student_analysis,
|
||||
teacher_analysis: teacher_analysis,
|
||||
together_analysis: together_analysis,
|
||||
|
||||
}
|
||||
end
|
||||
|
||||
def self.intersecting_ranges(ranges_a, ranges_b)
|
||||
intersections = []
|
||||
ranges_a.each do |range_a|
|
||||
ranges_b.each do |range_b|
|
||||
intersection = intersect(range_a, range_b)
|
||||
intersections << intersection if intersection
|
||||
end
|
||||
end
|
||||
|
||||
merge_overlapping_ranges(intersections)
|
||||
end
|
||||
|
||||
def self.analyse_intersection(lesson_session, ranges)
|
||||
start = lesson_session.music_session.scheduled_start
|
||||
|
||||
planned_duration_seconds = lesson_session.duration * 60
|
||||
measurable_start = start - (APP_CONFIG.lesson_analysis_slush_time_minutes * 60)
|
||||
measurable_end = (start + planned_duration_seconds) + (APP_CONFIG.lesson_analysis_slush_time_minutes * 60)
|
||||
|
||||
session_time = Range.new(measurable_start, measurable_end)
|
||||
|
||||
# let's see how much time they spent together, irrespective of scheduled time
|
||||
# and also, based on scheduled time
|
||||
total = 0
|
||||
in_scheduled_time = 0
|
||||
|
||||
ranges.each do |range|
|
||||
time = range.end - range.begin
|
||||
|
||||
total += time
|
||||
|
||||
in_session_time = intersect(range, session_time)
|
||||
|
||||
if in_session_time
|
||||
in_scheduled_time += in_scheduled_time.end - in_scheduled_time.begin
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
# percentage computation of time spent during the session time
|
||||
in_scheduled_percentage = in_scheduled_time.to_f / planned_duration_seconds.to_f
|
||||
|
||||
{
|
||||
total: total,
|
||||
time: in_scheduled_time,
|
||||
pct: in_scheduled_percentage
|
||||
}
|
||||
end
|
||||
|
||||
def self.intersect(a, b)
|
||||
min, max = a.first, a.exclude_end? ? a.max : a.last
|
||||
other_min, other_max = b.first, b.exclude_end? ? b.max : b.last
|
||||
|
||||
new_min = a === other_min ? other_min : b === min ? min : nil
|
||||
new_max = a === other_max ? other_max : b === max ? max : nil
|
||||
|
||||
new_min && new_max ? Range.new(new_min, new_max) : nil
|
||||
end
|
||||
|
||||
def self.time_ranges(histories)
|
||||
ranges = []
|
||||
histories.each do |history|
|
||||
ranges << history.range
|
||||
end
|
||||
ranges
|
||||
end
|
||||
|
||||
def self.ranges_overlap?(a, b)
|
||||
a.include?(b.begin) || b.include?(a.begin)
|
||||
end
|
||||
|
||||
def self.merge_ranges(a, b)
|
||||
[a.begin, b.begin].min..[a.end, b.end].max
|
||||
end
|
||||
|
||||
def self.merge_overlapping_ranges(ranges)
|
||||
ranges.sort_by(&:begin).inject([]) do |ranges, range|
|
||||
if !ranges.empty? && ranges_overlap?(ranges.last, range)
|
||||
ranges[0...-1] + [merge_ranges(ranges.last, range)]
|
||||
else
|
||||
ranges + [range]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -25,6 +25,10 @@ module JamRuby
|
|||
user.name
|
||||
end
|
||||
|
||||
def range
|
||||
Range.new(created_at, session_removed_at || Time.now)
|
||||
end
|
||||
|
||||
def music_session
|
||||
@msh ||= JamRuby::MusicSession.find_by_music_session_id(self.music_session_id)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -377,17 +377,17 @@ module JamRuby
|
|||
notification = Notification.new
|
||||
notification.description = NotificationTypes::LESSON_MESSAGE
|
||||
notification.student_directed = student_directed
|
||||
|
||||
if !student_directed
|
||||
notification.source_user_id = lesson_session.user.id
|
||||
notification.source_user_id = lesson_session.student.id
|
||||
notification.target_user_id = lesson_session.teacher.id
|
||||
else
|
||||
notification.source_user_id = lesson_session.teacher.id
|
||||
notification.target_user_id = lesson_session.user.id
|
||||
notification.target_user_id = lesson_session.student.id
|
||||
end
|
||||
|
||||
notification.purpose = purpose
|
||||
notification.lesson_session = lesson_session
|
||||
notification.session_id = lesson_session.music_session_id
|
||||
notification.session_id = lesson_session.music_session.id
|
||||
|
||||
notification_msg = 'Lesson Changed'
|
||||
|
||||
|
|
@ -412,37 +412,21 @@ module JamRuby
|
|||
notification.save
|
||||
|
||||
# receiver_id, sender_photo_url, sender_name, sender_id, msg, clipped_msg, notification_id, created_at
|
||||
message = @message_factory.lesson_message(
|
||||
notification.target.id,
|
||||
notification.source.resolved_photo_url,
|
||||
notification.source.name,
|
||||
notification.source.id,
|
||||
message = @@message_factory.lesson_message(
|
||||
notification.target_user.id,
|
||||
notification.source_user.resolved_photo_url,
|
||||
notification.source_user.name,
|
||||
notification.source_user.id,
|
||||
notification_msg,
|
||||
notification.id,
|
||||
notification.lesson_session.id,
|
||||
notification.created_date
|
||||
notification.session_id,
|
||||
notification.created_date,
|
||||
notification.student_directed,
|
||||
notification.purpose
|
||||
)
|
||||
|
||||
if follower.id != user.id
|
||||
if user.online
|
||||
msg = @@message_factory.new_user_follower(
|
||||
user.id,
|
||||
follower.photo_url,
|
||||
notification_msg,
|
||||
notification.id,
|
||||
notification.created_date
|
||||
)
|
||||
@@mq_router.publish_to_user(notification.target_user.id, message)
|
||||
|
||||
@@mq_router.publish_to_user(user.id, msg)
|
||||
|
||||
else
|
||||
begin
|
||||
UserMailer.new_user_follower(user, notification_msg).deliver
|
||||
rescue => e
|
||||
@@log.error("Unable to send NEW_USER_FOLLOWER email to offline user #{user.email} #{e}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def send_new_band_follower(follower, band)
|
||||
|
|
|
|||
|
|
@ -1898,8 +1898,10 @@ module JamRuby
|
|||
lesson = nil
|
||||
test_drive = nil
|
||||
User.transaction do
|
||||
|
||||
lesson = card_approved(params[:token], params[:zip])
|
||||
if params[:test_drive]
|
||||
self.reload
|
||||
test_drive = Sale.purchase_test_drive(self)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -921,7 +921,7 @@ FactoryGirl.define do
|
|||
factory :lesson_booking_slot, class: 'JamRuby::LessonBookingSlot' do
|
||||
factory :lesson_booking_slot_single do
|
||||
slot_type 'single'
|
||||
preferred_day Date.today
|
||||
preferred_day Date.today + 3
|
||||
day_of_week nil
|
||||
hour 12
|
||||
minute 30
|
||||
|
|
|
|||
|
|
@ -15,23 +15,26 @@ describe "TestDrive Lesson Flow" do
|
|||
|
||||
it "works" do
|
||||
|
||||
# user has no test drives, but attempts to book a lesson
|
||||
# user has no test drives, no credit card on file, but attempts to book a lesson
|
||||
booking = LessonBooking.book_test_drive(user, teacher_user, valid_single_slots, "Hey I've heard of you before.")
|
||||
puts booking.errors.inspect
|
||||
booking.errors.any?.should be_false
|
||||
booking.card_presumed_ok.should be_false
|
||||
booking.user.should eql user
|
||||
booking.card_presumed_ok.should be_false
|
||||
booking.should eql user.unprocessed_test_drive
|
||||
booking.sent_notices.should be_false
|
||||
|
||||
# so, they still need validate their take
|
||||
########## Need validate their credit card
|
||||
token = create_stripe_token
|
||||
puts "UPDATING PAYMENT"
|
||||
result = user.payment_update({token: token, zip: '78759', test_drive: true})
|
||||
lesson = result[:lesson]
|
||||
test_drive = result[:test_drive]
|
||||
lesson.errors.any?.should be_false
|
||||
test_drive.errors.any?.should be_false
|
||||
lesson.card_presumed_ok.should be_true
|
||||
lesson.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
|
||||
booking.reload
|
||||
booking.sent_notices.should be_true
|
||||
|
||||
test_drive.stripe_charge_id.should_not be_nil
|
||||
test_drive.recurly_tax_in_cents.should be 412
|
||||
|
|
@ -42,7 +45,6 @@ describe "TestDrive Lesson Flow" do
|
|||
line_item.quantity.should eql 1
|
||||
line_item.product_type.should eql SaleLineItem::LESSON
|
||||
line_item.product_id.should eq LessonPackageType.test_drive.id
|
||||
|
||||
user.reload
|
||||
user.stripe_customer_id.should_not be nil
|
||||
user.lesson_purchases.length.should eql 1
|
||||
|
|
@ -53,5 +55,90 @@ describe "TestDrive Lesson Flow" do
|
|||
|
||||
customer = Stripe::Customer.retrieve(user.stripe_customer_id)
|
||||
customer.email.should eql user.email
|
||||
|
||||
booking.lesson_sessions.length.should eql 1
|
||||
lesson_session = booking.lesson_sessions[0]
|
||||
lesson_session.status.should eql LessonBooking::STATUS_REQUESTED
|
||||
booking.status.should eql LessonBooking::STATUS_REQUESTED
|
||||
|
||||
######### Teacher counters with new slot
|
||||
teacher_countered_slot = FactoryGirl.build(:lesson_booking_slot_single, hour: 14)
|
||||
UserMailer.deliveries.clear
|
||||
lesson_session.counter({proposer: teacher_user, slot: teacher_countered_slot, message: 'Does this work?'})
|
||||
booking.errors.any?.should be false
|
||||
lesson_session.lesson_booking.errors.any?.should be false
|
||||
lesson_session.lesson_booking_slots.length.should eql 1
|
||||
lesson_session.lesson_booking_slots[0].proposer.should eql teacher_user
|
||||
teacher_counter = lesson_session.lesson_booking_slots.order(:created_at).last
|
||||
teacher_counter.should eql teacher_countered_slot
|
||||
teacher_counter.proposer.should eql teacher_user
|
||||
booking.lesson_booking_slots.length.should eql 3
|
||||
UserMailer.deliveries.length.should eql 1
|
||||
chat = ChatMessage.unscoped.order(:created_at).last
|
||||
chat.channel.should eql ChatMessage::CHANNEL_LESSON
|
||||
chat.message.should eql 'Does this work?'
|
||||
chat.user.should eql teacher_user
|
||||
chat.target_user.should eql user
|
||||
notification = Notification.unscoped.order(:created_at).last
|
||||
notification.session_id.should eql lesson_session.music_session.id
|
||||
notification.student_directed.should eql true
|
||||
notification.purpose.should eql 'counter'
|
||||
notification.description.should eql NotificationTypes::LESSON_MESSAGE
|
||||
notification.message.should eql "Instructor has proposed a different time for your lesson."
|
||||
|
||||
######### Student counters with new slot
|
||||
student_countered_slot = FactoryGirl.build(:lesson_booking_slot_single, hour: 16)
|
||||
UserMailer.deliveries.clear
|
||||
lesson_session.counter({proposer: user, slot: student_countered_slot, message: 'Does this work better?'})
|
||||
lesson_session.errors.any?.should be false
|
||||
lesson_session.lesson_booking.errors.any?.should be false
|
||||
lesson_session.lesson_booking_slots.length.should eql 2
|
||||
student_counter = booking.lesson_booking_slots.order(:created_at).last
|
||||
student_counter.proposer.should eql user
|
||||
booking.reload
|
||||
booking.lesson_booking_slots.length.should eql 4
|
||||
UserMailer.deliveries.length.should eql 1
|
||||
chat = ChatMessage.unscoped.order(:created_at).last
|
||||
chat.message.should eql 'Does this work better?'
|
||||
chat.channel.should eql ChatMessage::CHANNEL_LESSON
|
||||
chat.user.should eql user
|
||||
chat.target_user.should eql teacher_user
|
||||
notification = Notification.unscoped.order(:created_at).last
|
||||
notification.session_id.should eql lesson_session.music_session.id
|
||||
notification.student_directed.should eql false
|
||||
notification.purpose.should eql 'counter'
|
||||
notification.description.should eql NotificationTypes::LESSON_MESSAGE
|
||||
notification.message.should eql "Student has proposed a different time for your lesson."
|
||||
|
||||
######## Teacher accepts slot
|
||||
UserMailer.deliveries.clear
|
||||
|
||||
lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false})
|
||||
lesson_session.errors.any?.should be_false
|
||||
lesson_session.reload
|
||||
lesson_session.slot.should eql student_counter
|
||||
lesson_session.status.should eql LessonSession::STATUS_APPROVED
|
||||
booking.reload
|
||||
booking.default_slot.should eql student_counter
|
||||
lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
|
||||
booking.status.should eql LessonBooking::STATUS_APPROVED
|
||||
|
||||
UserMailer.deliveries.length.should eql 2
|
||||
chat = ChatMessage.unscoped.order(:created_at).last
|
||||
chat.message.should eql 'Yeah I got this'
|
||||
chat.channel.should eql ChatMessage::CHANNEL_LESSON
|
||||
chat.user.should eql teacher_user
|
||||
chat.target_user.should eql user
|
||||
notification = Notification.unscoped.order(:created_at).last
|
||||
notification.session_id.should eql lesson_session.music_session.id
|
||||
notification.student_directed.should eql true
|
||||
notification.purpose.should eql 'accept'
|
||||
notification.description.should eql NotificationTypes::LESSON_MESSAGE
|
||||
notification.message.should eql "Your lesson request is confirmed!"
|
||||
|
||||
|
||||
# teacher & student get into session
|
||||
|
||||
LessonSession
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,146 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe LessonSessionAnalyser do
|
||||
|
||||
let(:user) {FactoryGirl.create(:user)}
|
||||
let(:music_session) { FactoryGirl.create(:music_session, creator: user) }
|
||||
let(:start) {Time.now}
|
||||
|
||||
describe "intersecting_ranges" do
|
||||
|
||||
it "empty" do
|
||||
LessonSessionAnalyser.intersecting_ranges([], []).should eql []
|
||||
end
|
||||
|
||||
it "one specified, other empty" do
|
||||
|
||||
uh1Begin = start
|
||||
uh1End = start + 1
|
||||
|
||||
LessonSessionAnalyser.intersecting_ranges([], [Range.new(uh1Begin, uh1End)]).should eql []
|
||||
LessonSessionAnalyser.intersecting_ranges([Range.new(uh1Begin, uh1End)], []).should eql []
|
||||
end
|
||||
|
||||
it "both identical" do
|
||||
uh1Begin = start
|
||||
uh1End = start + 1
|
||||
|
||||
LessonSessionAnalyser.intersecting_ranges([Range.new(uh1Begin, uh1End)], [Range.new(uh1Begin, uh1End)]).should eql [Range.new(uh1Begin, uh1End)]
|
||||
end
|
||||
|
||||
it "one intersect" do
|
||||
uh1Begin = start
|
||||
uh1End = start + 4
|
||||
|
||||
uh2Begin = start + 1
|
||||
uh2End = start + 3
|
||||
|
||||
LessonSessionAnalyser.intersecting_ranges([Range.new(uh1Begin, uh1End)], [Range.new(uh2Begin, uh2End)]).should eql [Range.new(uh2Begin, uh2End)]
|
||||
end
|
||||
|
||||
it "overlapping intersect" do
|
||||
uh1Begin = start
|
||||
uh1End = start + 4
|
||||
|
||||
uh2Begin = start + -1
|
||||
uh2End = start + 2
|
||||
|
||||
uh3Begin = start + 3
|
||||
uh3End = start + 5
|
||||
|
||||
LessonSessionAnalyser.intersecting_ranges([Range.new(uh2Begin, uh2End), Range.new(uh3Begin, uh3End)], [Range.new(uh1Begin, uh1End)]).should eql [Range.new(uh1Begin, uh2End), Range.new(uh3Begin, uh1End)]
|
||||
end
|
||||
|
||||
it "no overlap" do
|
||||
uh1Begin = start
|
||||
uh1End = start + 4
|
||||
|
||||
uh2Begin = start + 5
|
||||
uh2End = start + 6
|
||||
|
||||
LessonSessionAnalyser.intersecting_ranges([Range.new(uh1Begin, uh1End)], [Range.new(uh2Begin, uh2End)]).should eql []
|
||||
end
|
||||
end
|
||||
|
||||
describe "merge_overlapping_ranges" do
|
||||
|
||||
it "empty" do
|
||||
LessonSessionAnalyser.merge_overlapping_ranges([]).should eql []
|
||||
end
|
||||
|
||||
it "one item" do
|
||||
uh1Begin = start
|
||||
uh1End = start + 1
|
||||
|
||||
uh1 = FactoryGirl.build(:music_session_user_history, user: user, history: music_session, created_at: uh1Begin, session_removed_at: uh1End)
|
||||
|
||||
ranges = LessonSessionAnalyser.time_ranges [uh1]
|
||||
|
||||
LessonSessionAnalyser.merge_overlapping_ranges(ranges).should eql [Range.new(uh1Begin, uh1End)]
|
||||
end
|
||||
|
||||
it "two identical items" do
|
||||
uh1Begin = start
|
||||
uh1End = start + 1
|
||||
|
||||
uh2Begin = uh1Begin
|
||||
uh2End = uh1End
|
||||
|
||||
uh1 = FactoryGirl.build(:music_session_user_history, user: user, history: music_session, created_at: uh1Begin, session_removed_at: uh1End)
|
||||
uh2 = FactoryGirl.build(:music_session_user_history, user: user, history: music_session, created_at: uh2Begin, session_removed_at: uh2End)
|
||||
|
||||
ranges = LessonSessionAnalyser.time_ranges [uh1, uh2]
|
||||
|
||||
LessonSessionAnalyser.merge_overlapping_ranges(ranges).should eql [Range.new(uh1Begin, uh1End)]
|
||||
end
|
||||
|
||||
it "two separate times" do
|
||||
uh1Begin = start
|
||||
uh1End = start + 1
|
||||
|
||||
uh2Begin = start + 3
|
||||
uh2End = start + 5
|
||||
|
||||
uh1 = FactoryGirl.build(:music_session_user_history, user: user, history: music_session, created_at: uh1Begin, session_removed_at: uh1End)
|
||||
uh2 = FactoryGirl.build(:music_session_user_history, user: user, history: music_session, created_at: uh2Begin, session_removed_at: uh2End)
|
||||
|
||||
ranges = LessonSessionAnalyser.time_ranges [uh1, uh2]
|
||||
|
||||
LessonSessionAnalyser.merge_overlapping_ranges(ranges).should eql [Range.new(uh1Begin, uh1End), Range.new(uh2Begin, uh2End)]
|
||||
end
|
||||
|
||||
it "two overlapping times" do
|
||||
uh1Begin = start
|
||||
uh1End = start + 1
|
||||
|
||||
uh2Begin = start + 0.5
|
||||
uh2End = start + 5
|
||||
|
||||
uh1 = FactoryGirl.build(:music_session_user_history, user: user, history: music_session, created_at: uh1Begin, session_removed_at: uh1End)
|
||||
uh2 = FactoryGirl.build(:music_session_user_history, user: user, history: music_session, created_at: uh2Begin, session_removed_at: uh2End)
|
||||
|
||||
ranges = LessonSessionAnalyser.time_ranges [uh1, uh2]
|
||||
|
||||
LessonSessionAnalyser.merge_overlapping_ranges(ranges).should eql [Range.new(uh1Begin, uh2End)]
|
||||
end
|
||||
|
||||
it "three overlapping times" do
|
||||
uh1Begin = start
|
||||
uh1End = start + 1
|
||||
|
||||
uh2Begin = start + 0.5
|
||||
uh2End = start + 5
|
||||
|
||||
uh3Begin = start + 0.1
|
||||
uh3End = start + 6
|
||||
|
||||
uh1 = FactoryGirl.build(:music_session_user_history, user: user, history: music_session, created_at: uh1Begin, session_removed_at: uh1End)
|
||||
uh2 = FactoryGirl.build(:music_session_user_history, user: user, history: music_session, created_at: uh2Begin, session_removed_at: uh2End)
|
||||
uh3 = FactoryGirl.build(:music_session_user_history, user: user, history: music_session, created_at: uh3Begin, session_removed_at: uh3End)
|
||||
|
||||
ranges = LessonSessionAnalyser.time_ranges [uh1, uh2, uh3]
|
||||
|
||||
LessonSessionAnalyser.merge_overlapping_ranges(ranges).should eql [Range.new(uh1Begin, uh3End)]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -259,7 +259,19 @@ def app_config
|
|||
end
|
||||
|
||||
def minimum_lesson_booking_hrs
|
||||
48
|
||||
24
|
||||
end
|
||||
|
||||
def lesson_analysis_slush_time_minutes
|
||||
5
|
||||
end
|
||||
|
||||
def lesson_stay_time
|
||||
10
|
||||
end
|
||||
|
||||
def lesson_together_threshold_pct
|
||||
0.8
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -426,6 +426,10 @@ if defined?(Bundler)
|
|||
:publishable_key => 'pk_test_9vO8ZnxBpb9Udb0paruV3qLv',
|
||||
:secret_key => 'sk_test_cPVRbtr9xbMiqffV8jwibwLA'
|
||||
}
|
||||
config.minimum_lesson_booking_hrs = 48
|
||||
end
|
||||
config.minimum_lesson_booking_hrs = 24
|
||||
config.lesson_analysis_slush_time_minutes = 5
|
||||
config.lesson_stay_time = 10
|
||||
config.lesson_together_threshold_pct = 0.80
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue