diff --git a/app/services/induction/amend_participant_cohort.rb b/app/services/induction/amend_participant_cohort.rb index 3e1e739fa77..e8a21cf00c2 100644 --- a/app/services/induction/amend_participant_cohort.rb +++ b/app/services/induction/amend_participant_cohort.rb @@ -22,7 +22,7 @@ # source_cohort_start_year: 2021, # schedule:).save # -# - For cohort changes on participants whose current cohort has been payments_frozen and are moved to +# - For cohort changes on participants whose current cohort has been payments_frozen and are transferred to # the active registration cohort, it will automatically flag the participant_profile as # cohort_changed_after_payments_frozen: true # @@ -63,8 +63,6 @@ class AmendParticipantCohort }, on: :start - validate :target_cohort_payments_active - validates :participant_profile, presence: true, on: :start @@ -72,14 +70,15 @@ class AmendParticipantCohort validate :target_cohort_start_year_matches_schedule validates :participant_profile, - active_participant_profile: true, - unfinished_training_participant_profile: true + active_participant_profile: true - validate :eligible_to_be_moved_after_payments_frozen, if: :cohort_changed_after_payments_frozen + validate :target_cohort_payments_active, unless: :back_to_payments_frozen_cohort? + validate :transfer_from_payments_frozen_cohort, if: :transfer_from_payments_frozen_cohort? + validate :transfer_to_payments_frozen_cohort, if: :back_to_payments_frozen_cohort? validates :participant_declarations, absence: { message: :billable_or_submitted }, - unless: :cohort_changed_after_payments_frozen + unless: :payments_frozen_transfer? validates :induction_record, presence: { @@ -98,10 +97,6 @@ class AmendParticipantCohort delegate :school, to: :induction_record, allow_nil: true - def cohort_changed_after_payments_frozen - source_cohort&.payments_frozen? && target_cohort == Cohort.active_registration_cohort - end - def save return false unless valid?(:start) return true if in_target?(induction_record) @@ -116,6 +111,14 @@ def initialize(*) @target_cohort_start_year = (@target_cohort_start_year || @schedule&.cohort_start_year).to_i end + def back_to_payments_frozen_cohort? + participant_profile&.cohort_changed_after_payments_frozen? && target_cohort&.payments_frozen? + end + + def billable_declarations_in_cohort?(cohort) + participant_profile.participant_declarations.where(cohort:).billable.exists? + end + def current_induction_record_updated? ActiveRecord::Base.transaction do Induction::ChangeInductionRecord.call(induction_record:, @@ -166,14 +169,7 @@ def participant_declarations return false unless participant_profile return @participant_declarations if instance_variable_defined?(:@participant_declarations) - @participant_declarations = if cohort_changed_after_payments_frozen - - else - participant_profile - .participant_declarations - .billable_or_changeable - .exists? - end + @participant_declarations = participant_profile.participant_declarations.billable_or_changeable.exists? end def schedule @@ -198,15 +194,31 @@ def target_school_cohort @target_school_cohort ||= SchoolCohort.find_by(school:, cohort: target_cohort) end + def payments_frozen_transfer? + transfer_from_payments_frozen_cohort? || back_to_payments_frozen_cohort? + end + + def transfer_from_payments_frozen_cohort? + source_cohort&.payments_frozen? && target_cohort == Cohort.active_registration_cohort + end + + alias_method :cohort_changed_after_payments_frozen, :transfer_from_payments_frozen_cohort? + # Validations - def eligible_to_be_moved_after_payments_frozen + + def target_cohort_payments_active + errors.add(:target_cohort_start_year, :payments_frozen) if target_cohort&.payments_frozen? + end + + def transfer_from_payments_frozen_cohort unless participant_profile.eligible_to_change_cohort_and_continue_training? - errors.add(:participant_profile, :participant_not_eligible_to_be_moved) + errors.add(:participant_profile, :not_eligible_to_be_transferred_from_current_cohort) end end - def target_cohort_payments_active - errors.add(:target_cohort_start_year, :payments_frozen) if target_cohort&.payments_frozen? + def transfer_to_payments_frozen_cohort + errors.add(:participant_profile, :billable_declarations_in_cohort) if billable_declarations_in_cohort?(source_cohort) + errors.add(:participant_profile, :no_billable_declarations_in_cohort) unless billable_declarations_in_cohort?(target_cohort) end def target_cohort_start_year_matches_schedule diff --git a/app/validators/unfinished_training_participant_profile_validator.rb b/app/validators/unfinished_training_participant_profile_validator.rb deleted file mode 100644 index 6e5d89de058..00000000000 --- a/app/validators/unfinished_training_participant_profile_validator.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -class UnfinishedTrainingParticipantProfileValidator < ActiveModel::EachValidator - def validate_each(record, attribute, value) - record.errors.add(attribute, I18n.t("errors.participant_profile.not_a_participant_profile")) unless participant_profile?(value) - record.errors.add(attribute, I18n.t("errors.participant_profile.training_complete")) if completed_training?(value) - end - - def participant_profile?(instance) - instance.is_a?(ParticipantProfile) - end - - def completed_training?(instance) - instance&.completed_training? - end -end diff --git a/config/locales/en.yml b/config/locales/en.yml index 91f1edca071..97445c06758 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -321,13 +321,15 @@ en: no_default_induction_programme: "No default induction programme set for %{start_academic_year} academic year by school %{school_name}" school_cohort_not_setup: "%{start_academic_year} academic year not setup by school %{school_name}" participant_declarations: - exist: "The participant must have no declarations" billable_or_submitted: "The participant has billable or submitted declarations" completed: "The participant has not had a 'completed' declaration submitted for them. Therefore you cannot update their outcome." participant_profile: + billable_declarations_in_cohort: "The participant has billable declarations in their current cohort" + no_billable_declarations_in_cohort: "The participant has no billable declarations in destination cohort" blank: "Not registered" not_active: "Not active" - not_eligible_to_be_moved: "Not eligible to be moved from their current cohort" + not_eligible_to_be_transferred_back: "Not eligible to be transferred back to their original cohort" + not_eligible_to_be_transferred_from_current_cohort: "Not eligible to be transferred from their current cohort" source_cohort_start_year: invalid: "Invalid value. Must be an integer between %{start} and %{end}" target_cohort_start_year: diff --git a/spec/services/induction/amend_participant_cohort_spec.rb b/spec/services/induction/amend_participant_cohort_spec.rb index 9f5773b4084..51da4fed2fe 100644 --- a/spec/services/induction/amend_participant_cohort_spec.rb +++ b/spec/services/induction/amend_participant_cohort_spec.rb @@ -86,18 +86,6 @@ end end - context "when payments in the target cohort has been frozen" do - before do - Cohort.find_by_start_year(target_cohort_start_year).update!(payments_frozen_at: Date.yesterday) - end - - it "returns false and set errors" do - expect(form.save).to be_falsey - expect(form.errors.messages[:target_cohort_start_year].first) - .to eq("Not permitted. The cohort is frozen for payments") - end - end - context "enrolling participant" do let!(:source_cohort) { create(:cohort, start_year: source_cohort_start_year) } let!(:source_school_cohort) { create(:school_cohort, :fip, cohort: source_cohort) } @@ -154,86 +142,102 @@ end end - context "when the participant has completed induction" do + context "when the participant is transferring to a payments-frozen cohort" do before do - participant_profile.induction_completion_date = Date.current + target_cohort.update!(payments_frozen_at: 1.month.ago) end - it "returns false and set errors" do + context "when they are transferring back to their original cohort" do + before do + participant_profile.update!(cohort_changed_after_payments_frozen: true) + end + + it "do not set errors" do + expect(form.save).to be_falsey + expect(form.errors.map(&:attribute)).not_to include(:target_cohort_start_year) + end + end + + it "set errors" do expect(form.save).to be_falsey - expect(form.errors.first.attribute).to eq(:participant_profile) - expect(form.errors.first.message).to eq("The participant has completed their ECF training") + expect(form.errors[:target_cohort_start_year]).to include("Not permitted. The cohort is frozen for payments") end end - context "when it is not transferred due to payments frozen in their current cohort" do - %i[submitted eligible payable paid].each do |declaration_state| - context "when the participant has #{declaration_state} declarations" do - before do - participant_profile.participant_declarations.create!(declaration_date: Date.new(2020, 10, 10), - declaration_type: :started, - state: declaration_state, - course_identifier: "ecf-induction", - cpd_lead_provider: create(:cpd_lead_provider), - user: participant_profile.user, - cohort: participant_profile.schedule.cohort) - end + context "when the participant is transferred from their their payments-frozen cohort to the currently one open for registration" do + let(:target_cohort_start_year) { Cohort.active_registration_cohort.start_year } - it "returns false and set errors on declarations" do - expect(form.save).to be_falsey - expect(form.errors.first.attribute).to eq(:participant_declarations) - expect(form.errors.first.message).to eq("The participant has billable or submitted declarations") - end - end + before do + source_cohort.update!(payments_frozen_at: Time.current) end - %i[voided ineligible awaiting_clawback clawed_back].each do |declaration_state| - context "when the participant has #{declaration_state} declarations" do - before do - participant_profile.participant_declarations.create!(declaration_date: Date.new(2020, 10, 10), - declaration_type: :started, - state: declaration_state, - course_identifier: "ecf-induction", - cpd_lead_provider: create(:cpd_lead_provider), - user: participant_profile.user, - cohort: participant_profile.schedule.cohort) - end + context "when the participant is eligible to be transferred" do + before do + allow(participant_profile).to receive(:eligible_to_change_cohort_and_continue_training?).and_return(true) + end - it "do not set errors on declarations" do - expect(form.save).to be_falsey - expect(form.errors.map(&:attribute)).not_to include(:participant_declarations) - end + it "do not set errors" do + expect(form.save).to be_falsey + expect(form.errors.map(&:attribute)).not_to include(:participant_profile) end end - context "when the participant has no declarations" do - it "do not set errors on declarations" do + context "when the participant is not eligible to be transferred" do + before do + allow(participant_profile).to receive(:eligible_to_change_cohort_and_continue_training?).and_return(false) + end + + it "set errors on participant profile" do expect(form.save).to be_falsey - expect(form.errors.map(&:attribute)).not_to include(:participant_declarations) + expect(form.errors[:participant_profile]).to include("Not eligible to be transferred from their current cohort") end end end - context "when it is transferred due to payments frozen in their current cohort" do - context "when the participant is eligible to be moved" do + context "when the participant is transferred back to their original payments-frozen cohort" do + before do + target_cohort.update!(payments_frozen_at: 1.month.ago) + participant_profile.update!(cohort_changed_after_payments_frozen: true) + end + + context "when the participant has billable declarations in current cohort" do before do - allow(:participant_profile).to receive(:eligible_to_change_cohort_and_continue_training?).and_return(true) + participant_profile.participant_declarations.create!(declaration_date: Date.new(source_cohort_start_year, 10, 10), + declaration_type: :started, + state: :eligible, + course_identifier: "ecf-induction", + cpd_lead_provider: create(:cpd_lead_provider), + user: participant_profile.user, + cohort: source_cohort) end - it "do not set errors" do + it "set errors" do expect(form.save).to be_falsey - expect(form.errors.map(&:attribute)).not_to include(:participant_profile) + expect(form.errors[:participant_profile]).to include("The participant has billable declarations in their current cohort") end end - context "when the participant is not eligible to be moved" do + context "when the participant has no billable declarations in destination cohort" do + it "set errors" do + expect(form.save).to be_falsey + expect(form.errors[:participant_profile]).to include("The participant has no billable declarations in destination cohort") + end + end + + context "when the participant is eligible to be transferred" do before do - allow(:participant_profile).to receive(:eligible_to_change_cohort_and_continue_training?).and_return(false) + participant_profile.participant_declarations.create!(declaration_date: Date.new(target_cohort_start_year, 10, 10), + declaration_type: :started, + state: :eligible, + course_identifier: "ecf-induction", + cpd_lead_provider: create(:cpd_lead_provider), + user: participant_profile.user, + cohort: target_cohort) end - it "set errors on participant profile" do + it "set no errors" do expect(form.save).to be_falsey - expect(form.errors[:participant_profile]).to include("Not eligible to be moved from their current cohort") + expect(form.errors[:participant_profile]).to be_blank end end end @@ -250,35 +254,40 @@ cohort: participant_profile.schedule.cohort) end - context "when it is cohort-moved to the active registration cohort due to payments frozen in their current cohort" do - let(:target_cohort_start_year) { Cohort.active_registration_cohort.start_year } - - before do - source_cohort.update!(payments_frozen_at: Time.current) - end - - context "when the declaration is of type 'completed'" do - before do - participant_profile.participant_declarations.first.update!(declaration_type: "completed") - end + it "returns false and set errors on declarations" do + expect(form.save).to be_falsey + expect(form.errors.first.attribute).to eq(:participant_declarations) + expect(form.errors.first.message).to eq("The participant has billable or submitted declarations") + end + end + end - if declaration_state != :submitted - it "returns false and set errors" do - expect(form.save).to be_falsey - expect(form.errors.first.attribute).to eq(:participant_profile) - expect(form.errors.first.message).to eq("Not eligible to be moved from their current cohort") - end - end - end + %i[voided ineligible awaiting_clawback clawed_back].each do |declaration_state| + context "when the participant has #{declaration_state} declarations" do + before do + participant_profile.participant_declarations.create!(declaration_date: Date.new(2020, 10, 10), + declaration_type: :started, + state: declaration_state, + course_identifier: "ecf-induction", + cpd_lead_provider: create(:cpd_lead_provider), + user: participant_profile.user, + cohort: participant_profile.schedule.cohort) + end - it "do not set errors" do - expect(form.save).to be_falsey - expect(form.errors.map(&:attribute)).not_to include(:participant_profile) - end + it "do not set errors on declarations" do + expect(form.save).to be_falsey + expect(form.errors.map(&:attribute)).not_to include(:participant_declarations) end end end + context "when the participant has no declarations" do + it "do not set errors on declarations" do + expect(form.save).to be_falsey + expect(form.errors.map(&:attribute)).not_to include(:participant_declarations) + end + end + context "when the participant is not enrolled on the source school cohort" do it "returns false and set errors" do expect(form.save).to be_falsey @@ -401,17 +410,18 @@ expect(form.errors).to be_empty end - context "when the move is due to payments frozen in the cohort of the participant" do + context "when the transfer is due to payments frozen in the cohort of the participant" do before do source_cohort.update!(payments_frozen_at: Time.current) + allow(participant_profile).to receive(:eligible_to_change_cohort_and_continue_training?).and_return(true) end - it "mark the participant as moved for that reason" do + it "mark the participant as transferred for that reason" do expect(form.save).to be_truthy expect(participant_profile).to be_cohort_changed_after_payments_frozen end - it "mark the participant as moved from the original cohort" do + it "mark the participant as transferred from the original cohort" do expect(form.save).to be_truthy end end diff --git a/spec/validators/unfinished_training_participant_profile_validator_spec.rb b/spec/validators/unfinished_training_participant_profile_validator_spec.rb deleted file mode 100644 index 090bb0743f1..00000000000 --- a/spec/validators/unfinished_training_participant_profile_validator_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -class UnfinishedTrainingParticipantProfileValidatorConsumerClass - include ActiveModel::Model - attr_accessor :participant_profile - - validates :participant_profile, unfinished_training_participant_profile: true -end - -RSpec.describe UnfinishedTrainingParticipantProfileValidator, type: :model do - subject { UnfinishedTrainingParticipantProfileValidatorConsumerClass.new(participant_profile:) } - - context "when the participant is not a ParticipantProfile instance" do - let(:participant_profile) { nil } - - it "is not valid" do - expect(subject).not_to be_valid - expect(subject.errors[:participant_profile]).to include(I18n.t("errors.participant_profile.not_a_participant_profile")) - end - end - - context "when the participant has not completed training" do - let(:participant_profile) { ParticipantProfile::ECT.new(induction_completion_date: nil) } - - it "is valid" do - expect(subject).to be_valid - end - end - - context "when the participant has completed training" do - let(:participant_profile) { ParticipantProfile::Mentor.new(mentor_completion_date: Date.current) } - - it "is not valid" do - expect(subject).to_not be_valid - expect(subject.errors[:participant_profile]).to include(I18n.t("errors.participant_profile.training_complete")) - end - end -end