mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
(quality) isolate auto cancel features in separate service
This commit is contained in:
parent
7208cd80b0
commit
78cb8b7854
@ -105,7 +105,7 @@ class SettingService
|
||||
deadline = settings.find { |s| s.name == 'trainings_auto_cancel_deadline' }
|
||||
|
||||
Training.find_each do |t|
|
||||
TrainingService.update_auto_cancel(t, tac, threshold, deadline)
|
||||
Trainings::AutoCancelService.update_auto_cancel(t, tac, threshold, deadline)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -18,58 +18,6 @@ class TrainingService
|
||||
trainings
|
||||
end
|
||||
|
||||
# @param training [Training]
|
||||
def auto_cancel_reservation(training)
|
||||
return unless training.auto_cancel
|
||||
|
||||
training.availabilities
|
||||
.includes(slots: :slots_reservations)
|
||||
.where('availabilities.start_at >= ?', DateTime.current - training.auto_cancel_deadline.hours)
|
||||
.find_each do |availability|
|
||||
next if availability.reservations.count >= training.auto_cancel_threshold
|
||||
|
||||
auto_refund = Setting.get('wallet_module')
|
||||
|
||||
NotificationCenter.call type: 'notify_admin_training_auto_cancelled',
|
||||
receiver: User.admins_and_managers,
|
||||
attached_object: availability,
|
||||
meta_data: { auto_refund: auto_refund }
|
||||
|
||||
availability.slots_reservations.find_each do |sr|
|
||||
NotificationCenter.call type: 'notify_member_training_auto_cancelled',
|
||||
receiver: sr.reservation.user,
|
||||
attached_object: sr,
|
||||
meta_data: { auto_refund: auto_refund }
|
||||
|
||||
sr.update(canceled_at: DateTime.current)
|
||||
refund_after_cancel(sr.reservation) if auto_refund
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# update the given training, depending on the provided settings
|
||||
# @param training [Training]
|
||||
# @param auto_cancel [Setting,NilClass]
|
||||
# @param threshold [Setting,NilClass]
|
||||
# @param deadline [Setting,NilClass]
|
||||
def update_auto_cancel(training, auto_cancel, threshold, deadline)
|
||||
previous_auto_cancel = auto_cancel.nil? ? Setting.find_by(name: 'trainings_auto_cancel').value : auto_cancel.previous_value
|
||||
previous_threshold = threshold.nil? ? Setting.find_by(name: 'trainings_auto_cancel_threshold').value : threshold.previous_value
|
||||
previous_deadline = deadline.nil? ? Setting.find_by(name: 'trainings_auto_cancel_deadline').value : deadline.previous_value
|
||||
is_default = training.auto_cancel.to_s == previous_auto_cancel &&
|
||||
[nil, previous_threshold].include?(training.auto_cancel_threshold.to_s) &&
|
||||
[nil, previous_deadline].include?(training.auto_cancel_deadline.to_s)
|
||||
|
||||
return unless is_default
|
||||
|
||||
# update parameters if the given training is default
|
||||
params = {}
|
||||
params[:auto_cancel] = auto_cancel.value unless auto_cancel.nil?
|
||||
params[:auto_cancel_threshold] = threshold.value unless threshold.nil?
|
||||
params[:auto_cancel_deadline] = deadline.value unless deadline.nil?
|
||||
training.update(params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @param trainings [ActiveRecord::Relation<Training>]
|
||||
@ -89,13 +37,5 @@ class TrainingService
|
||||
state = filters[:public_page] == 'false' ? [nil, false] : true
|
||||
trainings.where(public_page: state)
|
||||
end
|
||||
|
||||
# @param reservation [Reservation]
|
||||
def refund_after_cancel(reservation)
|
||||
invoice_item = reservation.invoice_items.joins(:invoice).where(invoices: { type: nil }).first
|
||||
service = WalletService.new(user: reservation.user, wallet: reservation.user.wallet)
|
||||
transaction = service.credit(invoice_item.amount_after_coupon / 100.00)
|
||||
service.create_avoir(transaction, DateTime.current, I18n.t('trainings.refund_for_auto_cancel')) if transaction
|
||||
end
|
||||
end
|
||||
end
|
||||
|
71
app/services/trainings/auto_cancel_service.rb
Normal file
71
app/services/trainings/auto_cancel_service.rb
Normal file
@ -0,0 +1,71 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Business logic around trainings
|
||||
module Trainings; end
|
||||
|
||||
# Automatically cancel trainings without enough reservation
|
||||
class Trainings::AutoCancelService
|
||||
class << self
|
||||
# @param training [Training]
|
||||
def auto_cancel_reservations(training)
|
||||
return unless training.auto_cancel
|
||||
|
||||
training.availabilities
|
||||
.includes(slots: :slots_reservations)
|
||||
.where('availabilities.start_at >= ?', DateTime.current - training.auto_cancel_deadline.hours)
|
||||
.find_each do |availability|
|
||||
next if availability.reservations.count >= training.auto_cancel_threshold
|
||||
|
||||
auto_refund = Setting.get('wallet_module')
|
||||
|
||||
NotificationCenter.call type: 'notify_admin_training_auto_cancelled',
|
||||
receiver: User.admins_and_managers,
|
||||
attached_object: availability,
|
||||
meta_data: { auto_refund: auto_refund }
|
||||
|
||||
availability.slots_reservations.find_each do |sr|
|
||||
NotificationCenter.call type: 'notify_member_training_auto_cancelled',
|
||||
receiver: sr.reservation.user,
|
||||
attached_object: sr,
|
||||
meta_data: { auto_refund: auto_refund }
|
||||
|
||||
sr.update(canceled_at: DateTime.current)
|
||||
refund_after_cancel(sr.reservation) if auto_refund
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# update the given training, depending on the provided settings
|
||||
# @param training [Training]
|
||||
# @param auto_cancel [Setting,NilClass]
|
||||
# @param threshold [Setting,NilClass]
|
||||
# @param deadline [Setting,NilClass]
|
||||
def update_auto_cancel(training, auto_cancel, threshold, deadline)
|
||||
previous_auto_cancel = auto_cancel.nil? ? Setting.find_by(name: 'trainings_auto_cancel').value : auto_cancel.previous_value
|
||||
previous_threshold = threshold.nil? ? Setting.find_by(name: 'trainings_auto_cancel_threshold').value : threshold.previous_value
|
||||
previous_deadline = deadline.nil? ? Setting.find_by(name: 'trainings_auto_cancel_deadline').value : deadline.previous_value
|
||||
is_default = training.auto_cancel.to_s == previous_auto_cancel &&
|
||||
[nil, previous_threshold].include?(training.auto_cancel_threshold.to_s) &&
|
||||
[nil, previous_deadline].include?(training.auto_cancel_deadline.to_s)
|
||||
|
||||
return unless is_default
|
||||
|
||||
# update parameters if the given training is default
|
||||
params = {}
|
||||
params[:auto_cancel] = auto_cancel.value unless auto_cancel.nil?
|
||||
params[:auto_cancel_threshold] = threshold.value unless threshold.nil?
|
||||
params[:auto_cancel_deadline] = deadline.value unless deadline.nil?
|
||||
training.update(params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @param reservation [Reservation]
|
||||
def refund_after_cancel(reservation)
|
||||
invoice_item = reservation.invoice_items.joins(:invoice).where(invoices: { type: nil }).first
|
||||
service = WalletService.new(user: reservation.user, wallet: reservation.user.wallet)
|
||||
transaction = service.credit(invoice_item.amount_after_coupon / 100.00)
|
||||
service.create_avoir(transaction, DateTime.current, I18n.t('trainings.refund_for_auto_cancel')) if transaction
|
||||
end
|
||||
end
|
||||
end
|
@ -6,7 +6,7 @@ class TrainingAutoCancelWorker
|
||||
|
||||
def perform
|
||||
Training.find_each do |t|
|
||||
TrainingService.auto_cancel_reservation(t)
|
||||
Trainings::AutoCancelService.auto_cancel_reservations(t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,65 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class TrainingServiceTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@training = Training.find(4)
|
||||
@availability = Availability.find(22)
|
||||
end
|
||||
|
||||
test 'auto cancel reservation with less reservations than the deadline' do
|
||||
@training.update(auto_cancel: true, auto_cancel_threshold: 3, auto_cancel_deadline: 24)
|
||||
customer = User.find(3)
|
||||
slot = @availability.slots.first
|
||||
r = Reservation.create!(
|
||||
reservable_id: @training.id,
|
||||
reservable_type: Training.name,
|
||||
slots_reservations_attributes: [{ slot_id: slot.id }],
|
||||
statistic_profile_id: StatisticProfile.find_by(user: customer).id
|
||||
)
|
||||
TrainingService.auto_cancel_reservation(@training)
|
||||
r.reload
|
||||
assert_not_nil r.slots_reservations.first&.canceled_at
|
||||
end
|
||||
|
||||
test 'do not auto cancel reservation with more reservations than the deadline' do
|
||||
@training.update(auto_cancel: true, auto_cancel_threshold: 3, auto_cancel_deadline: 24)
|
||||
slot = @availability.slots.first
|
||||
|
||||
# first reservation
|
||||
c1 = User.find(2)
|
||||
r1 = Reservation.create!(
|
||||
reservable_id: @training.id,
|
||||
reservable_type: Training.name,
|
||||
slots_reservations_attributes: [{ slot_id: slot.id }],
|
||||
statistic_profile_id: StatisticProfile.find_by(user: c1).id
|
||||
)
|
||||
|
||||
# second reservation
|
||||
c2 = User.find(3)
|
||||
r2 = Reservation.create!(
|
||||
reservable_id: @training.id,
|
||||
reservable_type: Training.name,
|
||||
slots_reservations_attributes: [{ slot_id: slot.id }],
|
||||
statistic_profile_id: StatisticProfile.find_by(user: c2).id
|
||||
)
|
||||
|
||||
# third reservation
|
||||
c3 = User.find(3)
|
||||
r3 = Reservation.create!(
|
||||
reservable_id: @training.id,
|
||||
reservable_type: Training.name,
|
||||
slots_reservations_attributes: [{ slot_id: slot.id }],
|
||||
statistic_profile_id: StatisticProfile.find_by(user: c3).id
|
||||
)
|
||||
|
||||
TrainingService.auto_cancel_reservation(@training)
|
||||
r1.reload
|
||||
assert_nil r1.slots_reservations.first&.canceled_at
|
||||
r2.reload
|
||||
assert_nil r2.slots_reservations.first&.canceled_at
|
||||
r3.reload
|
||||
assert_nil r3.slots_reservations.first&.canceled_at
|
||||
end
|
||||
end
|
155
test/services/trainings/auto_cancel_service_test.rb
Normal file
155
test/services/trainings/auto_cancel_service_test.rb
Normal file
@ -0,0 +1,155 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class Trainings::AutoCancelServiceTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@training = Training.find(4)
|
||||
@availability = Availability.find(22)
|
||||
end
|
||||
|
||||
test 'auto cancel reservation with less reservations than the threshold' do
|
||||
Setting.set('wallet_module', false)
|
||||
@training.update(auto_cancel: true, auto_cancel_threshold: 3, auto_cancel_deadline: 24)
|
||||
customer = User.find(3)
|
||||
slot = @availability.slots.first
|
||||
r = Reservation.create!(
|
||||
reservable_id: @training.id,
|
||||
reservable_type: Training.name,
|
||||
slots_reservations_attributes: [{ slot_id: slot.id }],
|
||||
statistic_profile_id: StatisticProfile.find_by(user: customer).id
|
||||
)
|
||||
Trainings::AutoCancelService.auto_cancel_reservations(@training)
|
||||
|
||||
# Check reservation was cancelled
|
||||
r.reload
|
||||
assert_not_nil r.slots_reservations.first&.canceled_at
|
||||
|
||||
# Check notification was sent to the user
|
||||
notification = Notification.find_by(
|
||||
notification_type_id: NotificationType.find_by_name('notify_member_training_auto_cancelled'), # rubocop:disable Rails/DynamicFindBy
|
||||
attached_object_type: 'SlotsReservation',
|
||||
attached_object_id: r.slots_reservations.first&.id
|
||||
)
|
||||
assert_not_nil notification, 'user notification was not created'
|
||||
assert_not notification.get_meta_data(:auto_refund)
|
||||
|
||||
# Check notification was sent to the admin
|
||||
notification = Notification.find_by(
|
||||
notification_type_id: NotificationType.find_by_name('notify_admin_training_auto_cancelled'), # rubocop:disable Rails/DynamicFindBy
|
||||
attached_object_type: 'Availability',
|
||||
attached_object_id: @availability.id
|
||||
)
|
||||
assert_not_nil notification, 'admin notification was not created'
|
||||
assert_not notification.get_meta_data(:auto_refund)
|
||||
end
|
||||
|
||||
test 'do not auto cancel reservation with more reservations than the threshold' do
|
||||
@training.update(auto_cancel: true, auto_cancel_threshold: 3, auto_cancel_deadline: 24)
|
||||
slot = @availability.slots.first
|
||||
|
||||
# first reservation
|
||||
c1 = User.find(2)
|
||||
r1 = Reservation.create!(
|
||||
reservable_id: @training.id,
|
||||
reservable_type: Training.name,
|
||||
slots_reservations_attributes: [{ slot_id: slot.id }],
|
||||
statistic_profile_id: StatisticProfile.find_by(user: c1).id
|
||||
)
|
||||
|
||||
# second reservation
|
||||
c2 = User.find(3)
|
||||
r2 = Reservation.create!(
|
||||
reservable_id: @training.id,
|
||||
reservable_type: Training.name,
|
||||
slots_reservations_attributes: [{ slot_id: slot.id }],
|
||||
statistic_profile_id: StatisticProfile.find_by(user: c2).id
|
||||
)
|
||||
|
||||
# third reservation
|
||||
c3 = User.find(3)
|
||||
r3 = Reservation.create!(
|
||||
reservable_id: @training.id,
|
||||
reservable_type: Training.name,
|
||||
slots_reservations_attributes: [{ slot_id: slot.id }],
|
||||
statistic_profile_id: StatisticProfile.find_by(user: c3).id
|
||||
)
|
||||
|
||||
Trainings::AutoCancelService.auto_cancel_reservations(@training)
|
||||
|
||||
# Check nothing was cancelled
|
||||
r1.reload
|
||||
assert_nil r1.slots_reservations.first&.canceled_at
|
||||
r2.reload
|
||||
assert_nil r2.slots_reservations.first&.canceled_at
|
||||
r3.reload
|
||||
assert_nil r3.slots_reservations.first&.canceled_at
|
||||
|
||||
# Check no notifications were sent
|
||||
assert_empty Notification.where(
|
||||
notification_type_id: NotificationType.find_by_name('notify_member_training_auto_cancelled'), # rubocop:disable Rails/DynamicFindBy
|
||||
attached_object_type: 'SlotsReservation',
|
||||
attached_object_id: [r1.slots_reservations.first&.id, r2.slots_reservations.first&.id, r3.slots_reservations.first&.id]
|
||||
)
|
||||
assert_nil Notification.find_by(
|
||||
notification_type_id: NotificationType.find_by_name('notify_admin_training_auto_cancelled'), # rubocop:disable Rails/DynamicFindBy
|
||||
attached_object_type: 'Availability',
|
||||
attached_object_id: @availability.id
|
||||
)
|
||||
end
|
||||
|
||||
test 'auto cancel reservation and generate refunds' do
|
||||
Setting.set('wallet_module', true)
|
||||
|
||||
wallet_transactions = WalletTransaction.count
|
||||
|
||||
@training.update(auto_cancel: true, auto_cancel_threshold: 3, auto_cancel_deadline: 24)
|
||||
customer = User.find(3)
|
||||
slot = @availability.slots.first
|
||||
|
||||
# Reserve through the cart service to get an invoice associated with the reservation
|
||||
cs = CartService.new(User.admins.first)
|
||||
cs.from_hash(ActionController::Parameters.new({
|
||||
customer_id: customer.id,
|
||||
items: [
|
||||
reservation: {
|
||||
reservable_id: @training.id,
|
||||
reservable_type: @training.class.name,
|
||||
slots_reservations_attributes: [{ slot_id: slot.id }]
|
||||
}
|
||||
]
|
||||
})).build_and_save(nil, nil)
|
||||
|
||||
# Go with cancelling
|
||||
Trainings::AutoCancelService.auto_cancel_reservations(@training)
|
||||
|
||||
# Check reservation was cancelled
|
||||
r = Reservation.last
|
||||
assert_not_nil r.slots_reservations.first&.canceled_at
|
||||
|
||||
# Check notification was sent to the user
|
||||
notification = Notification.find_by(
|
||||
notification_type_id: NotificationType.find_by_name('notify_member_training_auto_cancelled'), # rubocop:disable Rails/DynamicFindBy
|
||||
attached_object_type: 'SlotsReservation',
|
||||
attached_object_id: r.slots_reservations.first&.id
|
||||
)
|
||||
assert_not_nil notification, 'user notification was not created'
|
||||
assert notification.get_meta_data(:auto_refund)
|
||||
|
||||
# Check notification was sent to the admin
|
||||
notification = Notification.find_by(
|
||||
notification_type_id: NotificationType.find_by_name('notify_admin_training_auto_cancelled'), # rubocop:disable Rails/DynamicFindBy
|
||||
attached_object_type: 'Availability',
|
||||
attached_object_id: @availability.id
|
||||
)
|
||||
assert_not_nil notification, 'admin notification was not created'
|
||||
assert notification.get_meta_data(:auto_refund)
|
||||
|
||||
# Check customer was refunded on his wallet
|
||||
assert_equal wallet_transactions + 1, WalletTransaction.count
|
||||
transaction = WalletTransaction.last
|
||||
assert_equal transaction.wallet.user.id, customer.id
|
||||
assert_equal transaction.transaction_type, 'credit'
|
||||
assert_equal transaction.amount, r.invoice_items.first.amount
|
||||
end
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user