1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-11-28 09:24:24 +01:00

(wip) move slot status request

This commit is contained in:
Sylvain 2023-01-18 17:44:58 +01:00
parent 986a663c40
commit d0239a0e25
11 changed files with 141 additions and 59 deletions

View File

@ -29,6 +29,7 @@ class API::AvailabilitiesController < API::ApiController
{ machines: machine_ids, spaces: params[:s], trainings: params[:t] },
events: (params[:evt] && params[:evt] == 'true')
)
@user = current_user
@title_filter = { machine_ids: machine_ids.map(&:to_i) }
@availabilities = filter_availabilites(@availabilities)

View File

@ -37,8 +37,6 @@ class Availability < ApplicationRecord
scope :trainings, -> { includes(:trainings).where(available_type: 'training') }
scope :spaces, -> { includes(:spaces).where(available_type: 'space') }
attr_accessor :is_reserved, :current_user_slots_reservations_ids, :current_user_pending_reservations_ids, :can_modify
validates :start_at, :end_at, presence: true
validate :length_must_be_slot_multiple, unless: proc { end_at.blank? or start_at.blank? }
validate :should_be_associated
@ -114,6 +112,7 @@ class Availability < ApplicationRecord
# check if the reservations are complete?
# if a nb_total_places hasn't been defined, then places are unlimited
# @return [Boolean]
def full?
return false if nb_total_places.blank? && available_type != 'machines'
@ -124,6 +123,15 @@ class Availability < ApplicationRecord
end
end
# @return [Array<Integer>] Collection of User's IDs
def reserved_users
slots.map(&:reserved_users).flatten
end
def reserved?
slots.map(&:reserved?).reduce(:&)
end
# check availability don't have any reservation
def empty?
slots.map(&:empty?).reduce(:&)
@ -140,7 +148,7 @@ class Availability < ApplicationRecord
when 'machines'
reservable.nil? ? machines.count : 1
else
raise TypeError
raise TypeError, "unknown available type #{available_type} for availability #{id}"
end
end

View File

@ -16,29 +16,56 @@ class Slot < ApplicationRecord
attr_accessor :is_reserved, :machine, :space, :title, :can_modify, :current_user_slots_reservations_ids, :current_user_pending_reservations_ids
# @param reservable [Machine,Space,Training,Event,NilClass]
# @return [Integer] the total number of reserved places
def reserved_places(reservable = nil)
if reservable.nil?
places.pluck('reserved_places').reduce(:+)
else
places.detect { |p| p['reservable_type'] == reservable.class.name && p['reservable_id'] == reservable.id }['reserved_places']
end
end
# @param reservables [Array<Machine,Space,Training,Event>,NilClass]
# @return [Array<Integer>] Collection of User's IDs
def reserved_users(reservables = nil)
if reservable.nil?
places.pluck('user_ids').flatten
else
r_places = places.select do |p|
reservables.any? { |r| r.class.name == p['reservable_type'] && r.id == p['reservable_id'] } # rubocop:disable Style/ClassEqualityComparison
end
r_places.pluck('user_ids').flatten
end
end
# @param reservable [Machine, Space, Training, Event, NilClass]
# @return [Boolean] enough reservation to fill the whole slot?
def full?(reservable = nil)
availability_places = availability.available_places_per_slot(reservable)
return false if availability_places.nil?
reserved_places = if reservable.nil?
places.pluck('reserved_places').reduce(:+)
else
rp = places.detect do |p|
p['reservable_type'] == reservable.class.name && p['reservable_id'] == reservable&.id
end
rp['reserved_places']
end
reserved_places >= availability_places
reserved_places(reservable) >= availability_places
end
# @param reservable [Machine,Space,Training,Event,NilClass]
# @return [Boolean] any reservation or none?
def reserved?(reservable = nil)
reserved_places(reservable).positive?
end
# @param reservable [Machine,Space,Training,Event,NilClass]
# @return [Boolean] no reservations at all?
def empty?(reservable = nil)
if reservable.nil?
slots_reservations.where(canceled_at: nil).count.zero?
else
slots_reservations.includes(:reservation).where(canceled_at: nil).where('reservations.reservable': reservable).count.zero?
end
reserved_places(reservable).zero?
end
# @param operator_role [String,NilClass] 'admin' | 'manager' | 'member'
# @param user_id [Integer]
# @param reservable [Machine,Space,Training,Event,NilClass]
# @return [Boolean] the reservation on this slot can be modified?
def modifiable?(operator_role, user_id, reservable = nil)
%w[admin manager].include?(operator_role) || reserved_users([reservable]).include?(user_id)
end
def duration

View File

