1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-03-15 12:29:16 +01:00

Merge branch 'Guichaguri-feature/reservation-deadlines' into dev

This commit is contained in:
Sylvain 2022-12-19 10:21:10 +01:00
commit 639c460eb7
13 changed files with 181 additions and 3 deletions

View File

@ -78,6 +78,7 @@ export const bookingSettings = [
'reminder_delay',
'visibility_yearly',
'visibility_others',
'reservation_deadline',
'display_name_enable',
'book_overlapping_slots',
'slot_duration',

View File

@ -1179,7 +1179,7 @@ angular.module('application.router', ['ui.router'])
"'renew_pack_threshold', 'pack_only_for_subscription', 'overlapping_categories', 'public_registrations'," +
"'extended_prices_in_same_day', 'recaptcha_site_key', 'recaptcha_secret_key', 'user_validation_required', " +
"'user_validation_required_list', 'machines_module', 'user_change_group', 'show_username_in_admin_list', " +
"'store_module']"
"'store_module', 'reservation_deadline']"
}).$promise;
}],
privacyDraftsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'privacy_draft', history: true }).$promise; }],

View File

@ -105,6 +105,18 @@
</div>
</div>
<div class="section-separator"></div>
<div class="row">
<h3 class="m-l m-t-lg" translate>{{ 'app.admin.settings.reservation_deadline' }}</h3>
<number-setting name="reservation_deadline"
settings="allSettings"
label="app.admin.settings.deadline_minutes"
classes="col-md-4"
fa-icon="fas fa-clock"
min="0"
required="true">
</number-setting>
</div>
<div class="section-separator"></div>
<div class="row">
<h3 class="m-l m-t-lg" translate>{{ 'app.admin.settings.default_slot_duration' }}</h3>
<p class="alert alert-warning m-h-md" translate>{{ 'app.admin.settings.default_slot_duration_info' }}</p>

View File

@ -42,6 +42,10 @@ class CartItem::Reservation < CartItem::BaseItem
def valid?(all_items)
pending_subscription = all_items.find { |i| i.is_a?(CartItem::Subscription) }
reservation_deadline_minutes = Setting.get('reservation_deadline').to_i
reservation_deadline = reservation_deadline_minutes.minutes.since
@slots.each do |slot|
slot_db = Slot.find(slot[:slot_id])
if slot_db.nil?
@ -60,6 +64,11 @@ class CartItem::Reservation < CartItem::BaseItem
return false
end
if slot_db.start_at < reservation_deadline && !@operator.privileged?
@errors[:slot] = 'cannot reserve a slot ' + reservation_deadline_minutes.to_s + ' minutes prior to its start'
return false
end
next if availability.plan_ids.empty?
next if required_subscription?(availability, pending_subscription)

View File

@ -53,6 +53,7 @@ class Setting < ApplicationRecord
space_explications_alert
visibility_yearly
visibility_others
reservation_deadline
display_name_enable
machines_sort_by
accounting_journal_code

View File

@ -9,6 +9,7 @@ class Availabilities::AvailabilitiesService
year: Setting.get('visibility_yearly').to_i.months.since,
other: Setting.get('visibility_others').to_i.months.since
}
@minimum_visibility = Setting.get('reservation_deadline').to_i.minutes.since
@service = Availabilities::StatusService.new(current_user&.role)
@level = level
end
@ -93,7 +94,7 @@ class Availabilities::AvailabilitiesService
end_at = @maximum_visibility[:year] if subscription_year?(user) && type != 'training'
end_at = @maximum_visibility[:year] if show_more_trainings?(user) && type == 'training'
window_end = [end_at, range_end].min
window_start = [range_start, DateTime.current].max
window_start = [range_start, @minimum_visibility].max
availabilities.includes(:tags, :plans, :slots)
.joins(:slots)
.where('availabilities.start_at <= ? AND availabilities.end_at >= ? AND available_type = ?', window_end, window_start, type)

View File

@ -1350,6 +1350,8 @@ en:
max_visibility: "Maximum visibility (in months)"
visibility_for_yearly_members: "For currently running subscriptions, at least 1 year long"
visibility_for_other_members: "For all other members"
reservation_deadline: "Prevent last minute booking"
deadline_minutes: "Prior period (minutes)"
ability_for_the_users_to_move_their_reservations: "Ability for the users to move their reservations"
reservations_shifting: "Reservations shifting"
prior_period_hours: "Prior period (hours)"
@ -1405,6 +1407,7 @@ en:
default_value_is_24_hours: "If the field is leaved empty: 24 hours."
visibility_yearly: "maximum visibility for annual subscribers"
visibility_others: "maximum visibility for other members"
reservation_deadline: "reservation deadline"
display: "Display"
display_name_info_html: "When enabled, connected members browsing the calendar or booking a resource will see the name of the members who has already booked some slots. When disabled, only administrators and managers will view the names.<br/><strong>Warning:</strong> if you enable this feature, please write it down in your privacy policy."
display_reservation_user_name: "Display the full name of the user(s) who booked a slots"

View File

@ -524,6 +524,7 @@ en:
space_explications_alert: "Explanation message on the space reservation page"
visibility_yearly: "Maximum visibility for annual subscribers"
visibility_others: "Maximum visibility for other members"
reservation_deadline: "Prevent reservation before it starts"
display_name_enable: "Display names in the calendar"
machines_sort_by: "Machines display order"
accounting_journal_code: "Journal code"

