From 01ec43e53addd8177b196fddf59e0df5d9684ac8 Mon Sep 17 00:00:00 2001 From: Stella Rouzi Date: Fri, 22 Apr 2016 18:18:46 +0300 Subject: [PATCH] Registration for Events --- Gemfile.lock | 3 - app/controllers/admin/events_controller.rb | 19 ++++- .../conference_registrations_controller.rb | 1 - app/controllers/proposal_controller.rb | 6 +- app/helpers/application_helper.rb | 8 ++ app/models/event.rb | 35 +++++++- app/models/events_registration.rb | 8 ++ app/models/program.rb | 13 ++- app/models/registration.rb | 26 +++++- app/models/user.rb | 24 ++++++ app/views/admin/events/_proposal.html.haml | 7 +- app/views/admin/events/index.html.haml | 9 +- .../admin/events/registrations.html.haml | 40 +++++++++ .../users/_event_registrations.html.haml | 25 ++++++ app/views/admin/users/show.html.haml | 4 + .../conference_registrations/_form.html.haml | 2 +- .../_registration_info.html.haml | 19 ++++- .../conference_registrations/show.html.haml | 24 +++++- app/views/proposal/_proposal_form.html.haml | 5 +- app/views/proposal/index.html.haml | 8 +- app/views/proposal/registrations.html.haml | 36 ++++++++ app/views/proposal/show.html.haml | 20 +++-- config/routes.rb | 4 + ...60323154504_add_max_attendees_to_events.rb | 5 ++ ...26_add_attended_to_events_registrations.rb | 5 ++ ..._and_created_at_to_events_registrations.rb | 6 ++ db/schema.rb | 11 ++- lib/tasks/events_registrations.rake | 30 +++++++ ...conference_registration_controller_spec.rb | 7 +- spec/factories/events_registrations.rb | 8 ++ spec/helpers/application_helper_spec.rb | 18 ++++ spec/models/event_spec.rb | 82 +++++++++++++++++++ spec/models/registration_spec.rb | 30 ++++++- spec/models/user_spec.rb | 18 ++++ .../admin/events/index.html.haml_spec.rb | 2 +- 35 files changed, 522 insertions(+), 46 deletions(-) create mode 100644 app/views/admin/events/registrations.html.haml create mode 100644 app/views/admin/users/_event_registrations.html.haml create mode 100644 app/views/proposal/registrations.html.haml create mode 100644 db/migrate/20160323154504_add_max_attendees_to_events.rb create mode 100644 db/migrate/20160326075326_add_attended_to_events_registrations.rb create mode 100644 db/migrate/20160403214841_add_id_and_created_at_to_events_registrations.rb create mode 100644 lib/tasks/events_registrations.rake create mode 100644 spec/factories/events_registrations.rb diff --git a/Gemfile.lock b/Gemfile.lock index 8279eaa5..e029ca48 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -581,6 +581,3 @@ DEPENDENCIES web-console (~> 2.0) webmock whenever - -BUNDLED WITH - 1.11.2 diff --git a/app/controllers/admin/events_controller.rb b/app/controllers/admin/events_controller.rb index e60d3991..ad4bbc2c 100644 --- a/app/controllers/admin/events_controller.rb +++ b/app/controllers/admin/events_controller.rb @@ -3,6 +3,7 @@ module Admin load_and_authorize_resource :conference, find_by: :short_title load_and_authorize_resource :program, through: :conference, singleton: true load_and_authorize_resource :event, through: :program + load_and_authorize_resource :events_registration, only: :toggle_attendance before_action :get_event, except: [:index, :create] @@ -116,7 +117,7 @@ module Admin end else @url = admin_conference_program_event_path(@conference.short_title, @event) - flash[:notice] = 'Update not successful. ' + @event.errors.full_messages.to_sentence + flash[:error] = 'Update not successful. ' + @event.errors.full_messages.to_sentence render :edit end end @@ -165,6 +166,20 @@ module Admin end end + def registrations + @event_registrations = @event.events_registrations + end + + def toggle_attendance + @events_registration.attended = !@events_registration.attended + + if @events_registration.save + head :ok + else + head :unprocessable_entity + end + end + private def event_params @@ -172,7 +187,7 @@ module Admin # Set also in proposals controller :title, :subtitle, :event_type_id, :abstract, :description, :require_registration, :difficulty_level_id, # Set only in admin/events controller - :track_id, :state, :language, :start_time, :is_highlight, + :track_id, :state, :language, :start_time, :is_highlight, :max_attendees, # Not used anymore? :proposal_additional_speakers, :user, :users_attributes) end diff --git a/app/controllers/conference_registrations_controller.rb b/app/controllers/conference_registrations_controller.rb index 0492809e..a16659a2 100644 --- a/app/controllers/conference_registrations_controller.rb +++ b/app/controllers/conference_registrations_controller.rb @@ -33,7 +33,6 @@ class ConferenceRegistrationsController < ApplicationController end def show - @workshops = @registration.workshops @total_price = Ticket.total_price(@conference, current_user) @tickets = current_user.ticket_purchases.where(conference_id: @conference.id) end diff --git a/app/controllers/proposal_controller.rb b/app/controllers/proposal_controller.rb index ae8d82aa..4efe8555 100644 --- a/app/controllers/proposal_controller.rb +++ b/app/controllers/proposal_controller.rb @@ -145,10 +145,14 @@ class ProposalController < ApplicationController end end + def registrations; end + private def event_params - params.require(:event).permit(:title, :subtitle, :track_id, :event_type_id, :abstract, :description, :require_registration, :difficulty_level_id) + params.require(:event).permit(:event_type_id, :track_id, :difficulty_level_id, + :title, :subtitle, :abstract, :description, + :require_registration, :max_attendees) end def user_params diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index b3d32bec..a2bec3a7 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,4 +1,12 @@ module ApplicationHelper + ## + # ====Returns + # * +String+ -> number of registrations / max allowed registrations + def registered_text(event) + return "Registered: #{event.registrations.count}/#{event.max_attendees}" if event.max_attendees + "Registered: #{event.registrations.count}" + end + # Set resource_name for devise so that we can call the devise help links (views/devise/shared/_links) from anywhere (eg sign_up form in proposal#new) def resource_name :user diff --git a/app/models/event.rb b/app/models/event.rb index d8652c90..3e0c9d13 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -14,7 +14,8 @@ class Event < ActiveRecord::Base has_many :commercials, as: :commercialable, dependent: :destroy belongs_to :event_type - has_and_belongs_to_many :registrations + has_many :events_registrations + has_many :registrations, through: :events_registrations belongs_to :track belongs_to :room @@ -32,6 +33,10 @@ class Event < ActiveRecord::Base validates :abstract, presence: true validates :event_type, presence: true validates :program, presence: true + validates :max_attendees, numericality: { only_integer: true, greater_than_or_equal_to: 1, allow_nil: true } + + validate :max_attendees_and_require_registration + validate :max_attendees_no_more_than_room_size scope :confirmed, -> { where(state: 'confirmed') } scope :highlighted, -> { where(is_highlight: true) } @@ -64,6 +69,19 @@ class Event < ActiveRecord::Base end end + ## + # Checkes if the event has a start_time and a room + # ====Returns + # * +true+ or +false+ + def scheduled? + room && start_time ? true : false + end + + def registration_possible? + return false unless max_attendees + registrations.count < max_attendees + end + def voted?(event, user) event.votes.where('user_id = ?', user).first end @@ -207,6 +225,21 @@ class Event < ActiveRecord::Base private + ## + # If max_attendees variable is set (higher than 0) + # variable require_registration must also be set + def max_attendees_and_require_registration + errors.add(:require_registration, 'must be enabled, when you set max_attendees') if max_attendees && !require_registration + errors.add(:max_attendees, 'must be enabled, when you set require_registration') if require_registration && max_attendees.nil? + end + + ## + # 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 diff --git a/app/models/events_registration.rb b/app/models/events_registration.rb index 08dbb70e..187f4189 100644 --- a/app/models/events_registration.rb +++ b/app/models/events_registration.rb @@ -1,4 +1,12 @@ class EventsRegistration < ActiveRecord::Base belongs_to :registration belongs_to :event + + has_one :user, through: :registration + + delegate :name, to: :registration + delegate :email, to: :registration + + validates :event, :registration, presence: true + validates :event, uniqueness: { scope: :registration } end diff --git a/app/models/program.rb b/app/models/program.rb index cbc27418..e336be21 100644 --- a/app/models/program.rb +++ b/app/models/program.rb @@ -8,10 +8,21 @@ class Program < ActiveRecord::Base has_many :tracks, dependent: :destroy has_many :difficulty_levels, dependent: :destroy has_many :events, dependent: :destroy do - def workshops + def require_registration where(require_registration: true, state: :confirmed) end + def with_registration_open + where(require_registration: true, state: :confirmed). + map { |e| e if e.max_attendees > e.registrations.count }.compact + 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 diff --git a/app/models/registration.rb b/app/models/registration.rb index 2026af56..d6a549fc 100644 --- a/app/models/registration.rb +++ b/app/models/registration.rb @@ -7,7 +7,7 @@ class Registration < ActiveRecord::Base has_and_belongs_to_many :vchoices has_many :events_registrations - has_many :workshops, through: :events_registrations, source: :event + has_many :events, through: :events_registrations accepts_nested_attributes_for :user accepts_nested_attributes_for :qanswers @@ -24,16 +24,40 @@ class Registration < ActiveRecord::Base validates_uniqueness_of :user_id, scope: :conference_id, message: 'already Registered!' validate :registration_limit_not_exceed, on: :create + validate :registration_to_events_only_if_present after_create :set_week, :subscribe_to_conference, :send_registration_mail after_destroy :destroy_purchased_tickets + ## + # Makes a list of events that includes (in that order): + # Events that require registration, and registration to them is still possible + # Events to which the user is already registered to + # ==== RETURNS + # * +Array+ -> [event_to_register_to, event_already_registered_to] + def events_ordered + (conference.program.events.with_registration_open - events) + events + end + def week created_at.strftime('%W').to_i end private + ## + # If the user registers to attend events that are already scheduled, + # only allow registration to events if the user will be present + # (based on arrival and departure attributes) + # No validation if arrival/departure attributes are empty + def registration_to_events_only_if_present + if (arrival || departure) && events.pluck(:start_time).any? + errors.add(:arrival, 'is too late! You cannot register for events that take place before your arrival') if events.pluck(:start_time).compact.map { |x| x < arrival }.any? + + errors.add(:departure, 'is too early! You cannot register for events that take place after your departure') if events.pluck(:start_time).compact.map { |x| x > departure }.any? + end + end + def destroy_purchased_tickets ticket_purchased = TicketPurchase.where(conference_id: conference_id, user_id: user.id) ticket_purchased.destroy_all diff --git a/app/models/user.rb b/app/models/user.rb index 99eb8e98..44c3e815 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -36,6 +36,7 @@ class User < ActiveRecord::Base has_many :event_users, dependent: :destroy has_many :events, -> { uniq }, through: :event_users has_many :registrations, dependent: :destroy + has_many :events_registrations, through: :registrations has_many :ticket_purchases, dependent: :destroy has_many :tickets, through: :ticket_purchases, source: :ticket has_many :votes, dependent: :destroy @@ -53,10 +54,33 @@ class User < ActiveRecord::Base }, presence: true + ## + # Checkes if the user attended the event + # This is used for events that require registration + # The user must have registered to attend the event + # Gets an event + # === Returns + # * +true+ if the user attended the event + # * +false+ if the user did not attend the event + def attended_event? event + event_registration = event.events_registrations.find_by(registration: self.registrations) + + return false unless event_registration.present? + event_registration.attended + end + def name self[:name] || username end + ## + # Checks if a user has registered to an event + # ====Returns + # * +true+ or +false+ + def registered_to_event? event + event.registrations.pluck(:id).include? self.registrations.find_by(conference_id: event.program.conference.id).id + end + def subscribed? conference self.subscriptions.find_by(conference_id: conference.id).present? end diff --git a/app/views/admin/events/_proposal.html.haml b/app/views/admin/events/_proposal.html.haml index 5c66f543..7d7955c5 100644 --- a/app/views/admin/events/_proposal.html.haml +++ b/app/views/admin/events/_proposal.html.haml @@ -5,7 +5,9 @@ %br %small = @event.subtitle - = link_to 'Edit', edit_admin_conference_program_event_path(@conference.short_title, @event), class: 'btn btn-mini btn-primary pull-right' + .btn-group.pull-right + = link_to 'Registrations', registrations_admin_conference_program_event_path(@conference.short_title, @event), class: 'btn btn-success' + = link_to 'Edit', edit_admin_conference_program_event_path(@conference.short_title, @event), class: 'btn btn-mini btn-primary' .row .col-md-12 @@ -94,7 +96,8 @@ off_color: 'warning', on_text: 'Yes', off_text: 'No' } - + - if @event.require_registration + = registered_text(@event) - if !@event.room.nil? %tr %td diff --git a/app/views/admin/events/index.html.haml b/app/views/admin/events/index.html.haml index 6eea977f..e43fbfe0 100644 --- a/app/views/admin/events/index.html.haml +++ b/app/views/admin/events/index.html.haml @@ -23,7 +23,7 @@ %th %b Speaker %th - %b Pre-registration + %b Requires Registration %th %b Highlight %th @@ -83,15 +83,18 @@ - else Unknown speaker - %td{'data-order' => "#{event.require_registration}"} + %td.text-center{'data-order' => "#{event.require_registration}"} = check_box_tag @conference.short_title, event.id, event.require_registration, method: :patch, url: "/admin/conference/#{@conference.short_title}/program/events/#{event.id}?event[require_registration]=", class: 'switch-checkbox', data: { size: 'small', off_color: 'warning', on_text: 'Yes', off_text: 'No' } + - if event.require_registration + %br + = link_to registered_text(event), registrations_admin_conference_program_event_path(@conference.short_title, event), class: 'btn btn-xs btn-default' - %td{'data-order' => "#{event.is_highlight}"} + %td.text-center{'data-order' => "#{event.is_highlight}"} = check_box_tag @conference.short_title, event.id, event.is_highlight, method: :patch, url: "/admin/conference/#{@conference.short_title}/program/events/#{event.id}?event[is_highlight]=", class: 'switch-checkbox', data: { size: 'small', diff --git a/app/views/admin/events/registrations.html.haml b/app/views/admin/events/registrations.html.haml new file mode 100644 index 00000000..bbc83f55 --- /dev/null +++ b/app/views/admin/events/registrations.html.haml @@ -0,0 +1,40 @@ +.row + .col-md-12 + .page-header + %h1 + Registrations (#{@event_registrations.length}/#{@event.max_attendees}) + .text-muted + for + = @event.title + + - if @event.room && (@event_registrations.length > @event.room.size) + %b Attention: + You have more registrations than the capacity of the room! + + .well + %table.table.table-hover.table-borderd.table-striped.datatable#registrations + %thead + %th + %th Name + %th Email + %th Created At + %th Attended + %th Attended Conference + %tbody + - @event_registrations.each.with_index(1) do |event_registration, index| + %tr + %td= index + %td= event_registration.name + %td= event_registration.email + %td= event_registration.created_at + %td + = check_box_tag @conference.short_title, @event.id, event_registration.attended, class: 'switch-checkbox', method: :patch, url: "/admin/conference/#{@conference.short_title}/program/events/#{@event.id}/toggle_attendance?events_registration_id=#{event_registration.id}&&event_registration[attended]=", + data: { size: 'small', + off_color: 'danger', + on_text: 'Yes', + off_text: 'No' } + %td + - if event_registration.registration.attended + %i.fa.fa-check.text-success + -else + %i.fa.fa-close.text-danger diff --git a/app/views/admin/users/_event_registrations.html.haml b/app/views/admin/users/_event_registrations.html.haml new file mode 100644 index 00000000..e83a188f --- /dev/null +++ b/app/views/admin/users/_event_registrations.html.haml @@ -0,0 +1,25 @@ +.row + .col-md-12.page-header + %h2 + Registrations to Events + + .col-md-12 + .well + %table.table.table-bordered.table-striped.table-hover.datatable#event_registrations + %thead + %th ID + %th Conference + %th Title + %th Attended + %tbody + - @user.events_registrations.each do |event_registration| + - event = event_registration.event + %tr + %td= event.id + %td= link_to event.program.conference.short_title, admin_conference_path(event.program.conference.short_title) + %td= link_to event.title, admin_conference_program_event_path(event.program.conference.short_title, event) + %td + - if @user.attended_event?(event) + %i.fa.fa-check.text-success + - else + %i.fa.fa-times.text-danger diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 81804a95..0670665f 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -7,6 +7,8 @@ - unless @user.events.blank? %li{class: "#{'active' if params[:tab] == 'submissions-content'}"} = link_to 'Submissions', '#submissions-content', 'data-toggle'=>'tab' + %li{class: "#{'active' if params[:tab] == 'event_registrations-content'}"} + = link_to 'Event Registrations', '#event_registrations', 'data-toggle'=>'tab' .tab-content #user-info-content.tab-pane{class: "#{'active' unless params[:tab] == 'submissions-content'}"} - if can? :edit, @user @@ -31,3 +33,5 @@ %td= @user.send(attr) #submissions-content.tab-pane{class: "#{'active' if params[:tab] == 'submissions-content'}"} = render 'submissions' + #event_registrations.tab-pane{class: "#{'active' if params[:tab] == 'event_registrations'}"} + = render 'event_registrations' diff --git a/app/views/conference_registrations/_form.html.haml b/app/views/conference_registrations/_form.html.haml index 7678abef..53ad4a29 100644 --- a/app/views/conference_registrations/_form.html.haml +++ b/app/views/conference_registrations/_form.html.haml @@ -6,7 +6,7 @@ Registration for = @conference.title .row - .col-md-6 + .col-md-8 - if !current_user %legend %span diff --git a/app/views/conference_registrations/_registration_info.html.haml b/app/views/conference_registrations/_registration_info.html.haml index fddc8296..0bf15c71 100644 --- a/app/views/conference_registrations/_registration_info.html.haml +++ b/app/views/conference_registrations/_registration_info.html.haml @@ -1,8 +1,21 @@ - if @conference.questions.any? = render partial: 'conference_registrations/questions', locals: { f: f } -- if @conference.program.events.workshops.any? - =f.inputs 'Pre-registration required for the following:' do - = f.input :events, as: :check_boxes, label: false, collection: @conference.program.events.workshops +- if @conference.program.events.with_registration_open.any? || @registration.events.any? + = f.inputs 'Pre-registration required for the following:' do + + - @registration.events_ordered.each do |event| + %label + = hidden_field_tag "registration[event_ids][]", nil + = check_box_tag "registration[event_ids][]", event.id, event.registrations.include?(@registration) + = event.title + .text-muted + = registered_text(event) + - if event.scheduled? + (Scheduled on: #{event.start_time.to_date}) + %br + + + = f.inputs 'Your Travel Info' do = f.input :arrival, as: :string, label: 'Your arrival time', input_html: { value: (f.object.arrival.to_formatted_s(:db_without_seconds) unless f.object.arrival.nil?), id: 'registration-arrival-datepicker',start_date: @conference.start_date,end_date: @conference.end_date,readonly: 'readonly' } = f.input :departure, as: :string, label: 'Your departure time', input_html: { value: (f.object.departure.to_formatted_s(:db_without_seconds) unless f.object.departure.nil?), id: 'registration-departure-datepicker', readonly: 'readonly' } diff --git a/app/views/conference_registrations/show.html.haml b/app/views/conference_registrations/show.html.haml index 83395846..8659277c 100644 --- a/app/views/conference_registrations/show.html.haml +++ b/app/views/conference_registrations/show.html.haml @@ -54,18 +54,34 @@ = qa.answer.title - else You haven't answered - - if @workshops.any? + - if @registration.events.any? .row .col-md-12 %h4 %span.fa-stack %i.fa.fa-square-o.fa-stack-2x %i.fa.fa-check.fa-stack-1x - Event Registrations + Registered to the following event(s) %ul - - @workshops.each do |workshop| + - @registration.events.each do |event| %li - = link_to workshop.title, conference_program_proposal_path(@conference.short_title, workshop.id) + = link_to event.title, conference_program_proposal_path(@conference.short_title, event.id) + = '(' + registered_text(event) + ')' + + - if @registration.conference.program.events.remaining_for_registration(@registration).any? + .row + .col-md-12 + %h4 + %span.fa-stack + %i.fa.fa-square-o.fa-stack-2x + %i.fa.fa-question.fa-stack-1x + Events that require registration + %ul + - @registration.conference.program.events.remaining_for_registration(@registration).each do |event| + %li + = link_to event.title, conference_program_proposal_path(@conference.short_title, event.id) + = '(' + registered_text(event) + ')' + - if @conference.tickets.any? .row .col-md-12 diff --git a/app/views/proposal/_proposal_form.html.haml b/app/views/proposal/_proposal_form.html.haml index 5addd7d2..c7f0bb16 100644 --- a/app/views/proposal/_proposal_form.html.haml +++ b/app/views/proposal/_proposal_form.html.haml @@ -42,7 +42,10 @@ 250 words. - = f.input :require_registration, label: 'Require participants to register to your event' + = f.inputs 'Enable pre-registration' do + = f.input :require_registration, label: 'Require participants to register to your event' + - message = @event.room ? "Value must be between 1 and #{@event.room.size}" : 'Check room capacity after scheduling.' + = f.input :max_attendees, hint: 'The maximum number of participants. ' + message - if current_user.has_any_role? :admin, { name: :organizer, resource: @conference }, { name: :cfp, resource: @conference } = f.input :is_highlight diff --git a/app/views/proposal/index.html.haml b/app/views/proposal/index.html.haml index c6050b40..a24ed427 100644 --- a/app/views/proposal/index.html.haml +++ b/app/views/proposal/index.html.haml @@ -2,7 +2,7 @@ .row .col-md-12.page-header %h1 - Proposals for + Proposals for %span.notranslate = @conference.title @@ -68,9 +68,9 @@ = event.event_type.title = "(#{event.event_type.length} min)" = "in #{event.track.name}" if event.track - - if event.state == 'confirmed' && event.require_registration == true - , - Pre-registered: #{pre_registered(event).count} + - if event.require_registration + %br + = link_to registered_text(event), registrations_conference_program_proposal_path(@conference.short_title, event), class: 'btn btn-xs btn-danger' %td.col-md-2{style: "padding:20px 8px 20px 8px;"} = link_to 'Complete your proposal', 'javascript: void(0)', "type"=>"button", "data-trigger"=>"focus", "data-toggle"=>"popover", "title"=>"Your todo list", "data-content"=>"#{render partial: 'tooltip', locals: { event: event} }" diff --git a/app/views/proposal/registrations.html.haml b/app/views/proposal/registrations.html.haml new file mode 100644 index 00000000..b7e88778 --- /dev/null +++ b/app/views/proposal/registrations.html.haml @@ -0,0 +1,36 @@ +.container + .row + .col-md-10.col-md-offset-1 + .page-header + %h1 + Registrations (#{@event.events_registrations.length}/#{@event.max_attendees}) + .text-muted + for + = @event.title + + .well + %table.table.table-hover.table-borderd.table-striped.datatable#registrations + %thead + %th + %th Name + %th Email + %th Created AT + %th Attended + %th Attended Conference + %tbody + - @event.events_registrations.each.with_index(1) do |event_registration, index| + %tr + %td= index + %td= event_registration.name + %td= event_registration.email + %td= event_registration.created_at + %td.text-center + - if event_registration.attended + %i.fa.fa-check.text-success + - else + %i.fa.fa-times.text-danger + %td + - if event_registration.registration.attended + %i.fa.fa-check.text-success + -else + %i.fa.fa-times.text-danger diff --git a/app/views/proposal/show.html.haml b/app/views/proposal/show.html.haml index f2ae3f13..c005a57a 100644 --- a/app/views/proposal/show.html.haml +++ b/app/views/proposal/show.html.haml @@ -7,10 +7,13 @@ %br %small = @event.subtitle - - if can? :schedule, @conference - = link_to "Schedule", schedule_conference_path(@conference.short_title), :class =>"btn btn-success pull-right" - - if can? :edit, @event - = link_to "Edit", edit_conference_program_proposal_path(@conference.short_title, @event), :class => "btn btn-mini btn-primary pull-right" + .btn-group.pull-right + - if can? :update, @event + = link_to 'Registrations', registrations_conference_program_proposal_path(@conference.short_title, @event), class: 'btn btn-mini btn-success' + - if can? :edit, @event + = link_to "Edit", edit_conference_program_proposal_path(@conference.short_title, @event), :class => "btn btn-mini btn-primary" + - if can? :schedule, @conference + = link_to "Schedule", schedule_conference_path(@conference.short_title), :class =>"btn btn-success" .row .col-md-3 .speakerinfo @@ -28,7 +31,6 @@ = @speaker.affiliation -if @speaker.biography? = simple_format(@speaker.biography) - .col-md-9 .row .col-md-12 @@ -76,5 +78,9 @@ - if @event.difficulty_level_id %span.label{:style =>"background-color: #{@event.difficulty_level.color};"} = @event.difficulty_level.title - - if @event.require_registration - = link_to "Registration required!", new_conference_conference_registrations_path(@conference.short_title), :class => "btn btn-xs btn-warning" + + - if @event.require_registration + .col-md-12 + %dt Requires Registration: + %dd + = link_to "Yes (#{registered_text(@event)})", new_conference_conference_registrations_path(@conference.short_title), class: 'btn btn-xs btn-danger', disabled: !@event.registration_possible? diff --git a/config/routes.rb b/config/routes.rb index 60c6565d..5c0b3858 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -53,6 +53,8 @@ Osem::Application.routes.draw do resources :difficulty_levels resources :events do member do + patch :toggle_attendance + get :registrations post :comment patch :accept patch :confirm @@ -99,7 +101,9 @@ Osem::Application.routes.draw do get 'commercials/render_commercial' => 'commercials#render_commercial' resources :commercials, only: [:create, :update, :destroy] member do + get :registrations patch '/withdraw' => 'proposal#withdraw' + get :registrations patch '/confirm' => 'proposal#confirm' patch '/restart' => 'proposal#restart' end diff --git a/db/migrate/20160323154504_add_max_attendees_to_events.rb b/db/migrate/20160323154504_add_max_attendees_to_events.rb new file mode 100644 index 00000000..a5a0d415 --- /dev/null +++ b/db/migrate/20160323154504_add_max_attendees_to_events.rb @@ -0,0 +1,5 @@ +class AddMaxAttendeesToEvents < ActiveRecord::Migration + def change + add_column :events, :max_attendees, :integer + end +end diff --git a/db/migrate/20160326075326_add_attended_to_events_registrations.rb b/db/migrate/20160326075326_add_attended_to_events_registrations.rb new file mode 100644 index 00000000..2cbb6edc --- /dev/null +++ b/db/migrate/20160326075326_add_attended_to_events_registrations.rb @@ -0,0 +1,5 @@ +class AddAttendedToEventsRegistrations < ActiveRecord::Migration + def change + add_column :events_registrations, :attended, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20160403214841_add_id_and_created_at_to_events_registrations.rb b/db/migrate/20160403214841_add_id_and_created_at_to_events_registrations.rb new file mode 100644 index 00000000..903ec851 --- /dev/null +++ b/db/migrate/20160403214841_add_id_and_created_at_to_events_registrations.rb @@ -0,0 +1,6 @@ +class AddIdAndCreatedAtToEventsRegistrations < ActiveRecord::Migration + def change + add_column :events_registrations, :id, :primary_key + add_column :events_registrations, :created_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 140f0b81..97d13714 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160309183052) do +ActiveRecord::Schema.define(version: 20160403214841) do create_table "ahoy_events", force: :cascade do |t| t.uuid "visit_id", limit: 16 @@ -225,11 +225,14 @@ ActiveRecord::Schema.define(version: 20160309183052) do t.integer "week" t.boolean "is_highlight", default: false t.integer "program_id" + t.integer "max_attendees" end - create_table "events_registrations", id: false, force: :cascade do |t| - t.integer "registration_id" - t.integer "event_id" + create_table "events_registrations", force: :cascade do |t| + t.integer "registration_id" + t.integer "event_id" + t.boolean "attended", default: false, null: false + t.datetime "created_at" end create_table "lodgings", force: :cascade do |t| diff --git a/lib/tasks/events_registrations.rake b/lib/tasks/events_registrations.rake new file mode 100644 index 00000000..726b739b --- /dev/null +++ b/lib/tasks/events_registrations.rake @@ -0,0 +1,30 @@ +namespace :events_registrations do + desc "Deletes dupicate entries" + task deduplicate: :environment do + if ActiveRecord::Migrator.get_all_versions.include? 20160403214841 + duplicates = EventsRegistration.all.map { |er| er.id if er.valid? == false}.compact + + puts "Duplicates found: #{duplicates.count}" + if duplicates.count > 0 + puts "With IDs: #{duplicates}" + end + + EventsRegistration.all.each do |er| + records = EventsRegistration.where(registration_id: er.registration_id, event_id: er.event_id) + if records.count > 1 + # Iterate through duplicates (excluding 1st record) + (1..(records.count - 1)).each do |i| + puts "Deleting EventsRegistration record with ID #{records[i].id} ..." + if records[i].destroy + puts 'Succeeded!' + else + puts 'Faild!' + end + end + end + end + else + puts 'Please migrate to run this task. Make sure your migration include 20160403214841' + end + end +end diff --git a/spec/controllers/conference_registration_controller_spec.rb b/spec/controllers/conference_registration_controller_spec.rb index 0a00d3dc..54a6c4bd 100644 --- a/spec/controllers/conference_registration_controller_spec.rb +++ b/spec/controllers/conference_registration_controller_spec.rb @@ -10,17 +10,20 @@ describe ConferenceRegistrationsController, type: :controller do describe 'GET #show' do before do @registration = create(:registration, conference: conference, user: user) + @event_with_registration = create(:event, program: conference.program, require_registration: true, max_attendees: 5, state: 'confirmed') + @event_without_registration = create(:event, program: conference.program, require_registration: true, max_attendees: 5, state: 'confirmed') + @registration.events << @event_with_registration end context 'successful request' do before do get :show, conference_id: conference.short_title + end - it 'assigns conference, registration and workshops variables' do + it 'assigns variables' do expect(assigns(:conference)).to eq conference expect(assigns(:registration)).to eq @registration - expect(assigns(:workshops)).to eq @registration.workshops end it 'renders the show template' do diff --git a/spec/factories/events_registrations.rb b/spec/factories/events_registrations.rb new file mode 100644 index 00000000..8fd7286c --- /dev/null +++ b/spec/factories/events_registrations.rb @@ -0,0 +1,8 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :events_registration do + event + registration + end +end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index fd302d85..8359506d 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -1,10 +1,28 @@ require 'spec_helper' describe ApplicationHelper, type: :helper do + let(:conference) { create(:conference) } + let(:event) { create(:event, program: conference.program) } + describe 'show_roles' do it 'formats the hash passed' do roles = { 'organizer' => ['oSC16', 'oSC15'], 'cfp' => ['oSC16'] } expect(show_roles(roles)).to eq 'Organizer (oSC16, oSC15), Cfp (oSC16)' end end + + describe '#registered_text' do + describe 'returns correct string' do + it 'when there are no registrations' do + expect(registered_text(event)).to eq 'Registered: 0' + end + + it 'when there is 1 registration' do + event.require_registration = true + event.max_attendees = 3 + event.registrations << create(:registration, user: event.submitter) + expect(registered_text(event)).to eq 'Registered: 1/3' + end + end + end end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 9fb1b07c..e25dd354 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -10,6 +10,8 @@ describe Event do describe 'association' do it { is_expected.to belong_to :program } it { is_expected.to belong_to :event_type } + it { is_expected.to have_many :events_registrations } + it { is_expected.to have_many :registrations } end describe 'validation' do @@ -22,6 +24,51 @@ describe Event do it { is_expected.to validate_presence_of(:program) } it { is_expected.to validate_presence_of(:event_type) } + describe '#max_attendees_and_require_registration' do + it 'allows user to set max_attendees, only if require_registration is set' do + event.require_registration = true + event.max_attendees = 2 + + expect(event.valid?).to eq true + end + + it 'does not allow max_attendees to be set without require_registration' do + event.max_attendees = 2 + + expect(event.valid?).to eq false + expect(event.errors[:require_registration]).to eq ['must be enabled, when you set max_attendees'] + end + + it 'does not allow require_registration to be set without max_attendees' do + event.require_registration = true + event.max_attendees = nil + + expect(event.valid?).to eq false + expect(event.errors[:max_attendees]).to eq ['must be enabled, when you set require_registration'] + end + end + + describe 'max_attendees_no_more_than_room_size' do + before :each do + event.room = create(:room, size: 3) + event.require_registration = true + end + + it 'it is valid, if max_attendees is less than room size' do + event.max_attendees = 2 + + expect(event.valid?).to eq true + expect(event.errors.full_messages).to eq [] + end + + it 'it is not valid, if max_attendees attribute is bigger than size of room' do + event.max_attendees = 4 + + expect(event.valid?).to eq false + expect(event.errors[:max_attendees]).to eq ['cannot be more than the room\'s capacity (3)'] + end + end + describe '#abstract_limit' do before :each do event.event_type.maximum_abstract_length = 2 @@ -89,6 +136,41 @@ describe Event do end end + describe '#scheduled?' do + it { expect(event.scheduled?).to eq false } + it 'returns true if the event is scheduled' do + event.room = create(:room) + event.start_time = conference.start_date.to_time + expect(event.scheduled?).to eq true + end + end + + describe '#registration_possible?' do + describe 'when the event requires registration' do + before :each do + event.require_registration = true + event.max_attendees = 3 + event.registrations << create(:registration) + end + + it 'returns true, if the limit has not been reached' do + expect(event.registration_possible?).to eq true + end + + it 'returns false, if the limit has been reached' do + event.registrations << create(:registration) + event.registrations << create(:registration) + expect(event.registration_possible?).to eq false + end + end + + describe 'when the event does not require registration' do + it 'returns false' do + expect(event.registration_possible?).to eq false + end + end + end + describe '#voted?' do it 'returns nil if the event has no votes' do expect(event.voted?(event, user)).to eq nil diff --git a/spec/models/registration_spec.rb b/spec/models/registration_spec.rb index eff512db..3e71b772 100644 --- a/spec/models/registration_spec.rb +++ b/spec/models/registration_spec.rb @@ -4,7 +4,7 @@ describe 'Registration' do subject { create(:registration) } let!(:user) { create(:user) } let!(:conference) { create(:conference) } - let!(:registration1) { create(:registration, conference: conference, user: user) } + let!(:registration) { create(:registration, conference: conference, user: user) } describe 'validation' do it 'has a valid factory' do @@ -33,7 +33,7 @@ describe 'Registration' do it { is_expected.to have_and_belong_to_many(:qanswers) } it { is_expected.to have_and_belong_to_many(:vchoices) } it { is_expected.to have_many(:events_registrations) } - it { is_expected.to have_many(:workshops) } + it { is_expected.to have_many(:events) } end describe 'after create' do @@ -69,12 +69,34 @@ describe 'Registration' do end end + describe 'registration_to_events_only_if_present' do + context 'valid' do + it 'when user registers for events happening while user is at the conference' do + registration.arrival = conference.start_date + registration.departure = conference.end_date + registration.events << create(:event, program: conference.program, start_time: conference.end_date) + + expect(registration.valid?).to eq true + end + end + + context 'invalid' do + it 'when user registers for events happening while user is not at the conference' do + registration.arrival = conference.start_date + registration.departure = conference.start_date + registration.events << create(:event, program: conference.program, start_time: conference.end_date) + + expect(registration.valid?).to eq false + end + end + end + describe '#destroy_purchased_tickets' do it 'destroys purchased tickets if tickets are purchased' do create(:ticket_purchase, conference: conference, user: user) expect(user.registrations.size).to be 1 expect(user.ticket_purchases.size).to be 1 - registration1.destroy + registration.destroy expect(user.registrations.size).to be 0 expect(user.ticket_purchases.size).to be 0 end @@ -82,7 +104,7 @@ describe 'Registration' do it 'destroys no tickets if no tickets are purchased' do expect(user.registrations.size).to be 1 expect(user.ticket_purchases.size).to be 0 - registration1.destroy + registration.destroy expect(user.registrations.size).to be 0 expect(user.ticket_purchases.size).to be 0 end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 7306987d..fd14ddbb 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -11,6 +11,10 @@ describe User do let(:organizer) { create(:user, role_ids: [organizer_role.id]) } let(:user) { create(:user) } + let(:event1) { create(:event, program: conference.program) } + let(:another_conference) { create(:conference) } + let(:event2) { create(:event, program: another_conference.program) } + describe 'validation' do it 'has a valid factory' do expect(build(:user)).to be_valid @@ -26,6 +30,7 @@ describe User do it { is_expected.to have_many(:event_users).dependent(:destroy) } it { is_expected.to have_many(:events).through(:event_users) } it { is_expected.to have_many(:registrations).dependent(:destroy) } + it { is_expected.to have_many(:events_registrations).through(:registrations) } it { is_expected.to have_many(:ticket_purchases).dependent(:destroy) } it { is_expected.to have_many(:tickets).through(:ticket_purchases) } it { is_expected.to have_many(:votes).dependent(:destroy) } @@ -341,4 +346,17 @@ describe User do expect(second_user.is_admin).to be false end end + + describe 'has_many events_registrations' do + before :each do + registration1 = create(:registration, user: user, conference: conference) + registration2 = create(:registration, user: user, conference: another_conference) + @events_registration1 = create(:events_registration, registration: registration1, event: event1) + @events_registration2 = create(:events_registration, registration: registration2, event: event2) + end + + it 'returns all the events the user registered to' do + expect(user.events_registrations).to eq [@events_registration1, @events_registration2] + end + end end diff --git a/spec/views/admin/events/index.html.haml_spec.rb b/spec/views/admin/events/index.html.haml_spec.rb index 07b9fd11..353e35a8 100644 --- a/spec/views/admin/events/index.html.haml_spec.rb +++ b/spec/views/admin/events/index.html.haml_spec.rb @@ -20,7 +20,7 @@ describe 'admin/events/index' do expect(rendered).to have_selector('table thead th:nth-of-type(2)', text: 'Title') expect(rendered).to have_selector('table thead th:nth-of-type(3)', text: 'Submitter') expect(rendered).to have_selector('table thead th:nth-of-type(4)', text: 'Speaker') - expect(rendered).to have_selector('table thead th:nth-of-type(5)', text: 'Pre-registration') + expect(rendered).to have_selector('table thead th:nth-of-type(5)', text: 'Requires Registration') expect(rendered).to have_selector('table thead th:nth-of-type(6)', text: 'Highlight') expect(rendered).to have_selector('table thead th:nth-of-type(7)', text: 'Type') expect(rendered).to have_selector('table thead th:nth-of-type(8)', text: 'Track')