@ -3,7 +3,7 @@
# List all Availability's slots for the given resources
class Availabilities::AvailabilitiesService
# @param current_user [User]
# @param level [String]
# @param level [String] 'slot' | 'availability'
def initialize(current_user, level = 'slot')
@current_user = current_user
@maximum_visibility = {
@ -39,16 +39,16 @@ class Availabilities::AvailabilitiesService
# @param window [Hash] the time window the look through: {start: xxx, end: xxx}
# @option window [ActiveSupport::TimeWithZone] :start the beginning of the time window
# @option window [ActiveSupport::TimeWithZone] :end the end of the time window
def machines(machines, user, window)
ma_availabilities = Availability.includes(:machines_availabilities, :availability_tags, :machines, :slots_reservations,
slots: [:slots_reservations])
# @param no_status [Boolean] should the availability/slot reservation status be computed?
def machines(machines, user, window, no_status: false)
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, (machines & s.availability.machines)) }
availabilities.map(&:slots).flatten.map { |s| no_status ? s : @service.slot_reserved_status(s, user, (machines & s.availability.machines)) }
else
availabilities.map { |a| @service.availability_reserved_status(a, user, (machines & a.machines)) }
no_status ? availabilities : availabilities.map { |a| @service.availability_reserved_status(a, user, (machines & a.machines)) }
end
end
@ -58,15 +58,16 @@ class Availabilities::AvailabilitiesService
# @param window [Hash] the time window the look through: {start: xxx, end: xxx}
# @option window [ActiveSupport::TimeWithZone] :start
# @option window [ActiveSupport::TimeWithZone] :end
def spaces(spaces, user, window)
# @param no_status [Boolean] should the availability/slot reservation status be computed?
def spaces(spaces, user, window, no_status: false)
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, (spaces & s.availability.spaces)) }
availabilities.map(&:slots).flatten.map { |s| no_status ? s : @service.slot_reserved_status(s, user, (spaces & s.availability.spaces)) }
else
availabilities.map { |a| @service.availability_reserved_status(a, user, (spaces & a.spaces)) }
no_status ? availabilities : availabilities.map { |a| @service.availability_reserved_status(a, user, (spaces & a.spaces)) }
end
end
@ -76,15 +77,15 @@ class Availabilities::AvailabilitiesService
# @param window [Hash] the time window the look through: {start: xxx, end: xxx}
# @option window [ActiveSupport::TimeWithZone] :start
# @option window [ActiveSupport::TimeWithZone] :end
def trainings(trainings, user, window)
def trainings(trainings, user, window, no_status: false)
tr_availabilities = Availability.includes('trainings_availabilities')
.where('trainings_availabilities.training_id': trainings.map(&:id))
availabilities = availabilities(tr_availabilities, 'training', user, window[:start], window[:end])
if @level == 'slot'
availabilities.map(&:slots).flatten.map { |s| @service.slot_reserved_status(s, user, (trainings & s.availability.trainings)) }
availabilities.map(&:slots).flatten.map { |s| no_status ? s : @service.slot_reserved_status(s, user, (trainings & s.availability.trainings)) }
else
availabilities.map { |a| @service.availability_reserved_status(a, user, (trainings & a.trainings)) }
no_status ? availabilities : availabilities.map { |a| @service.availability_reserved_status(a, user, (trainings & a.trainings)) }
end
end
@ -94,18 +95,18 @@ class Availabilities::AvailabilitiesService
# @param window [Hash] the time window the look through: {start: xxx, end: xxx}
# @option window [ActiveSupport::TimeWithZone] :start
# @option window [ActiveSupport::TimeWithZone] :end
def events(events, user, window)
def events(events, user, window, no_status: false)
ev_availabilities = Availability.includes('event').where('events.id': events.map(&:id))
availabilities = availabilities(ev_availabilities, 'event', user, window[:start], window[:end])
if @level == 'slot'
availabilities.map(&:slots).flatten.map { |s| @service.slot_reserved_status(s, user, [s.availability.event]) }
availabilities.map(&:slots).flatten.map { |s| no_status ? s : @service.slot_reserved_status(s, user, [s.availability.event]) }
else
availabilities.map { |a| @service.availability_reserved_status(a, user, [a.event]) }
no_status ? availabilities : availabilities.map { |a| @service.availability_reserved_status(a, user, [a.event]) }
end
end
private
protected
# @param user [User]
def subscription_year?(user)
@ -131,7 +132,7 @@ class Availabilities::AvailabilitiesService
# 1) an admin (he can see all availabilities from 1 month ago to anytime in the future)
if @current_user&.admin? || @current_user&.manager?
window_start = [range_start, 1.month.ago].max
availabilities.includes(:tags, :plans, :slots)
availabilities.includes(:tags, :slots)
.joins(:slots)
.where('availabilities.start_at <= ? AND availabilities.end_at >= ? AND available_type = ?', range_end, window_start, type)
.where('slots.start_at > ? AND slots.end_at < ?', window_start, range_end)
@ -143,7 +144,7 @@ class Availabilities::AvailabilitiesService
end_at = @maximum_visibility[:year] if show_more_trainings?(user) && type == 'training'
window_end = [end_at, range_end].min
window_start = [range_start, @minimum_visibility].max
availabilities.includes(:tags, :plans, :slots)
availabilities.includes(:tags, :slots)
.joins(:slots)
.where('availabilities.start_at <= ? AND availabilities.end_at >= ? AND available_type = ?', window_end, window_start, type)
.where('slots.start_at > ? AND slots.end_at < ?', window_start, window_end)