View File

@ -729,6 +729,8 @@ Setting.set('visibility_yearly', 3) unless Setting.find_by(name: 'visibility_yea
Setting.set('visibility_others', 1) unless Setting.find_by(name: 'visibility_others').try(:value)
Setting.set('reservation_deadline', 0) unless Setting.find_by(name: 'reservation_deadline').try(:value)
Setting.set('display_name_enable', false) unless Setting.find_by(name: 'display_name_enable').try(:value)
Setting.set('machines_sort_by', 'default') unless Setting.find_by(name: 'machines_sort_by').try(:value)

View File

@ -199,3 +199,13 @@ availability_20:
updated_at: 2022-07-18 12:38:21.616510000 Z
nb_total_places: 5
destroying: false
availability_21:
id: 21
start_at: <%= 10.minutes.from_now.utc.strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (10.minutes.from_now + 1.hour).utc.strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
available_type: space
created_at: 2022-12-14 12:01:26.165110000 Z
updated_at: 2022-12-14 12:01:26.165110000 Z
nb_total_places: 2
destroying: false

View File

@ -566,3 +566,11 @@ slot_132:
created_at: '2022-07-18 12:38:21.616510'
updated_at: '2022-07-18 12:38:21.616510'
availability_id: 20
slot_133:
id: 133
start_at: <%= 10.minutes.from_now.utc.strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
end_at: <%= (10.minutes.from_now + 1.hour).utc.strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
created_at: '2022-12-14 12:01:26.165110'
updated_at: '2022-12-14 12:01:26.165110'
availability_id: 21

View File

@ -3,4 +3,11 @@ spaces_availability_1:
space_id: 1
availability_id: 18
created_at: 2017-02-15 15:59:08.892741000 Z
updated_at: 2017-02-15 15:59:08.892741000 Z
updated_at: 2017-02-15 15:59:08.892741000 Z
spaces_availability_2:
id: 2
space_id: 1
availability_id: 21
created_at: 2022-12-14 12:01:26.165110000 Z
updated_at: 2022-12-14 12:01:26.165110000 Z

View File

@ -0,0 +1,123 @@
# frozen_string_literal: true
require 'test_helper'
module Reservations; end
class Reservations::LastMinuteTest < ActionDispatch::IntegrationTest
setup do
@user = User.members.first
@space = Space.first
@availbility = Availability.find(21)
@admin = User.with_role(:admin).first
end
test 'user cannot reserve last minute booking' do
Setting.set('reservation_deadline', '120')
login_as(@user, scope: :user)
VCR.use_cassette('last_minute_space_reservations_not_allowed') do
post '/api/stripe/confirm_payment', params: {
payment_method_id: stripe_payment_method,
cart_items: {
items: [
{
reservation: {
reservable_id: @space.id,
reservable_type: @space.class.name,
slots_reservations_attributes: [
{
slot_id: @availbility.slots.first.id
}
]
}
}
]
}
}.to_json, headers: default_headers
end
# general assertions
assert_equal 422, response.status
assert_match(/cannot reserve a slot 120 minutes prior to its start/, response.body)
end
test 'user can reserve last minute booking' do
Setting.set('reservation_deadline', '0')
login_as(@user, scope: :user)
reservations_count = Reservation.count
invoice_count = Invoice.count
invoice_items_count = InvoiceItem.count
users_credit_count = UsersCredit.count
subscriptions_count = Subscription.count
VCR.use_cassette('last_minute_space_reservations_allowed') do
post '/api/stripe/confirm_payment', params: {
payment_method_id: stripe_payment_method,
cart_items: {
items: [
{
reservation: {
reservable_id: @space.id,
reservable_type: @space.class.name,
slots_reservations_attributes: [
{
slot_id: @availbility.slots.first.id
}
]
}
}
]
}
}.to_json, headers: default_headers
end
# general assertions
assert_equal 201, response.status
assert_equal reservations_count + 1, Reservation.count
assert_equal invoice_count + 1, Invoice.count
assert_equal invoice_items_count + 1, InvoiceItem.count
assert_equal users_credit_count, UsersCredit.count
assert_equal subscriptions_count, Subscription.count
end
test 'admin can reserve last minute booking anyway' do
Setting.set('reservation_deadline', '120')
login_as(@admin, scope: :user)
reservations_count = Reservation.count
invoice_count = Invoice.count
invoice_items_count = InvoiceItem.count
users_credit_count = UsersCredit.count
subscriptions_count = Subscription.count
post '/api/local_payment/confirm_payment', params: {
customer_id: @user.id,
items: [
{
reservation: {
reservable_id: @space.id,
reservable_type: @space.class.name,
slots_reservations_attributes: [
{
slot_id: @availbility.slots.first.id
}
]
}
}
]
}.to_json, headers: default_headers
# general assertions
assert_equal 201, response.status
assert_equal reservations_count + 1, Reservation.count
assert_equal invoice_count + 1, Invoice.count
assert_equal invoice_items_count + 1, InvoiceItem.count
assert_equal users_credit_count, UsersCredit.count
assert_equal subscriptions_count, Subscription.count
end
end