1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-19 13:54:25 +01:00

(bug) fix various issues due to slots behavior refactoring

This commit is contained in:
Sylvain 2022-07-19 11:32:12 +02:00
parent b3795e21ec
commit d26e2ae313
14 changed files with 69 additions and 66 deletions

View File

@ -4,6 +4,7 @@
- Improved calendars loading time
- Refactored and documented the availability-slot-reservation data model
- Display bookers names to connected users now apply to all resources
- Fix a bug: unable to book a space's slot with an existing reservation
- Fix a bug: Unable to import accounts from SSO when the transformation modal was opened but leaved empty

View File

@ -1,3 +1,3 @@
web: bundle exec rails server puma -p $PORT
#web: bundle exec rails server puma -p $PORT
worker: bundle exec sidekiq -C ./config/sidekiq.yml
webpack: bin/webpacker-dev-server

View File

@ -73,13 +73,13 @@ class API::AvailabilitiesController < API::ApiController
def machine
service = Availabilities::AvailabilitiesService.new(current_user)
@machine = Machine.friendly.find(params[:machine_id])
@slots = service.machines(@machine, @customer, window)
@slots = service.machines([@machine], @customer, window)
end
def trainings
service = Availabilities::AvailabilitiesService.new(current_user)
@trainings = if training_id.is_number? || (training_id.length.positive? && training_id != 'all')
[Training.friendly.find(training_id)]
@trainings = if params[:training_id].is_number? || (params[:training_id].length.positive? && params[:training_id] != 'all')
[Training.friendly.find(params[:training_id])]
else
Training.all
end
@ -88,8 +88,8 @@ class API::AvailabilitiesController < API::ApiController
def spaces
service = Availabilities::AvailabilitiesService.new(current_user)
@space = Space.friendly.find(space_id)
@slots = service.spaces(@space, @customer, window)
@space = Space.friendly.find(params[:space_id])
@slots = service.spaces([@space], @customer, window)
end
def reservations

View File