View File

@ -11,10 +11,18 @@ class Availabilities::PublicAvailabilitiesService
level = in_same_day(window[:start], window[:end]) ? 'slot' : 'availability'
service = Availabilities::AvailabilitiesService.new(@current_user, level)
machines_slots = Setting.get('machines_module') ? service.machines(Machine.where(id: ids[:machines]), @current_user, window) : []
spaces_slots = Setting.get('spaces_module') ? service.spaces(Space.where(id: ids[:spaces]), @current_user, window) : []
trainings_slots = Setting.get('trainings_module') ? service.trainings(Training.where(id: ids[:trainings]), @current_user, window) : []
events_slots = events ? service.events(Event.all, @current_user, window) : []
machines_slots = if Setting.get('machines_module')
service.machines(Machine.where(id: ids[:machines]), @current_user, window, no_status: true)
else
[]
end
spaces_slots = Setting.get('spaces_module') ? service.spaces(Space.where(id: ids[:spaces]), @current_user, window, no_status: true) : []
trainings_slots = if Setting.get('trainings_module')
service.trainings(Training.where(id: ids[:trainings]), @current_user, window, no_status: true)
else
[]
end
events_slots = events ? service.events(Event.all, @current_user, window, no_status: true) : []
[].concat(trainings_slots).concat(events_slots).concat(machines_slots).concat(spaces_slots)
end

View File

@ -1,5 +1,5 @@
# frozen_string_literal: true
# TODO, remove this
# Provides helper methods checking reservation status of any availabilities
class Availabilities::StatusService
# @param current_user_role [String]
@ -22,7 +22,7 @@ class Availabilities::StatusService
places = places(slot, reservables)
is_reserved = places.any? { |p| p['reserved_places'].positive? }
is_reserved_by_user = is_reserved && places.select { |p| p['user_ids'].include?(user.id) }.length.positive?
is_reserved_by_user = is_reserved && user && places.select { |p| p['user_ids'].include?(user.id) }.length.positive?
slot.is_reserved = is_reserved
slot.title = slot_title(slot, is_reserved, is_reserved_by_user, reservables)
slot.can_modify = true if %w[admin manager].include?(@current_user_role) || is_reserved

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
# Build the title of the provided slot
class Slots::TitleService
def initialize(operator_role, user)
@user = user
@show_name = (%w[admin manager].include?(operator_role) || (operator_role && Setting.get('display_name_enable')))
end
# @param slot [Slot]
# @param reservables [Array<Machine, Space, Training, Event>]
def slot_title(slot, reservables)
is_reserved = slot.reserved?
is_reserved_by_user = slot.reserved_users(reservables).include?(@user.id)
name = reservables.map(&:name).join(', ')
if !is_reserved && !is_reserved_by_user
name
elsif is_reserved && !is_reserved_by_user
"#{name} #{@show_name ? "- #{Slots::TitleService.slot_users_names(slot, reservables)}" : ''}"
else
"#{name} - #{I18n.t('availabilities.i_ve_reserved')}"
end
end
private
# @param slot [Slot]
# @param reservables [Array<Machine, Space, Training, Event>]
# @return [String]
def slot_users_names(slot, reservables)
user_ids = slot.reserved_users(reservables)
User.where(id: user_ids).includes(:profile)
.map { |u| u&.profile&.full_name || I18n.t('availabilities.deleted_user') }
.join(', ')
end
end

View File

@ -1,16 +1,16 @@
# frozen_string_literal: true
json.slot_id slot.id
json.can_modify slot.can_modify
json.title slot.title
json.can_modify slot.modifiable?(operator_role, @user.id, reservable)
json.title Slots::TitleService.new(operator_role, @user).slot_title(slot, [reservable])
json.start slot.start_at.iso8601
json.end slot.end_at.iso8601
json.is_reserved slot.is_reserved
json.is_reserved slot.reserved?(reservable)
json.is_completed slot.full?(reservable)
json.backgroundColor 'white'
json.availability_id slot.availability_id
json.slots_reservations_ids slot.current_user_slots_reservations_ids
json.slots_reservations_ids slot.current_user_slots_reservations_ids #TODO, move this out of attr_accessor
json.tag_ids slot.availability.tag_ids
json.tags slot.availability.tags do |t|

