diff --git a/api/app/controllers/v1/new_club_applications_controller.rb b/api/app/controllers/v1/new_club_applications_controller.rb index 1489b2e08..d16111fb3 100644 --- a/api/app/controllers/v1/new_club_applications_controller.rb +++ b/api/app/controllers/v1/new_club_applications_controller.rb @@ -130,7 +130,10 @@ def club_application_params :point_of_contact_id, :interviewed_at, :interview_duration, - :interview_notes + :interview_notes, + :rejected_at, + :rejected_reason, + :rejected_notes ) end end diff --git a/api/app/models/new_club_application.rb b/api/app/models/new_club_application.rb index 1c0cf3327..a14450f7c 100644 --- a/api/app/models/new_club_application.rb +++ b/api/app/models/new_club_application.rb @@ -26,6 +26,10 @@ class NewClubApplication < ApplicationRecord private_school charter_school ] + enum rejected_reason: %i[ + other + ] + with_options if: -> { submitted_at.present? } do |application| application.validates :high_school_name, :high_school_type, @@ -63,6 +67,15 @@ class NewClubApplication < ApplicationRecord interview_notes.present? } + # submitted_at must be set for rejected_at to be set + validates :submitted_at, presence: true, if: -> { rejected_at.present? } + validates :rejected_at, :rejected_reason, :rejected_notes, + presence: true, if: lambda { + rejected_at.present? || + rejected_reason.present? || + rejected_notes.present? + } + def submit! self.submitted_at = Time.current diff --git a/api/app/serializers/new_club_application_serializer.rb b/api/app/serializers/new_club_application_serializer.rb index b085c9258..0228e81be 100644 --- a/api/app/serializers/new_club_application_serializer.rb +++ b/api/app/serializers/new_club_application_serializer.rb @@ -30,9 +30,12 @@ class NewClubApplicationSerializer < ActiveModel::Serializer :point_of_contact_id, :submitted_at, :interviewed_at, - :interview_duration + :interview_duration, + :rejected_at attribute :interview_notes, if: :admin? + attribute :rejected_reason, if: :admin? + attribute :rejected_notes, if: :admin? has_many :leader_profiles diff --git a/api/db/migrate/20180131005432_add_rejection_to_new_club_applications.rb b/api/db/migrate/20180131005432_add_rejection_to_new_club_applications.rb new file mode 100644 index 000000000..a52ab00fa --- /dev/null +++ b/api/db/migrate/20180131005432_add_rejection_to_new_club_applications.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddRejectionToNewClubApplications < ActiveRecord::Migration[5.1] + def change + add_column :new_club_applications, :rejected_at, :datetime + add_column :new_club_applications, :rejected_reason, :integer + add_column :new_club_applications, :rejected_notes, :text + end +end diff --git a/api/db/schema.rb b/api/db/schema.rb index fe56b42d0..e91b25ccd 100644 --- a/api/db/schema.rb +++ b/api/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180130224937) do +ActiveRecord::Schema.define(version: 20180131005432) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -278,6 +278,9 @@ t.datetime "interviewed_at" t.text "interview_notes" t.integer "interview_duration" + t.datetime "rejected_at" + t.integer "rejected_reason" + t.text "rejected_notes" t.index ["point_of_contact_id"], name: "index_new_club_applications_on_point_of_contact_id" end diff --git a/api/spec/models/new_club_application_spec.rb b/api/spec/models/new_club_application_spec.rb index 50c4ca63f..621598873 100644 --- a/api/spec/models/new_club_application_spec.rb +++ b/api/spec/models/new_club_application_spec.rb @@ -55,9 +55,14 @@ it { should have_db_column :interview_duration } it { should have_db_column :interview_notes } + it { should have_db_column :rejected_at } + it { should have_db_column :rejected_reason } + it { should have_db_column :rejected_notes } + ## enums ## it { should define_enum_for :high_school_type } + it { should define_enum_for :rejected_reason } ## validations ## @@ -129,6 +134,56 @@ end end + describe 'rejected fields' do + subject { create(:completed_new_club_application) } + before { subject.submit! } + + it 'does not allow rejected_at to be set unless submitted_at is' do + subject.submitted_at = nil + subject.rejected_at = Time.current + + subject.validate + expect(subject.errors).to include('submitted_at') + end + + it 'should require other fields to be set if rejected_at is' do + expect(subject.valid?).to be(true) + + subject.rejected_at = Time.current + + subject.validate + expect(subject.errors).to include('rejected_reason') + expect(subject.errors).to include('rejected_notes') + end + + it 'should require other fields to be set if rejected_reason is' do + expect(subject.valid?).to be(true) + + subject.rejected_reason = :other + + subject.validate + expect(subject.errors).to include('rejected_at') + expect(subject.errors).to include('rejected_notes') + end + + it 'should require other fields to be set if rejected_notes is' do + expect(subject.valid?).to be(true) + + subject.rejected_notes = "Didn't have faith in leadship skills." + + subject.validate + expect(subject.errors).to include('rejected_at') + expect(subject.errors).to include('rejected_reason') + end + + it 'should be valid if all rejected fields are set' do + subject.rejected_at = Time.current + subject.rejected_reason = :other + subject.rejected_notes = 'Example rejection reason.' + expect(subject.valid?).to eq(true) + end + end + describe ':submit!' do subject { create(:completed_new_club_application, profile_count: 3) } let(:user) { subject.point_of_contact } diff --git a/api/spec/requests/v1/new_club_applications_spec.rb b/api/spec/requests/v1/new_club_applications_spec.rb index 61cb59802..9ec78769d 100644 --- a/api/spec/requests/v1/new_club_applications_spec.rb +++ b/api/spec/requests/v1/new_club_applications_spec.rb @@ -136,9 +136,14 @@ expect(json).to include('interviewed_at') expect(json).to include('interview_duration') expect(json).to_not include('interview_notes') + + # includes rejected_at, but not rejected_reason or rejected_notes + expect(json).to include('rejected_at') + expect(json).to_not include('rejected_reason') + expect(json).to_not include('rejected_notes') end - it 'includes interview_notes when authed as an admin' do + it 'includes private fields when authed as an admin' do user.make_admin! user.save @@ -146,7 +151,14 @@ headers: auth_headers expect(response.status).to eq(200) + + expect(json).to include('interviewed_at') + expect(json).to include('interview_duration') expect(json).to include('interview_notes') + + expect(json).to include('rejected_at') + expect(json).to include('rejected_reason') + expect(json).to include('rejected_notes') end it '404s when application does not exist' do @@ -309,6 +321,28 @@ expect(response.status).to eq(422) end + it 'fails to update rejection fields' do + application = create(:completed_new_club_application) + create(:completed_leader_profile, new_club_application: application, + user: user) + application.update_attributes(point_of_contact: user) + + # application must be submitted for any modification (even by admins) to + # be allowed + post "/v1/new_club_applications/#{application.id}/submit", + headers: auth_headers + + patch "/v1/new_club_applications/#{application.id}", + headers: auth_headers, + params: { + rejected_at: Time.current, + rejected_reason: :other, + rejected_notes: 'Example reason' + } + + expect(response.status).to eq(422) + end + context 'when admin' do let(:club_application) do app = create(:completed_new_club_application) @@ -352,7 +386,7 @@ expect(json['errors']).to include('interview_notes') end - it 'fails if application is not submitted' do + it 'fails to update interview fields if application is not submitted' do club_application = create(:new_club_application) patch "/v1/new_club_applications/#{club_application.id}", @@ -364,6 +398,47 @@ expect(response.status).to eq(422) expect(json['errors']['submitted_at']).to include("can't be blank") end + + it 'allows updating rejected fields' do + patch "/v1/new_club_applications/#{club_application.id}", + headers: auth_headers, + params: { + rejected_at: Time.current, + rejected_reason: :other, + rejected_notes: 'Example reason' + } + + expect(response.status).to eq(200) + expect( + Time.zone.parse(json['rejected_at']) + ).to be_within(3.seconds).of(Time.current) + expect(json).to include('rejected_reason' => 'other') + expect(json).to include('rejected_notes' => 'Example reason') + end + + it 'fails if not all rejected fields are set' do + patch "/v1/new_club_applications/#{club_application.id}", + headers: auth_headers, + params: { + rejected_at: Time.current + } + + expect(response.status).to eq(422) + expect(json['errors']).to include('rejected_reason', 'rejected_notes') + end + + it 'fails to update rejected fields if application is not submitted' do + club_application = create(:new_club_application) + + patch "/v1/new_club_applications/#{club_application.id}", + headers: auth_headers, + params: { + rejected_at: Time.current + } + + expect(response.status).to eq(422) + expect(json['errors']['submitted_at']).to include("can't be blank") + end end end