osem-fcy/app/models/event.rb
AEtherC0r3 d9faffc96a Implement track request acceptance
Allow track submitter to request specific dates
Redirect to Tracks#edit if a track doesn't have a room or start/end date
before accepting it

Don't allow the submitter or the track organizers to edit the request
after it has been accepted or confirmed

Restrict track selection in proposals and move track selection from
Proposals form to events helper

Mark cfp_active of the tracks table as not null and fill in true if nil
2017-08-11 16:25:21 +03:00

314 lines
9.4 KiB
Ruby

class Event < ActiveRecord::Base
include ActiveRecord::Transitions
include RevisionCount
has_paper_trail on: [:create, :update], ignore: [:updated_at, :guid, :week], meta: { conference_id: :conference_id }
acts_as_commentable
after_create :set_week
has_many :event_users, dependent: :destroy
has_many :users, through: :event_users
has_many :speaker_event_users, -> { where(event_role: 'speaker') }, class_name: 'EventUser'
has_many :speakers, through: :speaker_event_users, source: :user
has_one :submitter_event_user, -> { where(event_role: 'submitter') }, class_name: 'EventUser'
has_one :submitter, through: :submitter_event_user, source: :user
has_many :votes, dependent: :destroy
has_many :voters, through: :votes, source: :user
has_many :commercials, as: :commercialable, dependent: :destroy
belongs_to :event_type
has_many :events_registrations
has_many :registrations, through: :events_registrations
has_many :event_schedules, dependent: :destroy
belongs_to :track
belongs_to :difficulty_level
belongs_to :program
accepts_nested_attributes_for :event_users, allow_destroy: true
accepts_nested_attributes_for :speakers, allow_destroy: true
accepts_nested_attributes_for :users
before_create :generate_guid
validate :abstract_limit
validate :before_end_of_conference, on: :create
validates :title, presence: true
validates :abstract, presence: true
validates :event_type, presence: true
validates :program, presence: true
validates :speakers, presence: true
validates :max_attendees, numericality: { only_integer: true, greater_than_or_equal_to: 1, allow_nil: true }
validate :max_attendees_no_more_than_room_size
validate :acceptable_track
scope :confirmed, -> { where(state: 'confirmed') }
scope :canceled, -> { where(state: 'canceled') }
scope :withdrawn, -> { where(state: 'withdrawn') }
scope :highlighted, -> { where(is_highlight: true) }
state_machine initial: :new do
state :new
state :withdrawn
state :unconfirmed
state :confirmed
state :canceled
state :rejected
event :restart do
transitions to: :new, from: [:rejected, :withdrawn, :canceled]
end
event :withdraw do
transitions to: :withdrawn, from: [:new, :unconfirmed, :confirmed]
end
event :accept do
transitions to: :unconfirmed, from: [:new], on_transition: :process_acceptance
end
event :confirm do
transitions to: :confirmed, from: :unconfirmed, on_transition: :process_confirmation
end
event :cancel do
transitions to: :canceled, from: [:unconfirmed, :confirmed]
end
event :reject do
transitions to: :rejected, from: [:new], on_transition: :process_rejection
end
end
##
# Checkes if the event has a start_time and a room for the selected schedule if there is any
# ====Returns
# * +true+ or +false+
def scheduled?
event_schedules.find_by(schedule_id: program.selected_schedule_id).present?
end
def registration_possible?
return false unless require_registration && state == 'confirmed'
return true if max_attendees.nil?
registrations.count < max_attendees
end
##
# Finds the rating of the user for the event
# ====Returns
# * +integer+ -> the rating of the user for the event
def user_rating(user)
(vote = votes.find_by(user: user)) ? vote.rating : 0
end
##
# Checks if the event has votes
# If a user is provided, it checks if the event has votes by the user
# ====Returns
# * +true+ -> If the event has votes (optionally, by the user)
# * +false+ -> If the event does not have any votes (optionally, by the user)
def voted?(user=nil)
return votes.where(user: user).any? if user
votes.any?
end
def average_rating
@total_rating = 0
votes.each do |vote|
@total_rating += vote.rating
end
@total = votes.size
@total_rating > 0 ? number_with_precision(@total_rating / @total.to_f, precision: 2, strip_insignificant_zeros: true) : 0
end
# get event speakers with the event sumbmitter at the first position
# if the submitter is also a speaker for this event
def speakers_ordered
speakers_list = speakers.to_a
if speakers_list.reject! { |speaker| speaker == submitter }
speakers_list.unshift(submitter)
end
speakers_list
end
def transition_possible?(transition)
self.class.state_machine.events_for(current_state).include?(transition)
end
def process_confirmation
if program.conference.email_settings.send_on_confirmed_without_registration? &&
program.conference.email_settings.confirmed_without_registration_body &&
program.conference.email_settings.confirmed_without_registration_subject
if program.conference.registrations.where(user_id: submitter.id).first.nil?
Mailbot.confirm_reminder_mail(self).deliver_later
end
end
end
def process_acceptance(options)
if program.conference.email_settings.send_on_accepted &&
program.conference.email_settings.accepted_body &&
program.conference.email_settings.accepted_subject &&
!options[:send_mail].blank?
Mailbot.acceptance_mail(self).deliver_later
end
end
def process_rejection(options)
if program.conference.email_settings.send_on_rejected &&
program.conference.email_settings.rejected_body &&
program.conference.email_settings.rejected_subject &&
!options[:send_mail].blank?
Mailbot.rejection_mail(self).deliver_later
end
end
def abstract_word_count
abstract.to_s.split.size
end
def self.get_state_color(state)
color = {
new: '#0000FF', # blue
withdrawn: '#FF8000', # orange
confirmed: '#00FF00', # green
unconfirmed: '#FFFF00', # yellow
rejected: '#FF0000', # red
canceled: '#848484' # grey
}[state.to_sym]
color || '#00FFFF' # azure
end
def update_state(transition, mail = false, subject = false, send_mail = false, send_mail_param)
alert = ''
if mail && send_mail_param && subject && send_mail
alert = 'Update Email Subject before Sending Mails'
end
begin
if mail
send(transition,
send_mail: send_mail_param)
else
send(transition)
end
save
rescue Transitions::InvalidTransition => e
alert = "Update state failed. #{e.message}"
end
alert
end
def speaker_names
result = Set.new
speakers.each do |speaker|
result.add(speaker.name)
end
result.to_a.to_sentence
end
##
#
# Returns +Hash+
def progress_status
{
registered: speakers.all? { |speaker| program.conference.user_registered? speaker },
commercials: commercials.any?,
biographies: speakers.all? { |speaker| !speaker.biography.blank? },
subtitle: !subtitle.blank?,
track: (!track.blank? unless program.tracks.empty?),
difficulty_level: !difficulty_level.blank?,
title: true,
abstract: true
}.with_indifferent_access
end
##
# Returns the progress of the proposal's set up
#
# ====Returns
# * +String+ -> Progress in Percent
def calculate_progress
result = progress_status
(100 * result.values.count(true) / result.values.compact.count).to_s
end
##
# Returns the room in which the event is scheduled
#
def room
# We use try(:selected_schedule_id) because this function is used for
# validations so program could not be present there
event_schedules.find_by(schedule_id: program.try(:selected_schedule_id)).try(:room)
end
##
# Returns the start time at which this event is scheduled
#
def time
event_schedules.find_by(schedule_id: program.selected_schedule_id).try(:start_time)
end
def conference
program.conference
end
private
##
# Do not allow, for the event, more attendees than the size of the room
def max_attendees_no_more_than_room_size
return unless room && max_attendees_changed?
errors.add(:max_attendees, "cannot be more than the room's capacity (#{room.size})") if max_attendees && (max_attendees > room.size)
end
def abstract_limit
# If we don't have an event type, there is no need to count anything
return unless event_type && abstract
len = abstract.split.size
max_words = event_type.maximum_abstract_length
min_words = event_type.minimum_abstract_length
errors.add(:abstract, "cannot have less than #{min_words} words") if len < min_words
errors.add(:abstract, "cannot have more than #{max_words} words") if len > max_words
end
# TODO: create a module to be mixed into model to perform same operation
# venue.rb has same functionality which can be shared
# TODO: rename guid to UUID as guid is specifically Microsoft term
def generate_guid
loop do
@guid = SecureRandom.urlsafe_base64
break unless self.class.where(guid: guid).any?
end
self.guid = @guid
end
def set_week
self.week = created_at.strftime('%W')
without_versioning do
save!
end
end
def before_end_of_conference
errors
.add(:created_at, "can't be after the conference end date!") if program.conference && program.conference.end_date &&
(Date.today > program.conference.end_date)
end
def conference_id
program.conference_id
end
##
# Allow only confirmed tracks that belong to the same program and are included in the cfp
def acceptable_track
return unless track && track.program && program
errors.add(:track, 'is invalid') unless track.confirmed? && track.cfp_active && track.program == program
end
end