From 5f8a8ac6d9b618a4eb0151c4b9fb64aecd423820 Mon Sep 17 00:00:00 2001 From: Chrisbr Date: Thu, 28 Aug 2014 15:21:05 +0200 Subject: [PATCH] Refactoring Tickets - Tickets now independent from registration - Implemented money gem #419 --- Gemfile | 5 +- Gemfile.lock | 10 + app/assets/javascripts/application.js | 1 + app/assets/javascripts/osem-datatables.js | 9 +- app/assets/javascripts/osem-tickets.js | 25 ++ .../admin/registrations_controller.rb | 13 +- .../admin/supporter_levels_controller.rb | 23 -- .../admin/supporters_controller.rb | 19 -- app/controllers/admin/tickets_controller.rb | 54 ++++ .../conference_registrations_controller.rb | 54 ++-- .../ticket_purchases_controller.rb | 29 ++ app/controllers/tickets_controller.rb | 8 + app/helpers/registration_helper.rb | 14 - app/models/ability.rb | 2 +- app/models/conference.rb | 27 +- app/models/datatable_supporters.rb | 40 --- app/models/event.rb | 8 + app/models/events_registration.rb | 6 + app/models/registration.rb | 20 +- app/models/supporter_level.rb | 6 - app/models/supporter_registration.rb | 13 - app/models/ticket.rb | 59 ++++ app/models/ticket_purchase.rb | 51 ++++ app/models/user.rb | 9 + app/views/admin/registrations/edit.html.haml | 11 - app/views/admin/registrations/index.html.haml | 4 - .../_supporter_level_fields.html.erb | 10 - .../admin/supporter_levels/index.html.haml | 7 - app/views/admin/tickets/_form.html.haml | 5 + app/views/admin/tickets/edit.html.haml | 6 + app/views/admin/tickets/index.html.haml | 38 +++ app/views/admin/tickets/new.html.haml | 6 + app/views/admin/tickets/show.html.haml | 33 ++ app/views/conference/_registration.html.haml | 2 +- app/views/conference/_tickets.html.haml | 21 +- .../_registration.html.haml | 15 +- .../_ticket.html.haml | 21 ++ .../_tickets.html.haml | 26 ++ .../_tickets_bought.html.haml | 46 +++ .../conference_registrations/show.html.haml | 126 ++++++++ app/views/home/_conference_details.html.haml | 2 + app/views/layouts/_admin_sidebar.html.haml | 8 +- app/views/tickets/_ticket.html.haml | 25 ++ app/views/tickets/index.html.haml | 37 +++ config/initializers/money.rb | 52 ++++ config/routes.rb | 6 +- ...124315_rename_supporter_level_to_ticket.rb | 9 + ...supporter_registrations_to_ticket_users.rb | 69 +++++ ...plit_ticket_price_in_price_and_currency.rb | 27 ++ db/schema.rb | 282 ++++++++---------- spec/factories/supporter_levels.rb | 8 - spec/factories/ticket_purchases.rb | 8 + spec/factories/tickets.rb | 8 + spec/features/ability_spec.rb | 12 +- spec/features/commercials_spec.rb | 92 +++++- spec/features/conference_registration_spec.rb | 52 ++++ spec/features/proposal_spec.rb | 183 +++++------- spec/features/supporter_levels_spec.rb | 46 --- spec/features/ticket_purchases_spec.rb | 51 ++++ spec/features/tickets_spec.rb | 86 ++++++ spec/models/ticket_purchase_spec.rb | 105 +++++++ spec/models/ticket_spec.rb | 92 ++++++ .../supporter_levels/index.html.haml_spec.rb | 12 - 63 files changed, 1581 insertions(+), 573 deletions(-) create mode 100644 app/assets/javascripts/osem-tickets.js delete mode 100644 app/controllers/admin/supporter_levels_controller.rb delete mode 100644 app/controllers/admin/supporters_controller.rb create mode 100644 app/controllers/admin/tickets_controller.rb create mode 100644 app/controllers/ticket_purchases_controller.rb create mode 100644 app/controllers/tickets_controller.rb delete mode 100644 app/helpers/registration_helper.rb delete mode 100644 app/models/datatable_supporters.rb create mode 100644 app/models/events_registration.rb delete mode 100644 app/models/supporter_level.rb delete mode 100644 app/models/supporter_registration.rb create mode 100644 app/models/ticket.rb create mode 100644 app/models/ticket_purchase.rb delete mode 100644 app/views/admin/supporter_levels/_supporter_level_fields.html.erb delete mode 100644 app/views/admin/supporter_levels/index.html.haml create mode 100644 app/views/admin/tickets/_form.html.haml create mode 100644 app/views/admin/tickets/edit.html.haml create mode 100644 app/views/admin/tickets/index.html.haml create mode 100644 app/views/admin/tickets/new.html.haml create mode 100644 app/views/admin/tickets/show.html.haml create mode 100644 app/views/conference_registrations/_ticket.html.haml create mode 100644 app/views/conference_registrations/_tickets.html.haml create mode 100644 app/views/conference_registrations/_tickets_bought.html.haml create mode 100644 app/views/conference_registrations/show.html.haml create mode 100644 app/views/tickets/_ticket.html.haml create mode 100644 app/views/tickets/index.html.haml create mode 100644 config/initializers/money.rb create mode 100644 db/migrate/20140819124315_rename_supporter_level_to_ticket.rb create mode 100644 db/migrate/20140820093735_migrating_supporter_registrations_to_ticket_users.rb create mode 100644 db/migrate/20140821103643_split_ticket_price_in_price_and_currency.rb delete mode 100644 spec/factories/supporter_levels.rb create mode 100644 spec/factories/ticket_purchases.rb create mode 100644 spec/factories/tickets.rb create mode 100644 spec/features/conference_registration_spec.rb delete mode 100644 spec/features/supporter_levels_spec.rb create mode 100644 spec/features/ticket_purchases_spec.rb create mode 100644 spec/features/tickets_spec.rb create mode 100644 spec/models/ticket_purchase_spec.rb create mode 100644 spec/models/ticket_spec.rb delete mode 100644 spec/views/admin/supporter_levels/index.html.haml_spec.rb diff --git a/Gemfile b/Gemfile index ff6ac62d..ff617e69 100644 --- a/Gemfile +++ b/Gemfile @@ -103,7 +103,10 @@ gem 'activeuuid' gem 'whenever', :require => false # We use daemons to run scripts -gem 'daemons' +gem 'daemons' + +# We use money-rails to encapsulate money in objects +gem 'money-rails' # Use guard and spring for testing in development group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 18e23731..7b74fd6a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -195,6 +195,15 @@ GEM minitest (5.4.0) momentjs-rails (2.8.1) railties (>= 3.1) + monetize (0.3.0) + money (~> 6.1.0.beta1) + money (6.1.1) + i18n (~> 0.6.4) + money-rails (0.12.0) + activesupport (>= 3.0) + monetize (~> 0.3.0) + money (~> 6.1.1) + railties (>= 3.0) multi_json (1.10.1) multi_xml (0.5.5) multipart-post (2.0.0) @@ -436,6 +445,7 @@ DEPENDENCIES letter_opener mina momentjs-rails (>= 2.8.1) + money-rails mysql2 omniauth omniauth-facebook diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index cea3f971..d78e69ec 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -29,6 +29,7 @@ //= require bootstrap-datetimepicker //= require osem-datepickers //= require osem-datatables +//= require osem-tickets $(document).ready(function() { $('a[disabled=disabled]').click(function(event){ diff --git a/app/assets/javascripts/osem-datatables.js b/app/assets/javascripts/osem-datatables.js index 135cec94..fc76f569 100644 --- a/app/assets/javascripts/osem-datatables.js +++ b/app/assets/javascripts/osem-datatables.js @@ -1,11 +1,14 @@ $(function () { $(document).ready(function() { $('#registrations-datatable').dataTable(); - } ); + }); $(document).ready(function() { $('#users-datatable').dataTable(); - } ); + }); $(document).ready(function() { $('#events-datatable').dataTable(); - } ); + }); + $(document).ready(function() { + $('#buyers-datatable').dataTable(); + }); } ); diff --git a/app/assets/javascripts/osem-tickets.js b/app/assets/javascripts/osem-tickets.js new file mode 100644 index 00000000..51926a3f --- /dev/null +++ b/app/assets/javascripts/osem-tickets.js @@ -0,0 +1,25 @@ +function update_price($this){ + var id = $this.data('id'); + + // Calculate price for row + var value = $this.val(); + var price = $('#price_' + id).text(); + $('#total_row_' + id).text(value * price); + + // Calculate total price + var total = 0; + $('.total_row').each(function( index ) { + total += parseInt($(this).text()); + }); + $('#total_price').text(total); +} + +$( document ).ready(function() { + $('.quantity').each(function() { + update_price($(this)); + }); + + $('.quantity').change(function() { + update_price($(this)); + }); +}); diff --git a/app/controllers/admin/registrations_controller.rb b/app/controllers/admin/registrations_controller.rb index 7d07e2de..45a3a301 100644 --- a/app/controllers/admin/registrations_controller.rb +++ b/app/controllers/admin/registrations_controller.rb @@ -17,13 +17,13 @@ module Admin flash[:notice] = "Successfully updated Attended for #{@user.email}" redirect_to admin_conference_registrations_path(@conference.short_title) else - flash[:notice] = "Update Attended for #{@user.email} failed!" + flash[:notice] = "Update Attended for #{@user.email} failed!" \ + "#{@registration.errors.full_messages.join('. ')}" redirect_to admin_conference_registrations_path(@conference.short_title) end end - def edit - end + def edit; end def update @registration.update_attributes(registration_params) @@ -51,7 +51,7 @@ module Admin protected def set_user - @user = User.where('id = ?', @registration.user_id).first + @user = User.find_by(id: @registration.user_id) end def registration_params @@ -63,10 +63,7 @@ module Admin qanswers_attributes: [], user_attributes: [ :id, :name, :tshirt, :mobile, :volunteer_experience, :languages, - :nickname, :affiliation ], - supporter_registration_attributes: [ - :id, :supporter_level_id, :code - ]) + :nickname, :affiliation ]) end end end diff --git a/app/controllers/admin/supporter_levels_controller.rb b/app/controllers/admin/supporter_levels_controller.rb deleted file mode 100644 index f0721c94..00000000 --- a/app/controllers/admin/supporter_levels_controller.rb +++ /dev/null @@ -1,23 +0,0 @@ -module Admin - class SupporterLevelsController < Admin::BaseController - load_and_authorize_resource :conference, find_by: :short_title - authorize_resource through: :conference - - def index - authorize! :update, SupporterLevel.new(conference_id: @conference.id) - end - - def show - render :supporter_levels - end - - def update - begin - @conference.update_attributes!(params[:conference]) - redirect_to(admin_conference_supporter_levels_path(conference_id: @conference.short_title), notice: 'Supporter levels were successfully updated.') - rescue => e - redirect_to(admin_conference_supporter_levels_path(conference_id: @conference.short_title), alert: "Supporter levels update failed: #{e.message}") - end - end - end -end diff --git a/app/controllers/admin/supporters_controller.rb b/app/controllers/admin/supporters_controller.rb deleted file mode 100644 index f1610631..00000000 --- a/app/controllers/admin/supporters_controller.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Admin - class SupportersController < Admin::BaseController - load_and_authorize_resource :conference, find_by: :short_title - load_and_authorize_resource through: :conference - - def index - respond_to do |format| - format.html - format.json { render json: DatatableSupporters.new(@conference.supporter_registrations, view_context) } - end - end - - def create - params[:supporter_registration][:conference_id] = @conference.id - SupporterRegistration.create!(params[:supporter_registration]) - redirect_to(admin_conference_supporters_path(conference_id: @conference.short_title), notice: "Supporter added") - end - end -end diff --git a/app/controllers/admin/tickets_controller.rb b/app/controllers/admin/tickets_controller.rb new file mode 100644 index 00000000..c90fe2cc --- /dev/null +++ b/app/controllers/admin/tickets_controller.rb @@ -0,0 +1,54 @@ +module Admin + class TicketsController < Admin::BaseController + load_and_authorize_resource :conference, find_by: :short_title + load_and_authorize_resource :ticket, through: :conference + + def index + authorize! :update, Ticket.new(conference_id: @conference.id) + end + + def new + @ticket = @conference.tickets.new + end + + def create + @ticket = @conference.tickets.new(ticket_params) + if @ticket.save(ticket_params) + redirect_to(admin_conference_tickets_path(conference_id: @conference.short_title), + notice: 'Ticket successfully created.') + else + flash[:alert] = "Creating Ticket failed: #{@ticket.errors.full_messages.join('. ')}." + render :new + end + end + + def edit; end + + def update + if @ticket.update_attributes(ticket_params) + redirect_to(admin_conference_tickets_path(conference_id: @conference.short_title), + notice: 'Ticket successfully updated.') + else + flash[:alert] = "Ticket update failed: #{@ticket.errors.full_messages.join('. ')}." + render :edit + end + end + + def destroy + if @ticket.destroy + redirect_to(admin_conference_tickets_path(conference_id: @conference.short_title), + notice: 'Ticket successfully destroyed.') + else + redirect_to(admin_conference_tickets_path(conference_id: @conference.short_title), + alert: "Ticket was successfully destroyed." \ + "#{@ticket.errors.full_messages.join('. ')}.") + end + end + + private + + def ticket_params + params[:ticket] + end + end +end diff --git a/app/controllers/conference_registrations_controller.rb b/app/controllers/conference_registrations_controller.rb index 48323b1f..47604cc3 100644 --- a/app/controllers/conference_registrations_controller.rb +++ b/app/controllers/conference_registrations_controller.rb @@ -2,17 +2,19 @@ class ConferenceRegistrationsController < ApplicationController before_filter :verify_user load_resource :conference, find_by: :short_title authorize_resource :conference_registrations, class: Registration - before_action :set_registration, only: [:edit, :update, :destroy] - before_action :set_workshops, only: [:new, :edit, :update, :create] + before_action :set_registration, only: [:edit, :update, :destroy, :show] def new @registration = current_user.registrations.build(conference_id: @conference.id) - @registration.build_supporter_registration end - def edit + def show + @workshops = @registration.workshops if @registration + @total_price = Ticket.total_price(@conference, current_user) end + def edit; end + def create user_attributes = registration_params[:user_attributes] params[:registration].delete :user_attributes @@ -24,16 +26,13 @@ class ConferenceRegistrationsController < ApplicationController # Trigger ahoy event ahoy.track 'Registered', title: 'New registration' - # Send registration mail - if @conference.email_settings.send_on_registration? - Mailbot.delay.registration_mail(@conference, current_user) + if @conference.tickets.any? + redirect_to conference_tickets_path(@conference.short_title), + notice: 'You are now registered and will be receiving E-Mail notifications.' + else + redirect_to conference_conference_registrations_path(@conference.short_title), + notice: 'You are now registered and will be receiving E-Mail notifications.' end - - # Set subscription for the conference - Subscription.create(conference_id: @conference.id, user_id: current_user.id) - - redirect_to edit_conference_conference_registrations_path(@conference.short_title), - notice: 'You are now registered and will be receiving E-Mail notifications.' else flash[:alert] = "A error prohibited the registration for #{@conference.title}: "\ "#{@registration.errors.full_messages.join('. ')}." @@ -42,9 +41,9 @@ class ConferenceRegistrationsController < ApplicationController end def update - if @registration.update(registration_params) - redirect_to edit_conference_conference_registrations_path(@conference.short_title), - notice: 'Registration was successfully updated.' + if @registration.update_attributes(registration_params) + redirect_to conference_conference_registrations_path(@conference.short_title), + notice: 'Registration was successfully updated.' else flash[:alert] = "A error prohibited the registration for #{@conference.title}: "\ "#{@registration.errors.full_messages.join('. ')}." @@ -53,19 +52,20 @@ class ConferenceRegistrationsController < ApplicationController end def destroy - @registration.destroy - redirect_to root_path, - notice: "You are not registered for #{@conference.title} anymore!" + if @registration.destroy + redirect_to root_path, + notice: "You are not registered for #{@conference.title} anymore!" + else + redirect_to root_path, + alert: "A error prohibited deleting the registration for #{@conference.title}: "\ + "#{@registration.errors.full_messages.join('. ')}." + end end protected - def set_workshops - @workshops = @conference.events.where('require_registration = ? AND state LIKE ?', true, 'confirmed') - end - def set_registration - @registration = current_user.registrations.where(conference_id: @conference.id).first + @registration = current_user.registrations.find_by(conference_id: @conference.id) end def registration_params @@ -77,9 +77,7 @@ class ConferenceRegistrationsController < ApplicationController qanswers_attributes: [], event_ids: [], user_attributes: [ - :id, :name, :tshirt, :mobile, :volunteer_experience, :languages], - supporter_registration_attributes: [ - :id, :supporter_level_id, :code - ]) + :id, :name, :tshirt, :mobile, :volunteer_experience, :languages] + ) end end diff --git a/app/controllers/ticket_purchases_controller.rb b/app/controllers/ticket_purchases_controller.rb new file mode 100644 index 00000000..d15a5f1c --- /dev/null +++ b/app/controllers/ticket_purchases_controller.rb @@ -0,0 +1,29 @@ +class TicketPurchasesController < ApplicationController + before_filter :verify_user + load_resource :conference, find_by: :short_title + authorize_resource :conference_registrations, class: Registration + + def create + message = TicketPurchase.purchase(@conference, current_user, params[:tickets][0]) + if message.blank? + redirect_to conference_conference_registrations_path(@conference.short_title), + notice: "Congratulations, you have successfully purchased a ticket! " \ + "You can pay it cash on check in! Thank you for supporting #{@conference.title}!" + else + redirect_to conference_conference_registrations_path(@conference.short_title), + alert: "Oops, something went wrong with your purchase! #{message}" + end + end + + def destroy + @ticket_purchases = current_user.ticket_purchases.find_by(ticket_id: params[:id]) + if @ticket_purchases.destroy + redirect_to conference_conference_registrations_path(@conference.short_title), + notice: 'Ticket successfully destroyed.' + else + redirect_to conference_conference_registrations_path(@conference.short_title), + notice: "A error prohibited deleting your purchase! "\ + "#{@ticket_purchases.errors.full_messages.join('. ')}." + end + end +end diff --git a/app/controllers/tickets_controller.rb b/app/controllers/tickets_controller.rb new file mode 100644 index 00000000..6b655847 --- /dev/null +++ b/app/controllers/tickets_controller.rb @@ -0,0 +1,8 @@ +class TicketsController < ApplicationController + before_filter :verify_user + load_resource :conference, find_by: :short_title + load_resource :tickets, class: Ticket + authorize_resource :conference_registrations, class: Registration + + def index; end +end diff --git a/app/helpers/registration_helper.rb b/app/helpers/registration_helper.rb deleted file mode 100644 index ab5f4a07..00000000 --- a/app/helpers/registration_helper.rb +++ /dev/null @@ -1,14 +0,0 @@ -module RegistrationHelper - def generate_supporter_level_js(conference) - str = "" - conference.supporter_levels.map do |t| - next if t.url.empty? - - str += "if ($('#registration_supporter_registration_attributes_supporter_level_id option:selected').text() == '#{t.title}') {\n" - str += "console.log('#{t.title}');\n" - str += "str = 'If you have a confirmation or registration code, enter it here. Otherwise, you can purchase a #{t.title} ticket here, if you need to.';\n" - str += "}\n\n" - end.join("\n") - str - end -end diff --git a/app/models/ability.rb b/app/models/ability.rb index 559c46c3..34734a53 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -97,7 +97,7 @@ class Ability can :manage, Room, conference_id: conf_ids_for_organizer + conf_ids_for_cfp can :manage, Sponsor, conference_id: conf_ids_for_organizer can :manage, SponsorshipLevel, conference_id: conf_ids_for_organizer - can :manage, SupporterLevel, conference_id: conf_ids_for_organizer + can :manage, Ticket, conference_id: conf_ids_for_organizer can :manage, Target, conference_id: conf_ids_for_organizer can :index, Commercial, commercialable_type: 'Conference' can :manage, Commercial, commercialable_type: 'Conference', commercialable_id: conf_ids_for_organizer diff --git a/app/models/conference.rb b/app/models/conference.rb index 4e7a5d98..460fab37 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -8,8 +8,8 @@ class Conference < ActiveRecord::Base attr_accessible :title, :short_title, :timezone, :html_export_path, :start_date, :end_date, :rooms_attributes, :tracks_attributes, - :dietary_choices_attributes, :use_dietary_choices, :use_supporter_levels, - :supporter_levels_attributes, :social_events_attributes, :event_types_attributes, + :dietary_choices_attributes, :use_dietary_choices, + :tickets_attributes, :social_events_attributes, :event_types_attributes, :logo, :questions_attributes, :question_ids, :answers_attributes, :answer_ids, :difficulty_levels_attributes, :use_difficulty_levels, :use_vpositions, :use_vdays, :vdays_attributes, @@ -33,12 +33,25 @@ class Conference < ActiveRecord::Base has_one :email_settings, dependent: :destroy has_one :call_for_papers, dependent: :destroy has_many :social_events, dependent: :destroy - has_many :supporter_registrations, dependent: :destroy - has_many :supporter_levels, dependent: :destroy + has_many :ticket_purchases + has_many :supporters, through: :ticket_purchases, source: :user + has_many :tickets, dependent: :destroy has_many :dietary_choices, dependent: :destroy - has_many :events, dependent: :destroy + has_many :events, dependent: :destroy do + def workshops + where(require_registration: true, state: :confirmed) + end + + def confirmed + where(state: :confirmed) + end + end has_many :event_users, through: :events - has_many :speakers, -> { distinct }, through: :event_users, source: :user + has_many :speakers, -> { distinct }, through: :event_users, source: :user do + def confirmed + joins(:events).where(events: { state: :confirmed }) + end + end has_many :event_types, dependent: :destroy has_many :tracks, dependent: :destroy has_many :difficulty_levels, dependent: :destroy @@ -62,7 +75,7 @@ class Conference < ActiveRecord::Base accepts_nested_attributes_for :social_events, allow_destroy: true accepts_nested_attributes_for :venue accepts_nested_attributes_for :dietary_choices, allow_destroy: true - accepts_nested_attributes_for :supporter_levels, allow_destroy: true + accepts_nested_attributes_for :tickets, allow_destroy: true accepts_nested_attributes_for :sponsorship_levels, allow_destroy: true accepts_nested_attributes_for :sponsors, allow_destroy: true accepts_nested_attributes_for :event_types, allow_destroy: true diff --git a/app/models/datatable_supporters.rb b/app/models/datatable_supporters.rb deleted file mode 100644 index 4f6c4f25..00000000 --- a/app/models/datatable_supporters.rb +++ /dev/null @@ -1,40 +0,0 @@ -class DatatableSupporters < Datatable - def data - arr = [] - items.each do |i| - item = [] - if i.name.blank? - if !i.registration.nil? && !i.registration.user.nil? - item << i.registration.user.name - else - item << 'Unknown' - end - - else - item << i.name - end - - if i.email.blank? - if !i.registration.nil? && !i.registration.user.nil? - item << i.registration.user.email - else - item << 'Unknown' - end - - else - item << i.email - end - - item << i.supporter_level.title - item << i.code - item << i.code_is_valid - arr << item - end - - arr - end - - def columns - ['name', 'email', 'name', 'name', 'name'] - end -end diff --git a/app/models/event.rb b/app/models/event.rb index 92bfeb15..12a222b4 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -206,6 +206,14 @@ class Event < ActiveRecord::Base alert end + def speaker_names + result = [] + speakers.each do |speaker| + result.push(speaker.name) + end + result.to_sentence + end + private def abstract_limit diff --git a/app/models/events_registration.rb b/app/models/events_registration.rb new file mode 100644 index 00000000..1f0869b8 --- /dev/null +++ b/app/models/events_registration.rb @@ -0,0 +1,6 @@ +class EventsRegistration < ActiveRecord::Base + attr_accessible :registration_id, :event_id + + belongs_to :registration + belongs_to :event +end diff --git a/app/models/registration.rb b/app/models/registration.rb index ae5de5b6..59162c95 100644 --- a/app/models/registration.rb +++ b/app/models/registration.rb @@ -3,21 +3,21 @@ class Registration < ActiveRecord::Base belongs_to :conference belongs_to :dietary_choice - has_one :supporter_registration - has_one :supporter_level, through: :supporter_registration has_and_belongs_to_many :social_events has_and_belongs_to_many :events has_and_belongs_to_many :qanswers has_and_belongs_to_many :vchoices + has_many :events_registrations + has_many :workshops, through: :events_registrations, source: :event + attr_accessible :user_id, :conference_id, :attending_social_events, :attending_with_partner, :using_affiliated_lodging, :arrival, :departure, :user_attributes, :attended, :other_dietary_choice, :dietary_choice_id, :handicapped_access_required, - :supporter_registration_attributes, :social_event_ids, :other_special_needs, + :social_event_ids, :other_special_needs, :event_ids, :volunteer, :vchoice_ids, :qanswer_ids, :qanswers_attributes accepts_nested_attributes_for :user - accepts_nested_attributes_for :supporter_registration accepts_nested_attributes_for :social_events accepts_nested_attributes_for :qanswers @@ -30,7 +30,7 @@ class Registration < ActiveRecord::Base validates_uniqueness_of :user_id, scope: :conference_id, message: 'already Registered!' - after_create :set_week + after_create :set_week, :subscribe_to_conference, :send_registration_mail def week created_at.strftime('%W').to_i @@ -38,6 +38,16 @@ class Registration < ActiveRecord::Base private + def subscribe_to_conference + Subscription.create(conference_id: conference.id, user_id: user.id) + end + + def send_registration_mail + if conference.email_settings.send_on_registration? + Mailbot.delay.registration_mail(conference, user) + end + end + def set_week self.week = created_at.strftime('%W') save! diff --git a/app/models/supporter_level.rb b/app/models/supporter_level.rb deleted file mode 100644 index deb12aa8..00000000 --- a/app/models/supporter_level.rb +++ /dev/null @@ -1,6 +0,0 @@ -class SupporterLevel < ActiveRecord::Base - belongs_to :conference - has_many :supporter_registrations - - attr_accessible :conference, :title, :url, :description, :ticket_price, :conference_id -end diff --git a/app/models/supporter_registration.rb b/app/models/supporter_registration.rb deleted file mode 100644 index 5b7482f1..00000000 --- a/app/models/supporter_registration.rb +++ /dev/null @@ -1,13 +0,0 @@ -class SupporterRegistration < ActiveRecord::Base - belongs_to :supporter_level - belongs_to :registration - before_save :set_attributes_from_user - - attr_accessible :registration, :supporter_level_id, :name, :email, :supporter_level, :code, :code_is_valid, :conference_id - - def set_attributes_from_user - self.name ||= registration.try(:user).try(:name) - self.email ||= registration.try(:user).try(:email) - true - end -end diff --git a/app/models/ticket.rb b/app/models/ticket.rb new file mode 100644 index 00000000..45558a5b --- /dev/null +++ b/app/models/ticket.rb @@ -0,0 +1,59 @@ +class Ticket < ActiveRecord::Base + belongs_to :conference + has_many :ticket_purchases + has_many :buyers, -> { distinct }, through: :ticket_purchases, source: :user + + attr_accessible :conference, :title, :url, :description, :conference_id, :price_cents, :price_currency, :price + monetize :price_cents, with_model_currency: :price_currency + + # This validation is for the sake of simplicity. + # If we would allow different currencies per conference we also have to handle convertions between currencies! + validate :tickets_of_conference_have_same_currency + + validates :price_cents, :price_currency, :title, presence: true + + validates_numericality_of :price_cents, greater_than: 0 + + def bought?(user) + buyers.include?(user) + end + + def paid?(user) + ticket_purchases.where(user_id: user.id, paid: false).count == 0 + end + + def quantity_bought_by(user) + result = ticket_purchases.where(user_id: user.id).first + result ? result.quantity : 0 + end + + def total_price(user) + quantity_bought_by(user) * price + end + + def self.total_price(conference, user) + tickets = Ticket.where(conference_id: conference.id) + result = nil + begin + tickets.each do |ticket| + price = ticket.total_price(user) + if result + result += price unless price.zero? + else + result = price + end + end + rescue Money::Bank::UnknownRate + result = Money.new(-1, 'USD') + end + result ? result : Money.new(0, 'USD') + end + + private + + def tickets_of_conference_have_same_currency + unless Ticket.where(conference_id: conference_id).all?{|t| t.price_currency == self.price_currency } + errors.add(:price_currency, 'Currency is different from the exist ticktes of this conference.') + end + end +end diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb new file mode 100644 index 00000000..6dd070e1 --- /dev/null +++ b/app/models/ticket_purchase.rb @@ -0,0 +1,51 @@ +class TicketPurchase < ActiveRecord::Base + belongs_to :ticket + belongs_to :user + belongs_to :conference + + attr_accessible :ticket_id, :user_id, :conference_id, :paid, :quantity + + validates :ticket_id, :user_id, :conference_id, :quantity, presence: true + + validates_numericality_of :quantity, greater_than: 0 + + validates_uniqueness_of :user_id, + scope: :ticket_id, + message: 'already bought this ticket!' + + def self.purchase(conference, user, purchases) + errors = [] + ActiveRecord::Base.transaction do + conference.tickets.each do |ticket| + quantity = purchases[ticket.id.to_s].to_i + if ticket.bought?(user) + purchase = update_quantity(conference, quantity, ticket, user) + else + purchase = purchase_ticket(conference, quantity, ticket, user) + end + + if purchase && !purchase.save + errors.push(purchase.errors.full_messages) + end + end + end + errors.join('. ') + end + + def self.purchase_ticket(conference, quantity, ticket, user) + purchase = new(ticket_id: ticket.id, + conference_id: conference.id, + user_id: user.id, + quantity: quantity) if quantity > 0 + purchase + end + + def self.update_quantity(conference, quantity, ticket, user) + purchase = TicketPurchase.where(ticket_id: ticket.id, + conference_id: conference.id, + user_id: user.id).first + + purchase.quantity = quantity if quantity > 0 + purchase + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 08d70dad..745df91b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -22,6 +22,8 @@ class User < ActiveRecord::Base has_many :event_users, dependent: :destroy has_many :events, -> { uniq }, through: :event_users has_many :registrations, dependent: :destroy + has_many :ticket_purchases + has_many :tickets, through: :ticket_purchases, source: :ticket has_many :votes, dependent: :destroy has_many :voted_events, through: :votes, source: :events has_many :subscriptions, dependent: :destroy @@ -29,6 +31,13 @@ class User < ActiveRecord::Base validates :name, presence: true + # Returns the ticket purchased ticket + # ====Returns + # * +TicketUser::ActiveRecord_Relation+ -> user + def ticket(id) + ticket_purchases.where(ticket_id: id).first + end + # Searches for user based on email. Returns found user or new user. # ====Returns # * +User::ActiveRecord_Relation+ -> user diff --git a/app/views/admin/registrations/edit.html.haml b/app/views/admin/registrations/edit.html.haml index e2e7e501..9780702b 100644 --- a/app/views/admin/registrations/edit.html.haml +++ b/app/views/admin/registrations/edit.html.haml @@ -12,10 +12,6 @@ = u.input :nickname, as: :string = u.input :affiliation, placeholder: 'Company/User Group/nothing', as: :string = f.inputs 'Registration Information' do - - if @conference.use_supporter_levels? and @conference.supporter_levels.length > 0 - = f.semantic_fields_for :supporter_registration do |reg| - = reg.input :supporter_level, as: :select, collection: @conference.supporter_levels - %span#supporter-link.help-block - @conference.questions.each do |q| %h5 = "Q: #{q.title}" @@ -33,10 +29,3 @@ %br = f.input :social_events, as: :check_boxes, label: false, collection: @conference.social_events = f.action :submit, button_html: { value: 'Edit Registration', class: 'btn btn-primary' } -:javascript - $("#registration_supporter_registration_attributes_supporter_level_id").change(function () { - var str = ""; - #{generate_supporter_level_js @conference} - $("#supporter-link").html(str); - }) - .trigger('change'); diff --git a/app/views/admin/registrations/index.html.haml b/app/views/admin/registrations/index.html.haml index 5e7ae965..6e81e341 100644 --- a/app/views/admin/registrations/index.html.haml +++ b/app/views/admin/registrations/index.html.haml @@ -16,7 +16,6 @@ %th # %th Name %th E-Mail - %th Ticket %th Arrival %th Departure %th Attended @@ -32,9 +31,6 @@ = registration.name %td = registration.email - %td - - if registration.supporter_level - = registration.supporter_level.title %td - if registration.arrival = registration.arrival.strftime("%d %b %H:%M") diff --git a/app/views/admin/supporter_levels/_supporter_level_fields.html.erb b/app/views/admin/supporter_levels/_supporter_level_fields.html.erb deleted file mode 100644 index 7b6621a9..00000000 --- a/app/views/admin/supporter_levels/_supporter_level_fields.html.erb +++ /dev/null @@ -1,10 +0,0 @@ -
- <%= f.inputs do %> - <%= f.input :title%> - <%= f.input :url %> - <%= f.input :description, hint: markdown_hint, input_html: { data: { provide: "markdown-editable" } } %> - <%= f.input :ticket_price, hint: 'Please enter price with currency symbol. - For example, $200,₹300' %> - <%= remove_association_link :supporter_level, f %> - <% end %> -
diff --git a/app/views/admin/supporter_levels/index.html.haml b/app/views/admin/supporter_levels/index.html.haml deleted file mode 100644 index 23ecee60..00000000 --- a/app/views/admin/supporter_levels/index.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -.row - .col-md-8 - = semantic_form_for(@conference, :url => admin_conference_supporter_level_path(@conference.short_title, @conference.supporter_levels)) do |f| - = f.input :use_supporter_levels, :label => false - = f.input :include_tickets_in_splash, hint: 'On setting this true you will enable the tickets to be displayed on the splash page' - = dynamic_association :supporter_levels, "Supporter Levels", f - = f.action :submit, :as => :button, :button_html => {:class => "btn btn-primary"} diff --git a/app/views/admin/tickets/_form.html.haml b/app/views/admin/tickets/_form.html.haml new file mode 100644 index 00000000..77e6c9e4 --- /dev/null +++ b/app/views/admin/tickets/_form.html.haml @@ -0,0 +1,5 @@ += f.input :title += f.input :description, input_html: { rows: 5, data: { provide: "markdown-editable" } } += f.input :price += f.input :price_currency, as: :select, class: 'form-control', collection: ['USD', 'EUR', 'GBP', 'INR', 'CNY'], include_blank: false += f.action :submit, as: :button, button_html: { class: 'btn btn-primary' } diff --git a/app/views/admin/tickets/edit.html.haml b/app/views/admin/tickets/edit.html.haml new file mode 100644 index 00000000..2bef591a --- /dev/null +++ b/app/views/admin/tickets/edit.html.haml @@ -0,0 +1,6 @@ +%h1 + Edit Ticket +.row + .col-md-8 + = semantic_form_for(@ticket, :url => admin_conference_ticket_path(@conference.short_title, @ticket)) do |f| + = render partial: 'form', locals: { f: f } diff --git a/app/views/admin/tickets/index.html.haml b/app/views/admin/tickets/index.html.haml new file mode 100644 index 00000000..f8d30ed9 --- /dev/null +++ b/app/views/admin/tickets/index.html.haml @@ -0,0 +1,38 @@ +%h1 Tickets +%p.lead + If you add Tickets to your Conference, people will be redirected after registration to a page where they can purchase tickets. +- if @conference.tickets.any? + .row + .col-md-12 + %table.table + %thead + %th # + %th Title + %th Price + %th Buyer + %th Show + %th Edit + %th Delete + %tbody + - @conference.tickets.each_with_index do |ticket, index| + %tr + %td + = index + 1 + %td + = ticket.title + %td + = humanized_money_with_symbol ticket.price + %td + = ticket.buyers.count + %td + = link_to 'Show', admin_conference_ticket_path(@conference.short_title, ticket.id), + method: :get, class: 'btn btn-success' + %td + = link_to 'Edit', edit_admin_conference_ticket_path(@conference.short_title, ticket.id), + method: :get, class: 'btn btn-primary' + %td + = link_to 'Delete', admin_conference_ticket_path(@conference.short_title, ticket.id), + method: :delete, class: 'btn btn-danger', data: { confirm: "Do you really want to delete the Ticket for #{ticket.title}?" } + += link_to 'Add Ticket', new_admin_conference_ticket_path, class: 'btn btn-success' + diff --git a/app/views/admin/tickets/new.html.haml b/app/views/admin/tickets/new.html.haml new file mode 100644 index 00000000..129de0c7 --- /dev/null +++ b/app/views/admin/tickets/new.html.haml @@ -0,0 +1,6 @@ +%h1 + New Ticket +.row + .col-md-8 + = semantic_form_for(@ticket, :url => admin_conference_tickets_path(@conference.short_title, @ticket)) do |f| + = render partial: 'form', locals: { f: f } diff --git a/app/views/admin/tickets/show.html.haml b/app/views/admin/tickets/show.html.haml new file mode 100644 index 00000000..066b8648 --- /dev/null +++ b/app/views/admin/tickets/show.html.haml @@ -0,0 +1,33 @@ +%h1 + = @ticket.title + %small + = humanized_money_with_symbol @ticket.price +- if @ticket.description.present? + %p.lead + = markdown(@ticket.description) + +%h4 The following persons bought this ticket: +%table.table#buyers-datatable + %thead + %th # + %th Name + %th E-Mail + %th Affiliation + %th Paid + %tbody + - @ticket.buyers.each_with_index do |buyer, index| + %tr + %td + = index + 1 + %td + = buyer.name + %span.label.label-success + = buyer.ticket_purchases.find_by(ticket_id: @ticket.id).quantity + %td + = buyer.email + %td + = buyer.affiliation + %td + = @ticket.paid?(buyer) += link_to 'Edit', edit_admin_conference_ticket_path(@conference.short_title, @ticket.id), + method: :get, class: 'btn btn-primary' diff --git a/app/views/conference/_registration.html.haml b/app/views/conference/_registration.html.haml index 4c4d7c90..f9816397 100644 --- a/app/views/conference/_registration.html.haml +++ b/app/views/conference/_registration.html.haml @@ -17,6 +17,6 @@ = link_to "Modify Registration for #{@conference.short_title}", edit_conference_conference_registrations_path(@conference.short_title), class: "btn btn-success btn-lg", target: '_blank' - else = link_to "Register for #{@conference.short_title}", new_conference_conference_registrations_path(@conference.short_title), class: "btn btn-success btn-lg", target: '_blank' - - if @conference.use_supporter_levels? + - if @conference.tickets.any? - if @conference.include_tickets_in_splash? = render 'tickets' diff --git a/app/views/conference/_tickets.html.haml b/app/views/conference/_tickets.html.haml index 635a82f2..82f17408 100644 --- a/app/views/conference/_tickets.html.haml +++ b/app/views/conference/_tickets.html.haml @@ -1,16 +1,15 @@ %div.row %h3 Tickets - -if !@conference.ticket_description.blank? + -if @conference.ticket_description.present? .lead = markdown(@conference.ticket_description) - - @conference.supporter_levels.each do |s| + - @conference.tickets.each do |ticket| %div.col-md-6 - - if !s.title.blank? - %h4 #{ s.title } - -if !s.description.blank? - .lead #{ s.description } - - if !s.url.blank? - %div.btn-group - = link_to "Buy Ticket", s.url, class: 'btn btn-success', target: '_blank' - - if !s.ticket_price.blank? - = button_tag "#{s.ticket_price}", class: 'btn btn-success' + - if ticket.title.present? + %h4 #{ ticket.title } + - if ticket.description.present? + .lead + = markdown(ticket.description) + %div.btn-group + = link_to "Buy Ticket", conference_tickets_path(@conference.short_title), class: 'btn btn-success' + = button_tag "#{humanized_money_with_symbol ticket.price}", class: 'btn btn-success' diff --git a/app/views/conference_registrations/_registration.html.haml b/app/views/conference_registrations/_registration.html.haml index fc95c6eb..bf7e3624 100644 --- a/app/views/conference_registrations/_registration.html.haml +++ b/app/views/conference_registrations/_registration.html.haml @@ -14,22 +14,11 @@ - if @conference.questions = render partial: 'questions', locals: { f: f } %br - - if @conference.use_supporter_levels? && @conference.supporter_levels.length > 0 - = f.semantic_fields_for :supporter_registration do |reg| - = reg.input :supporter_level, as: :select, collection: @conference.supporter_levels - %span#supporter-link.help-block - - if @workshops.count > 0 + - if @conference.events.workshops.any? =f.inputs 'Register to Workshops' do - = f.input :events, as: :check_boxes, label: false, collection: @workshops + = f.input :events, as: :check_boxes, label: false, collection: @conference.events.workshops = f.inputs 'Travel Info' do = f.input :arrival, as: :string, input_html: { value: (f.object.arrival.to_formatted_s(:db_without_seconds) unless f.object.arrival.nil?), id: 'registration-arrival-datepicker', readonly: 'readonly' } = f.input :departure, as: :string, input_html: { value: (f.object.departure.to_formatted_s(:db_without_seconds) unless f.object.departure.nil?), id: 'registration-departure-datepicker', readonly: 'readonly' } - -:javascript - $("#registration_supporter_registration_attributes_supporter_level_id").change(function () { - var str = ""; - #{generate_supporter_level_js @conference} - $("#supporter-link").html(str); - }).trigger('change'); diff --git a/app/views/conference_registrations/_ticket.html.haml b/app/views/conference_registrations/_ticket.html.haml new file mode 100644 index 00000000..e8ac5510 --- /dev/null +++ b/app/views/conference_registrations/_ticket.html.haml @@ -0,0 +1,21 @@ +%tr + %td.col-sm-8.col-md-6 + .media + .media-body + %h4.media-heading + = ticket.title + %h5.media-heading + -if !ticket.description.blank? + = markdown(ticket.description) + %td.col-sm-1.col-md-1 + = text_field_tag("tickets[][#{ticket.id}]", 0, type: 'number', min: 0, + class: 'form-control quantity', 'data-id' => ticket.id) + %td.col-sm-1.col-md-1.text-center + = ticket.price.symbol + %span{id: "price_#{ticket.id}"} + = humanized_money ticket.price + %td.col-sm-1.col-md-1.text-center + %strong + = ticket.price.symbol + %span.total_row{id: "total_row_#{ticket.id}"} + 0 diff --git a/app/views/conference_registrations/_tickets.html.haml b/app/views/conference_registrations/_tickets.html.haml new file mode 100644 index 00000000..82a995b9 --- /dev/null +++ b/app/views/conference_registrations/_tickets.html.haml @@ -0,0 +1,26 @@ += form_tag(conference_ticket_purchases_path, method: :post) do |f| + %table.table.table-hover + %thead + %tr + %th Ticket + %th Quantity + %th Price + %th Total + %tbody + - tickets.each do |ticket| + = render partial: 'ticket', f: f, locals: { ticket: ticket } + %tr + %td + %td + %td.col-sm-1.col-md-1.text-center + %h4 + Total + %td.col-sm-1.col-md-1.text-center + %h4 + %strong + %span{ id: 'total_price' } + 0 + %p + = button_tag(type: 'submit', class: 'btn btn-success btn-lg pull-right') do + Support + %i.fa.fa-shopping-cart diff --git a/app/views/conference_registrations/_tickets_bought.html.haml b/app/views/conference_registrations/_tickets_bought.html.haml new file mode 100644 index 00000000..20b2f9dd --- /dev/null +++ b/app/views/conference_registrations/_tickets_bought.html.haml @@ -0,0 +1,46 @@ +%table.table.table-hover + %thead + %tr + %th Ticket + %th.text-center Quantity + %th.text-center Price + %th.text-center Total + %th + %tbody + - tickets.each do |ticket| + %tr + %td.col-sm-8.col-md-6 + .media + .media-body + %h4.media-heading + = ticket.title + %h5.media-heading + -if !ticket.description.blank? + = markdown(ticket.description) + %td.col-sm-1.col-md-1.text-center + = ticket.quantity_bought_by(current_user) + %td.col-sm-1.col-md-1.text-center + = humanized_money_with_symbol ticket.price + %td.col-sm-1.col-md-1.text-center + %strong + = ticket.total_price(current_user) + %td.col-sm-1.col-md-1.text-center + = link_to conference_ticket_purchase_path(@conference.short_title, ticket.id), + method: :delete, class: 'btn btn-danger', + data: { confirm: "Do you really want to delete the #{ticket.title} for #{@conference.title}?" } do + Delete + %i.fa.fa-trash-o + %tr + %td + %td + %td + %td.col-sm-1.col-md-1.text-center + %h4 + Total + %td.col-sm-1.col-md-1.text-center + %h4 + %strong + - if @total_price.cents == -1 + not-calculable + - else + = humanized_money_with_symbol Ticket.total_price(@conference, current_user) diff --git a/app/views/conference_registrations/show.html.haml b/app/views/conference_registrations/show.html.haml new file mode 100644 index 00000000..e09955ef --- /dev/null +++ b/app/views/conference_registrations/show.html.haml @@ -0,0 +1,126 @@ +%h1 + = @conference.title + %small + = date_string(@conference.start_date, @conference.end_date) +- if @conference.venue.name and @conference.venue.website and @conference.venue.address + %p + %small + at + = link_to @conference.venue.name, @conference.venue.website + , + \#{link_to @conference.venue.address, "http://maps.google.com/maps?q=#{@conference.venue.address}"} +.row + .col-md-4 + .row + - if @conference.contact.facebook.present? + %div.col-md-3 + = link_to "#{ @conference.contact.facebook }" do + %i.fa.fa-facebook-square.fa-2x + - if @conference.contact.twitter.present? + %div.col-md-3 + = link_to "#{ @conference.contact.twitter }" do + %i.fa.fa-twitter.fa-2x + - if @conference.contact.instagram.present? + %div.col-md-3 + = link_to "#{ @conference.contact.instagram }" do + %i.fa.fa-instagram.fa-2x + - if @conference.contact.googleplus.present? + %div.col-md-3 + = link_to "#{ @conference.contact.googleplus }" do + %i.fa.fa-google-plus-square.fa-2x + +%br + +- if @conference.tickets.any? + %h3 + Tickets + - if current_user.tickets.any? + %p + You have already purchased the following tickets: + %p + If you would like to buy more tickets, please click + = link_to 'here', conference_tickets_path(@conference.short_title) + = render partial: 'tickets_bought', locals: { tickets: current_user.tickets } + + - else + = render partial: 'tickets', locals: { tickets: @conference.tickets } + %br +- if @conference.speakers.confirmed.any? + %h3 + = pluralize(@conference.speakers.confirmed.count, 'Speaker') + - @conference.speakers.confirmed.limit(12).each_slice(4) do |slice| + .row + - slice.each do |speaker| + .col-md-3 + .row + .col-md-3 + = image_tag(speaker.gravatar_url(size: '25'), + title: "Yo #{speaker.name}!", + alt: '', 'class' => 'img-circle img-responsive text-center') + .col-md-9 + %h4 + = speaker.name + %hr + +- if @conference.events.confirmed.any? + %h3 + = pluralize(@conference.events.confirmed.count, 'Event') + %ul.list-unstyled + - @conference.events.confirmed.limit(10).each do |event| + %li + %h4 + = link_to event.title, conference_proposal_path(@conference.short_title, event.id) + %strong + presented by + = event.speaker_names + %hr + +- if @conference.participants.any? + %h3 + = pluralize(@conference.participants.count, 'Participant') + - @conference.participants.limit(36).each_slice(12) do |slice| + .row + - slice.each do |participant| + .col-md-1 + = image_tag(participant.gravatar_url(size: '25'), + title: "Yo #{participant.name}!", + alt: '', 'class' => 'img-circle img-responsive text-center') + %hr + +- if @registration + %h2 + Congratulations! You are now registered for + = "#{@conference.title}!" + + - if @conference.questions.any? + %h3 Your answers to the registrations questions are: + - @conference.questions.each do |q| + %p + %b Question: + = q.title + + %b Your Answer: + - @registration.qanswers.where(:question_id => q.id).each do |qa| + = qa.answer.title + %br + + - if @workshops.any? + %h3 You are registered for the following workshops: + %ul.list-unstyled + - @workshops.each do |workshop| + %li + %h4 + = link_to workshop.title, conference_proposal_path(@conference.short_title, workshop.id) + %strong + presented by + = workshop.speaker_names + %br + + %div + = link_to 'Modify your Registration', edit_conference_conference_registrations_path(@conference.short_title), class: 'btn btn-success' + = link_to 'Unregister', conference_conference_registrations_path(@conference.short_title), + method: :delete, class: 'btn btn-danger', confirm: 'Are you sure you want to unregister?' +- else + %p.lead + = "Unfortunately you are not registered for #{@conference.title}. If you want to register click" + = link_to 'here.', new_conference_conference_registrations_path(@conference.short_title) diff --git a/app/views/home/_conference_details.html.haml b/app/views/home/_conference_details.html.haml index 4a29a228..3188bc9b 100644 --- a/app/views/home/_conference_details.html.haml +++ b/app/views/home/_conference_details.html.haml @@ -39,3 +39,5 @@ = link_to "View My Proposals", conference_proposal_index_path(conference.short_title), :class =>"btn btn-default" - elsif conference.cfp_open? = link_to "Submit Proposal", conference_proposal_index_path(conference.short_title), :class =>"btn btn-default" + - if !current_user.nil? && conference.tickets.any? + = link_to 'Support', conference_tickets_path(conference.short_title), class: 'btn btn-default' \ No newline at end of file diff --git a/app/views/layouts/_admin_sidebar.html.haml b/app/views/layouts/_admin_sidebar.html.haml index d37d35c0..a9abb5ca 100644 --- a/app/views/layouts/_admin_sidebar.html.haml +++ b/app/views/layouts/_admin_sidebar.html.haml @@ -102,11 +102,11 @@ - if can? :update, @conference.sponsors.build %li{:class=> active_nav_li(admin_conference_sponsors_path(@conference.short_title))} = link_to 'Sponsors', admin_conference_sponsors_path(@conference.short_title) - - if can? :update, @conference.supporter_levels.build - %li{ class: active_nav_li(admin_conference_supporter_levels_path(@conference.short_title)) } - = link_to(admin_conference_supporter_levels_path(@conference.short_title)) do + - if can? :update, @conference.tickets.build + %li{ class: active_nav_li(admin_conference_tickets_path(@conference.short_title)) } + = link_to(admin_conference_tickets_path(@conference.short_title)) do %span.fa.fa-usd - Supporter Levels + Tickets - if can? :update, @conference.email_settings %li{:class=> active_nav_li(admin_conference_emails_path(@conference.short_title))} = link_to(admin_conference_emails_path(@conference.short_title)) do diff --git a/app/views/tickets/_ticket.html.haml b/app/views/tickets/_ticket.html.haml new file mode 100644 index 00000000..9f2d731f --- /dev/null +++ b/app/views/tickets/_ticket.html.haml @@ -0,0 +1,25 @@ +%tr + %td.col-sm-8.col-md-6 + .media + .media-body + %h4.media-heading + = ticket.title + %h5.media-heading + -if !ticket.description.blank? + = markdown(ticket.description) + %td.col-sm-1.col-md-1 + - if ticket.bought?(current_user) + = text_field_tag("tickets[][#{ticket.id}]", ticket.quantity_bought_by(current_user), + type: 'number', min: 0, class: 'form-control quantity', 'data-id' => ticket.id) + - else + = text_field_tag("tickets[][#{ticket.id}]", 0, type: 'number', min: 0, + class: 'form-control quantity', 'data-id' => ticket.id) + %td.col-sm-1.col-md-1.text-center + = ticket.price.symbol + %span{id: "price_#{ticket.id}"} + = humanized_money ticket.price + %td.col-sm-1.col-md-1.text-center + %strong + = ticket.price.symbol + %span.total_row{id: "total_row_#{ticket.id}"} + 0 diff --git a/app/views/tickets/index.html.haml b/app/views/tickets/index.html.haml new file mode 100644 index 00000000..435019c5 --- /dev/null +++ b/app/views/tickets/index.html.haml @@ -0,0 +1,37 @@ +.row + .col-sm-12.col-md-10.col-md-offset-1 + %h1 + Tickets + %p.lead + Please buy a ticket if you want to support + %b + = @conference.title + ! + =form_tag(conference_ticket_purchases_path, method: :post) do |f| + %table.table.table-hover + %thead + %tr + %th Ticket + %th Quantity + %th Price + %th Total + %tbody + - @conference.tickets.each do |ticket| + = render partial: 'ticket', f: f, locals: {ticket: ticket} + %tr + %td + %td + %td.col-sm-1.col-md-1.text-center + %h4 + Total + %td.col-sm-1.col-md-1.text-center + %h4 + %strong + %span{id: 'total_price'} + 0 + .pull-right + = button_tag(type: 'submit', class: 'btn btn-success btn-lg') do + Support + %i.fa.fa-shopping-cart + = link_to 'Continue without a Ticket!', conference_conference_registrations_path(@conference.short_title), + class: 'btn btn-danger btn-sm' diff --git a/config/initializers/money.rb b/config/initializers/money.rb new file mode 100644 index 00000000..2e218202 --- /dev/null +++ b/config/initializers/money.rb @@ -0,0 +1,52 @@ +# encoding : utf-8 + +MoneyRails.configure do |config| + + # To set the default currency + # + config.default_currency = :usd + + # Set default bank object + # + # Example: + # config.default_bank = EuCentralBank.new + + # Add exchange rates to current money bank object. + # (The conversion rate refers to one direction only) + # + # Example: + # config.add_rate "USD", "CAD", 1.24515 + # config.add_rate "CAD", "USD", 0.803115 + + # To handle the inclusion of validations for monetized fields + # The default value is true + # + # config.include_validations = true + + # Default ActiveRecord migration configuration values for columns: + # + # config.amount_column = { prefix: '', # column name prefix + # postfix: '_cents', # column name postfix + # column_name: nil, # full column name (overrides prefix, postfix and accessor name) + # type: :integer, # column type + # present: true, # column will be created + # null: false, # other options will be treated as column options + # default: 0 + # } + # + # config.currency_column = { prefix: '', + # postfix: '_currency', + # column_name: nil, + # type: :string, + # present: true, + # null: false, + # default: 'USD' + # } + + # Set money formatted output globally. + # Default value is nil meaning "ignore this option". + # Options are nil, true, false. + # + # config.no_cents_if_whole = nil + # config.symbol = nil +end diff --git a/config/routes.rb b/config/routes.rb index 2273cee1..bdec1b12 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -56,7 +56,7 @@ Osem::Application.routes.draw do resources :social_events, only: [:show, :update, :index] - resources :supporter_levels, only: [:show, :update, :index] + resources :tickets resources :emails, only: [:show, :update, :index] @@ -83,8 +83,6 @@ Osem::Application.routes.draw do end resource :speaker, only: [:edit, :update] end - - resources :supporters end end @@ -99,6 +97,8 @@ Osem::Application.routes.draw do end resource :conference_registrations, path: 'register' + resources :tickets, only: [:index] + resources :ticket_purchases, only: [:create, :destroy] resource :schedule, only: [] do get "/" => "schedule#index" diff --git a/db/migrate/20140819124315_rename_supporter_level_to_ticket.rb b/db/migrate/20140819124315_rename_supporter_level_to_ticket.rb new file mode 100644 index 00000000..38fb10b6 --- /dev/null +++ b/db/migrate/20140819124315_rename_supporter_level_to_ticket.rb @@ -0,0 +1,9 @@ +class RenameSupporterLevelToTicket < ActiveRecord::Migration + def up + rename_table :supporter_levels, :tickets + end + + def down + rename_table :tickets, :supporter_levels + end +end diff --git a/db/migrate/20140820093735_migrating_supporter_registrations_to_ticket_users.rb b/db/migrate/20140820093735_migrating_supporter_registrations_to_ticket_users.rb new file mode 100644 index 00000000..e3208691 --- /dev/null +++ b/db/migrate/20140820093735_migrating_supporter_registrations_to_ticket_users.rb @@ -0,0 +1,69 @@ +class MigratingSupporterRegistrationsToTicketUsers < ActiveRecord::Migration + class TempSupporterRegistrations < ActiveRecord::Base + self.table_name = 'supporter_registrations' + attr_accessible :conference_id, :supporter_level_id, :registration_id, :user_id + end + + class TempUser < ActiveRecord::Base + self.table_name = 'users' + attr_accessible :user_id + end + + class TempRegistration < ActiveRecord::Base + self.table_name = 'registrations' + attr_accessible :user_id + end + + def change + rename_column :supporter_registrations, :supporter_level_id, :ticket_id + rename_column :supporter_registrations, :code_is_valid, :paid + + add_column :supporter_registrations, :quantity, :integer, default: 1 + add_column :supporter_registrations, :user_id, :integer + + deleted_user = TempUser.find_by(email: 'deleted@localhost.osem') + + TempSupporterRegistrations.all.each do |s| + # Change relation from registration to user + registration = TempRegistration.find_by(id: s.registration_id) + if registration + user = TempUser.find_by(id: registration.user_id) + if user + s.user_id = user.id + s.save + end + end + if !s.user_id + s.user_id = deleted_user.id + s.save + end + end + + # Sum up if a user has bought more than one ticket + TempSupporterRegistrations.all.each do |s| + sup_reg = TempSupporterRegistrations.where( + ticket_id: s.ticket_id, + user_id: s.user_id, + conference_id: s.conference_id) + quantity = sup_reg.count + + if quantity > 1 + # Save the amount in the first one + s.quantity = quantity + s.save + + # Delete the other + sup_reg = sup_reg.where('id not in (?)', [s.id]) + sup_reg.destroy_all + end + end + + remove_column :supporter_registrations, :registration_id + remove_column :supporter_registrations, :code + remove_column :supporter_registrations, :name + remove_column :supporter_registrations, :email + remove_column :conferences, :use_supporter_levels + + rename_table :supporter_registrations, :ticket_purchases + end +end diff --git a/db/migrate/20140821103643_split_ticket_price_in_price_and_currency.rb b/db/migrate/20140821103643_split_ticket_price_in_price_and_currency.rb new file mode 100644 index 00000000..a2dbf9ce --- /dev/null +++ b/db/migrate/20140821103643_split_ticket_price_in_price_and_currency.rb @@ -0,0 +1,27 @@ +class SplitTicketPriceInPriceAndCurrency < ActiveRecord::Migration + class TempTicket < ActiveRecord::Base + self.table_name = 'tickets' + attr_accessible :ticket_price, :price_cents, :price_currency + end + + def change + add_money :tickets, :price + + TempTicket.all.each do |ticket| + # Replace currency symbol with ISO Code + ticket.ticket_price.gsub!('€', 'EUR') + ticket.ticket_price.gsub!('$', 'USD') + ticket.ticket_price.gsub!('£', 'GBP') + ticket.ticket_price.gsub!('¥', 'CNY') + ticket.ticket_price.gsub!('₹', 'INR') + + money = ticket.ticket_price.to_money + ticket.price_cents = money.cents + ticket.price_currency = money.currency_as_string + ticket.save + end + + remove_column :tickets, :ticket_price + remove_column :tickets, :url + end +end diff --git a/db/schema.rb b/db/schema.rb index fcc3564f..eed1ea9c 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: 20140820124117) do +ActiveRecord::Schema.define(version: 20140821103643) do create_table "ahoy_events", force: true do |t| t.uuid "visit_id" @@ -21,27 +21,27 @@ ActiveRecord::Schema.define(version: 20140820124117) do t.datetime "time" end - add_index "ahoy_events", ["time"], name: "index_ahoy_events_on_time", using: :btree - add_index "ahoy_events", ["user_id"], name: "index_ahoy_events_on_user_id", using: :btree - add_index "ahoy_events", ["visit_id"], name: "index_ahoy_events_on_visit_id", using: :btree + add_index "ahoy_events", ["time"], name: "index_ahoy_events_on_time" + add_index "ahoy_events", ["user_id"], name: "index_ahoy_events_on_user_id" + add_index "ahoy_events", ["visit_id"], name: "index_ahoy_events_on_visit_id" create_table "answers", force: true do |t| t.string "title" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" end create_table "call_for_papers", force: true do |t| - t.date "start_date", null: false - t.date "end_date", null: false - t.text "description", limit: 16777215, null: false + t.date "start_date", null: false + t.date "end_date", null: false + t.text "description", null: false t.integer "conference_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "schedule_changes", default: false - t.integer "rating", default: 3 + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "schedule_changes", default: false + t.integer "rating", default: 3 t.boolean "schedule_public" - t.boolean "include_cfp_in_splash", default: false + t.boolean "include_cfp_in_splash", default: false end create_table "campaigns", force: true do |t| @@ -57,22 +57,22 @@ ActiveRecord::Schema.define(version: 20140820124117) do end create_table "comments", force: true do |t| - t.string "title", limit: 50, default: "" - t.text "body", limit: 16777215 + t.string "title", limit: 50, default: "" + t.text "body" t.integer "commentable_id" t.string "commentable_type" t.integer "user_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" t.string "subject" t.integer "parent_id" t.integer "lft" t.integer "rgt" end - add_index "comments", ["commentable_id"], name: "index_comments_on_commentable_id", using: :btree - add_index "comments", ["commentable_type"], name: "index_comments_on_commentable_type", using: :btree - add_index "comments", ["user_id"], name: "index_comments_on_user_id", using: :btree + add_index "comments", ["commentable_id"], name: "index_comments_on_commentable_id" + add_index "comments", ["commentable_type"], name: "index_comments_on_commentable_type" + add_index "comments", ["user_id"], name: "index_comments_on_user_id" create_table "commercials", force: true do |t| t.string "commercial_id" @@ -92,14 +92,13 @@ ActiveRecord::Schema.define(version: 20140820124117) do t.date "start_date", null: false t.date "end_date", null: false t.integer "venue_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" t.string "logo_file_name" t.string "logo_content_type" t.integer "logo_file_size" t.datetime "logo_updated_at" t.boolean "use_dietary_choices", default: false - t.boolean "use_supporter_levels", default: false t.integer "revision" t.boolean "use_vpositions", default: false t.boolean "use_vdays", default: false @@ -111,12 +110,12 @@ ActiveRecord::Schema.define(version: 20140820124117) do t.text "sponsor_description" t.string "sponsor_email" t.text "lodging_description" - t.boolean "make_conference_public", default: false t.boolean "include_registrations_in_splash", default: false t.boolean "include_sponsors_in_splash", default: false t.boolean "include_tracks_in_splash", default: false t.boolean "include_tickets_in_splash", default: false t.boolean "include_program_in_splash", default: false + t.boolean "make_conference_public", default: false t.string "banner_photo_file_name" t.string "banner_photo_content_type" t.integer "banner_photo_file_size" @@ -157,13 +156,13 @@ ActiveRecord::Schema.define(version: 20140820124117) do t.datetime "updated_at" end - add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree + add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority" create_table "dietary_choices", force: true do |t| t.integer "conference_id" t.string "title", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" end create_table "difficulty_levels", force: true do |t| @@ -171,37 +170,37 @@ ActiveRecord::Schema.define(version: 20140820124117) do t.string "title" t.text "description" t.string "color" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" end create_table "email_settings", force: true do |t| t.integer "conference_id" - t.boolean "send_on_registration", default: false - t.boolean "send_on_accepted", default: false - t.boolean "send_on_rejected", default: false - t.boolean "send_on_confirmed_without_registration", default: false - t.text "registration_email_template", limit: 16777215 - t.text "accepted_email_template", limit: 16777215 - t.text "rejected_email_template", limit: 16777215 - t.text "confirmed_email_template", limit: 16777215 - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.boolean "send_on_registration", default: false + t.boolean "send_on_accepted", default: false + t.boolean "send_on_rejected", default: false + t.boolean "send_on_confirmed_without_registration", default: false + t.text "registration_email_template" + t.text "accepted_email_template" + t.text "rejected_email_template" + t.text "confirmed_email_template" + t.datetime "created_at" + t.datetime "updated_at" t.string "registration_subject" t.string "accepted_subject" t.string "rejected_subject" t.string "confirmed_without_registration_subject" - t.boolean "send_on_updated_conference_dates", default: false + t.boolean "send_on_updated_conference_dates", default: false t.string "updated_conference_dates_subject" t.text "updated_conference_dates_template" - t.boolean "send_on_updated_conference_registration_dates", default: false + t.boolean "send_on_updated_conference_registration_dates", default: false t.string "updated_conference_registration_dates_subject" t.text "updated_conference_registration_dates_template" - t.boolean "send_on_venue_update", default: false + t.boolean "send_on_venue_update", default: false t.string "venue_update_subject" t.text "venue_update_template" - t.boolean "send_on_call_for_papers_dates_updates", default: false - t.boolean "send_on_call_for_papers_schedule_public", default: false + t.boolean "send_on_call_for_papers_dates_updates", default: false + t.boolean "send_on_call_for_papers_schedule_public", default: false t.string "call_for_papers_schedule_public_subject" t.string "call_for_papers_dates_updates_subject" t.text "call_for_papers_schedule_public_template" @@ -210,24 +209,14 @@ ActiveRecord::Schema.define(version: 20140820124117) do create_table "event_attachments", force: true do |t| t.integer "event_id" - t.string "title", null: false + t.string "title", null: false t.string "attachment_file_name" t.string "attachment_content_type" t.integer "attachment_file_size" t.datetime "attachment_updated_at" - t.boolean "public", default: true - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "event_people", force: true do |t| - t.integer "proposal_id" - t.integer "person_id" - t.integer "event_id" - t.string "event_role", default: "participant", null: false - t.string "comment" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.boolean "public", default: false + t.datetime "created_at" + t.datetime "updated_at" end create_table "event_types", force: true do |t| @@ -249,28 +238,28 @@ ActiveRecord::Schema.define(version: 20140820124117) do end create_table "events", force: true do |t| - t.string "guid", null: false + t.string "guid", null: false t.integer "conference_id" t.integer "event_type_id" - t.string "title", null: false + t.string "title", null: false t.string "subtitle" t.integer "time_slots" - t.string "state", default: "new", null: false - t.string "progress", default: "new", null: false + t.string "state", default: "new", null: false + t.string "progress", default: "new", null: false t.string "language" t.datetime "start_time" - t.text "abstract", limit: 16777215 - t.text "description", limit: 16777215 - t.boolean "public", default: true + t.text "abstract" + t.text "description" + t.boolean "public", default: true t.string "logo_file_name" t.string "logo_content_type" t.integer "logo_file_size" t.datetime "logo_updated_at" - t.text "proposal_additional_speakers", limit: 16777215 + t.text "proposal_additional_speakers" t.integer "track_id" t.integer "room_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" t.boolean "require_registration" t.integer "difficulty_level_id" t.integer "week" @@ -303,29 +292,6 @@ ActiveRecord::Schema.define(version: 20140820124117) do t.datetime "updated_at" end - create_table "people", force: true do |t| - t.string "guid", null: false - t.text "first_name" - t.text "last_name" - t.text "public_name" - t.text "company" - t.string "email", null: false - t.boolean "email_public" - t.string "avatar_file_name" - t.string "avatar_content_type" - t.integer "avatar_file_size" - t.datetime "avatar_updated_at" - t.text "biography" - t.integer "user_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "irc_nickname" - t.text "volunteer_experience" - t.string "tshirt" - t.string "mobile" - t.string "languages" - end - create_table "photos", force: true do |t| t.text "description" t.string "picture_file_name" @@ -349,8 +315,8 @@ ActiveRecord::Schema.define(version: 20140820124117) do create_table "question_types", force: true do |t| t.string "title" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" end create_table "questions", force: true do |t| @@ -358,8 +324,8 @@ ActiveRecord::Schema.define(version: 20140820124117) do t.integer "question_type_id" t.integer "conference_id" t.boolean "global" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" end create_table "registration_periods", force: true do |t| @@ -373,18 +339,18 @@ ActiveRecord::Schema.define(version: 20140820124117) do create_table "registrations", force: true do |t| t.integer "conference_id" - t.boolean "attending_social_events", default: true - t.boolean "attending_with_partner", default: false - t.boolean "using_affiliated_lodging", default: false + t.boolean "attending_social_events", default: true + t.boolean "attending_with_partner", default: false + t.boolean "using_affiliated_lodging", default: false t.datetime "arrival" t.datetime "departure" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" t.integer "dietary_choice_id" - t.text "other_dietary_choice", limit: 16777215 - t.boolean "handicapped_access_required", default: false - t.text "other_special_needs", limit: 16777215 - t.boolean "attended", default: false + t.text "other_dietary_choice" + t.boolean "handicapped_access_required", default: false + t.text "other_special_needs" + t.boolean "attended", default: false t.boolean "volunteer" t.integer "user_id" t.integer "week" @@ -402,22 +368,22 @@ ActiveRecord::Schema.define(version: 20140820124117) do create_table "roles", force: true do |t| t.string "name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" t.string "description" t.integer "resource_id" t.string "resource_type" end - add_index "roles", ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id", using: :btree - add_index "roles", ["name"], name: "index_roles_on_name", using: :btree + add_index "roles", ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id" + add_index "roles", ["name"], name: "index_roles_on_name" create_table "roles_users", id: false, force: true do |t| t.integer "role_id" t.integer "user_id" end - add_index "roles_users", ["user_id", "role_id"], name: "index_roles_users_on_user_id_and_role_id", using: :btree + add_index "roles_users", ["user_id", "role_id"], name: "index_roles_users_on_user_id_and_role_id" create_table "rooms", force: true do |t| t.string "guid", null: false @@ -462,25 +428,6 @@ ActiveRecord::Schema.define(version: 20140820124117) do t.datetime "updated_at" end - create_table "supporter_levels", force: true do |t| - t.integer "conference_id" - t.string "title", null: false - t.string "url" - t.text "description" - t.string "ticket_price" - end - - create_table "supporter_registrations", force: true do |t| - t.integer "registration_id" - t.integer "supporter_level_id" - t.integer "conference_id" - t.string "name" - t.string "email" - t.string "code" - t.boolean "code_is_valid", default: false - t.datetime "created_at" - end - create_table "targets", force: true do |t| t.integer "conference_id" t.integer "campaign_id" @@ -491,14 +438,31 @@ ActiveRecord::Schema.define(version: 20140820124117) do t.datetime "updated_at" end - create_table "tracks", force: true do |t| - t.string "guid", null: false + create_table "ticket_purchases", force: true do |t| + t.integer "ticket_id" t.integer "conference_id" - t.string "name", null: false - t.text "description", limit: 16777215 + t.boolean "paid", default: false + t.datetime "created_at" + t.integer "quantity", default: 1 + t.integer "user_id" + end + + create_table "tickets", force: true do |t| + t.integer "conference_id" + t.string "title", null: false + t.text "description" + t.integer "price_cents", default: 0, null: false + t.string "price_currency", default: "USD", null: false + end + + create_table "tracks", force: true do |t| + t.string "guid", null: false + t.integer "conference_id" + t.string "name", null: false + t.text "description" t.string "color" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" end create_table "users", force: true do |t| @@ -516,8 +480,8 @@ ActiveRecord::Schema.define(version: 20140820124117) do t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" t.string "name" t.boolean "email_public" t.text "biography" @@ -534,9 +498,9 @@ ActiveRecord::Schema.define(version: 20140820124117) do t.boolean "is_admin", default: false end - add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree - add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree - add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree + add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true + add_index "users", ["email"], name: "index_users_on_email", unique: true + add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true create_table "vchoices", force: true do |t| t.integer "vday_id" @@ -547,39 +511,39 @@ ActiveRecord::Schema.define(version: 20140820124117) do t.integer "conference_id" t.date "day" t.text "description" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" end create_table "venues", force: true do |t| t.string "guid" - t.text "name" - t.text "address" + t.text "name", limit: 255 + t.text "address", limit: 255 t.string "website" t.text "description" t.string "offline_map_url" t.string "offline_map_bounds" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" t.string "photo_file_name" t.string "photo_content_type" t.integer "photo_file_size" t.datetime "photo_updated_at" - t.boolean "include_venue_in_splash", default: false - t.boolean "include_lodgings_in_splash", default: false + t.boolean "include_venue_in_splash", default: false + t.boolean "include_lodgings_in_splash", default: false end create_table "versions", force: true do |t| - t.string "item_type", null: false - t.integer "item_id", null: false - t.string "event", null: false + t.string "item_type", null: false + t.integer "item_id", null: false + t.string "event", null: false t.string "whodunnit" - t.text "object", limit: 16777215 - t.text "object_changes", limit: 16777215 + t.text "object" + t.text "object_changes" t.datetime "created_at" end - add_index "versions", ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id", using: :btree + add_index "versions", ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id" create_table "visits", force: true do |t| t.uuid "visitor_id" @@ -604,13 +568,13 @@ ActiveRecord::Schema.define(version: 20140820124117) do t.datetime "started_at" end - add_index "visits", ["user_id"], name: "index_visits_on_user_id", using: :btree + add_index "visits", ["user_id"], name: "index_visits_on_user_id" create_table "votes", force: true do |t| t.integer "event_id" t.integer "rating" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" t.integer "user_id" end @@ -618,8 +582,8 @@ ActiveRecord::Schema.define(version: 20140820124117) do t.integer "conference_id" t.string "title", null: false t.text "description" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" end end diff --git a/spec/factories/supporter_levels.rb b/spec/factories/supporter_levels.rb deleted file mode 100644 index df6624af..00000000 --- a/spec/factories/supporter_levels.rb +++ /dev/null @@ -1,8 +0,0 @@ -FactoryGirl.define do - factory :supporter_level do - title 'Example Supporter Level' - url 'www.example.com' - conference - end - -end diff --git a/spec/factories/ticket_purchases.rb b/spec/factories/ticket_purchases.rb new file mode 100644 index 00000000..82103db3 --- /dev/null +++ b/spec/factories/ticket_purchases.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :ticket_purchase do + user + conference + ticket + quantity 10 + end +end diff --git a/spec/factories/tickets.rb b/spec/factories/tickets.rb new file mode 100644 index 00000000..1e3105cc --- /dev/null +++ b/spec/factories/tickets.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :ticket do + title 'Business Ticket' + price_cents 1000 + price_currency 'USD' + conference + end +end diff --git a/spec/features/ability_spec.rb b/spec/features/ability_spec.rb index 616fdf0e..dc201d62 100644 --- a/spec/features/ability_spec.rb +++ b/spec/features/ability_spec.rb @@ -34,7 +34,7 @@ feature 'Has correct abilities' do expect(page).to have_link('Lodgings', href: "/admin/conference/#{conference1.short_title}/lodgings") expect(page).to have_link('Sponsorship', href: "/admin/conference/#{conference1.short_title}/sponsorship_levels") expect(page).to have_link('Sponsors', href: "/admin/conference/#{conference1.short_title}/sponsors") - expect(page).to have_link('Supporter Levels', href: "/admin/conference/#{conference1.short_title}/supporter_levels") + expect(page).to have_link('Tickets', href: "/admin/conference/#{conference1.short_title}/tickets") expect(page).to have_link('E-Mails', href: "/admin/conference/#{conference1.short_title}/emails") expect(page).to have_link('Call for papers', href: "/admin/conference/#{conference1.short_title}/callforpapers") expect(page).to have_link('Tracks', href: "/admin/conference/#{conference1.short_title}/tracks") @@ -70,8 +70,8 @@ feature 'Has correct abilities' do visit admin_conference_sponsorship_levels_path(conference1.short_title) expect(current_path).to eq(admin_conference_sponsorship_levels_path(conference1.short_title)) - visit admin_conference_supporter_levels_path(conference1.short_title) - expect(current_path).to eq(admin_conference_supporter_levels_path(conference1.short_title)) + visit admin_conference_tickets_path(conference1.short_title) + expect(current_path).to eq(admin_conference_tickets_path(conference1.short_title)) visit admin_conference_emails_path(conference1.short_title) expect(current_path).to eq(admin_conference_emails_path(conference1.short_title)) @@ -141,7 +141,7 @@ feature 'Has correct abilities' do visit admin_conference_sponsorship_levels_path(conference2.short_title) expect(current_path).to eq(root_path) - visit admin_conference_supporter_levels_path(conference2.short_title) + visit admin_conference_tickets_path(conference2.short_title) expect(current_path).to eq(root_path) visit admin_conference_emails_path(conference2.short_title) @@ -212,7 +212,7 @@ feature 'Has correct abilities' do visit admin_conference_sponsorship_levels_path(conference3.short_title) expect(current_path).to eq(root_path) - visit admin_conference_supporter_levels_path(conference3.short_title) + visit admin_conference_tickets_path(conference3.short_title) expect(current_path).to eq(root_path) visit admin_conference_emails_path(conference3.short_title) @@ -284,7 +284,7 @@ feature 'Has correct abilities' do visit admin_conference_sponsorship_levels_path(conference4.short_title) expect(current_path).to eq(root_path) - visit admin_conference_supporter_levels_path(conference4.short_title) + visit admin_conference_tickets_path(conference4.short_title) expect(current_path).to eq(root_path) visit admin_conference_emails_path(conference4.short_title) diff --git a/spec/features/commercials_spec.rb b/spec/features/commercials_spec.rb index e53524b7..249bc28f 100644 --- a/spec/features/commercials_spec.rb +++ b/spec/features/commercials_spec.rb @@ -5,17 +5,15 @@ feature Commercial do let!(:conference) { create(:conference) } let!(:organizer_role) { create(:organizer_role, resource: conference) } let!(:organizer) { create(:user, role_ids: [organizer_role.id]) } + let!(:participant) { create(:user) } - shared_examples 'adds and updates a commercial' do - scenario 'of a conference', - feature: true, js: true do - + context 'in admin area' do + scenario 'adds, updates, deletes of a conference', feature: true, js: true do expected_count = conference.commercials.count + 1 sign_in organizer visit admin_conference_commercials_path(conference.short_title) - click_link 'New Commercial' # Create without an commercial id @@ -60,7 +58,87 @@ feature Commercial do end end - describe 'organizer' do - it_behaves_like 'adds and updates a commercial' + context 'in public area' do + let!(:event) { create(:event, conference: conference, title: 'Example Proposal') } + + before(:each) do + event.event_users = [create(:event_user, + user_id: participant.id, + event_id: event.id, + event_role: 'submitter')] + + @expected_count = Commercial.count + 1 + sign_in participant + end + + after(:each) do + sign_out + end + + scenario 'adds a invalid commercial to an event', feature: true, js: true do + visit edit_conference_proposal_path(conference.short_title, event.id) + + click_link 'Commercials' + click_link 'Add Commercial' + + select('SlideShare', from: 'commercial_commercial_type') + fill_in 'commercial_commercial_id', with: '12345' + + click_button 'Create Commercial' + expect(flash).to eq('Commercial was successfully created.') + expect(event.commercials.count).to eq(@expected_count) + end + + scenario 'adds a valid commercial to an event', feature: true, js: true do + visit edit_conference_proposal_path(conference.short_title, event.id) + click_link 'Commercials' + click_link 'Add Commercial' + + select('SlideShare', from: 'commercial_commercial_type') + + click_button 'Create Commercial' + expect(flash).to eq("A error prohibited this Commercial from being saved: Commercial can't be blank.") + expect(event.commercials.count).to eq(@expected_count - 1) + end + + scenario 'updates a valid commercial to an event', feature: true, js: true do + create(:commercial, + commercialable_id: event.id, + commercialable_type: 'Event') + visit edit_conference_proposal_path(conference.short_title, event.id) + click_link 'Commercials' + click_link 'Edit' + select('SlideShare', from: 'commercial_commercial_type') + fill_in 'commercial_commercial_id', with: '56789' + click_button 'Update Commercial' + expect(flash).to eq('Commercial was successfully updated.') + expect(event.commercials.count).to eq(@expected_count) + end + + scenario 'updates a invalid commercial to an event', feature: true, js: true do + create(:commercial, + commercialable_id: event.id, + commercialable_type: 'Event') + visit edit_conference_proposal_path(conference.short_title, event.id) + click_link 'Commercials' + click_link 'Edit' + select('SlideShare', from: 'commercial_commercial_type') + fill_in 'commercial_commercial_id', with: '' + click_button 'Update Commercial' + expect(flash).to eq("A error prohibited this Commercial from being saved: Commercial can't be blank.") + expect(event.commercials.count).to eq(@expected_count) + end + + scenario 'deletes a commercial to an event', feature: true, js: true do + create(:commercial, + commercialable_id: event.id, + commercialable_type: 'Event') + visit edit_conference_proposal_path(conference.short_title, event.id) + click_link 'Commercials' + click_link 'Delete' + page.driver.network_traffic + expect(flash).to eq('Commercial was successfully destroyed.') + expect(event.commercials.count).to eq(@expected_count - 1) + end end end diff --git a/spec/features/conference_registration_spec.rb b/spec/features/conference_registration_spec.rb new file mode 100644 index 00000000..72c2e590 --- /dev/null +++ b/spec/features/conference_registration_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +feature Registration do + let!(:conference) { create(:conference, registration_period: create(:registration_period, start_date: 3.days.ago)) } + let!(:participant) { create(:user) } + + context 'as a participant' do + before(:each) do + sign_in participant + end + + after(:each) do + sign_out + end + + context 'who is already registered' do + let!(:registration) { create(:registration, user: participant, conference: conference) } + + scenario 'updates conference registration', feature: true, js: true do + visit root_path + click_link 'Modify Registration' + + expect(current_path).to eq(edit_conference_conference_registrations_path(conference.short_title)) + click_button 'Update Registration' + + expect(conference.user_registered?(participant)).to be(true) + end + + scenario 'unregisters for a conference', feature: true, js: true do + visit root_path + click_link 'Modify Registration' + + expect(current_path).to eq(edit_conference_conference_registrations_path(conference.short_title)) + click_link 'Unregister' + + expect(conference.user_registered?(participant)).to be(false) + end + end + + context 'who is not registered' do + scenario 'registers for a conference', feature: true, js: true do + visit root_path + click_link 'Register' + + expect(current_path).to eq(new_conference_conference_registrations_path(conference.short_title)) + click_button 'Register' + + expect(conference.user_registered?(participant)).to be(true) + end + end + end +end diff --git a/spec/features/proposal_spec.rb b/spec/features/proposal_spec.rb index c43fedf4..0f389650 100644 --- a/spec/features/proposal_spec.rb +++ b/spec/features/proposal_spec.rb @@ -4,21 +4,76 @@ feature Event do let!(:conference) { create(:conference) } let!(:organizer_role) { create(:organizer_role, resource: conference) } let!(:organizer) { create(:user, email: 'admin@example.com', role_ids: [organizer_role.id]) } - let!(:participant) { create(:user, biography: '') } + let!(:participant) { create(:user) } + let!(:participant_without_bio) { create(:user, biography: '') } - shared_examples 'proposal workflow' do - scenario 'submitts a proposal, accepts and confirms', - feature: true, js: true do + before(:each) do + conference.call_for_papers = create(:call_for_papers) + conference.event_types = [create(:event_type)] + @options = {} + @options[:send_mail] = 'false' + + @event = create(:event, conference: conference, title: 'Example Proposal') + end + + after(:each) do + sign_out + end + + context 'as an conference organizer' do + before(:each) do + sign_in organizer + end + + scenario 'rejects a proposal', feature: true, js: true do + visit admin_conference_events_path(conference.short_title) + expect(page.has_content?('Example Proposal')).to be true + + click_button 'New' + click_link "reject_event_#{@event.id}" + expect(flash).to eq('Event rejected!') + @event.reload + expect(@event.state).to eq('rejected') + end + + scenario 'accepts a proposal', feature: true, js: true do + visit admin_conference_events_path(conference.short_title) + expect(page.has_content?('Example Proposal')).to be true + + click_button 'New' + click_link "accept_event_#{@event.id}" + expect(flash).to eq('Event accepted!') + expect(page.has_content?('Unconfirmed')).to be true + @event.reload + expect(@event.state).to eq('unconfirmed') + end + + scenario 'restarts review of a proposal', feature: true, js: true do + @event.reject!(@options) + visit admin_conference_events_path(conference.short_title) + expect(page.has_content?('Example Proposal')).to be true + + click_button 'Rejected' + click_link "restart_event_#{@event.id}" + expect(flash).to eq('Review started!') + @event.reload + expect(@event.state).to eq('new') + end + end + + context 'as a participant' do + before(:each) do + @event.accept!(@options) + @event.event_users = [create(:event_user, + user_id: participant.id, + event_id: @event.id, + event_role: 'submitter')] + end + + scenario 'submits a valid proposal', feature: true, js: true do + sign_in participant_without_bio expected_count = Event.count + 1 - - conference.call_for_papers = create(:call_for_papers) - conference.email_settings = create(:email_settings) - conference.event_types = [create(:event_type)] - - # Submit a new proposal as participant - sign_in participant - visit conference_proposal_index_path(conference.short_title) click_link 'New Proposal' @@ -36,109 +91,31 @@ feature Event do expect(flash).to eq('Event was successfully submitted. You should register for the conference now.') expect(current_path).to eq(new_conference_conference_registrations_path(conference.short_title)) - expect(Event.count).to eq(expected_count) + end - event = Event.where(title: 'Example Proposal').first - - visit conference_proposal_index_path(conference.short_title) - expect(page.has_content?('Example Proposal')).to be true - - expected_count_commercial = Commercial.count + 1 - # Add a invalid commercial - visit edit_conference_proposal_path(conference.short_title, event.id) - - click_link 'Commercials' - click_link 'Add Commercial' - - select('SlideShare', from: 'commercial_commercial_type') - - click_button 'Create Commercial' - expect(flash).to eq("A error prohibited this Commercial from being saved: Commercial can't be blank.") - expect(event.commercials.count).to eq(expected_count_commercial - 1) - - # Add a valid commercial - visit edit_conference_proposal_path(conference.short_title, event.id) - - click_link 'Commercials' - click_link 'Add Commercial' - - select('SlideShare', from: 'commercial_commercial_type') - fill_in 'commercial_commercial_id', with: '12345' - - click_button 'Create Commercial' - expect(flash).to eq('Commercial was successfully created.') - expect(event.commercials.count).to eq(expected_count_commercial) - - # Edit an invalid commercial - click_link 'Commercials' - click_link 'Edit' - - select('SlideShare', from: 'commercial_commercial_type') - fill_in 'commercial_commercial_id', with: '' - - click_button 'Update Commercial' - expect(flash).to eq("A error prohibited this Commercial from being saved: Commercial can't be blank.") - expect(event.commercials.count).to eq(expected_count_commercial) - - # Edit a valid commercial - select('SlideShare', from: 'commercial_commercial_type') - fill_in 'commercial_commercial_id', with: '56789' - - click_button 'Update Commercial' - expect(flash).to eq('Commercial was successfully updated.') - expect(event.commercials.count).to eq(expected_count_commercial) - - # Delete a commercial - click_link 'Commercials' - click_link 'Delete' - page.driver.network_traffic - expect(flash).to eq('Commercial was successfully destroyed.') - expect(event.commercials.count).to eq(expected_count_commercial - 1) - - sign_out - sign_in organizer - - # Reject proposal - visit admin_conference_events_path(conference.short_title) - expect(page.has_content?('Example Proposal')).to be true - - click_button 'New' - click_link "reject_event_#{event.id}" - expect(flash).to eq('Event rejected!') - click_button 'Rejected' - click_link "restart_event_#{event.id}" - expect(flash).to eq('Review started!') - - # Start review - click_button 'New' - click_link "accept_event_#{event.id}" - expect(flash).to eq('Event accepted!') - expect(page.has_content?('Unconfirmed')).to be true - sign_out - - # Confirm proposal as participant + scenario 'confirms a proposal', feature: true, js: true do sign_in participant visit conference_proposal_index_path(conference.short_title) expect(page.has_content?('Example Proposal')).to be true expect(page.has_content?('Unconfirmed')).to be true - click_link "confirm_proposal_#{event.id}" + click_link "confirm_proposal_#{@event.id}" expect(flash). to eq('The proposal was confirmed. Please register to attend the conference.') + @event.reload + expect(@event.state).to eq('confirmed') + end - # Register for conference - find('#register').click - expect(flash).to eq('You are now registered and will be receiving E-Mail notifications.') - - # Withdraw proposal + scenario 'withdraw a proposal', feature: true, js: true do + sign_in participant + @event.confirm! visit conference_proposal_index_path(conference.short_title) + expect(page.has_content?('Example Proposal')).to be true expect(page.has_content?('Confirmed')).to be true - click_link "delete_proposal_#{event.id}" + click_link "delete_proposal_#{@event.id}" expect(flash).to eq('Proposal was successfully withdrawn.') + @event.reload + expect(@event.state).to eq('withdrawn') end end - - describe 'proposal' do - it_behaves_like 'proposal workflow' - end end diff --git a/spec/features/supporter_levels_spec.rb b/spec/features/supporter_levels_spec.rb deleted file mode 100644 index adafa978..00000000 --- a/spec/features/supporter_levels_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'spec_helper' - -feature SupporterLevel do - let!(:conference) { create(:conference) } - let!(:organizer_role) { create(:organizer_role, resource: conference) } - let!(:user) { create(:user, role_ids: [organizer_role.id]) } - - shared_examples 'supporter levels' do - scenario 'adds and updates supporter level', feature: true, js: true do - sign_in user - visit admin_conference_supporter_levels_path(conference_id: conference.short_title) - - # Add supporter level - click_link 'Add supporter_level' - expect(page.all('div.nested-fields').count == 1).to be true - - page. - find('div.nested-fields:nth-of-type(1) div:nth-of-type(1) input'). - set('Example supporter level') - - page. - find('div.nested-fields:nth-of-type(1) div:nth-of-type(2) input'). - set('http://www.google.de') - - click_button 'Update Conference' - - # Validations - expect(flash).to eq('Supporter levels were successfully updated.') - expect(find('div.nested-fields:nth-of-type(1) div:nth-of-type(1) input'). - value).to eq('Example supporter level') - expect(find('div.nested-fields:nth-of-type(1) div:nth-of-type(2) input'). - value).to eq('http://www.google.de') - - # Remove supporter level - click_link 'Remove supporter_level' - expect(page.all('div.nested-fields').count == 0).to be true - find('button', text: 'Update Conference').trigger('click') - expect(flash).to eq('Supporter levels were successfully updated.') - expect(page.all('div.nested-fields').count == 0).to be true - end - end - - describe 'organizer' do - it_behaves_like 'supporter levels' - end -end diff --git a/spec/features/ticket_purchases_spec.rb b/spec/features/ticket_purchases_spec.rb new file mode 100644 index 00000000..64cc4f31 --- /dev/null +++ b/spec/features/ticket_purchases_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +feature Registration do + let!(:ticket) { create(:ticket) } + let!(:conference) { create(:conference, title: 'ExampleCon', tickets: [ticket]) } + let!(:participant) { create(:user) } + + context 'as a participant' do + before(:each) do + sign_in participant + end + + after(:each) do + sign_out + end + + context 'who is not registered' do + + scenario 'purchases a ticket', feature: true, js: true do + visit root_path + click_link 'Support' + + fill_in "tickets__#{ticket.id}", with: '2' + expect(current_path).to eq(conference_tickets_path(conference.short_title)) + + click_button 'Support' + + purchase = TicketPurchase.where(user_id: participant.id, ticket_id: ticket.id).first + expect(purchase.quantity).to eq(2) + expect(current_path).to eq(conference_conference_registrations_path(conference.short_title)) + expect(flash). + to eq('Congratulations, you have successfully purchased a ticket! You can pay it cash on check in! Thank you for supporting ExampleCon!') + expect(page.has_content?('Business Ticket')).to be true + end + + scenario 'deletes a purchased ticket', feature: true, js: true do + create(:ticket_purchase, + user_id: participant.id, + ticket_id: ticket.id, + quantity: 2) + + visit conference_conference_registrations_path(conference.short_title) + expect(page.has_content?('Business Ticket')).to be true + + click_link 'Delete' + expect(flash).to eq('Ticket successfully destroyed.') + expect(TicketPurchase.count).to eq(0) + end + end + end +end diff --git a/spec/features/tickets_spec.rb b/spec/features/tickets_spec.rb new file mode 100644 index 00000000..b7bde589 --- /dev/null +++ b/spec/features/tickets_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +feature Ticket do + let!(:conference) { create(:conference, title: 'ExampleCon') } + let!(:organizer_role) { create(:organizer_role, resource: conference) } + let!(:organizer) { create(:user, email: 'admin@example.com', role_ids: [organizer_role.id]) } + + context 'as a organizer' do + before(:each) do + sign_in organizer + end + + after(:each) do + sign_out + end + + scenario 'add a valid ticket', feature: true, js: true do + visit admin_conference_tickets_path(conference.short_title) + click_link 'Add Ticket' + + fill_in 'ticket_title', with: 'Business Ticket' + fill_in 'ticket_description', with: 'The business ticket' + fill_in 'ticket_price', with: '100' + + click_button 'Create Ticket' + expect(flash).to eq('Ticket successfully created.') + expect(Ticket.count).to eq(1) + end + + scenario 'add a invalid ticket', feature: true, js: true do + visit admin_conference_tickets_path(conference.short_title) + click_link 'Add Ticket' + + fill_in 'ticket_title', with: '' + fill_in 'ticket_price', with: '-1' + + click_button 'Create Ticket' + expect(flash).to eq("Creating Ticket failed: Title can't be blank. Price cents must be greater than 0.") + expect(Ticket.count).to eq(0) + end + + context 'Ticket already created' do + let!(:ticket) { create(:ticket, title: 'Business Ticket', price: 100, conference_id: conference.id) } + + scenario 'edit valid ticket', feature: true, js: true do + visit admin_conference_tickets_path(conference.short_title) + click_link 'Edit' + + fill_in 'ticket_title', with: 'Free Ticket' + fill_in 'ticket_price', with: '50' + + click_button 'Update Ticket' + + ticket.reload + expect(ticket.price).to eq(50) + expect(ticket.title).to eq('Free Ticket') + expect(flash).to eq('Ticket successfully updated.') + expect(Ticket.count).to eq(1) + end + + scenario 'edit invalid ticket', feature: true, js: true do + visit admin_conference_tickets_path(conference.short_title) + click_link 'Edit' + + fill_in 'ticket_title', with: '' + fill_in 'ticket_price', with: '-5' + + click_button 'Update Ticket' + + ticket.reload + expect(ticket.price).to eq(100) + expect(ticket.title).to eq('Business Ticket') + expect(flash).to eq("Ticket update failed: Title can't be blank. Price cents must be greater than 0.") + expect(Ticket.count).to eq(1) + end + + scenario 'delete ticket', feature: true, js: true do + visit admin_conference_tickets_path(conference.short_title) + click_link 'Delete' + + expect(flash).to eq('Ticket successfully destroyed.') + expect(Ticket.count).to eq(0) + end + end + end +end diff --git a/spec/models/ticket_purchase_spec.rb b/spec/models/ticket_purchase_spec.rb new file mode 100644 index 00000000..9248b52f --- /dev/null +++ b/spec/models/ticket_purchase_spec.rb @@ -0,0 +1,105 @@ +require 'spec_helper' + +describe TicketPurchase do + + describe 'validations' do + it 'has a valid factory' do + expect(build(:ticket_purchase)).to be_valid + end + + it 'is not valid without a conference_id' do + should validate_presence_of(:conference_id) + end + + it 'is not valid without a ticket_id' do + should validate_presence_of(:ticket_id) + end + + it 'is not valid without a user_id' do + should validate_presence_of(:user_id) + end + + it 'is not valid without a quantity' do + should validate_presence_of(:quantity) + end + + it 'is not valid with a quantity equals zero' do + should_not allow_value(0).for(:quantity) + end + + it 'is not valid with a quantity smaller than zero' do + should_not allow_value(-1).for(:quantity) + end + + it 'is valid with a quantity greater than zero' do + should allow_value(1).for(:quantity) + end + + end + + describe 'self#purchase' do + let!(:participant) { create(:user) } + let!(:ticket_1) { create(:ticket) } + let!(:ticket_2) { create(:ticket) } + let!(:conference) { create(:conference, tickets: [ticket_1, ticket_2]) } + + it 'creates a purchase for one ticket' do + tickets = { ticket_1.id.to_s => '1' } + message = TicketPurchase.purchase(conference, participant, tickets) + purchase = TicketPurchase.where(conference_id: conference.id, + user_id: participant.id, + ticket_id: ticket_1.id).first + + expect(TicketPurchase.count).to eq(1) + expect(purchase.quantity).to eq(1) + expect(message.blank?).to be true + end + + it 'creates several purchases for more than one ticket' do + tickets = { ticket_1.id.to_s => '1', ticket_2.id.to_s => '1' } + message = TicketPurchase.purchase(conference, participant, tickets) + purchase_1 = TicketPurchase.where(conference_id: conference.id, + user_id: participant.id, + ticket_id: ticket_1.id).first + + purchase_2 = TicketPurchase.where(conference_id: conference.id, + user_id: participant.id, + ticket_id: ticket_2.id).first + + expect(TicketPurchase.count).to eq(2) + expect(purchase_1.quantity).to eq(1) + expect(purchase_2.quantity).to eq(1) + expect(message.blank?).to be true + end + + it 'creates no purchase if quantity is less than 1' do + tickets = { ticket_1.id.to_s => '-1' } + TicketPurchase.purchase(conference, participant, tickets) + + expect(TicketPurchase.count).to eq(0) + end + + it 'creates no purchase if quantity is 0' do + tickets = { ticket_1.id.to_s => '0' } + TicketPurchase.purchase(conference, participant, tickets) + + expect(TicketPurchase.count).to eq(0) + end + + it 'updates the quantity if the user already bought this ticket' do + purchase = create(:ticket_purchase, + conference: conference, + user: participant, + ticket: ticket_1, + quantity: 5) + + tickets = { ticket_1.id.to_s => '10' } + message = TicketPurchase.purchase(conference, participant, tickets) + purchase.reload + + expect(TicketPurchase.count).to eq(1) + expect(purchase.quantity).to eq(10) + expect(message.blank?).to be true + end + end +end diff --git a/spec/models/ticket_spec.rb b/spec/models/ticket_spec.rb new file mode 100644 index 00000000..c093b072 --- /dev/null +++ b/spec/models/ticket_spec.rb @@ -0,0 +1,92 @@ +require 'spec_helper' + +describe Ticket do + let(:conference) { create(:conference) } + let(:ticket) { create(:ticket, price: 50, conference: conference) } + let(:user) { create(:user) } + + describe 'validations' do + it 'has a valid factory' do + expect(build(:ticket)).to be_valid + end + + it 'is not valid without a title' do + should validate_presence_of(:title) + end + + it 'is not valid without a price_cents' do + should validate_presence_of(:price_cents) + end + + it 'is not valid without a price_currency' do + should validate_presence_of(:price_currency) + end + + it 'is not valid with a price_cents equals zero' do + should_not allow_value(0).for(:price_cents) + end + + it 'is not valid with a price_cents smaller than zero' do + should_not allow_value(-1).for(:price_cents) + end + + it 'is valid with a price_cents greater than zero' do + should allow_value(1).for(:price_cents) + end + end + + describe '#bought?' do + it 'returns true if the user has bought this ticket' do + create(:ticket_purchase, + user: user, + ticket: ticket) + expect(ticket.bought?(user)).to eq(true) + end + + it 'returns true if the user has bought this ticket' do + expect(ticket.bought?(user)).to eq(false) + end + end + + describe '#quantity_bought_by' do + it 'returns the correct value if the user has bought this ticket' do + create(:ticket_purchase, + user: user, + ticket: ticket, + quantity: 20) + expect(ticket.quantity_bought_by(user)).to eq(20) + end + + it 'returns zero if the user has not bought this ticket' do + expect(ticket.quantity_bought_by(user)).to eq(0) + end + end + + describe '#total_price' do + it 'returns the correct value if the user has bought this ticket' do + create(:ticket_purchase, + user: user, + ticket: ticket, + quantity: 20) + expect(ticket.total_price(user)).to eq(20 * 50) + end + + it 'returns zero if the user has not bought this ticket' do + expect(ticket.total_price(user)).to eq(0) + end + end + + describe 'self#total_price' do + it 'returns the correct value if the user has bought this ticket' do + create(:ticket_purchase, + user: user, + ticket: ticket, + quantity: 20) + expect(Ticket.total_price(conference, user)).to eq(20 * 50) + end + + it 'returns zero if the user has not bought this ticket' do + expect(Ticket.total_price(conference, user)).to eq(0) + end + end +end diff --git a/spec/views/admin/supporter_levels/index.html.haml_spec.rb b/spec/views/admin/supporter_levels/index.html.haml_spec.rb deleted file mode 100644 index fb7bcbc5..00000000 --- a/spec/views/admin/supporter_levels/index.html.haml_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'spec_helper' - -describe 'admin/supporter_levels/index' do - - it 'renders supporter levels' do - @support_level = create(:supporter_level) - assign :conference, @support_level.conference - render - expect(rendered).to include('Example Supporter Level') - expect(rendered).to include('www.example.com') - end -end