1
0
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:
Sylvain 2021-05-31 15:39:56 +02:00
parent 55b0e25ee9
commit d0011a10f0
9 changed files with 339 additions and 29 deletions

View File

@ -5,6 +5,12 @@ module CartItem; end
# This is an abstract class implemented by classes that can be added to the shopping cart
class CartItem::BaseItem
attr_reader :errors
def initialize(*)
@errors = {}
end
def price
{ elements: {}, amount: 0 }
end
@ -13,5 +19,11 @@ class CartItem::BaseItem
''
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
end

View File

@ -12,6 +12,7 @@ class CartItem::Reservation < CartItem::BaseItem
@operator = operator
@reservable = reservable
@slots = slots
super
end
def price
@ -33,6 +34,23 @@ class CartItem::Reservation < CartItem::BaseItem
@reservable.name
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
def credits

View File

@ -7,6 +7,7 @@ class CartItem::Subscription < CartItem::BaseItem
@plan = plan
@customer = customer
super
end
def plan

View File

@ -24,23 +24,12 @@ class Reservation < ApplicationRecord
validate :machine_not_already_reserved, if: -> { reservable.is_a?(Machine) }
validate :training_not_fully_reserved, if: -> { reservable.is_a?(Training) }
validate :slots_not_locked
# validates_with ReservationSlotSubscriptionValidator
attr_accessor :plan_id, :subscription
after_commit :notify_member_create_reservation, on: :create
after_commit :notify_admin_member_create_reservation, on: :create
after_commit :extend_subscription, on: :create
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
def total_booked_seats(canceled: false)

View File

@ -63,13 +63,17 @@ class ShoppingCart
payment.post_save(payment_id)
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
private
# Save the object associated with the provided item or raise and Rollback if something wrong append.
def save_item(item)
raise ActiveRecord::Rollback unless item.valid?(@items)
object = item.to_object
object.save
raise ActiveRecord::Rollback unless object.errors.empty?

View File

@ -1,5 +1,8 @@
# frozen_string_literal: true
require 'stripe/helper'
require 'pay_zen/helper'
# create remote items on currently active payment gateway
class PaymentGatewayService
def initialize

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class SubscriptionExtensionAfterReservation
attr_accessor :user, :reservation

View File

@ -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

View 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