mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-18 07:52:23 +01:00
check for slots restricted to subscribers at cart level
This commit is contained in:
parent
55b0e25ee9
commit
d0011a10f0
@ -5,6 +5,12 @@ module CartItem; end
|
|||||||
|
|
||||||
# This is an abstract class implemented by classes that can be added to the shopping cart
|
# This is an abstract class implemented by classes that can be added to the shopping cart
|
||||||
class CartItem::BaseItem
|
class CartItem::BaseItem
|
||||||
|
attr_reader :errors
|
||||||
|
|
||||||
|
def initialize(*)
|
||||||
|
@errors = {}
|
||||||
|
end
|
||||||
|
|
||||||
def price
|
def price
|
||||||
{ elements: {}, amount: 0 }
|
{ elements: {}, amount: 0 }
|
||||||
end
|
end
|
||||||
@ -13,5 +19,11 @@ class CartItem::BaseItem
|
|||||||
''
|
''
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# This method run validations at cart-level, possibly using the other items in the cart, to validate the current one.
|
||||||
|
# Other validations that may occurs at record-level (ActiveRecord validations) can't be related to other items.
|
||||||
|
def valid?(_all_items)
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
def to_object; end
|
def to_object; end
|
||||||
end
|
end
|
||||||
|
@ -12,6 +12,7 @@ class CartItem::Reservation < CartItem::BaseItem
|
|||||||
@operator = operator
|
@operator = operator
|
||||||
@reservable = reservable
|
@reservable = reservable
|
||||||
@slots = slots
|
@slots = slots
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def price
|
def price
|
||||||
@ -33,6 +34,23 @@ class CartItem::Reservation < CartItem::BaseItem
|
|||||||
@reservable.name
|
@reservable.name
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def valid?(all_items)
|
||||||
|
pending_subscription = all_items.find { |i| i.is_a?(CartItem::Subscription) }
|
||||||
|
@slots.each do |slot|
|
||||||
|
availability = Availability.find(slot[:availability_id])
|
||||||
|
next if availability.plan_ids.empty?
|
||||||
|
next if (@customer.subscribed_plan && availability.plan_ids.include?(@customer.subscribed_plan.id)) ||
|
||||||
|
(pending_subscription && availability.plan_ids.include?(pending_subscription.plan.id)) ||
|
||||||
|
(@operator.manager? && @customer.id != @operator.id) ||
|
||||||
|
@operator.admin?
|
||||||
|
|
||||||
|
@errors[:slot] = 'slot is restricted for subscribers'
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def credits
|
def credits
|
||||||
|
@ -7,6 +7,7 @@ class CartItem::Subscription < CartItem::BaseItem
|
|||||||
|
|
||||||
@plan = plan
|
@plan = plan
|
||||||
@customer = customer
|
@customer = customer
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def plan
|
def plan
|
||||||
|
@ -24,23 +24,12 @@ class Reservation < ApplicationRecord
|
|||||||
validate :machine_not_already_reserved, if: -> { reservable.is_a?(Machine) }
|
validate :machine_not_already_reserved, if: -> { reservable.is_a?(Machine) }
|
||||||
validate :training_not_fully_reserved, if: -> { reservable.is_a?(Training) }
|
validate :training_not_fully_reserved, if: -> { reservable.is_a?(Training) }
|
||||||
validate :slots_not_locked
|
validate :slots_not_locked
|
||||||
# validates_with ReservationSlotSubscriptionValidator
|
|
||||||
|
|
||||||
attr_accessor :plan_id, :subscription
|
|
||||||
|
|
||||||
after_commit :notify_member_create_reservation, on: :create
|
after_commit :notify_member_create_reservation, on: :create
|
||||||
after_commit :notify_admin_member_create_reservation, on: :create
|
after_commit :notify_admin_member_create_reservation, on: :create
|
||||||
after_commit :extend_subscription, on: :create
|
after_commit :extend_subscription, on: :create
|
||||||
after_save :update_event_nb_free_places, if: proc { |reservation| reservation.reservable_type == 'Event' }
|
after_save :update_event_nb_free_places, if: proc { |reservation| reservation.reservable_type == 'Event' }
|
||||||
|
|
||||||
## Generate the subscription associated with for the current reservation
|
|
||||||
def generate_subscription
|
|
||||||
return unless plan_id
|
|
||||||
|
|
||||||
self.subscription = Subscription.new(plan_id: plan_id, statistic_profile_id: statistic_profile_id, expiration_date: nil)
|
|
||||||
subscription.init_save
|
|
||||||
subscription
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param canceled if true, count the number of seats for this reservation, including canceled seats
|
# @param canceled if true, count the number of seats for this reservation, including canceled seats
|
||||||
def total_booked_seats(canceled: false)
|
def total_booked_seats(canceled: false)
|
||||||
|
@ -63,13 +63,17 @@ class ShoppingCart
|
|||||||
payment.post_save(payment_id)
|
payment.post_save(payment_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
{ success: objects.map(&:errors).flatten.map(&:empty?).all?, payment: payment, errors: objects.map(&:errors).flatten }
|
success = objects.map(&:errors).flatten.map(&:empty?).all? && items.map(&:errors).map(&:empty?).all?
|
||||||
|
errors = objects.map(&:errors).flatten.concat(items.map(&:errors))
|
||||||
|
{ success: success, payment: payment, errors: errors }
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Save the object associated with the provided item or raise and Rollback if something wrong append.
|
# Save the object associated with the provided item or raise and Rollback if something wrong append.
|
||||||
def save_item(item)
|
def save_item(item)
|
||||||
|
raise ActiveRecord::Rollback unless item.valid?(@items)
|
||||||
|
|
||||||
object = item.to_object
|
object = item.to_object
|
||||||
object.save
|
object.save
|
||||||
raise ActiveRecord::Rollback unless object.errors.empty?
|
raise ActiveRecord::Rollback unless object.errors.empty?
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'stripe/helper'
|
||||||
|
require 'pay_zen/helper'
|
||||||
|
|
||||||
# create remote items on currently active payment gateway
|
# create remote items on currently active payment gateway
|
||||||
class PaymentGatewayService
|
class PaymentGatewayService
|
||||||
def initialize
|
def initialize
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class SubscriptionExtensionAfterReservation
|
class SubscriptionExtensionAfterReservation
|
||||||
attr_accessor :user, :reservation
|
attr_accessor :user, :reservation
|
||||||
|
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class ReservationSlotSubscriptionValidator < ActiveModel::Validator
|
|
||||||
def validate(record)
|
|
||||||
record.slots.each do |s|
|
|
||||||
unless s.availability.plan_ids.empty?
|
|
||||||
if record.user.subscribed_plan && s.availability.plan_ids.include?(record.user.subscribed_plan.id)
|
|
||||||
elsif s.availability.plan_ids.include?(record.plan_id)
|
|
||||||
else
|
|
||||||
# TODO, this validation requires to check if the operator is privileged.
|
|
||||||
# Meanwhile we can't check this, we disable the validation
|
|
||||||
record.errors[:slots] << 'slot is restrict for subscriptions'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
298
test/integration/reservations/restricted_test.rb
Normal file
298
test/integration/reservations/restricted_test.rb
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
module Reservations; end
|
||||||
|
|
||||||
|
class Reservations::RestrictedTest < ActionDispatch::IntegrationTest
|
||||||
|
setup do
|
||||||
|
@admin = User.with_role(:admin).first
|
||||||
|
@pdurand = User.find(3) # user with subscription to plan 2
|
||||||
|
@jdupont = User.find(2) # user without subscription
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'reserve slot restricted to subscribers with success' do
|
||||||
|
login_as(@admin, scope: :user)
|
||||||
|
|
||||||
|
reservations_count = Reservation.count
|
||||||
|
availabilities_count = Availability.count
|
||||||
|
invoices_count = Invoice.count
|
||||||
|
slots_count = Slot.count
|
||||||
|
|
||||||
|
# first, create the restricted availability
|
||||||
|
date = 4.days.from_now.utc.change(hour: 8, min: 0, sec: 0)
|
||||||
|
post '/api/availabilities',
|
||||||
|
params: {
|
||||||
|
availability: {
|
||||||
|
start_at: date.iso8601,
|
||||||
|
end_at: (date + 6.hours).iso8601,
|
||||||
|
available_type: 'machines',
|
||||||
|
slot_duration: 60,
|
||||||
|
machine_ids: [2],
|
||||||
|
occurrences: [
|
||||||
|
{ start_at: date.iso8601, end_at: (date + 6.hours).iso8601 }
|
||||||
|
],
|
||||||
|
plan_ids: [2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal 201, response.status
|
||||||
|
|
||||||
|
# Check the id
|
||||||
|
availability = json_response(response.body)
|
||||||
|
assert_not_nil availability[:id], 'availability ID was unexpectedly nil'
|
||||||
|
|
||||||
|
assert_equal availabilities_count + 1, Availability.count
|
||||||
|
|
||||||
|
# change connected user
|
||||||
|
login_as(@pdurand, scope: :user)
|
||||||
|
|
||||||
|
# book a reservation
|
||||||
|
VCR.use_cassette('reservations_create_for_restricted_slot_success') do
|
||||||
|
post '/api/stripe/confirm_payment',
|
||||||
|
params: {
|
||||||
|
payment_method_id: stripe_payment_method,
|
||||||
|
cart_items: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
reservation: {
|
||||||
|
reservable_id: 2,
|
||||||
|
reservable_type: 'Machine',
|
||||||
|
slots_attributes: [
|
||||||
|
{
|
||||||
|
start_at: availability[:start_at],
|
||||||
|
end_at: (DateTime.parse(availability[:start_at]) + 1.hour).to_s(:iso8601),
|
||||||
|
availability_id: availability[:id]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}.to_json, headers: default_headers
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 201, response.status
|
||||||
|
|
||||||
|
assert_equal reservations_count + 1, Reservation.count
|
||||||
|
assert_equal invoices_count + 1, Invoice.count
|
||||||
|
assert_equal slots_count + 1, Slot.count
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'unable to reserve slot restricted to subscribers' do
|
||||||
|
login_as(@admin, scope: :user)
|
||||||
|
|
||||||
|
reservations_count = Reservation.count
|
||||||
|
availabilities_count = Availability.count
|
||||||
|
invoices_count = Invoice.count
|
||||||
|
slots_count = Slot.count
|
||||||
|
|
||||||
|
# first, create the restricted availability
|
||||||
|
date = 4.days.from_now.utc.change(hour: 8, min: 0, sec: 0)
|
||||||
|
post '/api/availabilities',
|
||||||
|
params: {
|
||||||
|
availability: {
|
||||||
|
start_at: date.iso8601,
|
||||||
|
end_at: (date + 6.hours).iso8601,
|
||||||
|
available_type: 'machines',
|
||||||
|
slot_duration: 60,
|
||||||
|
machine_ids: [2],
|
||||||
|
occurrences: [
|
||||||
|
{ start_at: date.iso8601, end_at: (date + 6.hours).iso8601 }
|
||||||
|
],
|
||||||
|
plan_ids: [2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal 201, response.status
|
||||||
|
|
||||||
|
# Check the id
|
||||||
|
availability = json_response(response.body)
|
||||||
|
assert_not_nil availability[:id], 'availability ID was unexpectedly nil'
|
||||||
|
|
||||||
|
assert_equal availabilities_count + 1, Availability.count
|
||||||
|
|
||||||
|
# change connected user
|
||||||
|
login_as(@jdupont, scope: :user)
|
||||||
|
|
||||||
|
# book a reservation
|
||||||
|
VCR.use_cassette('reservations_create_for_restricted_slot_fails') do
|
||||||
|
post '/api/stripe/confirm_payment',
|
||||||
|
params: {
|
||||||
|
payment_method_id: stripe_payment_method,
|
||||||
|
cart_items: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
reservation: {
|
||||||
|
reservable_id: 2,
|
||||||
|
reservable_type: 'Machine',
|
||||||
|
slots_attributes: [
|
||||||
|
{
|
||||||
|
start_at: availability[:start_at],
|
||||||
|
end_at: (DateTime.parse(availability[:start_at]) + 1.hour).to_s(:iso8601),
|
||||||
|
availability_id: availability[:id]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}.to_json, headers: default_headers
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 422, response.status
|
||||||
|
assert_match /slot is restricted for subscribers/, response.body
|
||||||
|
|
||||||
|
assert_equal reservations_count, Reservation.count
|
||||||
|
assert_equal invoices_count, Invoice.count
|
||||||
|
assert_equal slots_count, Slot.count
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'admin force reservation of a slot restricted to subscribers' do
|
||||||
|
login_as(@admin, scope: :user)
|
||||||
|
|
||||||
|
reservations_count = Reservation.count
|
||||||
|
availabilities_count = Availability.count
|
||||||
|
invoices_count = Invoice.count
|
||||||
|
slots_count = Slot.count
|
||||||
|
|
||||||
|
# first, create the restricted availability
|
||||||
|
date = 4.days.from_now.utc.change(hour: 8, min: 0, sec: 0)
|
||||||
|
post '/api/availabilities',
|
||||||
|
params: {
|
||||||
|
availability: {
|
||||||
|
start_at: date.iso8601,
|
||||||
|
end_at: (date + 6.hours).iso8601,
|
||||||
|
available_type: 'machines',
|
||||||
|
slot_duration: 60,
|
||||||
|
machine_ids: [2],
|
||||||
|
occurrences: [
|
||||||
|
{ start_at: date.iso8601, end_at: (date + 6.hours).iso8601 }
|
||||||
|
],
|
||||||
|
plan_ids: [2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal 201, response.status
|
||||||
|
|
||||||
|
# Check the id
|
||||||
|
availability = json_response(response.body)
|
||||||
|
assert_not_nil availability[:id], 'availability ID was unexpectedly nil'
|
||||||
|
|
||||||
|
assert_equal availabilities_count + 1, Availability.count
|
||||||
|
|
||||||
|
# book a reservation
|
||||||
|
VCR.use_cassette('reservations_create_for_restricted_slot_fails') do
|
||||||
|
post '/api/local_payment/confirm_payment',
|
||||||
|
params: {
|
||||||
|
customer_id: @jdupont.id,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
reservation: {
|
||||||
|
reservable_id: 2,
|
||||||
|
reservable_type: 'Machine',
|
||||||
|
slots_attributes: [
|
||||||
|
{
|
||||||
|
start_at: availability[:start_at],
|
||||||
|
end_at: (DateTime.parse(availability[:start_at]) + 1.hour).to_s(:iso8601),
|
||||||
|
availability_id: availability[:id]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}.to_json, headers: default_headers
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 201, response.status
|
||||||
|
|
||||||
|
# Check the result
|
||||||
|
result = json_response(response.body)
|
||||||
|
assert_not_nil result[:id], 'invoice ID was unexpectedly nil'
|
||||||
|
|
||||||
|
assert_equal reservations_count + 1, Reservation.count
|
||||||
|
assert_equal invoices_count + 1, Invoice.count
|
||||||
|
assert_equal slots_count + 1, Slot.count
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'book a slot restricted to subscribers and a subscription at the same time' do
|
||||||
|
login_as(@admin, scope: :user)
|
||||||
|
|
||||||
|
reservations_count = Reservation.count
|
||||||
|
availabilities_count = Availability.count
|
||||||
|
invoices_count = Invoice.count
|
||||||
|
invoice_items_count = InvoiceItem.count
|
||||||
|
slots_count = Slot.count
|
||||||
|
subscriptions_count = Subscription.count
|
||||||
|
|
||||||
|
# first, create the restricted availability
|
||||||
|
date = 4.days.from_now.utc.change(hour: 8, min: 0, sec: 0)
|
||||||
|
post '/api/availabilities',
|
||||||
|
params: {
|
||||||
|
availability: {
|
||||||
|
start_at: date.iso8601,
|
||||||
|
end_at: (date + 6.hours).iso8601,
|
||||||
|
available_type: 'machines',
|
||||||
|
slot_duration: 60,
|
||||||
|
machine_ids: [2],
|
||||||
|
occurrences: [
|
||||||
|
{ start_at: date.iso8601, end_at: (date + 6.hours).iso8601 }
|
||||||
|
],
|
||||||
|
plan_ids: [2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal 201, response.status
|
||||||
|
|
||||||
|
# Check the id
|
||||||
|
availability = json_response(response.body)
|
||||||
|
assert_not_nil availability[:id], 'availability ID was unexpectedly nil'
|
||||||
|
|
||||||
|
assert_equal availabilities_count + 1, Availability.count
|
||||||
|
|
||||||
|
# change connected user
|
||||||
|
login_as(@jdupont, scope: :user)
|
||||||
|
|
||||||
|
# book a reservation
|
||||||
|
VCR.use_cassette('reservations_create_for_restricted_slot_fails') do
|
||||||
|
post '/api/stripe/confirm_payment',
|
||||||
|
params: {
|
||||||
|
payment_method_id: stripe_payment_method,
|
||||||
|
cart_items: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
reservation: {
|
||||||
|
reservable_id: 2,
|
||||||
|
reservable_type: 'Machine',
|
||||||
|
slots_attributes: [
|
||||||
|
{
|
||||||
|
start_at: availability[:start_at],
|
||||||
|
end_at: (DateTime.parse(availability[:start_at]) + 1.hour).to_s(:iso8601),
|
||||||
|
availability_id: availability[:id]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subscription: {
|
||||||
|
plan_id: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}.to_json, headers: default_headers
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 201, response.status
|
||||||
|
|
||||||
|
# Check the result
|
||||||
|
result = json_response(response.body)
|
||||||
|
assert_not_nil result[:id], 'invoice ID was unexpectedly nil'
|
||||||
|
|
||||||
|
assert_equal reservations_count + 1, Reservation.count
|
||||||
|
assert_equal invoices_count + 1, Invoice.count
|
||||||
|
assert_equal slots_count + 1, Slot.count
|
||||||
|
assert_equal subscriptions_count + 1, Subscription.count
|
||||||
|
assert_equal invoice_items_count + 2, InvoiceItem.count
|
||||||
|
end
|
||||||
|
end
|
Loading…
x
Reference in New Issue
Block a user