diff --git a/app/models/application_choice.rb b/app/models/application_choice.rb index 7d6bb466ab8..f61cbcdc194 100644 --- a/app/models/application_choice.rb +++ b/app/models/application_choice.rb @@ -26,6 +26,8 @@ class ApplicationChoice < ApplicationRecord has_many :notes, dependent: :destroy has_many :interviews, dependent: :destroy + has_many :withdrawal_reasons, dependent: :destroy + has_many :draft_withdrawal_reasons, -> { draft }, class_name: 'WithdrawalReason' has_many :work_experiences, as: :experienceable, class_name: 'ApplicationWorkExperience' has_many :volunteering_experiences, as: :experienceable, class_name: 'ApplicationVolunteeringExperience' diff --git a/app/models/withdrawal_reason.rb b/app/models/withdrawal_reason.rb new file mode 100644 index 00000000000..fa589e4bb15 --- /dev/null +++ b/app/models/withdrawal_reason.rb @@ -0,0 +1,28 @@ +class WithdrawalReason < ApplicationRecord + belongs_to :application_choice + validates :reason, presence: true + + PERSONAL_CIRCUMSTANCES_KEY = 'personal-circumstances-have-changed'.freeze + CONFIG_PATH = 'config/candidate_withdrawal_reasons.yml'.freeze + + enum :status, { + draft: 'draft', + published: 'published', + } + + def publish! + update!(status: 'published') + end + + def self.selectable_reasons + YAML.load_file(CONFIG_PATH).fetch('candidate-withdrawal-reasons') + end + + def self.find_reason_options(reason = '') + if reason.empty? + selectable_reasons + else + selectable_reasons.dig(*reason.split('.')) + end + end +end diff --git a/app/services/feature_flag.rb b/app/services/feature_flag.rb index ba595229133..3905dd8f2db 100644 --- a/app/services/feature_flag.rb +++ b/app/services/feature_flag.rb @@ -29,6 +29,7 @@ def feature TEMPORARY_FEATURE_FLAGS = [ [:block_provider_activity_log, 'Block provider activity log if causing problems', 'Lori Bailey'], [:show_reference_confidentiality_status, 'Control whether the confidentiality status of references is explicitly communicated to candidates, referees and providers', 'Avin Hurry'], + [:new_candidate_withdrawal_reasons, 'Turn on new withdrawal reasons', 'Lori Bailey'], ].freeze CACHE_EXPIRES_IN = 1.day diff --git a/config/candidate_withdrawal_reasons.yml b/config/candidate_withdrawal_reasons.yml new file mode 100644 index 00000000000..40a982d6af6 --- /dev/null +++ b/config/candidate_withdrawal_reasons.yml @@ -0,0 +1,36 @@ +candidate-withdrawal-reasons: + applying-to-another-provider: + accepted-another-offer: {} + seen-a-course-that-suits-me-better: {} + provider-has-not-replied-to-me: {} + location-is-too-far-away: {} + personal-circumstances-have-changed: + concerns-about-cost-of-doing-course: {} + concerns-about-having-enough-time-to-train: {} + concerns-about-training-with-a-disability-or-health-condition: {} + other: {} + course-no-longer-available: {} + other: {} + change-or-update-application-with-this-provider: + update-my-application-correct-an-error-or-add-information: {} + change-study-pattern: {} + apply-for-a-different-subject-with-the-same-provider: {} + other: {} + apply-in-the-future: + personal-circumstances-have-changed: + concerns-about-cost-of-doing-course: {} + concerns-about-having-enough-time-to-train: {} + concerns-about-training-with-a-disability-or-health-condition: {} + other: {} + gain-more-experience: {} + improve-qualifications: {} + other: {} + do-not-want-to-train-anymore: + personal-circumstances-have-changed: + concerns-about-cost-of-doing-course: {} + concerns-about-having-enough-time-to-train: {} + concerns-about-training-with-a-disability-or-health-condition: {} + other: {} + another-career-path-or-accepted-a-job-offer: {} + other: {} + other: {} diff --git a/spec/models/application_choice_spec.rb b/spec/models/application_choice_spec.rb index 5537e8397d1..ce457f8842f 100644 --- a/spec/models/application_choice_spec.rb +++ b/spec/models/application_choice_spec.rb @@ -4,6 +4,7 @@ it { is_expected.to have_many(:work_experiences).class_name('ApplicationWorkExperience') } it { is_expected.to have_many(:volunteering_experiences).class_name('ApplicationVolunteeringExperience') } it { is_expected.to have_many(:work_history_breaks).class_name('ApplicationWorkHistoryBreak') } + it { is_expected.to have_many(:withdrawal_reasons) } describe 'auditing', :with_audited do it 'creates audit entries' do diff --git a/spec/models/withdrawal_reason_spec.rb b/spec/models/withdrawal_reason_spec.rb new file mode 100644 index 00000000000..945dbd6182c --- /dev/null +++ b/spec/models/withdrawal_reason_spec.rb @@ -0,0 +1,90 @@ +require 'rails_helper' + +RSpec.describe WithdrawalReason do + describe 'validations' do + it { is_expected.to validate_presence_of(:reason) } + end + + describe 'associations' do + it { is_expected.to belong_to(:application_choice) } + end + + describe '#selectable_reasons' do + it 'returns a hash of selectable reasons' do + expect(described_class.selectable_reasons).to eq(selectable_reasons) + end + end + + describe '#find_reason_options' do + context 'without a reason_id' do + it 'returns all selectable reasons' do + expect(described_class.find_reason_options).to eq(selectable_reasons) + end + end + + context 'with primary reason id' do + it 'returns all the reasons under that id' do + expect( + described_class.find_reason_options('applying-to-another-provider'), + ).to eq(selectable_reasons['applying-to-another-provider']) + end + end + + context 'with nested reason id' do + it 'returns all secondary reasons when there are any' do + expect( + described_class.find_reason_options('applying-to-another-provider.personal-circumstances-have-changed'), + ).to eq({ 'concerns-about-cost-of-doing-course' => {}, + 'concerns-about-having-enough-time-to-train' => {}, + 'concerns-about-training-with-a-disability-or-health-condition' => {}, + 'other' => {} }) + end + + it 'returns an empty hash when there are none' do + expect( + described_class.find_reason_options('applying-to-another-provider.location-is-too-far-away'), + ).to eq({}) + end + end + end + +private + + def selectable_reasons + { 'applying-to-another-provider' => + { 'accepted-another-offer' => {}, + 'seen-a-course-that-suits-me-better' => {}, + 'provider-has-not-replied-to-me' => {}, + 'location-is-too-far-away' => {}, + 'personal-circumstances-have-changed' => + { 'concerns-about-cost-of-doing-course' => {}, + 'concerns-about-having-enough-time-to-train' => {}, + 'concerns-about-training-with-a-disability-or-health-condition' => {}, + 'other' => {} }, + 'course-no-longer-available' => {}, + 'other' => {} }, + 'change-or-update-application-with-this-provider' => + { 'update-my-application-correct-an-error-or-add-information' => {}, + 'change-study-pattern' => {}, + 'apply-for-a-different-subject-with-the-same-provider' => {}, + 'other' => {} }, + 'apply-in-the-future' => + { 'personal-circumstances-have-changed' => + { 'concerns-about-cost-of-doing-course' => {}, + 'concerns-about-having-enough-time-to-train' => {}, + 'concerns-about-training-with-a-disability-or-health-condition' => {}, + 'other' => {} }, + 'gain-more-experience' => {}, + 'improve-qualifications' => {}, + 'other' => {} }, + 'do-not-want-to-train-anymore' => + { 'personal-circumstances-have-changed' => + { 'concerns-about-cost-of-doing-course' => {}, + 'concerns-about-having-enough-time-to-train' => {}, + 'concerns-about-training-with-a-disability-or-health-condition' => {}, + 'other' => {} }, + 'another-career-path-or-accepted-a-job-offer' => {}, + 'other' => {} }, + 'other' => {} } + end +end