2019-06-06 16:34:53 +02:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
# Availability stores time slots that are available to reservation for an associated reservable
|
|
|
|
# Eg. a 3D printer will be reservable on thursday from 9 to 11 pm
|
|
|
|
# Availabilities may be subdivided into Slots (of 1h), for some types of reservables (eg. Machine)
|
2015-05-05 03:10:25 +02:00
|
|
|
class Availability < ActiveRecord::Base
|
2016-09-05 15:15:31 +02:00
|
|
|
# elastic initialisations
|
|
|
|
include Elasticsearch::Model
|
|
|
|
index_name 'fablab'
|
|
|
|
document_type 'availabilities'
|
|
|
|
|
2016-03-23 18:39:41 +01:00
|
|
|
has_many :machines_availabilities, dependent: :destroy
|
|
|
|
has_many :machines, through: :machines_availabilities
|
|
|
|
accepts_nested_attributes_for :machines, allow_destroy: true
|
|
|
|
|
|
|
|
has_many :trainings_availabilities, dependent: :destroy
|
|
|
|
has_many :trainings, through: :trainings_availabilities
|
|
|
|
|
2017-02-15 13:18:03 +01:00
|
|
|
has_many :spaces_availabilities, dependent: :destroy
|
|
|
|
has_many :spaces, through: :spaces_availabilities
|
|
|
|
|
2016-03-23 18:39:41 +01:00
|
|
|
has_many :slots
|
|
|
|
has_many :reservations, through: :slots
|
2015-05-05 03:10:25 +02:00
|
|
|
|
|
|
|
has_one :event
|
|
|
|
|
2016-03-23 18:39:41 +01:00
|
|
|
has_many :availability_tags, dependent: :destroy
|
|
|
|
has_many :tags, through: :availability_tags
|
|
|
|
accepts_nested_attributes_for :tags, allow_destroy: true
|
|
|
|
|
2020-02-07 17:37:00 +01:00
|
|
|
has_many :plans_availabilities, dependent: :destroy
|
|
|
|
has_many :plans, through: :plans_availabilities
|
|
|
|
accepts_nested_attributes_for :plans, allow_destroy: true
|
|
|
|
|
2016-03-23 18:39:41 +01:00
|
|
|
scope :machines, -> { where(available_type: 'machines') }
|
2016-06-22 12:54:12 +02:00
|
|
|
scope :trainings, -> { includes(:trainings).where(available_type: 'training') }
|
2017-02-15 13:18:03 +01:00
|
|
|
scope :spaces, -> { includes(:spaces).where(available_type: 'space') }
|
2016-03-23 18:39:41 +01:00
|
|
|
|
|
|
|
attr_accessor :is_reserved, :slot_id, :can_modify
|
|
|
|
|
2016-04-05 09:57:09 +02:00
|
|
|
validates :start_at, :end_at, presence: true
|
2019-11-13 12:13:22 +01:00
|
|
|
validate :length_must_be_slot_multiple, unless: proc { end_at.blank? or start_at.blank? }
|
2016-03-23 18:39:41 +01:00
|
|
|
validate :should_be_associated
|
|
|
|
|
2016-09-05 15:15:31 +02:00
|
|
|
## elastic callbacks
|
2019-06-13 16:29:12 +02:00
|
|
|
after_save { AvailabilityIndexerWorker.perform_async(:index, id) }
|
|
|
|
after_destroy { AvailabilityIndexerWorker.perform_async(:delete, id) }
|
2016-09-05 15:15:31 +02:00
|
|
|
|
|
|
|
# elastic mapping
|
2018-04-04 14:05:48 +02:00
|
|
|
settings index: { number_of_replicas: 0 } do
|
2016-09-05 15:15:31 +02:00
|
|
|
mappings dynamic: 'true' do
|
|
|
|
indexes 'available_type', analyzer: 'simple'
|
2017-01-03 17:07:23 +01:00
|
|
|
indexes 'subType', index: 'not_analyzed'
|
2016-09-05 15:15:31 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-03-23 18:39:41 +01:00
|
|
|
def safe_destroy
|
2017-02-15 13:18:03 +01:00
|
|
|
case available_type
|
2018-12-03 15:10:04 +01:00
|
|
|
when 'machines'
|
|
|
|
reservations = Reservation.where(reservable_type: 'Machine', reservable_id: machine_ids)
|
|
|
|
.joins(:slots)
|
|
|
|
.where('slots.availability_id = ?', id)
|
|
|
|
when 'training'
|
|
|
|
reservations = Reservation.where(reservable_type: 'Training', reservable_id: training_ids)
|
|
|
|
.joins(:slots)
|
|
|
|
.where('slots.availability_id = ?', id)
|
|
|
|
when 'space'
|
|
|
|
reservations = Reservation.where(reservable_type: 'Space', reservable_id: space_ids)
|
|
|
|
.joins(:slots)
|
|
|
|
.where('slots.availability_id = ?', id)
|
|
|
|
when 'event'
|
|
|
|
reservations = Reservation.where(reservable_type: 'Event', reservable_id: event&.id)
|
|
|
|
.joins(:slots)
|
|
|
|
.where('slots.availability_id = ?', id)
|
|
|
|
else
|
|
|
|
STDERR.puts "[safe_destroy] Availability with unknown type #{available_type}"
|
|
|
|
reservations = []
|
2016-03-23 18:39:41 +01:00
|
|
|
end
|
2018-12-03 15:10:04 +01:00
|
|
|
if reservations.size.zero?
|
2016-03-23 18:39:41 +01:00
|
|
|
# this update may not call any rails callbacks, that's why we use direct SQL update
|
|
|
|
update_column(:destroying, true)
|
|
|
|
destroy
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-02-28 16:51:56 +01:00
|
|
|
## compute the total number of places over the whole space availability
|
|
|
|
def available_space_places
|
2018-12-03 15:10:04 +01:00
|
|
|
return unless available_type == 'space'
|
|
|
|
|
2020-02-07 17:37:00 +01:00
|
|
|
((end_at - start_at) / ApplicationHelper::SLOT_DURATION.minutes).to_i * nb_total_places
|
2017-02-28 16:51:56 +01:00
|
|
|
end
|
|
|
|
|
2016-07-14 18:36:52 +02:00
|
|
|
def title(filter = {})
|
2017-02-15 13:18:03 +01:00
|
|
|
case available_type
|
2018-12-03 15:10:04 +01:00
|
|
|
when 'machines'
|
2019-06-06 16:34:53 +02:00
|
|
|
return machines.to_ary.delete_if { |m| !filter[:machine_ids].include?(m.id) }.map(&:name).join(' - ') if filter[:machine_ids]
|
2018-12-03 15:10:04 +01:00
|
|
|
|
|
|
|
machines.map(&:name).join(' - ')
|
|
|
|
when 'event'
|
|
|
|
event.name
|
|
|
|
when 'training'
|
|
|
|
trainings.map(&:name).join(' - ')
|
|
|
|
when 'space'
|
|
|
|
spaces.map(&:name).join(' - ')
|
|
|
|
else
|
|
|
|
STDERR.puts "[title] Availability with unknown type #{available_type}"
|
|
|
|
'???'
|
2016-03-23 18:39:41 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# return training reservations is complete?
|
|
|
|
# if haven't defined a nb_total_places, places are unlimited
|
2018-12-03 15:10:04 +01:00
|
|
|
def completed?
|
2016-03-23 18:39:41 +01:00
|
|
|
return false if nb_total_places.blank?
|
2018-12-03 15:10:04 +01:00
|
|
|
|
2017-02-15 13:18:03 +01:00
|
|
|
if available_type == 'training' || available_type == 'space'
|
2019-06-13 16:29:12 +02:00
|
|
|
nb_total_places <= slots.to_a.select { |s| s.canceled_at.nil? }.size
|
2016-06-29 17:37:22 +02:00
|
|
|
elsif available_type == 'event'
|
2018-12-03 15:10:04 +01:00
|
|
|
event.nb_free_places.zero?
|
2016-06-29 17:37:22 +02:00
|
|
|
end
|
2016-03-23 18:39:41 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def nb_total_places
|
2017-02-15 13:18:03 +01:00
|
|
|
case available_type
|
2018-12-03 15:10:04 +01:00
|
|
|
when 'training'
|
|
|
|
super.presence || trainings.map(&:nb_total_places).reduce(:+)
|
|
|
|
when 'event'
|
|
|
|
event.nb_total_places
|
|
|
|
when 'space'
|
|
|
|
super.presence || spaces.map(&:default_places).reduce(:+)
|
|
|
|
else
|
|
|
|
nil
|
2016-03-23 18:39:41 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-06-13 16:29:12 +02:00
|
|
|
# the resulting JSON will be indexed in ElasticSearch, as /fablab/availabilities
|
2016-09-06 16:32:41 +02:00
|
|
|
def as_indexed_json
|
|
|
|
json = JSON.parse(to_json)
|
2017-01-03 12:07:16 +01:00
|
|
|
json['hours_duration'] = (end_at - start_at) / (60 * 60)
|
2018-12-03 15:10:04 +01:00
|
|
|
json['subType'] = case available_type
|
|
|
|
when 'machines'
|
2019-06-06 16:34:53 +02:00
|
|
|
machines_availabilities.map { |ma| ma.machine.friendly_id }
|
2018-12-03 15:10:04 +01:00
|
|
|
when 'training'
|
2019-06-06 16:34:53 +02:00
|
|
|
trainings_availabilities.map { |ta| ta.training.friendly_id }
|
2018-12-03 15:10:04 +01:00
|
|
|
when 'event'
|
|
|
|
[event.category.friendly_id]
|
|
|
|
when 'space'
|
2019-06-06 16:34:53 +02:00
|
|
|
spaces_availabilities.map { |sa| sa.space.friendly_id }
|
2018-12-03 15:10:04 +01:00
|
|
|
else
|
|
|
|
[]
|
|
|
|
end
|
2017-01-03 17:07:23 +01:00
|
|
|
json['bookable_hours'] = json['hours_duration'] * json['subType'].length
|
|
|
|
json['date'] = start_at.to_date
|
2016-09-06 16:32:41 +02:00
|
|
|
json.to_json
|
|
|
|
end
|
|
|
|
|
2016-03-23 18:39:41 +01:00
|
|
|
private
|
2018-12-03 15:10:04 +01:00
|
|
|
|
2019-11-13 12:13:22 +01:00
|
|
|
def length_must_be_slot_multiple
|
|
|
|
if end_at < (start_at + Rails.application.secrets.slot_duration.minutes)
|
|
|
|
errors.add(:end_at, I18n.t('availabilities.length_must_be_slot_multiple', MIN: Rails.application.secrets.slot_duration))
|
|
|
|
end
|
2016-03-23 18:39:41 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def should_be_associated
|
2019-06-06 16:34:53 +02:00
|
|
|
return unless available_type == 'machines' && machine_ids.count.zero?
|
|
|
|
|
|
|
|
errors.add(:machine_ids, I18n.t('availabilities.must_be_associated_with_at_least_1_machine'))
|
2016-03-23 18:39:41 +01:00
|
|
|
end
|
2015-05-05 03:10:25 +02:00
|
|
|
end
|