diff --git a/app/controllers/admin/resources_controller.rb b/app/controllers/admin/resources_controller.rb new file mode 100644 index 00000000..7501f9ae --- /dev/null +++ b/app/controllers/admin/resources_controller.rb @@ -0,0 +1,52 @@ +module Admin + class ResourcesController < ApplicationController + load_and_authorize_resource :conference, find_by: :short_title + load_and_authorize_resource :resource, only: [:show, :edit, :update, :destroy] + + def index; end + + def edit; end + + def new + @resource = @conference.resources.new + end + + def create + @resource = @conference.resources.new(resource_params) + if @resource.save + redirect_to admin_conference_resources_path(conference_id: @conference.short_title), + notice: 'Resource successfully created.' + else + flash[:error] = "Creating resource failed: #{@resource.errors.full_messages.join('. ')}." + render :new + end + end + + def update + if @resource.update_attributes(resource_params) + redirect_to admin_conference_resources_path(conference_id: @conference.short_title), + notice: 'Resource successfully updated.' + else + flash[:error] = "Resource update failed: #{@resource.errors.full_messages.join('. ')}." + render :edit + end + end + + def destroy + if @resource.destroy + redirect_to admin_conference_resources_path(conference_id: @conference.short_title), + notice: 'Resource successfully destroyed.' + else + redirect_to admin_conference_resources_path(conference_id: @conference.short_title), + error: 'Resource was successfully destroyed.' \ + "#{@resource.errors.full_messages.join('. ')}." + end + end + + private + + def resource_params + params.require(:resource).permit(:name, :description, :quantity, :used, :conference_id) + end + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 7d898595..6535f4bb 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -536,4 +536,8 @@ module ApplicationHelper end end end + + def quantity_left_of(resource) + "#{resource.quantity - resource.used}/#{resource.quantity}" + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 760f43fe..09d73da9 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -134,6 +134,7 @@ class Ability # ids of all the conferences for which the user has the 'organizer' role conf_ids_for_organizer = Conference.with_role(:organizer, user).pluck(:id) + can :manage, Resource, conference_id: conf_ids_for_organizer can [:new, :create], Conference if user.has_role?(:organizer, :any) can :manage, Conference, id: conf_ids_for_organizer can :manage, Splashpage, conference_id: conf_ids_for_organizer @@ -187,6 +188,7 @@ class Ability # ids of all the conferences for which the user has the 'cfp' role conf_ids_for_cfp = Conference.with_role(:cfp, user).pluck(:id) + can [:index, :show, :update], Resource, conference_id: conf_ids_for_cfp can :manage, Event, program: { conference_id: conf_ids_for_cfp } can :manage, EventType, program: { conference_id: conf_ids_for_cfp } can :manage, Track, program: { conference_id: conf_ids_for_cfp } @@ -223,6 +225,7 @@ class Ability # ids of all the conferences for which the user has the 'info_desk' role conf_ids_for_info_desk = Conference.with_role(:info_desk, user).pluck(:id) + can [:index, :show, :update], Resource, conference_id: conf_ids_for_info_desk can :manage, Registration, conference_id: conf_ids_for_info_desk can :manage, Question, conference_id: conf_ids_for_info_desk can :manage, Question do |question| @@ -244,6 +247,7 @@ class Ability # ids of all the conferences for which the user has the 'volunteers_coordinator' role conf_ids_for_volunteers_coordinator = Conference.with_role(:volunteers_coordinator, user).pluck(:id) + can [:index, :show, :update], Resource, conference_id: conf_ids_for_volunteers_coordinator can :manage, Vposition, conference_id: conf_ids_for_volunteers_coordinator can :manage, Vday, conference_id: conf_ids_for_volunteers_coordinator diff --git a/app/models/conference.rb b/app/models/conference.rb index 31c61d73..f6e03585 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -24,6 +24,7 @@ class Conference < ActiveRecord::Base has_many :payments, dependent: :destroy has_many :supporters, through: :ticket_purchases, source: :user has_many :tickets, dependent: :destroy + has_many :resources, dependent: :destroy has_many :lodgings, dependent: :destroy has_many :registrations, dependent: :destroy diff --git a/app/models/resource.rb b/app/models/resource.rb new file mode 100644 index 00000000..c113522e --- /dev/null +++ b/app/models/resource.rb @@ -0,0 +1,10 @@ +class Resource < ActiveRecord::Base + belongs_to :conference + validate :used_less_than_quantity + + private + + def used_less_than_quantity + errors.add(:used, 'can not be higher than total quantity') unless used <= quantity + end +end diff --git a/app/views/admin/resources/_form.html.haml b/app/views/admin/resources/_form.html.haml new file mode 100644 index 00000000..818bfc27 --- /dev/null +++ b/app/views/admin/resources/_form.html.haml @@ -0,0 +1,16 @@ +.row + .col-md-12 + .page-header + %h1 + - if @resource.new_record? + New + Resource +.row + .col-md-8 + = semantic_form_for(@resource, :url => (@resource.new_record? ? admin_conference_resources_path : admin_conference_resource_path(@conference.short_title, @resource))) do |f| + = f.input :name + = f.input :description, input_html: { rows: 5, data: { provide: "markdown-editable" } } + = f.input :used + = f.input :quantity + %p.text-right + = f.action :submit, as: :button, button_html: { class: 'btn btn-primary' } diff --git a/app/views/admin/resources/index.html.haml b/app/views/admin/resources/index.html.haml new file mode 100644 index 00000000..857d6cc4 --- /dev/null +++ b/app/views/admin/resources/index.html.haml @@ -0,0 +1,38 @@ +.row + .col-md-12 + .page-header + %h1 Resources + %p.text-muted + Manage resources in conference +- if @conference.resources.any? + .row + .col-md-12 + %table.table.table-hover.datatable + %thead + %th Name + %th Left( Left/Total ) + %th Used + %th Actions + %tbody + - @conference.resources.each do |resource| + %tr + =semantic_form_for(resource, url: admin_conference_resource_path(@conference.short_title, resource.id), html: { method: :put }) do |f| + %td + = link_to(admin_conference_resource_path(@conference.short_title, resource.id)) do + = resource.name + %td + = quantity_left_of(resource) + %td.col-sm-1.col-md-1 + = f.input :used, label: false + %td + .btn-group + = f.action :submit, as: :button, label: 'Save', button_html: { class: 'btn btn-success' } + = link_to 'Edit', edit_admin_conference_resource_path(@conference.short_title, resource.id), + method: :get, class: 'btn btn-primary', disabled: !(can? :update, resource) + = link_to 'Delete', admin_conference_resource_path(@conference.short_title, resource.id), + method: :delete, class: 'btn btn-danger', disabled: !(can? :destroy, resource), + data: { confirm: "Do you really want to delete #{resource.name}?" } +.row + .col-md-12 + = link_to 'Add Resource', new_admin_conference_resource_path, class: 'btn btn-success pull-right', disabled: !(can? :create, Resource.new(conference_id: @conference_id)) + diff --git a/app/views/admin/resources/new.html.haml b/app/views/admin/resources/new.html.haml new file mode 100644 index 00000000..bcc58327 --- /dev/null +++ b/app/views/admin/resources/new.html.haml @@ -0,0 +1 @@ += render 'form' diff --git a/app/views/admin/resources/show.html.haml b/app/views/admin/resources/show.html.haml new file mode 100644 index 00000000..e4ac025e --- /dev/null +++ b/app/views/admin/resources/show.html.haml @@ -0,0 +1,26 @@ +.row + .col-md-12 + %h3 + = @resource.name + .btn-group.pull-right + = link_to 'Edit', edit_admin_conference_resource_path(@conference.short_title, @resource), class: 'btn btn-mini btn-primary' + +%br +.row + .col-md-12 + %table.table + %tr + %td.col-md-2 + %b Description + %td + = @resource.description + %tr + %td + %b Total Quantity + %td + = @resource.quantity + %tr + %td + %b Quantity left + %td + = quantity_left_of(@resource) diff --git a/app/views/layouts/_admin_sidebar.html.haml b/app/views/layouts/_admin_sidebar.html.haml index b1e4730c..9dbbe981 100644 --- a/app/views/layouts/_admin_sidebar.html.haml +++ b/app/views/layouts/_admin_sidebar.html.haml @@ -139,3 +139,8 @@ = link_to(admin_conference_roles_path(@conference.short_title)) do %span.fa.fa-group Roles + - if can? :index, @conference.resources.new + %li + = link_to admin_conference_resources_path(@conference.short_title) do + %span.fa.fa-pencil-square + Resources diff --git a/config/routes.rb b/config/routes.rb index 2a44f93a..ba9228aa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -72,6 +72,7 @@ Osem::Application.routes.draw do get 'reports' => 'events#reports' end + resources :resources resources :tickets resources :sponsors, except: [:show] resources :lodgings, except: [:show] diff --git a/db/migrate/20170129075434_create_resources_table.rb b/db/migrate/20170129075434_create_resources_table.rb new file mode 100644 index 00000000..8228ddda --- /dev/null +++ b/db/migrate/20170129075434_create_resources_table.rb @@ -0,0 +1,11 @@ +class CreateResourcesTable < ActiveRecord::Migration + def change + create_table :resources do |t| + t.string :name + t.text :description + t.integer :quantity + t.integer :used, default: 0 + t.references :conference + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 7da98fe3..5b7be51c 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: 20160815140302) do +ActiveRecord::Schema.define(version: 20170129075434) do create_table "ahoy_events", force: :cascade do |t| t.uuid "visit_id", limit: 16 @@ -344,6 +344,14 @@ ActiveRecord::Schema.define(version: 20160815140302) do t.integer "vchoice_id" end + create_table "resources", force: :cascade do |t| + t.string "name" + t.text "description" + t.integer "quantity" + t.integer "used", default: 0 + t.integer "conference_id" + end + create_table "roles", force: :cascade do |t| t.string "name" t.datetime "created_at" diff --git a/spec/factories/resource.rb b/spec/factories/resource.rb new file mode 100644 index 00000000..83045e9e --- /dev/null +++ b/spec/factories/resource.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :resource do + name { 'Resource' } + quantity { 10 } + used { 5 } + conference + end +end diff --git a/spec/features/resource_spec.rb b/spec/features/resource_spec.rb new file mode 100644 index 00000000..f8b93e3f --- /dev/null +++ b/spec/features/resource_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +feature Resource do + let!(:conference) { create(:conference) } + let!(:admin) { create(:admin) } + let!(:resource) { create(:resource, conference: conference) } + + context 'as an admin' do + before(:each) do + sign_in admin + end + + scenario 'create a new resource' do + visit admin_conference_resources_path(conference.short_title) + click_link 'Add Resource' + + fill_in 'resource_name', with: 'shirts' + fill_in 'resource_description', with: 'what you love to wear!' + fill_in 'resource_quantity', with: 10 + + click_button 'Create Resource' + + expect(Resource.count).to eq(2) + expect(flash).to eq('Resource successfully created.') + end + + scenario 'edit an existing resource' do + visit admin_conference_resources_path(conference.short_title) + click_link('Edit', edit_admin_conference_resource_path(conference.short_title, resource.id)) + + fill_in 'resource_name', with: 'changed_name' + + click_button 'Update Resource' + resource.reload + + expect(flash).to eq('Resource successfully updated.') + expect(resource.name).to eq('changed_name') + end + + scenario 'destroy a resource' do + visit admin_conference_resources_path(conference.short_title) + click_link('Delete', href: admin_conference_resource_path(conference.short_title, resource.id)) + + expect(flash).to eq('Resource successfully destroyed.') + end + + context 'on admin/resources#index' do + + scenario 'update an existing resource' do + visit admin_conference_resources_path(conference.short_title) + fill_in 'resource_used', with: 7 + + click_button 'Save' + resource.reload + + expect(flash).to eq('Resource successfully updated.') + expect(resource.used).to eq(7) + end + end + end +end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index df12e8b9..8637f3dd 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -29,7 +29,7 @@ describe 'User' do let(:commercial_event_confirmed) { create(:commercial, commercialable: event_confirmed) } let(:commercial_event_unconfirmed) { create(:commercial, commercialable: event_unconfirmed) } - + let(:resource) { create(:resource, conference: my_conference)} let(:registration) { create(:registration) } let(:program_with_cfp) { create(:program, cfp: create(:cfp)) } @@ -235,6 +235,8 @@ describe 'User' do it{ should be_able_to(:index, my_event.comment_threads.first) } it{ should_not be_able_to(:index, other_event.comment_threads.first) } + it{ should be_able_to(:manage, resource)} + %w(organizer cfp info_desk volunteers_coordinator).each do |role| it{ should be_able_to(:toggle_user, Role.find_by(name: role, resource: my_conference)) } it{ should be_able_to(:edit, Role.find_by(name: role, resource: my_conference)) } @@ -304,6 +306,11 @@ describe 'User' do it{ should be_able_to(:index, my_event.comment_threads.first) } it{ should_not be_able_to(:index, other_event.comment_threads.first) } + it{ should_not be_able_to(:manage, resource)} + it{ should be_able_to(:index, resource)} + it{ should be_able_to(:show, resource)} + it{ should be_able_to(:update, resource)} + it_behaves_like 'user with any role' it_behaves_like 'user with non-organizer role', 'cfp' end @@ -366,6 +373,11 @@ describe 'User' do it{ should_not be_able_to(:index, my_event.comment_threads.first) } it{ should_not be_able_to(:index, other_event.comment_threads.first) } + it{ should_not be_able_to(:manage, resource)} + it{ should be_able_to(:index, resource)} + it{ should be_able_to(:show, resource)} + it{ should be_able_to(:update, resource)} + it_behaves_like 'user with any role' it_behaves_like 'user with non-organizer role', 'info_desk' end @@ -427,6 +439,12 @@ describe 'User' do it{ should_not be_able_to(:manage, other_event.commercials.first) } it{ should_not be_able_to(:index, my_event.comment_threads.first) } it{ should_not be_able_to(:index, other_event.comment_threads.first) } + + it{ should_not be_able_to(:manage, resource)} + it{ should be_able_to(:index, resource)} + it{ should be_able_to(:show, resource)} + it{ should be_able_to(:update, resource)} + it 'should be_able to :manage Vposition' it 'should be_able to :manage Vday' diff --git a/spec/models/resource_spec.rb b/spec/models/resource_spec.rb new file mode 100644 index 00000000..51406f62 --- /dev/null +++ b/spec/models/resource_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Resource do + let(:conference) { create(:conference) } + let(:resource) { create :resource } + + it 'has a valid factory' do + expect(build(:resource)).to be_valid + end + + it 'is not valid with used greater than quantity' do + resource.used = resource.quantity + 1 + expect(resource.valid?).to eq false + end +end