Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 10 additions & 20 deletions app/decorators/proposal_decorator.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
class ProposalDecorator < Draper::Decorator
include Proposal::State
decorates :proposal
delegate_all
decorates_association :speakers
Expand All @@ -8,7 +7,7 @@ def speaker_state(small: false)
speaker_state = if object.awaiting_confirmation?
'Waiting for speaker confirmation'
elsif state.include?('soft')
SUBMITTED
Proposal.states[:submitted]
else
state
end
Expand All @@ -18,7 +17,7 @@ def speaker_state(small: false)

def reviewer_state(small: false)
reviewer_state = if state.include?('soft')
SUBMITTED
Proposal.states[:submitted]
else
state
end
Expand All @@ -27,11 +26,10 @@ def reviewer_state(small: false)
end

def state
current_state = object.state
if current_state == REJECTED
NOT_ACCEPTED
if object.rejected?
Proposal.states[:not_accepted]
else
current_state
Proposal.states[object.state.to_sym]
end
end

Expand Down Expand Up @@ -125,21 +123,13 @@ def speaker

def state_class(state)
case state
when NOT_ACCEPTED
if h.current_user.reviewer_for_event?(object.event)
'label-danger'
else
'label-info'
end
when SOFT_REJECTED
when Proposal.states[:not_accepted]
h.current_user.reviewer_for_event?(object.event) ? 'label-danger' : 'label-info'
when Proposal.states[:soft_rejected]
'label-danger'
when SOFT_WAITLISTED
when Proposal.states[:soft_waitlisted], Proposal.states[:withdrawn]
'label-warning'
when WITHDRAWN
'label-warning'
when ACCEPTED
'label-success'
when SOFT_ACCEPTED
when Proposal.states[:accepted], Proposal.states[:soft_accepted]
'label-success'
else
'label-default'
Expand Down
12 changes: 6 additions & 6 deletions app/decorators/staff/proposal_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def state_buttons(states: nil, show_finalize: true, show_hard_reset: false, smal

def small_state_buttons
state_buttons(
states: [ SOFT_ACCEPTED, SOFT_WAITLISTED, SOFT_REJECTED, SUBMITTED ],
states: [:soft_accepted, :soft_waitlisted, :soft_rejected, :submitted],
show_finalize: false,
small: true
)
Expand Down Expand Up @@ -103,13 +103,13 @@ def finalize_state_button
end

def reset_state_button
state_button('Reset Status', update_state_path(SUBMITTED),
state_button('Reset Status', update_state_path(:submitted),
type: 'btn-default',
hidden: reset_button_hidden?)
end

def hard_reset_button
state_button('Hard Reset', update_state_path(SUBMITTED),
state_button('Hard Reset', update_state_path(:submitted),
data: {
turbo_confirm:
"This proposal's status has been finalized. Proceed with status reset?"
Expand All @@ -125,9 +125,9 @@ def update_state_path(state)

def buttons
[
[ 'Accept', SOFT_ACCEPTED, 'btn-success', !object.draft? ],
[ 'Waitlist', SOFT_WAITLISTED, 'btn-warning', !object.draft? ],
[ 'Reject', SOFT_REJECTED, 'btn-danger', !object.draft? ]
['Accept', :soft_accepted, 'btn-success', !object.draft?],
['Waitlist', :soft_waitlisted, 'btn-warning', !object.draft?],
['Reject', :soft_rejected, 'btn-danger', !object.draft?]
]
end

Expand Down
19 changes: 9 additions & 10 deletions app/mailers/staff/proposal_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ class Staff::ProposalMailer < ApplicationMailer
attr_accessor :test_mode

def send_email(proposal)
case proposal.state
when Proposal::State::ACCEPTED
accept_email(proposal.event, proposal)
when Proposal::State::REJECTED
reject_email(proposal.event, proposal)
when Proposal::State::WAITLISTED
waitlist_email(proposal.event, proposal)
if proposal.accepted?
accept_email(proposal.event, proposal)
elsif proposal.rejected?
reject_email(proposal.event, proposal)
elsif proposal.waitlisted?
waitlist_email(proposal.event, proposal)
end
end

Expand All @@ -29,23 +28,23 @@ def accept_email(event, proposal)
@proposal = proposal.decorate
@event = event
@template_name = 'accept_email'
subject = subject_for(proposal: @proposal, type: Proposal::State::ACCEPTED)
subject = subject_for(proposal: @proposal, type: :accepted)
mail_to_speakers(event, proposal, subject)
end

def reject_email(event, proposal)
@proposal = proposal
@event = event
@template_name = 'reject_email'
subject = subject_for(proposal: @proposal, type: Proposal::State::REJECTED)
subject = subject_for(proposal: @proposal, type: :rejected)
mail_to_speakers(event, proposal, subject)
end

def waitlist_email(event, proposal)
@proposal = proposal.decorate
@event = event
@template_name = 'waitlist_email'
subject = subject_for(proposal: proposal, type: Proposal::State::WAITLISTED)
subject = subject_for(proposal: proposal, type: :waitlisted)
mail_to_speakers(event, proposal, subject)
end

Expand Down
8 changes: 4 additions & 4 deletions app/models/concerns/finalization_messages.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

module FinalizationMessages
MESSAGES = {
Proposal::State::ACCEPTED => ->(event_name) { "Your proposal for #{event_name} has been accepted" },
Proposal::State::REJECTED => ->(event_name) { "Your proposal for #{event_name} has not been accepted" },
Proposal::State::WAITLISTED => ->(event_name) { "Your proposal for #{event_name} has been added to the waitlist" }
}.freeze
accepted: ->(event_name) { "Your proposal for #{event_name} has been accepted" },
rejected: ->(event_name) { "Your proposal for #{event_name} has not been accepted" },
waitlisted: ->(event_name) { "Your proposal for #{event_name} has been added to the waitlist" }
}.with_indifferent_access.freeze

def subject_for(proposal:, type: proposal.state)
default_builder = ->(_) { 'Invalid final proposal type' }
Expand Down
20 changes: 10 additions & 10 deletions app/models/event_stats.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ def user_ratable_proposals(user, include_withdrawn=false)
end

def all_accepted_proposals(track_id='all')
q = event.proposals.where(state: [Proposal::ACCEPTED, Proposal::SOFT_ACCEPTED])
q = event.proposals.where(state: [:accepted, :soft_accepted])
q = filter_by_track(q, track_id)
q.size + active_custom_sessions(track_id)
end

def all_waitlisted_proposals(track_id='all')
q = event.proposals.where(state: [Proposal::WAITLISTED, Proposal::SOFT_WAITLISTED])
q = event.proposals.where(state: [:waitlisted, :soft_waitlisted])
q = filter_by_track(q, track_id)
q.size
end
Expand Down Expand Up @@ -78,18 +78,18 @@ def program
proposals_per_track_and_state = event.proposals.left_joins(:track).group('tracks.name', :state).count

stats = {'Total' => {
accepted: proposals_per_track_and_state.select {|k, _v| k[1] == Proposal::State::ACCEPTED }.sum(&:second) || 0,
soft_accepted: proposals_per_track_and_state.select {|k, _v| k[1] == Proposal::State::SOFT_ACCEPTED }.sum(&:second) || 0,
waitlisted: proposals_per_track_and_state.select {|k, _v| k[1] == Proposal::State::WAITLISTED }.sum(&:second) || 0,
soft_waitlisted: proposals_per_track_and_state.select {|k, _v| k[1] == Proposal::State::SOFT_WAITLISTED }.sum(&:second) || 0
accepted: proposals_per_track_and_state.select {|k, _v| k[1] == 'accepted' }.sum(&:second) || 0,
soft_accepted: proposals_per_track_and_state.select {|k, _v| k[1] == 'soft_accepted' }.sum(&:second) || 0,
waitlisted: proposals_per_track_and_state.select {|k, _v| k[1] == 'waitlisted' }.sum(&:second) || 0,
soft_waitlisted: proposals_per_track_and_state.select {|k, _v| k[1] == 'soft_waitlisted' }.sum(&:second) || 0
}}
# Prepending `nil` for "General" track
event.tracks.pluck(:name).prepend(nil).each do |track_name|
stats[track_name || Track::NO_TRACK] = {
accepted: proposals_per_track_and_state[[track_name, Proposal::State::ACCEPTED]] || 0,
soft_accepted: proposals_per_track_and_state[[track_name, Proposal::State::SOFT_ACCEPTED]] || 0,
waitlisted: proposals_per_track_and_state[[track_name, Proposal::State::WAITLISTED]] || 0,
soft_waitlisted: proposals_per_track_and_state[[track_name, Proposal::State::SOFT_WAITLISTED]] || 0
accepted: proposals_per_track_and_state[[track_name, 'accepted']] || 0,
soft_accepted: proposals_per_track_and_state[[track_name, 'soft_accepted']] || 0,
waitlisted: proposals_per_track_and_state[[track_name, 'waitlisted']] || 0,
soft_waitlisted: proposals_per_track_and_state[[track_name, 'soft_waitlisted']] || 0
}
end
stats
Expand Down
32 changes: 11 additions & 21 deletions app/models/proposal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ class Proposal < ApplicationRecord
validates :title, :abstract, :session_format, presence: true
validate :abstract_length
validates :title, length: { maximum: 60 }
validates_inclusion_of :state, in: valid_states, allow_nil: true, message: "'%{value}' not a valid state."
validates_inclusion_of :state, in: FINAL_STATES, allow_nil: false, message: "'%{value}' not a confirmable state.",
if: :confirmed_at_changed?
validate :state_must_be_final_for_confirmation, if: :confirmed_at_changed?

serialize :last_change, coder: YAML
serialize :proposal_data, type: Hash, coder: YAML
Expand All @@ -39,20 +37,12 @@ class Proposal < ApplicationRecord
before_update :save_attr_history
after_save :save_tags, :save_review_tags

scope :accepted, -> { where(state: ACCEPTED) }
scope :waitlisted, -> { where(state: WAITLISTED) }
scope :submitted, -> { where(state: SUBMITTED) }
scope :confirmed, -> { where.not(confirmed_at: nil) }

scope :soft_accepted, -> { where(state: SOFT_ACCEPTED) }
scope :soft_waitlisted, -> { where(state: SOFT_WAITLISTED) }
scope :soft_rejected, -> { where(state: SOFT_REJECTED) }
scope :soft_states, -> { where(state: SOFT_STATES) }
scope :working_program, -> { where(state: [SOFT_ACCEPTED, SOFT_WAITLISTED, ACCEPTED, WAITLISTED]) }
scope :working_program, -> { where(state: [:soft_accepted, :soft_waitlisted, :accepted, :waitlisted]) }

scope :unrated, -> { where.not(id: Rating.select(:proposal_id)) }
scope :rated, -> { where(id: Rating.select(:proposal_id)) }
scope :not_withdrawn, -> { where.not(state: WITHDRAWN) }
scope :not_owned_by, ->(user) { where.not(id: user.proposals) }
scope :for_state, lambda { |state|
where(state: state).order(:title).includes(:event, { speakers: :user }, :review_taggings)
Expand Down Expand Up @@ -131,7 +121,7 @@ def finalize
end

def withdraw
update(state: WITHDRAWN)
withdrawn!
reviewers.each do |reviewer|
Notification.create_for(reviewer, proposal: self,
message: "Proposal, #{title}, withdrawn")
Expand All @@ -144,24 +134,20 @@ def confirm
end

def promote
update(state: ACCEPTED) if state == WAITLISTED
accepted! if waitlisted?
end

def decline
update(state: WITHDRAWN, confirmed_at: Time.current)
update(state: :withdrawn, confirmed_at: Time.current)
program_session.update(state: :declined)
end

def draft?
state == SUBMITTED
end

def finalized?
FINAL_STATES.include?(state)
FINAL_STATES.include?(state.to_sym)
end

def becomes_program_session?
BECOMES_PROGRAM_SESSION.include?(state)
BECOMES_PROGRAM_SESSION.include?(state.to_sym)
end

def confirmed?
Expand Down Expand Up @@ -258,6 +244,10 @@ def changeset_fields

private

def state_must_be_final_for_confirmation
errors.add(:state, "'#{state}' not a confirmable state.") unless FINAL_STATES.include?(state.to_sym)
end

def abstract_length
return unless abstract_changed? && abstract.gsub(/\r/, '').gsub(/\n/, '').length > 600

Expand Down
61 changes: 23 additions & 38 deletions app/models/proposal/state.rb
Original file line number Diff line number Diff line change
@@ -1,49 +1,34 @@
module Proposal::State
extend ActiveSupport::Concern

# TODO: Currently a defect exists in the rake db:migrate task that is
# causing our files to be loaded more than once. Because our files are being
# loaded more than once, we are receiving errors stating that these
# constants have already been defined. Therefore, until this defect is
# fixed, we need to check to ensure that the constants are not already
# defined prior to defining them.
unless const_defined?(:ACCEPTED)
SOFT_ACCEPTED = 'soft accepted'
SOFT_WAITLISTED = 'soft waitlisted'
SOFT_REJECTED = 'soft rejected'
SOFT_STATES = [:soft_accepted, :soft_waitlisted, :soft_rejected, :submitted].freeze
FINAL_STATES = [:accepted, :waitlisted, :rejected, :withdrawn, :not_accepted].freeze

ACCEPTED = 'accepted'
WAITLISTED = 'waitlisted'
REJECTED = 'rejected'
WITHDRAWN = 'withdrawn'
NOT_ACCEPTED = 'not accepted'
SUBMITTED = 'submitted'
SOFT_TO_FINAL = {
soft_accepted: :accepted,
soft_rejected: :rejected,
soft_waitlisted: :waitlisted,
submitted: :rejected
}.with_indifferent_access.freeze

SOFT_STATES = [ SOFT_ACCEPTED, SOFT_WAITLISTED, SOFT_REJECTED, SUBMITTED ]
FINAL_STATES = [ ACCEPTED, WAITLISTED, REJECTED, WITHDRAWN, NOT_ACCEPTED ]

SOFT_TO_FINAL = {
SOFT_ACCEPTED => ACCEPTED,
SOFT_REJECTED => REJECTED,
SOFT_WAITLISTED => WAITLISTED,
SUBMITTED => REJECTED
}

BECOMES_PROGRAM_SESSION = [ ACCEPTED, WAITLISTED ]
end
BECOMES_PROGRAM_SESSION = [:accepted, :waitlisted].freeze

included do
def self.valid_states
@valid_states ||= Proposal::State.constants.map{|c| const_get(c) if const_get(c).is_a?(String)}.compact!
end
enum :state, {
submitted: 'submitted',
soft_accepted: 'soft accepted',
soft_waitlisted: 'soft waitlisted',
soft_rejected: 'soft rejected',
accepted: 'accepted',
waitlisted: 'waitlisted',
rejected: 'rejected',
withdrawn: 'withdrawn',
not_accepted: 'not accepted'
}, default: :submitted

# Create all state accessor methods like (accepted?, waitlisted?, etc...)
Proposal::State.constants.each do |constant|
next unless const_get(constant).is_a?(String)
method_name = constant.to_s.downcase + '?'
define_method(method_name) do
Proposal::State.const_get(constant) == self.state
end
# draft? is an alias for submitted?
def draft?
submitted?
end
end
end
2 changes: 1 addition & 1 deletion app/models/rating.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class Rating < ApplicationRecord
belongs_to :user

scope :for_event, -> (event) { joins(:proposal).where("proposals.event_id = ?", event.id) }
scope :not_withdrawn, -> { joins(:proposal).where("proposals.state != ?", Proposal::State::WITHDRAWN) }
scope :not_withdrawn, -> { joins(:proposal).where.not(proposals: {state: :withdrawn}) }

def teammate
user.teammates.where(event: proposal.event).first
Expand Down
6 changes: 3 additions & 3 deletions app/models/speaker_email_template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ class SpeakerEmailTemplate
}.with_indifferent_access

TYPES_TO_STATES = {
accept: Proposal::ACCEPTED,
waitlist: Proposal::WAITLISTED,
reject: Proposal::REJECTED
accept: :accepted,
waitlist: :waitlisted,
reject: :rejected
}.with_indifferent_access

attr_accessor :email, :type_key
Expand Down
2 changes: 1 addition & 1 deletion app/views/staff/proposals/bulk_finalize.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
%tbody
- @remaining_by_state.each do |proposals|
%tr{data: {state: proposals.first.gsub(/\s+/, "-")}}
%td.col-sm-9= "#{proposals.last.count} #{proposals.first} proposal".pluralize(proposals.last.count)
%td.col-sm-9= "#{proposals.last.count} #{proposals.first.humanize.downcase} proposal".pluralize(proposals.last.count)
%td.col-sm-3.text-center
= link_to "Finalize", finalize_by_state_event_staff_program_proposals_path(event, proposals_state: proposals.first),
data: {turbo: true, turbo_method: :post, turbo_confirm: "This will finalize #{proposals.last.count} proposal".pluralize(proposals.last.count) + " in the #{proposals.first} state. Proceed?"},
Expand Down
Loading