View File

@ -21,9 +21,9 @@ json.array!(@availabilities) do |availability|
end
json.is_completed availability.full?
json.is_reserved availability.is_reserved
json.is_reserved availability.reserved?
json.borderColor availability_border_color(availability)
if availability.is_reserved && !availability.current_user_slots_reservations_ids.empty?
if availability.reserved? && !@user.nil? && availability.reserved_users.include?(@user.id)
json.title "#{availability.title}' - #{t('trainings.i_ve_reserved')}"
elsif availability.full?
json.title "#{availability.title} - #{t('trainings.completed')}"
@ -38,7 +38,7 @@ json.array!(@availabilities) do |availability|
json.id t.id
json.name t.name
end
json.is_reserved availability.is_reserved
json.is_reserved availability.reserved?
json.is_completed availability.full?
case availability.availability.available_type
when 'machines'

View File

@ -19,8 +19,8 @@ ActiveRecord::Schema.define(version: 2023_01_31_104958) do
enable_extension "unaccent"
create_table "abuses", id: :serial, force: :cascade do |t|
t.string "signaled_type"
t.integer "signaled_id"
t.string "signaled_type"
t.string "first_name"
t.string "last_name"
t.string "email"
@ -68,8 +68,8 @@ ActiveRecord::Schema.define(version: 2023_01_31_104958) do
t.string "locality"
t.string "country"
t.string "postal_code"
t.string "placeable_type"
t.integer "placeable_id"
t.string "placeable_type"
t.datetime "created_at"
t.datetime "updated_at"
end
@ -93,8 +93,8 @@ ActiveRecord::Schema.define(version: 2023_01_31_104958) do
end
create_table "assets", id: :serial, force: :cascade do |t|
t.string "viewable_type"
t.integer "viewable_id"
t.string "viewable_type"
t.string "attachment"
t.string "type"
t.datetime "created_at"
@ -281,8 +281,8 @@ ActiveRecord::Schema.define(version: 2023_01_31_104958) do
end
create_table "credits", id: :serial, force: :cascade do |t|
t.string "creditable_type"
t.integer "creditable_id"
t.string "creditable_type"
t.integer "plan_id"
t.integer "hours"
t.datetime "created_at"
@ -524,15 +524,15 @@ ActiveRecord::Schema.define(version: 2023_01_31_104958) do
create_table "notifications", id: :serial, force: :cascade do |t|
t.integer "receiver_id"
t.string "attached_object_type"
t.integer "attached_object_id"
t.string "attached_object_type"
t.integer "notification_type_id"
t.boolean "is_read", default: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "receiver_type"
t.boolean "is_send", default: false
t.jsonb "meta_data", default: "{}"
t.jsonb "meta_data", default: {}
t.index ["notification_type_id"], name: "index_notifications_on_notification_type_id"
t.index ["receiver_id"], name: "index_notifications_on_receiver_id"
end
@ -772,8 +772,8 @@ ActiveRecord::Schema.define(version: 2023_01_31_104958) do
create_table "prices", id: :serial, force: :cascade do |t|
t.integer "group_id"
t.integer "plan_id"
t.string "priceable_type"
t.integer "priceable_id"
t.string "priceable_type"
t.integer "amount"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
@ -976,8 +976,8 @@ ActiveRecord::Schema.define(version: 2023_01_31_104958) do
t.text "message"
t.datetime "created_at"
t.datetime "updated_at"
t.string "reservable_type"
t.integer "reservable_id"
t.string "reservable_type"
t.integer "nb_reserve_places"
t.integer "statistic_profile_id"
t.index ["reservable_type", "reservable_id"], name: "index_reservations_on_reservable_type_and_reservable_id"
@ -986,8 +986,8 @@ ActiveRecord::Schema.define(version: 2023_01_31_104958) do
create_table "roles", id: :serial, force: :cascade do |t|
t.string "name"
t.string "resource_type"
t.integer "resource_id"
t.string "resource_type"
t.datetime "created_at"
t.datetime "updated_at"
t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id"

View File

@ -32,7 +32,7 @@ Especially, if you have an automatic redirection (e.g. from example.org to examp
Once you have reconfigured these variables, please switch back the active authentication provider to FabManager, restart the application, then delete the OIDC provider you configured and re-create a new one for the new settings to be used.
```
Unable to decode ID token
JSON::JWK::Set::KidNotFound (JSON::JWK::Set::KidNotFound)
```
This issue may occur if the ID Token signature algorithm is not set to `RSxxx` on your IDP.
Especially, this is not the default option when using LemonLDAP::NG, which uses `HSxxx` as the default algorithm, but you can configure it in `OpenID Connect Relaying Parties` > `my-fab-manager` > `Options` > `Security` > `ID Token signature algorithm`.