mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-19 13:54:25 +01:00
(feat) slots/availabilities status for pending reservations
This commit is contained in:
parent
6433767846
commit
9f9a2e616f
@ -37,7 +37,7 @@ 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, :can_modify
|
||||
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? }
|
||||
|
@ -1,11 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Items that can be added to the shopping cart
|
||||
module CartItem
|
||||
def self.table_name_prefix
|
||||
'cart_item_'
|
||||
end
|
||||
end
|
||||
require_relative 'cart_item'
|
||||
|
||||
# This is an abstract class implemented by classes that can be added to the shopping cart
|
||||
class CartItem::BaseItem < ApplicationRecord
|
||||
|
8
app/models/cart_item/cart_item.rb
Normal file
8
app/models/cart_item/cart_item.rb
Normal file
@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Items that can be added to the shopping cart
|
||||
module CartItem
|
||||
def self.table_name_prefix
|
||||
'cart_item_'
|
||||
end
|
||||
end
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'cart_item'
|
||||
|
||||
# A discount coupon applied to the whole shopping cart
|
||||
class CartItem::Coupon < ApplicationRecord
|
||||
belongs_to :operator_profile, class_name: 'InvoicingProfile'
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'cart_item'
|
||||
|
||||
# A relation table between a pending event reservation and a special price for this event
|
||||
class CartItem::EventReservationTicket < ApplicationRecord
|
||||
belongs_to :cart_item_event_reservation, class_name: 'CartItem::EventReservation', inverse_of: :cart_item_event_reservation_tickets
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'cart_item'
|
||||
|
||||
# A payment schedule applied to plan in the shopping cart
|
||||
class CartItem::PaymentSchedule < ApplicationRecord
|
||||
belongs_to :customer_profile, class_name: 'InvoicingProfile'
|
||||
|
@ -1,8 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'cart_item'
|
||||
|
||||
# A relation table between a pending reservation and a slot
|
||||
class CartItem::ReservationSlot < ApplicationRecord
|
||||
self.table_name = 'cart_item_reservation_slots'
|
||||
|
||||
belongs_to :cart_item, polymorphic: true
|
||||
belongs_to :cart_item_machine_reservation, foreign_type: 'CartItem::MachineReservation', foreign_key: 'cart_item_id',
|
||||
inverse_of: :cart_item_reservation_slots, class_name: 'CartItem::MachineReservation'
|
||||
belongs_to :cart_item_space_reservation, foreign_type: 'CartItem::SpaceReservation', foreign_key: 'cart_item_id',
|
||||
inverse_of: :cart_item_reservation_slots, class_name: 'CartItem::SpaceReservation'
|
||||
belongs_to :cart_item_training_reservation, foreign_type: 'CartItem::TrainingReservation', foreign_key: 'cart_item_id',
|
||||
inverse_of: :cart_item_reservation_slots, class_name: 'CartItem::TrainingReservation'
|
||||
belongs_to :cart_item_event_reservation, foreign_type: 'CartItem::EventReservation', foreign_key: 'cart_item_id',
|
||||
inverse_of: :cart_item_reservation_slots, class_name: 'CartItem::EventReservation'
|
||||
|
||||
belongs_to :slot
|
||||
belongs_to :slots_reservation
|
||||
|
@ -12,7 +12,7 @@ class Slot < ApplicationRecord
|
||||
|
||||
has_many :cart_item_reservation_slots, class_name: 'CartItem::ReservationSlot', dependent: :destroy
|
||||
|
||||
attr_accessor :is_reserved, :machine, :space, :title, :can_modify, :current_user_slots_reservations_ids
|
||||
attr_accessor :is_reserved, :machine, :space, :title, :can_modify, :current_user_slots_reservations_ids, :current_user_pending_reservations_ids
|
||||
|
||||
def full?(reservable = nil)
|
||||
availability_places = availability.available_places_per_slot(reservable)
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
# List all Availability's slots for the given resources
|
||||
class Availabilities::AvailabilitiesService
|
||||
# @param current_user [User]
|
||||
# @param level [String]
|
||||
def initialize(current_user, level = 'slot')
|
||||
@current_user = current_user
|
||||
@maximum_visibility = {
|
||||
@ -13,6 +15,11 @@ class Availabilities::AvailabilitiesService
|
||||
@level = level
|
||||
end
|
||||
|
||||
# @param window [Hash] the time window the look through: {start: xxx, end: xxx}
|
||||
# @option window [ActiveSupport::TimeWithZone] :start
|
||||
# @option window [ActiveSupport::TimeWithZone] :end
|
||||
# @param ids [Array<Integer>]
|
||||
# @param events [Boolean] should events be included in the results?
|
||||
def index(window, ids, events: false)
|
||||
machines_availabilities = Setting.get('machines_module') ? machines(Machine.where(id: ids[:machines]), @current_user, window) : []
|
||||
spaces_availabilities = Setting.get('spaces_module') ? spaces(Space.where(id: ids[:spaces]), @current_user, window) : []
|
||||
@ -27,6 +34,11 @@ class Availabilities::AvailabilitiesService
|
||||
end
|
||||
|
||||
# list all slots for the given machines, with visibility relative to the given user
|
||||
# @param machines [ActiveRecord::Relation<Machine>]
|
||||
# @param user [User]
|
||||
# @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])
|
||||
@ -41,6 +53,11 @@ class Availabilities::AvailabilitiesService
|
||||
end
|
||||
|
||||
# list all slots for the given space, with visibility relative to the given user
|
||||
# @param spaces [ActiveRecord::Relation<Space>]
|
||||
# @param user [User]
|
||||
# @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)
|
||||
sp_availabilities = Availability.includes('spaces_availabilities')
|
||||
.where('spaces_availabilities.space_id': spaces.map(&:id))
|
||||
@ -54,6 +71,11 @@ class Availabilities::AvailabilitiesService
|
||||
end
|
||||
|
||||
# list all slots for the given training(s), with visibility relative to the given user
|
||||
# @param trainings [ActiveRecord::Relation<Training>]
|
||||
# @param user [User]
|
||||
# @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)
|
||||
tr_availabilities = Availability.includes('trainings_availabilities')
|
||||
.where('trainings_availabilities.training_id': trainings.map(&:id))
|
||||
@ -67,6 +89,11 @@ class Availabilities::AvailabilitiesService
|
||||
end
|
||||
|
||||
# list all slots for the given event(s), with visibility relative to the given user
|
||||
# @param events [ActiveRecord::Relation<Event>]
|
||||
# @param user [User]
|
||||
# @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)
|
||||
ev_availabilities = Availability.includes('event').where('events.id': events.map(&:id))
|
||||
availabilities = availabilities(ev_availabilities, 'event', user, window[:start], window[:end])
|
||||
@ -80,6 +107,7 @@ class Availabilities::AvailabilitiesService
|
||||
|
||||
private
|
||||
|
||||
# @param user [User]
|
||||
def subscription_year?(user)
|
||||
user&.subscription && user.subscription.plan.interval == 'year' && user.subscription.expired_at >= Time.current
|
||||
end
|
||||
@ -87,10 +115,17 @@ class Availabilities::AvailabilitiesService
|
||||
# members must have validated at least 1 training and must have a valid yearly subscription to view
|
||||
# the trainings further in the futur. This is used to prevent users with a rolling subscription to take
|
||||
# their first training in a very long delay.
|
||||
# @param user [User]
|
||||
def show_more_trainings?(user)
|
||||
user&.trainings&.size&.positive? && subscription_year?(user)
|
||||
end
|
||||
|
||||
# @param availabilities [ActiveRecord::Relation<Availability>]
|
||||
# @param type [String]
|
||||
# @param user [User]
|
||||
# @param range_start [ActiveSupport::TimeWithZone]
|
||||
# @param range_end [ActiveSupport::TimeWithZone]
|
||||
# @return ActiveRecord::Relation<Availability>
|
||||
def availabilities(availabilities, type, user, range_start, range_end)
|
||||
# who made the request?
|
||||
# 1) an admin (he can see all availabilities from 1 month ago to anytime in the future)
|
||||
|
@ -9,62 +9,121 @@ class Availabilities::StatusService
|
||||
|
||||
# check that the provided slot is reserved for the given reservable (machine, training or space).
|
||||
# Mark it accordingly for display in the calendar
|
||||
# @param slot [Slot]
|
||||
# @param user [User]
|
||||
# @param reservables [Array<Machine, Space, Training, Event>]
|
||||
# @return [Slot]
|
||||
def slot_reserved_status(slot, user, reservables)
|
||||
if reservables.map(&:class).map(&:name).uniq.size > 1
|
||||
raise TypeError('[Availabilities::StatusService#slot_reserved_status] reservables have differents types')
|
||||
raise TypeError('[Availabilities::StatusService#slot_reserved_status] reservables have differents types: ' \
|
||||
"#{reservables.map(&:class).map(&:name).uniq} , with slot #{slot.id}")
|
||||
end
|
||||
|
||||
statistic_profile_id = user&.statistic_profile&.id
|
||||
slots_reservations, user_slots_reservations = slots_reservations(slot.slots_reservations, reservables, user)
|
||||
|
||||
slots_reservations = slot.slots_reservations
|
||||
.includes(:reservation)
|
||||
.where('reservations.reservable_type': reservables.map(&:class).map(&:name))
|
||||
.where('reservations.reservable_id': reservables.map(&:id))
|
||||
.where('slots_reservations.canceled_at': nil)
|
||||
pending_reserv_slot_ids = slot.cart_item_reservation_slots.select('id').map(&:id)
|
||||
pending_reservations, user_pending_reservations = pending_reservations(pending_reserv_slot_ids, reservables, user)
|
||||
|
||||
user_slots_reservations = slots_reservations.where('reservations.statistic_profile_id': statistic_profile_id)
|
||||
|
||||
slot.is_reserved = !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)
|
||||
is_reserved = slots_reservations.count.positive? || pending_reservations.count.positive?
|
||||
slot.is_reserved = is_reserved
|
||||
slot.title = slot_title(slots_reservations, user_slots_reservations, user_pending_reservations, reservables)
|
||||
slot.can_modify = true if %w[admin manager].include?(@current_user_role) || is_reserved
|
||||
slot.current_user_slots_reservations_ids = user_slots_reservations.select('id').map(&:id)
|
||||
slot.current_user_pending_reservations_ids = user_pending_reservations.select('id').map(&:id)
|
||||
|
||||
slot
|
||||
end
|
||||
|
||||
# check that the provided ability is reserved by the given user
|
||||
# @param availability [Availability]
|
||||
# @param user [User]
|
||||
# @param reservables [Array<Machine, Space, Training, Event>]
|
||||
# @return [Availability]
|
||||
def availability_reserved_status(availability, user, reservables)
|
||||
if reservables.map(&:class).map(&:name).uniq.size > 1
|
||||
raise TypeError('[Availabilities::StatusService#availability_reserved_status] reservables have differents types')
|
||||
raise TypeError('[Availabilities::StatusService#availability_reserved_status] reservables have differents types: ' \
|
||||
"#{reservables.map(&:class).map(&:name).uniq}, with availability #{availability.id}")
|
||||
end
|
||||
|
||||
slots_reservations = availability.slots_reservations
|
||||
.includes(:reservation)
|
||||
.where('reservations.reservable_type': reservables.map(&:class).map(&:name))
|
||||
.where('reservations.reservable_id': reservables.map(&:id))
|
||||
.where('slots_reservations.canceled_at': nil)
|
||||
slots_reservations, user_slots_reservations = slots_reservations(availability.slots_reservations, reservables, user)
|
||||
|
||||
user_slots_reservations = slots_reservations.where('reservations.statistic_profile_id': user&.statistic_profile&.id)
|
||||
pending_reserv_slot_ids = availability.joins(slots: :cart_item_reservation_slots)
|
||||
.select('cart_item_reservation_slots.id as cirs_id')
|
||||
pending_reservations, user_pending_reservations = pending_reservations(pending_reserv_slot_ids, reservables, user)
|
||||
|
||||
availability.is_reserved = !slots_reservations.empty?
|
||||
availability.current_user_slots_reservations_ids = user_slots_reservations.map(&:id)
|
||||
availability.is_reserved = slots_reservations.count.positive? || pending_reservations.count.positive?
|
||||
availability.current_user_slots_reservations_ids = user_slots_reservations.select('id').map(&:id)
|
||||
availability.current_user_pending_reservations_ids = user_pending_reservations.select('id').map(&:id)
|
||||
availability
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def slot_title(slots_reservations, user_slots_reservations, reservables)
|
||||
# @param slots_reservations [ActiveRecord::Relation<SlotsReservation>]
|
||||
# @param user_slots_reservations [ActiveRecord::Relation<SlotsReservation>] same as slots_reservations but filtered by the current user
|
||||
# @param user_pending_reservations [ActiveRecord::Relation<CartItem::ReservationSlot>]
|
||||
# @param reservables [Array<Machine, Space, Training, Event>]
|
||||
def slot_title(slots_reservations, user_slots_reservations, user_pending_reservations, reservables)
|
||||
name = reservables.map(&:name).join(', ')
|
||||
if user_slots_reservations.empty? && slots_reservations.empty?
|
||||
if user_slots_reservations.count.zero? && slots_reservations.count.zero?
|
||||
name
|
||||
elsif user_slots_reservations.empty? && !slots_reservations.empty?
|
||||
user_names = slots_reservations.map(&:reservation)
|
||||
.map(&:user)
|
||||
.map { |u| u&.profile&.full_name || I18n.t('availabilities.deleted_user') }
|
||||
.join(', ')
|
||||
"#{name} #{@show_name ? "- #{user_names}" : ''}"
|
||||
elsif user_slots_reservations.count.zero? && slots_reservations.count.positive?
|
||||
"#{name} #{@show_name ? "- #{slot_users_names(slots_reservations)}" : ''}"
|
||||
elsif user_pending_reservations.count.positive?
|
||||
"#{name} - #{I18n.t('availabilities.reserving')}"
|
||||
else
|
||||
"#{name} - #{I18n.t('availabilities.i_ve_reserved')}"
|
||||
end
|
||||
end
|
||||
|
||||
# @param slots_reservations [ActiveRecord::Relation<SlotsReservation>]
|
||||
# @return [String]
|
||||
def slot_users_names(slots_reservations)
|
||||
slots_reservations.map(&:reservation)
|
||||
.map(&:user)
|
||||
.map { |u| u&.profile&.full_name || I18n.t('availabilities.deleted_user') }
|
||||
.join(', ')
|
||||
end
|
||||
|
||||
# @param slot_ids [Array<number>]
|
||||
# @param reservables [Array<Machine, Space, Training, Event>]
|
||||
# @param user [User]
|
||||
# @return [Array<ActiveRecord::Relation<CartItem::ReservationSlot>>]
|
||||
def pending_reservations(slot_ids, reservables, user)
|
||||
reservable_types = reservables.map(&:class).map(&:name).uniq
|
||||
if reservable_types.size > 1
|
||||
raise TypeError("[Availabilities::StatusService#pending_reservations] reservables have differents types: #{reservable_types}")
|
||||
end
|
||||
|
||||
relation = "cart_item_#{reservable_types.first&.downcase}_reservation"
|
||||
table = reservable_types.first == 'Event' ? 'cart_item_event_reservations' : 'cart_item_reservations'
|
||||
pending_reservations = CartItem::ReservationSlot.where(id: slot_ids)
|
||||
.includes(relation.to_sym)
|
||||
.where(table => { reservable_type: reservable_types })
|
||||
.where(table => { reservable_id: reservables.map(&:id) })
|
||||
|
||||
user_pending_reservations = pending_reservations.where(table => { customer_profile_id: user&.invoicing_profile&.id })
|
||||
|
||||
[pending_reservations, user_pending_reservations]
|
||||
end
|
||||
|
||||
# @param slots_reservations [ActiveRecord::Relation<SlotsReservation>]
|
||||
# @param reservables [Array<Machine, Space, Training, Event>]
|
||||
# @param user [User]
|
||||
# @return [Array<ActiveRecord::Relation<SlotsReservation>>]
|
||||
def slots_reservations(slots_reservations, reservables, user)
|
||||
reservable_types = reservables.map(&:class).map(&:name).uniq
|
||||
if reservable_types.size > 1
|
||||
raise TypeError("[Availabilities::StatusService#slot_reservations] reservables have differents types: #{reservable_types}")
|
||||
end
|
||||
|
||||
reservations = slots_reservations.includes(:reservation)
|
||||
.where('reservations.reservable_type': reservable_types)
|
||||
.where('reservations.reservable_id': reservables.map(&:id))
|
||||
.where('slots_reservations.canceled_at': nil)
|
||||
|
||||
user_slots_reservations = reservations.where('reservations.statistic_profile_id': user&.statistic_profile&.id)
|
||||
|
||||
[reservations, user_slots_reservations]
|
||||
end
|
||||
end
|
||||
|
@ -63,6 +63,7 @@ en:
|
||||
#availability slots in the calendar
|
||||
availabilities:
|
||||
not_available: "Not available"
|
||||
reserving: "I'm reserving"
|
||||
i_ve_reserved: "I've reserved"
|
||||
length_must_be_slot_multiple: "must be at least %{MIN} minutes after the start date"
|
||||
must_be_associated_with_at_least_1_machine: "must be associated with at least 1 machine"
|
||||
|
Loading…
x
Reference in New Issue
Block a user