@ -16,8 +16,8 @@
* Controller used in the public calendar global
*/
Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', 'iCalendarPromise',
function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise, iCalendarPromise) {
Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', 'iCalendarPromise',
function ($scope, $state, $aside, moment, Availability, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise, iCalendarPromise) {
/* PRIVATE STATIC CONSTANTS */
let currentMachineEvent = null;
machinesPromise.forEach(m => m.checked = true);

View File

@ -493,7 +493,14 @@ Application.Controllers.controller('ReserveSpaceController', ['$scope', '$transi
if ($scope.ctrl.member) {
Member.get({ id: $scope.ctrl.member.id }, function (member) {
$scope.ctrl.member = member;
return Availability.spaces({ spaceId: $scope.space.id, member_id: $scope.ctrl.member.id }, function (spaces) {
const view = uiCalendarConfig.calendars.calendar.fullCalendar('getView');
return Availability.spaces({
spaceId: $scope.space.id,
member_id: $scope.ctrl.member.id,
start: view.start,
end: view.end,
timezone: Fablab.timezone
}, function (spaces) {
uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents');
return $scope.eventSources.splice(0, 1, {
events: spaces,

View File

@ -282,10 +282,17 @@ Application.Controllers.controller('ReserveTrainingController', ['$scope', '$tra
if ($scope.ctrl.member) {
Member.get({ id: $scope.ctrl.member.id }, function (member) {
$scope.ctrl.member = member;
const view = uiCalendarConfig.calendars.calendar.fullCalendar('getView');
const id = $transition$.params().id === 'all' ? $transition$.params().id : $scope.training.id;
return Availability.trainings({ trainingId: id, member_id: $scope.ctrl.member.id }, function (trainings) {
Availability.trainings({
trainingId: id,
member_id: $scope.ctrl.member.id,
start: view.start,
end: view.end,
timezone: Fablab.timezone
}, function (trainings) {
uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents');
return $scope.eventSources.splice(0, 1, {
$scope.eventSources.splice(0, 1, {
events: trainings,
textColor: 'black'
}
@ -296,7 +303,7 @@ Application.Controllers.controller('ReserveTrainingController', ['$scope', '$tra
// as the events are re-fetched for the new user, we must re-init the cart
$scope.events.reserved = [];
$scope.selectedPlan = null;
return $scope.plansAreShown = false;
$scope.plansAreShown = false;
};
/**

View File

@ -179,7 +179,7 @@
</div>
<div class="panel-body">
<div class="row" ng-show="$root.modules.machines">
<h3 class="m-l" translate>{{ 'app.admin.settings.display_machine_reservation_user_name' }}</h3>
<h3 class="m-l" translate>{{ 'app.admin.settings.display_reservation_user_name' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.display_name_info_html' | translate"></p>
<boolean-setting name="'display_name_enable'"
label="'app.admin.settings.display_name' | translate"

View File

@ -13,25 +13,29 @@ class Availabilities::AvailabilitiesService
@level = level
end
# list all slots for the given machine, with visibility relative to the given user
def machines(machine, user, window)
availabilities = availabilities(machine.availabilities, 'machines', user, window[:start], window[:end])
# list all slots for the given machines, with visibility relative to the given user
def machines(machines, user, window)
ma_availabilities = Availability.includes('machines_availabilities')
.where('machines_availabilities.machine_id': machines.map(&:id))
availabilities = availabilities(ma_availabilities, 'machines', user, window[:start], window[:end])
if @level == 'slot'
availabilities.map(&:slots).flatten.map { |s| @service.slot_reserved_status(s, user, [machine]) }
availabilities.map(&:slots).flatten.map { |s| @service.slot_reserved_status(s, user, s.availability.machines) }
else
availabilities.map { |a| @service.availability_reserved_status(a, user, [machine]) }
availabilities.map { |a| @service.availability_reserved_status(a, user, a.machines) }
end
end
# list all slots for the given space, with visibility relative to the given user
def spaces(space, user, window)
availabilities = availabilities(space.availabilities, 'space', user, window[:start], window[:end])
def spaces(spaces, user, window)
sp_availabilities = Availability.includes('spaces_availabilities')
.where('spaces_availabilities.space_id': spaces.map(&:id))
availabilities = availabilities(sp_availabilities, 'space', user, window[:start], window[:end])
if @level == 'slot'
availabilities.map(&:slots).flatten.map { |s| @service.slot_reserved_status(s, user, [space]) }
availabilities.map(&:slots).flatten.map { |s| @service.slot_reserved_status(s, user, s.availability.spaces) }
else
availabilities.map { |a| @service.availability_reserved_status(a, user, [space]) }
availabilities.map { |a| @service.availability_reserved_status(a, user, a.spaces) }
end
end

View File

@ -38,10 +38,18 @@ class Availabilities::CreateAvailabilitiesService
def create_slots(availability)
slot_duration = availability.slot_duration || Setting.get('slot_duration').to_i
((availability.end_at - availability.start_at) / slot_duration.minutes).to_i.times do |i|
if %w[machines space].include?(availability.available_type)
((availability.end_at - availability.start_at) / slot_duration.minutes).to_i.times do |i|
Slot.new(
start_at: availability.start_at + (i * slot_duration).minutes,
end_at: availability.start_at + (i * slot_duration).minutes + slot_duration.minutes,
availability_id: availability.id
).save!
end
else
Slot.new(
start_at: availability.start_at + (i * slot_duration).minutes,
end_at: availability.start_at + (i * slot_duration).minutes + slot_duration.minutes,
start_at: availability.start_at,
end_at: availability.end_at,
availability_id: availability.id
).save!
end

View File

@ -7,36 +7,12 @@ class Availabilities::PublicAvailabilitiesService
@service = Availabilities::StatusService.new('public')
end
# provides a list of slots and availabilities for the machines, between the given dates
def machines(window, machine_ids, level)
machine_ids = [] if machine_ids.nil?
service = Availabilities::AvailabilitiesService.new(@current_user, level)
slots = []
machine_ids.each do |machine_id|
machine = Machine.friendly.find(machine_id)
slots.concat(service.machines(machine, @current_user, window))
end
slots
end
# provides a list of slots and availabilities for the spaces, between the given dates
def spaces(window, spaces_ids, level)
spaces_ids = [] if spaces_ids.nil?
service = Availabilities::AvailabilitiesService.new(@current_user, level)
slots = []
spaces_ids.each do |space_id|
space = Space.friendly.find(space_id)
slots.concat(service.spaces(space, @current_user, window))
end
slots
end
def public_availabilities(window, ids, events = false)
level = in_same_day(window[:start], window[:end]) ? 'slot' : 'availability'
service = Availabilities::AvailabilitiesService.new(@current_user, level)
machines_slots = machines(window, ids[:machines], level)
spaces_slots = spaces(window, ids[:spaces], level)
machines_slots = service.machines(Machine.where(id: ids[:machines]), @current_user, window)
spaces_slots = service.spaces(Space.where(id:ids[:spaces]), @current_user, window)
trainings_slots = service.trainings(Training.where(id: ids[:trainings]), @current_user, window)
events_slots = events ? service.events(Event.all, @current_user, window) : []

View File

@ -4,13 +4,13 @@
class Availabilities::StatusService
def initialize(current_user_role)
@current_user_role = current_user_role
@show_name = (%w[admin manager].include?(@current_user_role) || Setting.get('display_name_enable'))
@show_name = (%w[admin manager].include?(@current_user_role) || (current_user_role && Setting.get('display_name_enable')))
end
# check that the provided slot is reserved for the given reservable (machine, training or space).
# Mark it accordingly for display in the calendar
def slot_reserved_status(slot, user, reservables)
unless reservables.map(&:class).map(&:name).reduce(:==)
if reservables.map(&:class).map(&:name).uniq.size > 1
raise TypeError('[Availabilities::StatusService#slot_reserved_status] reservables have differents types')
end
@ -24,7 +24,7 @@ class Availabilities::StatusService
user_slots_reservations = slots_reservations.where('reservations.statistic_profile_id': statistic_profile_id)
slot.is_reserved = !slots_reservations.empty?
slot.is_reserved = !user_slots_reservations.empty?
slot.title = slot_title(slots_reservations, user_slots_reservations, reservables)
slot.can_modify = true if %w[admin manager].include?(@current_user_role) || !user_slots_reservations.empty?
slot.current_user_slots_reservations_ids = user_slots_reservations.map(&:id)
@ -34,7 +34,7 @@ class Availabilities::StatusService
# check that the provided ability is reserved by the given user
def availability_reserved_status(availability, user, reservables)
unless reservables.map(&:class).map(&:name).reduce(:==)
if reservables.map(&:class).map(&:name).uniq.size > 1
raise TypeError('[Availabilities::StatusService#availability_reserved_status] reservables have differents types')
end
@ -46,7 +46,7 @@ class Availabilities::StatusService
user_slots_reservations = slots_reservations.where('reservations.statistic_profile_id': user&.statistic_profile&.id)
availability.is_reserved = !slots_reservations.empty?
availability.is_reserved = !user_slots_reservations.empty?
availability.current_user_slots_reservations_ids = user_slots_reservations.map(&:id)
availability
end

View File

@ -2,10 +2,10 @@
json.array!(@slots) do |slot|
json.partial! 'api/availabilities/slot', slot: slot, operator_role: @operator_role
json.borderColor trainings_events_border_color(slot)
json.borderColor trainings_events_border_color(slot.availability)
json.is_completed slot.full?
json.nb_total_places slot.nb_total_places
json.nb_total_places slot.availability.nb_total_places
json.training do
json.id slot.availability.trainings.first.id

View File

@ -1373,8 +1373,8 @@ en:
visibility_yearly: "maximum visibility for annual subscribers"
visibility_others: "maximum visibility for other members"
display: "Display"
display_name_info_html: "When enabled, members and visitors browsing the calendar or booking a machine will see the name of the members who has booked some slots. When disabled, only administrators and managers will view the names.<br/><strong>Warning:</strong> if you enable this feature, remember to write it in your privacy policy."
display_machine_reservation_user_name: "Display the full name of the user who booked a machine slot"
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"
display_name: "Display the name"
display_name_enable: "name display"
events_in_the_calendar: "Display the events in the calendar"

View File

@ -13,28 +13,28 @@ class AvailabilitiesServiceTest < ActiveSupport::TestCase
test 'no machines availabilities during given window' do
service = Availabilities::AvailabilitiesService.new(@no_subscription)
slots = service.machines(Machine.find(3), @no_subscription, { start: DateTime.current.beginning_of_day, end: 1.day.from_now.end_of_day })
slots = service.machines([Machine.find(3)], @no_subscription, { start: DateTime.current.beginning_of_day, end: 1.day.from_now.end_of_day })
assert_empty slots
end
test 'no machines availabilities for user tags' do
service = Availabilities::AvailabilitiesService.new(@no_subscription)
slots = service.machines(Machine.find(3), @no_subscription, { start: 2.days.from_now.beginning_of_day, end: 4.days.from_now.end_of_day })
slots = service.machines([Machine.find(3)], @no_subscription, { start: 2.days.from_now.beginning_of_day, end: 4.days.from_now.end_of_day })
assert_empty slots
end
test 'no past availabilities for members' do
service = Availabilities::AvailabilitiesService.new(@no_subscription)
slots = service.machines(Machine.find(2), @no_subscription, { start: DateTime.parse('2015-06-15').beginning_of_day, end: DateTime.parse('2015-06-15').end_of_day })
slots = service.machines([Machine.find(2)], @no_subscription, { start: DateTime.parse('2015-06-15').beginning_of_day, end: DateTime.parse('2015-06-15').end_of_day })
assert_empty slots
end
test 'admin cannot see past availabilities further than 1 month' do
service = Availabilities::AvailabilitiesService.new(@admin)
slots = service.machines(Machine.find(2), @no_subscription, { start: DateTime.parse('2015-06-15').beginning_of_day, end: DateTime.parse('2015-06-15').end_of_day })
slots = service.machines([Machine.find(2)], @no_subscription, { start: DateTime.parse('2015-06-15').beginning_of_day, end: DateTime.parse('2015-06-15').end_of_day })
assert_empty slots
end
@ -51,7 +51,7 @@ class AvailabilitiesServiceTest < ActiveSupport::TestCase
test 'machines availabilities' do
service = Availabilities::AvailabilitiesService.new(@no_subscription)
slots = service.machines(Machine.find(1), @no_subscription, { start: 2.days.from_now.beginning_of_day, end: 4.days.from_now.end_of_day })
slots = service.machines([Machine.find(1)], @no_subscription, { start: 2.days.from_now.beginning_of_day, end: 4.days.from_now.end_of_day })
assert_not_empty slots
assert_equal Availability.find(7).slots.count, slots.count
@ -61,7 +61,7 @@ class AvailabilitiesServiceTest < ActiveSupport::TestCase
test 'spaces availabilities' do
service = Availabilities::AvailabilitiesService.new(@no_subscription)
slots = service.spaces(Space.find(1), @no_subscription, { start: 2.days.from_now.beginning_of_day, end: 4.days.from_now.end_of_day })
slots = service.spaces([Space.find(1)], @no_subscription, { start: 2.days.from_now.beginning_of_day, end: 4.days.from_now.end_of_day })
assert_not_empty slots
assert_equal Availability.find(18).slots.count, slots.count