diff --git a/app/models/participant_profile/ecf.rb b/app/models/participant_profile/ecf.rb index 84a0706707..10a09af71d 100644 --- a/app/models/participant_profile/ecf.rb +++ b/app/models/participant_profile/ecf.rb @@ -70,11 +70,39 @@ def self.eligible_to_change_cohort_and_continue_training(in_cohort_start_year:, query end + def self.archivable(for_cohort_start_year:, restrict_to_participant_ids: []) + latest_cohort = Cohort.order(start_year: :desc).first + return none unless for_cohort_start_year <= latest_cohort.start_year - Cohort::OPEN_COHORTS_COUNT + + unbillable_states = %i[ineligible voided submitted].freeze + + # Find all participants that have no FIP induction records (as finding those with only FIP is more complicated). + not_fip_induction_records = InductionRecord.left_joins(:induction_programme).where.not(induction_programme: { training_programme: :full_induction_programme }) + not_fip_induction_records = not_fip_induction_records.where(participant_profile_id: restrict_to_participant_ids) if restrict_to_participant_ids.any? + + query = left_joins(:participant_declarations, schedule: :cohort) + # Exclude participants that have any induction records that are not FIP. + .where.not(id: not_fip_induction_records.select(:participant_profile_id)) + .where(cohorts: { start_year: for_cohort_start_year }) + # Exclude participants that have any billable/submitted declarations, but + # retain participants that have no declarations at all. + .where.not("participant_declarations.id IS NOT NULL AND participant_declarations.state NOT IN (?)", unbillable_states) + .distinct + + query = query.where(id: restrict_to_participant_ids) if restrict_to_participant_ids.any? + + query + end + # Instance Methods def eligible_to_change_cohort_and_continue_training?(in_cohort_start_year:) self.class.eligible_to_change_cohort_and_continue_training(in_cohort_start_year:, restrict_to_participant_ids: [id]).exists? end + def archivable?(for_cohort_start_year:) + self.class.archivable(for_cohort_start_year:, restrict_to_participant_ids: [id]).exists? + end + def completed_induction? induction_completion_date.present? end diff --git a/app/models/participant_profile/ect.rb b/app/models/participant_profile/ect.rb index 28c30174f7..ab1bab383f 100644 --- a/app/models/participant_profile/ect.rb +++ b/app/models/participant_profile/ect.rb @@ -10,6 +10,14 @@ class ParticipantProfile::ECT < ParticipantProfile::ECF where(induction_start_date: nil).joins(:ecf_participant_eligibility).merge(ECFParticipantEligibility.waiting_for_induction) } + def self.archivable(for_cohort_start_year:, restrict_to_participant_ids: []) + latest_induction_start_date = Date.new(for_cohort_start_year, 9, 1) + + super(for_cohort_start_year:, restrict_to_participant_ids:) + .where(induction_completion_date: nil) + .where("induction_start_date IS NULL OR induction_start_date < ?", latest_induction_start_date) + end + def ect? true end diff --git a/app/models/participant_profile/mentor.rb b/app/models/participant_profile/mentor.rb index f8ca1a3a58..70c323d71f 100644 --- a/app/models/participant_profile/mentor.rb +++ b/app/models/participant_profile/mentor.rb @@ -21,6 +21,12 @@ class ParticipantProfile::Mentor < ParticipantProfile::ECF started_not_completed: "started_not_completed", } + def self.archivable(for_cohort_start_year:, restrict_to_participant_ids: []) + super(for_cohort_start_year:, restrict_to_participant_ids:) + .where(mentor_completion_date: nil) + .where.not(id: InductionRecord.where.not(mentor_profile_id: nil).select(:mentor_profile_id).distinct) + end + def complete_training!(completion_date:, completion_reason:) self.mentor_completion_date = completion_date self.mentor_completion_reason = completion_reason diff --git a/spec/factories/services/mentors/create.rb b/spec/factories/services/mentors/create.rb index c9491f859e..4a1a6b0395 100644 --- a/spec/factories/services/mentors/create.rb +++ b/spec/factories/services/mentors/create.rb @@ -6,10 +6,11 @@ lead_provider { create(:cpd_lead_provider, :with_lead_provider).lead_provider } uplifts { [] } trn { user.teacher_profile&.trn || sprintf("%07i", Random.random_number(9_999_999)) } + cohort { Cohort.current || create(:cohort, :current) } end user { create(:user) } - school_cohort { create(:school_cohort, :fip, :with_induction_programme, *uplifts, lead_provider:) } + school_cohort { create(:school_cohort, :fip, :with_induction_programme, *uplifts, lead_provider:, cohort:) } full_name { user.full_name } email { user.email } diff --git a/spec/models/participant_profile/ect_spec.rb b/spec/models/participant_profile/ect_spec.rb index 781852a2a9..da29a98f0e 100644 --- a/spec/models/participant_profile/ect_spec.rb +++ b/spec/models/participant_profile/ect_spec.rb @@ -47,4 +47,27 @@ end include_context "can change cohort and continue training", :ect, :mentor, :induction_completion_date + + include_context "can archive participant profile", :mentor, :induction_completion_date do + def create_declaration(attrs = {}) + create(:ect_participant_declaration, attrs) + end + + def create_profile(attrs = {}) + create(:ect_participant_profile, attrs) + end + + describe ".archivable" do + subject { described_class.archivable(for_cohort_start_year:) } + + it "does not include participants where the induction_start_date is 1/9/ or later" do + build_profile(cohort: eligible_cohort, induction_start_date: Date.new(for_cohort_start_year, 9, 1)) + build_profile(cohort: eligible_cohort, induction_start_date: Date.new(for_cohort_start_year + 1, 3, 1)) + + eligible_participant = build_profile(cohort: eligible_cohort) + + is_expected.to contain_exactly(eligible_participant) + end + end + end end diff --git a/spec/models/participant_profile/mentor_spec.rb b/spec/models/participant_profile/mentor_spec.rb index a32eeb79d4..cf80ad2284 100644 --- a/spec/models/participant_profile/mentor_spec.rb +++ b/spec/models/participant_profile/mentor_spec.rb @@ -61,4 +61,28 @@ end include_context "can change cohort and continue training", :mentor, :ect, :mentor_completion_date + + include_context "can archive participant profile", :ect, :mentor_completion_date do + def create_declaration(attrs = {}) + create(:mentor_participant_declaration, attrs) + end + + def create_profile(attrs = {}) + create(:mentor_participant_profile, attrs) + end + + describe ".archivable" do + subject { described_class.archivable(for_cohort_start_year:) } + + it "does not include participants that have mentees" do + build_profile(cohort: eligible_cohort).tap do |mentor_profile| + create(:induction_record, :ect, mentor_profile:) + end + + eligible_participant = build_profile(cohort: eligible_cohort) + + is_expected.to contain_exactly(eligible_participant) + end + end + end end diff --git a/spec/support/shared_examples/archivable_support.rb b/spec/support/shared_examples/archivable_support.rb new file mode 100644 index 0000000000..cc18d1ed4d --- /dev/null +++ b/spec/support/shared_examples/archivable_support.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +RSpec.shared_examples "can archive participant profile" do |other_participant_type, completed_training_at_attribute| + let(:for_cohort_start_year) { latest_cohort.start_year - Cohort::OPEN_COHORTS_COUNT } + let(:eligible_cohort) { create(:cohort, start_year: for_cohort_start_year) } + let(:latest_cohort) { create(:cohort, :current) } + + def build_profile(attrs = {}) + create_profile(attrs).tap do |participant_profile| + create(:induction_record, participant_profile:, induction_programme: create(:induction_programme, :fip)) + end + end + + def build_declaration(attrs = {}) + create_declaration(attrs) do |declaration| + participant_profile = declaration.participant_profile + create(:induction_record, participant_profile:, induction_programme: create(:induction_programme, :fip)) + end + end + + describe ".archivable" do + let(:restrict_to_participant_ids) { [] } + let(:eligible_no_declarations) { build_profile(cohort: eligible_cohort) } + let(:eligible_only_unbillable_declarations) { build_declaration(state: :voided, cohort: eligible_cohort).participant_profile } + + before do + # Participant not in an eligible cohort. + build_profile(cohort: latest_cohort) + + # Participant with billable declarations. + build_declaration(state: :paid, cohort: eligible_cohort) + + # Other participant type. + create("#{other_participant_type}_participant_profile", cohort: eligible_cohort) + + # Ineligible due to having the completed_training_at_attribute populated. + build_declaration(state: :ineligible, cohort: eligible_cohort).participant_profile.update!("#{completed_training_at_attribute}": 1.month.ago) + + # Participant with CIP induction record. + build_profile(cohort: eligible_cohort).tap do |participant_profile| + create(:induction_record, participant_profile:, induction_programme: create(:induction_programme, :cip)) + end + end + + subject { described_class.archivable(for_cohort_start_year:, restrict_to_participant_ids:) } + + it { is_expected.to contain_exactly(eligible_no_declarations, eligible_only_unbillable_declarations) } + + context "when restricted to a set of participant IDs" do + let(:restrict_to_participant_ids) { [eligible_no_declarations.id] } + + it { is_expected.to contain_exactly(eligible_no_declarations) } + end + end + + describe "archivable?" do + it "returns true if the participant is in an eligible cohort and has no declarations" do + participant_profile = build_profile(cohort: eligible_cohort) + expect(participant_profile).to be_archivable(for_cohort_start_year:) + end + + %i[ineligible voided submitted].each do |unbillable_state| + it "returns true if the participant is in an eligible cohort and the participant has only #{unbillable_state} declarations" do + participant_profile = build_declaration(state: unbillable_state, cohort: eligible_cohort).participant_profile + expect(participant_profile).to be_archivable(for_cohort_start_year:) + end + end + + it "returns false if the participant is not in an eligible cohort" do + participant_profile = build_profile(cohort: latest_cohort) + expect(participant_profile).not_to be_archivable(for_cohort_start_year:) + end + + it "returns false if the participant has billable declarations" do + participant_profile = build_declaration(state: :paid, cohort: eligible_cohort).participant_profile + expect(participant_profile).not_to be_archivable(for_cohort_start_year:) + end + + it "returns false if the participant has a CIP induction record" do + participant_profile = build_profile(cohort: latest_cohort) + create(:induction_record, participant_profile:, induction_programme: create(:induction_programme, :cip)) + expect(participant_profile).not_to be_archivable(for_cohort_start_year:) + end + end +end