osem-fcy/app/models/program.rb

275 lines
9.8 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
# cannot delete program if there are events submitted
class Program < ApplicationRecord
has_paper_trail on: [:update], ignore: [:updated_at], meta: { conference_id: :conference_id }
belongs_to :conference
has_many :cfps, dependent: :destroy
has_many :event_types, dependent: :destroy
has_many :tracks, dependent: :destroy
has_many :difficulty_levels, dependent: :destroy
has_many :schedules, dependent: :destroy
has_many :event_schedules, through: :schedules
belongs_to :selected_schedule, class_name: 'Schedule'
has_many :events, dependent: :destroy do
2016-04-22 18:18:46 +03:00
def require_registration
where(require_registration: true, state: :confirmed)
end
2016-04-22 18:18:46 +03:00
def with_registration_open
2016-05-15 14:01:30 +03:00
select { |e| e if e.registration_possible? }
2016-04-22 18:18:46 +03:00
end
# All confirmed events of the conference with attribute require_registration
# excluding the events the user has already registered to
def remaining_for_registration(registration)
require_registration - registration.events
end
def confirmed
where(state: :confirmed)
end
def scheduled(schedule_id)
joins(:event_schedules).where('event_schedules.schedule_id = ?', schedule_id)
end
def highlights
where(state: :confirmed, is_highlight: true)
end
end
has_many :event_schedules, through: :events
has_many :event_users, through: :events
2017-11-09 02:00:49 +05:30
has_many :program_events_speakers, -> {where(event_role: 'speaker')}, through: :events, source: :event_users
2017-11-09 01:40:56 +05:30
has_many :speakers, -> { distinct }, through: :program_events_speakers, source: :user do
def confirmed
joins(:events).where(events: { state: :confirmed })
end
def registered(conference)
joins(:registrations).where('registrations.conference_id = ?', conference.id)
end
def unregistered(conference)
self - registered(conference)
end
end
accepts_nested_attributes_for :event_types, allow_destroy: true
accepts_nested_attributes_for :tracks, reject_if: proc { |r| r['name'].blank? }, allow_destroy: true
accepts_nested_attributes_for :difficulty_levels, allow_destroy: true
# validates :conference_id, presence: true, uniqueness: true
2016-07-05 16:44:53 +03:00
validates :rating, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 10, only_integer: true }
validates :schedule_interval, numericality: { greater_than_or_equal_to: 5, less_than_or_equal_to: 60 }, presence: true
validate :schedule_interval_divisor_60
2016-07-05 16:44:53 +03:00
validate :voting_start_date_before_end_date
validate :voting_dates_exist
after_create :create_event_types
after_create :create_difficulty_levels
after_save :unschedule_unfit_events, if: :saved_change_to_schedule_interval?
after_save :normalize_event_types_length, if: :saved_change_to_schedule_interval?
2016-03-21 00:18:20 +01:00
validate :check_languages_format
# Returns all event_schedules for the selected schedule ordered by start_time
2018-04-11 15:03:05 -07:00
def selected_event_schedules(includes: [:event])
event_schedules = []
event_schedules = selected_schedule.event_schedules.includes(*includes).order(start_time: :asc) if selected_schedule
tracks.self_organized.confirmed.includes(selected_schedule: { event_schedules: includes }).order(start_date: :asc).each do |track|
next unless track.selected_schedule
2018-11-13 18:01:49 -08:00
2018-04-11 15:03:05 -07:00
event_schedules += track.selected_schedule.event_schedules
Implement track scheduling Add track association to schedule Show schedules in admin sidebar to track organizers Allow track organizers to manage the schedules of their tracks Don't allow self-organized track events to be dragged or unscheduled in a conference schedule Make scheduled events of self-organized tracks appear semitransparent in conference schedules Make the rooms of confirmed self_organized tracks appear semitransparent and don't allow events to be scheduled to it in the conference schedules during the dates of its track Create admin/SchedulesController#new action Add a button in admin/Schedules#index to create schedules for tracks Add self_organized scope to Track Modify Schedules#show to handle track schedules and show a unified schedule Allow track organizers to create new schedules for their tracks Correctly identify scheduled and unscheduled events in Schedules#events Fix Event#room and Event#time for when the event is scheduled in a track schedule Modify Program#selected_event_schedules to include the event_schedules of selected track schedules Modify Track#revoke_role_and_cleanup to destroy the track's schedules and revert its events' state to new Add tabs for conference and track schedules in admin/Schedules#index Add button to Create/Show a tracks schedule in Tracks#index and #show Fix concurrent_events in application_helper because of changes in Program#selected_event_schedules Do not take into account cfp_active in Event#valid_track Modify EventsController#get_tracks accordingly Enforce cfp_active of track to be enabled for proposals in ProposalsController#create and #update Add support for multiple schedules per track Add selected_schedule_id to Track Load EventSchedules of selected track schedules for conference schedules in admin/SchedulesController#show Modify SchedulesController#show to take into account only the selected track schedules Create Event#selected_schedule_id and use it in Event#scheduled? and Event#time Validate that an EventSchedule for an event of a self-organized track belongs to one of the track's schedules Add 'Manage' button in Tracks#index, #show that sends you to the admin side of things Add admin/TracksController#update_selected_schedule to update the selected_schedule_id of tracks
2017-08-09 18:12:20 +03:00
end
2018-04-11 15:03:05 -07:00
event_schedules.sort_by(&:start_time)
end
2016-07-05 16:44:53 +03:00
##
# Checks if blind_voting is enabled and if voting period is over
# ====Returns
# * +true+ -> If we can show voting details
# * +false+ -> If we cannot show voting details
def show_voting?
return true unless blind_voting
Time.current > voting_end_date
2016-07-05 16:44:53 +03:00
end
##
# Checks if we are still in voting period
# ====Returns
# * +true+ -> If the voting period is not over yet (or if the voting dates are not set)
2016-07-05 16:44:53 +03:00
# * +false+ -> If the voting period is over
def voting_period?
return true unless voting_start_date && voting_end_date
2016-07-05 16:44:53 +03:00
(voting_start_date.to_datetime..voting_end_date.to_datetime).cover? Time.current
end
##
# Checks if both voting_start_date and voting_end_date are set
# ====Returns
# Errors when the condition is not true
def voting_dates_exist
errors.add(:voting_start_date, 'must be set, when blind voting is enabled') if blind_voting && !voting_start_date && !voting_end_date
errors.add(:voting_end_date, 'must be set, when blind voting is enabled') if blind_voting && !voting_start_date && !voting_end_date
errors.add(:voting_end_date, 'must be set, when voting_start_date is set') if voting_start_date && !voting_end_date
errors.add(:voting_start_date, 'must be set, when voting_end_date is set') if voting_end_date && !voting_start_date
end
##
# Checks if voting_start_date is before voting_end_date
# ====Returns
# Errors when the condition is not true
def voting_start_date_before_end_date
errors.add(:voting_start_date, 'must be before voting end date') if voting_start_date && voting_end_date && voting_start_date > voting_end_date
end
##
# Checks if the program has rating enabled
#
# ====Returns
# * +false+ -> If rating is not enabled
# * +true+ -> If rating is enabled
def rating_enabled?
rating && rating > 0
end
##
# Checks if the call for papers for the conference is currently open
#
# ====Returns
# * +false+ -> If the CFP is not set or today isn't in the CFP period.
# * +true+ -> If today is in the CFP period.
def cfp_open?
cfp = self.cfp
cfp.present? && (cfp.start_date..cfp.end_date).cover?(Date.current)
end
2016-03-31 16:51:16 +02:00
def notify_on_schedule_public?
return false unless conference.email_settings.send_on_program_schedule_public
# do not notify if the schedule is not public
return false unless schedule_public
2018-11-13 18:01:49 -08:00
2016-03-31 16:51:16 +02:00
# do not notify unless the mail content is set up
(!conference.email_settings.program_schedule_public_subject.blank? && !conference.email_settings.program_schedule_public_body.blank?)
end
2016-03-21 00:18:20 +01:00
def languages_list
languages.split(',').map {|l| ISO_639.find(l).english_name} if languages.present?
2016-03-21 00:18:20 +01:00
end
##
# Checks if there is any event in the program that starts in the given date
#
# ====Returns
# * +True+ -> If there is any event for the given date
# * +False+ -> If there is not any event for the given date
def any_event_for_this_date?(date)
2018-04-11 15:03:24 -07:00
return false unless selected_schedule.present?
2018-11-13 18:01:49 -08:00
parsed_date = DateTime.parse("#{date} 00:00").utc
2018-04-11 15:03:05 -07:00
range = parsed_date..(parsed_date + 1.day)
selected_schedule.event_schedules.any? { |es| range.cover?(es.start_time) }
end
##
# Provides backwards compatibility for when the program had one cfp
#
# ====Returns
# * +ActiveRecord+ -> The program's cfp with cfp_type == 'events'
def cfp
return nil if cfps.for_events.blank?
2018-11-13 18:01:49 -08:00
cfps.for_events
end
##
# ====Returns
# * +Array+ -> The types of cfps for which a cfp doesn't exist yet
def remaining_cfp_types
Cfp::TYPES - cfps.pluck(:cfp_type)
end
private
##
# Creates default EventTypes for this Conference. Used as before_create.
#
def create_event_types
EventType.create(title: 'Talk', length: 30, color: '#FF0000', description: 'Presentation in lecture format',
minimum_abstract_length: 0,
maximum_abstract_length: 500, program_id: id)
EventType.create(title: 'Workshop', length: 60, color: '#0000FF', description: 'Interactive hands-on practice',
minimum_abstract_length: 0,
maximum_abstract_length: 500, program_id: id)
end
##
# Creates default DifficultyLevels for this Conference. Used as before_create.
#
def create_difficulty_levels
DifficultyLevel.create(title: 'Easy',
description: 'Events are understandable for everyone without knowledge of the topic.',
color: '#32CB2A', program_id: id)
DifficultyLevel.create(title: 'Medium',
description: 'Events require a basic understanding of the topic.',
color: '#E6B65B', program_id: id)
DifficultyLevel.create(title: 'Hard',
description: 'Events require expert knowledge of the topic.',
color: '#EF6E69', program_id: id)
end
2016-03-21 00:18:20 +01:00
##
# Check if languages string has the right format. Used as validation.
#
def check_languages_format
return unless languages.present?
2018-11-13 18:01:49 -08:00
2016-03-21 00:18:20 +01:00
# All white spaces are removed to allow languages to be separated by ',' and ', '. The languages string without spaces is saved
self.languages = languages.delete(' ').downcase
2016-03-21 00:18:20 +01:00
errors.add(:languages, 'must be two letters separated by commas') && return unless
languages.match(/^$|(\A[a-z][a-z](,[a-z][a-z])*\z)/).present?
languages_array = languages.split(',')
2016-03-21 00:18:20 +01:00
# We check that languages are not repeated
errors.add(:languages, "can't be repeated") && return unless languages_array.uniq!.nil?
# We check if every language is a valid ISO 639-1 language
errors.add(:languages, 'must be ISO 639-1 valid codes') unless languages_array.select{ |x| ISO_639.find(x).nil? }.empty?
end
##
# Check if schedule_interval is a divisor of 60 minutes
#
def schedule_interval_divisor_60
errors.add(:schedule_interval, 'must be a divisor of 60') if schedule_interval > 0 && 60 % schedule_interval > 0
end
##
# Unschedule all the events which don't fit
#
def unschedule_unfit_events
unfit_schedules = event_schedules.select do |event_schedule|
event_schedule.start_time.min % schedule_interval > 0
end
EventSchedule.where(id: unfit_schedules.map(&:id)).destroy_all
end
##
# Change event type length according schedule interval
#
def normalize_event_types_length
event_types.each do |event_type|
new_length = event_type.length > schedule_interval ? event_type.length - (event_type.length % schedule_interval) : schedule_interval
event_type.update_attribute(:length, new_length)
end
end
end