diff --git a/app/queries/get_applications_to_send_deadline_reminders_to.rb b/app/queries/get_applications_to_send_deadline_reminders_to.rb index 480ba88ef16..789c6b9b2fc 100644 --- a/app/queries/get_applications_to_send_deadline_reminders_to.rb +++ b/app/queries/get_applications_to_send_deadline_reminders_to.rb @@ -6,7 +6,10 @@ def self.call def self.deadline_reminder_query ApplicationForm .joins(:candidate) - .where(submitted_at: nil, recruitment_cycle_year: RecruitmentCycle.current_year) + .current_cycle + .unsubmitted .where.not(candidate: { unsubscribed_from_emails: true }) + .where.not(candidate: { submission_blocked: true }) + .where.not(candidate: { account_locked: true }) end end diff --git a/app/queries/get_incomplete_course_choice_applications_ready_to_nudge.rb b/app/queries/get_incomplete_course_choice_applications_ready_to_nudge.rb index 7f4fafd73d0..0198c98e566 100644 --- a/app/queries/get_incomplete_course_choice_applications_ready_to_nudge.rb +++ b/app/queries/get_incomplete_course_choice_applications_ready_to_nudge.rb @@ -8,7 +8,10 @@ class GetIncompleteCourseChoiceApplicationsReadyToNudge def call ApplicationForm - .unsubmitted + .joins(:candidate) + .where.not('candidate.submission_blocked': true) + .where.not('candidate.account_locked': true) + .where.not('candidate.unsubscribed_from_emails': true) .inactive_since(7.days.ago) .with_completion(COMPLETION_ATTRS) .current_cycle diff --git a/app/queries/get_incomplete_personal_statement_applications_ready_to_nudge.rb b/app/queries/get_incomplete_personal_statement_applications_ready_to_nudge.rb index 241fc9727ca..dbd33003688 100644 --- a/app/queries/get_incomplete_personal_statement_applications_ready_to_nudge.rb +++ b/app/queries/get_incomplete_personal_statement_applications_ready_to_nudge.rb @@ -10,12 +10,15 @@ class GetIncompletePersonalStatementApplicationsReadyToNudge def call ApplicationForm - .unsubmitted .inactive_since(7.days.ago) .with_completion(COMPLETION_ATTRS) .current_cycle + .joins(:candidate) + .where.not('candidate.submission_blocked': true) + .where.not('candidate.account_locked': true) + .where.not('candidate.unsubscribed_from_emails': true) .where(INCOMPLETION_ATTRS.map { |attr| "#{attr} = false" }.join(' AND ')) .has_not_received_email(MAILER, MAIL_TEMPLATE) - .includes(:application_choices).where('application_choices.status': 'unsubmitted') + .joins(:application_choices).where('application_choices.status': 'unsubmitted') end end diff --git a/app/queries/get_incomplete_reference_applications_ready_to_nudge.rb b/app/queries/get_incomplete_reference_applications_ready_to_nudge.rb index 6a0c3d92bc4..6ffbd641adc 100644 --- a/app/queries/get_incomplete_reference_applications_ready_to_nudge.rb +++ b/app/queries/get_incomplete_reference_applications_ready_to_nudge.rb @@ -10,8 +10,11 @@ def call uk_and_irish = uk_and_irish_names.map { |name| ActiveRecord::Base.connection.quote(name) }.join(',') ApplicationForm + .joins(:candidate) + .where.not('candidate.submission_blocked': true) + .where.not('candidate.account_locked': true) + .where.not('candidate.unsubscribed_from_emails': true) .current_cycle - .unsubmitted .inactive_since(7.days.ago) .with_completion(COMMON_COMPLETION_ATTRS) .has_not_received_email(MAILER, MAIL_TEMPLATE) @@ -40,7 +43,7 @@ def call .joins( "LEFT OUTER JOIN \"application_choices\" ON \"application_choices\".application_form_id = application_forms.id AND \"application_choices\".status = 'unsubmitted'", ) - .group('application_forms.id') + .group('application_forms.id', 'candidate.id') .having('count("references".id) < 2 AND count("application_choices".id) > 0') end end diff --git a/app/queries/get_unsubmitted_applications_ready_to_nudge.rb b/app/queries/get_unsubmitted_applications_ready_to_nudge.rb index 2d83540525b..4f5ae8e2ba4 100644 --- a/app/queries/get_unsubmitted_applications_ready_to_nudge.rb +++ b/app/queries/get_unsubmitted_applications_ready_to_nudge.rb @@ -11,12 +11,15 @@ def call uk_and_irish = uk_and_irish_names.map { |name| ActiveRecord::Base.connection.quote(name) }.join(',') ApplicationForm - .unsubmitted .inactive_since(7.days.ago) .with_completion(COMMON_COMPLETION_ATTRS) .current_cycle .has_not_received_email(MAILER, MAIL_TEMPLATE) - .includes(:application_choices).where('application_choices.status': 'unsubmitted') + .joins(:candidate) + .where.not('candidate.submission_blocked': true) + .where.not('candidate.account_locked': true) + .where.not('candidate.unsubscribed_from_emails': true) + .joins(:application_choices).where('application_choices.status': 'unsubmitted') .and(ApplicationForm .where(science_gcse_completed: true) .or( diff --git a/app/queries/get_unsuccessful_and_unsubmitted_candidates.rb b/app/queries/get_unsuccessful_and_unsubmitted_candidates.rb index 27834d4cc4f..ae7dafb12a3 100644 --- a/app/queries/get_unsuccessful_and_unsubmitted_candidates.rb +++ b/app/queries/get_unsuccessful_and_unsubmitted_candidates.rb @@ -1,20 +1,27 @@ class GetUnsuccessfulAndUnsubmittedCandidates def self.call + previous_recruitment_year = RecruitmentCycle.previous_year + # Candidates who didn't have successful applications last year Candidate .left_outer_joins(:application_forms) .where.not(candidates: { unsubscribed_from_emails: true }) .where.not(candidates: { submission_blocked: true }) .where.not(candidates: { account_locked: true }) .where( - '(application_forms.recruitment_cycle_year = 2023 AND NOT EXISTS (:successful))', + '(application_forms.recruitment_cycle_year = (:previous_recruitment_year) AND NOT EXISTS (:successful))', + previous_recruitment_year:, successful: ApplicationChoice .select(1) - .where(status: %w[recruited pending_conditions offer offer_deferred]) + .where(status: ApplicationStateChange::SUCCESSFUL_STATES) .where('application_choices.application_form_id = application_forms.id'), ) .or( + # Candidates who have started working on applications this year, but not submitted. Candidate - .where(application_forms: { recruitment_cycle_year: 2024 }) + .where(application_forms: { + recruitment_cycle_year: RecruitmentCycle.current_year, + submitted_at: nil, + }) .where.not(candidates: { unsubscribed_from_emails: true }) .where.not(candidates: { submission_blocked: true }) .where.not(candidates: { account_locked: true }), diff --git a/app/workers/cancel_unsubmitted_applications_worker.rb b/app/workers/cancel_unsubmitted_applications_worker.rb index 236c9ae0fe0..ed117dc36a1 100644 --- a/app/workers/cancel_unsubmitted_applications_worker.rb +++ b/app/workers/cancel_unsubmitted_applications_worker.rb @@ -13,11 +13,9 @@ def unsubmitted_applications_from_earlier_cycle return [] unless CycleTimetable.cancel_unsubmitted_applications? ApplicationForm - .where(submitted_at: nil) - .where(recruitment_cycle_year: RecruitmentCycle.current_year) + .current_cycle .where.not(application_forms: { candidate_id: Candidate.where(hide_in_reporting: true).select(:id) }) - .where(application_forms: { id: ApplicationChoice.unsubmitted.select(:application_form_id) }) - .includes(:application_choices) + .includes(:application_choices).where('application_choices.status': 'unsubmitted') .distinct end end diff --git a/spec/queries/get_incomplete_course_choice_applications_ready_to_nudge_spec.rb b/spec/queries/get_incomplete_course_choice_applications_ready_to_nudge_spec.rb index 2c8967c7ec4..a9e5b8f74dc 100644 --- a/spec/queries/get_incomplete_course_choice_applications_ready_to_nudge_spec.rb +++ b/spec/queries/get_incomplete_course_choice_applications_ready_to_nudge_spec.rb @@ -1,11 +1,10 @@ require 'rails_helper' RSpec.describe GetIncompleteCourseChoiceApplicationsReadyToNudge do - it 'includes unsubmitted applications that have no application choices' do + it 'includes applications that have no application choices' do application_form = create( :completed_application_form, :with_completed_references, - submitted_at: nil, ) application_form.update_columns( updated_at: 10.days.ago, @@ -14,11 +13,47 @@ expect(described_class.new.call).to eq([application_form]) end + it 'omits candidates with locked accounts' do + candidate = create(:candidate, account_locked: true) + application_form = create( + :completed_application_form, + :with_completed_references, + candidate:, + ) + + application_form.update_columns(updated_at: 10.days.ago) + + expect(described_class.new.call).not_to include(application_form) + end + + it 'omits candidates with submission blocked' do + candidate = create(:candidate, submission_blocked: true) + application_form = create( + :completed_application_form, + :with_completed_references, + candidate:, + ) + application_form.update_columns(updated_at: 10.days.ago) + + expect(described_class.new.call).not_to include(application_form) + end + + it 'omits candidates who have unsubscribed from emails' do + candidate = create(:candidate, unsubscribed_from_emails: true) + application_form = create( + :completed_application_form, + :with_completed_references, + candidate:, + ) + application_form.update_columns(updated_at: 10.days.ago) + + expect(described_class.new.call).not_to include(application_form) + end + it 'omits unsubmitted applications that have an application choice' do application_form = create( :completed_application_form, :with_completed_references, - submitted_at: nil, ) create(:application_choice, application_form:) application_form.update_columns( @@ -32,8 +67,8 @@ application_form = create( :completed_application_form, :with_completed_references, - submitted_at: Time.zone.now, ) + create(:application_choice, status: ApplicationStateChange::STATES_VISIBLE_TO_PROVIDER.sample, application_form:) application_form.update_columns( updated_at: 10.days.ago, @@ -46,7 +81,6 @@ application_form = create( :completed_application_form, :with_completed_references, - submitted_at: nil, ) application_form.update_columns( references_completed: false, @@ -60,7 +94,6 @@ application_form = create( :completed_application_form, :with_completed_references, - submitted_at: nil, ) application_form.update_columns( becoming_a_teacher_completed: false, @@ -74,7 +107,6 @@ application_form = create( :completed_application_form, :with_completed_references, - submitted_at: nil, course_choices_completed: false, ) application_form.update_columns( @@ -88,7 +120,6 @@ application_form = create( :completed_application_form, :with_completed_references, - submitted_at: nil, course_choices_completed: false, recruitment_cycle_year: RecruitmentCycle.previous_year, ) @@ -103,7 +134,6 @@ application_form = create( :completed_application_form, :with_completed_references, - submitted_at: nil, course_choices_completed: false, ) application_form.update_columns( diff --git a/spec/queries/get_incomplete_personal_statement_applications_ready_to_nudge_spec.rb b/spec/queries/get_incomplete_personal_statement_applications_ready_to_nudge_spec.rb index 1f02cbc29be..022d8c96160 100644 --- a/spec/queries/get_incomplete_personal_statement_applications_ready_to_nudge_spec.rb +++ b/spec/queries/get_incomplete_personal_statement_applications_ready_to_nudge_spec.rb @@ -1,14 +1,14 @@ require 'rails_helper' RSpec.describe GetIncompletePersonalStatementApplicationsReadyToNudge do - it 'includes unsubmitted applications which don\'t have any completed personal statements' do + it 'includes unsubmitted applications choices which don\'t have any completed personal statements' do application_form = create( :completed_application_form, :with_completed_references, - submitted_at: nil, + submitted_at: 10.days.ago, becoming_a_teacher_completed: false, ) - create(:application_choice, application_form:) + create(:application_choice, :unsubmitted, application_form:) application_form.update_columns( updated_at: 10.days.ago, ) @@ -16,10 +16,53 @@ expect(described_class.new.call).to eq([application_form]) end + it 'omits candidates with locked accounts' do + candidate = create(:candidate, account_locked: true) + application_form = create( + :completed_application_form, + :with_completed_references, + candidate:, + submitted_at: 10.days.ago, + becoming_a_teacher_completed: false, + ) + + application_form.update_columns(updated_at: 10.days.ago) + + expect(described_class.new.call).not_to include(application_form) + end + + it 'omits candidates with submission blocked' do + candidate = create(:candidate, submission_blocked: true) + application_form = create( + :completed_application_form, + :with_completed_references, + candidate:, + submitted_at: 10.days.ago, + becoming_a_teacher_completed: false, + ) + application_form.update_columns(updated_at: 10.days.ago) + + expect(described_class.new.call).not_to include(application_form) + end + + it 'omits candidates who have unsubscribed from emails' do + candidate = create(:candidate, unsubscribed_from_emails: true) + application_form = create( + :completed_application_form, + :with_completed_references, + candidate:, + submitted_at: 10.days.ago, + becoming_a_teacher_completed: false, + ) + application_form.update_columns(updated_at: 10.days.ago) + + expect(described_class.new.call).not_to include(application_form) + end + it 'omits unsubmitted applications that have not completed references' do application_form = create( :completed_application_form, - submitted_at: nil, + submitted_at: 10.days.ago, becoming_a_teacher_completed: true, references_completed: false, ) @@ -35,7 +78,7 @@ application_form = create( :completed_application_form, :with_completed_references, - submitted_at: nil, + submitted_at: 10.days.ago, becoming_a_teacher_completed: true, ) create(:application_choice, application_form:) @@ -50,7 +93,7 @@ application_form = create( :completed_application_form, :with_completed_references, - submitted_at: nil, + submitted_at: 10.days.ago, becoming_a_teacher_completed: false, ) create(:application_choice, application_form:) @@ -61,11 +104,11 @@ expect(described_class.new.call).to eq([]) end - it 'omits unsubmitted applications that have no application choices' do + it 'omits applications that have no application choices' do application_form = create( :completed_application_form, :with_completed_references, - submitted_at: nil, + submitted_at: 10.days.ago, becoming_a_teacher_completed: false, ) application_form.update_columns( @@ -79,7 +122,7 @@ application_form = create( :completed_application_form, :with_completed_references, - submitted_at: nil, + submitted_at: 10.days.ago, becoming_a_teacher_completed: false, ) create(:application_choice, application_form:) diff --git a/spec/queries/get_incomplete_reference_applications_ready_to_nudge_spec.rb b/spec/queries/get_incomplete_reference_applications_ready_to_nudge_spec.rb index 63bd4c65969..a25b80d4744 100644 --- a/spec/queries/get_incomplete_reference_applications_ready_to_nudge_spec.rb +++ b/spec/queries/get_incomplete_reference_applications_ready_to_nudge_spec.rb @@ -1,13 +1,13 @@ require 'rails_helper' RSpec.describe GetIncompleteReferenceApplicationsReadyToNudge do - it 'returns unsubmitted applications that are complete except for having no references' do + it 'includes forms with unsubmitted application choices that are complete except for having no references' do application_form = create( :completed_application_form, - submitted_at: nil, + submitted_at: 10.days.ago, references_count: 0, ) - create(:application_choice, application_form:) + create(:application_choice, :unsubmitted, application_form:) application_form.update_columns( updated_at: 10.days.ago, ) @@ -18,11 +18,11 @@ it 'returns unsubmitted applications that are complete except for having only one requested references' do application_form = create( :completed_application_form, - submitted_at: nil, + submitted_at: 10.days.ago, references_count: 1, references_state: :feedback_requested, ) - create(:application_choice, application_form:) + create(:application_choice, :unsubmitted, application_form:) application_form.update_columns( updated_at: 10.days.ago, ) @@ -33,11 +33,11 @@ it 'returns unsubmitted applications that are complete except for having only one provided references' do application_form = create( :completed_application_form, - submitted_at: nil, + submitted_at: 10.days.ago, references_count: 1, references_state: :feedback_provided, ) - create(:application_choice, application_form:) + create(:application_choice, :unsubmitted, application_form:) application_form.update_columns( updated_at: 10.days.ago, ) @@ -45,14 +45,56 @@ expect(described_class.new.call).to include(application_form) end + it 'omits candidates with locked accounts' do + candidate = create(:candidate, account_locked: true) + application_form = create( + :completed_application_form, + submitted_at: 10.days.ago, + references_count: 0, + candidate:, + ) + create(:application_choice, :unsubmitted, application_form:) + application_form.update_columns(updated_at: 10.days.ago) + + expect(described_class.new.call).not_to include(application_form) + end + + it 'omits candidates with submission blocked' do + candidate = create(:candidate, submission_blocked: true) + application_form = create( + :completed_application_form, + submitted_at: 10.days.ago, + references_count: 0, + candidate:, + ) + create(:application_choice, :unsubmitted, application_form:) + application_form.update_columns(updated_at: 10.days.ago) + + expect(described_class.new.call).not_to include(application_form) + end + + it 'omits candidates who have unsubscribed from emails' do + candidate = create(:candidate, unsubscribed_from_emails: true) + application_form = create( + :completed_application_form, + submitted_at: 10.days.ago, + references_count: 0, + candidate:, + ) + create(:application_choice, :unsubmitted, application_form:) + application_form.update_columns(updated_at: 10.days.ago) + + expect(described_class.new.call).not_to include(application_form) + end + it 'omits unsubmitted applications that have 2 requested references' do application_form = create( :completed_application_form, - submitted_at: nil, + submitted_at: 10.days.ago, references_count: 2, references_state: :feedback_requested, ) - create(:application_choice, application_form:) + create(:application_choice, :unsubmitted, application_form:) application_form.update_columns( updated_at: 10.days.ago, ) diff --git a/spec/queries/get_unsubmitted_applications_ready_to_nudge_spec.rb b/spec/queries/get_unsubmitted_applications_ready_to_nudge_spec.rb index 11739b02cb7..31e74ebabba 100644 --- a/spec/queries/get_unsubmitted_applications_ready_to_nudge_spec.rb +++ b/spec/queries/get_unsubmitted_applications_ready_to_nudge_spec.rb @@ -1,19 +1,61 @@ require 'rails_helper' RSpec.describe GetUnsubmittedApplicationsReadyToNudge do - it 'returns unsubmitted applications that are complete' do + it 'returns applications that are complete and at least one choice is unsubmitted' do application_form = create( :completed_application_form, :with_completed_references, - submitted_at: nil, + submitted_at: 10.days.ago, ) - create(:application_choice, application_form:) + create(:application_choice, :unsubmitted, application_form:) application_form.update_columns(updated_at: 10.days.ago) expect(described_class.new.call).to include(application_form) end - it 'omits submitted applications that are complete' do + it 'omits candidates with locked accounts' do + candidate = create(:candidate, account_locked: true) + application_form = create( + :completed_application_form, + :with_completed_references, + candidate:, + submitted_at: 10.days.ago, + ) + create(:application_choice, :unsubmitted, application_form:) + application_form.update_columns(updated_at: 10.days.ago) + + expect(described_class.new.call).not_to include(application_form) + end + + it 'omits candidates with submission blocked' do + candidate = create(:candidate, submission_blocked: true) + application_form = create( + :completed_application_form, + :with_completed_references, + candidate:, + submitted_at: 10.days.ago, + ) + create(:application_choice, :unsubmitted, application_form:) + application_form.update_columns(updated_at: 10.days.ago) + + expect(described_class.new.call).not_to include(application_form) + end + + it 'omits candidates who have unsubscribed from emails' do + candidate = create(:candidate, unsubscribed_from_emails: true) + application_form = create( + :completed_application_form, + :with_completed_references, + candidate:, + submitted_at: 10.days.ago, + ) + create(:application_choice, :unsubmitted, application_form:) + application_form.update_columns(updated_at: 10.days.ago) + + expect(described_class.new.call).not_to include(application_form) + end + + it 'omits submitted applications where no choices are "unsubmitted"' do application_form = create( :completed_application_form, :with_completed_references, @@ -29,9 +71,9 @@ application_form = create( :completed_application_form, :with_completed_references, - submitted_at: nil, + submitted_at: 10.days.ago, ) - create(:application_choice, application_form:) + create(:application_choice, :unsubmitted, application_form:) application_form.update_columns( references_completed: false, updated_at: 10.days.ago, @@ -44,9 +86,9 @@ application_form = create( :completed_application_form, :with_completed_references, - submitted_at: nil, + submitted_at: 10.days.ago, ) - create(:application_choice, application_form:) + create(:application_choice, :unsubmitted, application_form:) application_form.update_columns( updated_at: 5.days.ago, ) @@ -62,7 +104,7 @@ first_nationality: 'British', efl_completed: false, ) - create(:application_choice, application_form:) + create(:application_choice, :unsubmitted, application_form:) application_form.update_columns( updated_at: 10.days.ago, ) @@ -78,7 +120,7 @@ first_nationality: 'French', efl_completed: false, ) - create(:application_choice, application_form:) + create(:application_choice, :unsubmitted, application_form:) application_form.update_columns( updated_at: 10.days.ago, ) @@ -95,11 +137,13 @@ ) create( :application_choice, + :unsubmitted, application_form:, course: create(:course, level: 'secondary'), ) create( :application_choice, + :unsubmitted, application_form:, course: create(:course, level: 'primary'), ) @@ -119,11 +163,13 @@ ) create( :application_choice, + :unsubmitted, application_form:, course: create(:course, level: 'secondary'), ) create( :application_choice, + :unsubmitted, application_form:, course: create(:course, level: 'primary'), ) diff --git a/spec/queries/get_unsuccessful_and_unsubmitted_candidates_spec.rb b/spec/queries/get_unsuccessful_and_unsubmitted_candidates_spec.rb index 196f45797b7..03c3dfdec16 100644 --- a/spec/queries/get_unsuccessful_and_unsubmitted_candidates_spec.rb +++ b/spec/queries/get_unsuccessful_and_unsubmitted_candidates_spec.rb @@ -29,7 +29,7 @@ candidate: rejected_application_form_from_previous_cycle.candidate, ) - application_form_current_year = create(:application_form, :minimum_info, recruitment_cycle_year: RecruitmentCycle.current_year) + application_form_current_year = create(:application_form, :minimum_info, submitted_at: nil, recruitment_cycle_year: RecruitmentCycle.current_year) application_form_previous_year = create(:application_form, submitted_at: 1.year.ago, recruitment_cycle_year: RecruitmentCycle.previous_year) diff --git a/spec/services/sample_applications_factory_spec.rb b/spec/services/sample_applications_factory_spec.rb index 834059dc5ba..06b52fcebcc 100644 --- a/spec/services/sample_applications_factory_spec.rb +++ b/spec/services/sample_applications_factory_spec.rb @@ -263,7 +263,7 @@ it 'assigns the correct hesa code for sex' do if equality_and_diversity['sex'] == 'Prefer not to say' - expect(equality_and_diversity['hesa_sex']).to be '96' + expect(equality_and_diversity['hesa_sex']).to eq '96' else expect(equality_and_diversity['hesa_sex']).to eq( Hesa::Sex.find(equality_and_diversity['sex'], RecruitmentCycle.current_year)['hesa_code'], diff --git a/spec/workers/send_eoc_deadline_reminder_email_to_candidates_worker_spec.rb b/spec/workers/send_eoc_deadline_reminder_email_to_candidates_worker_spec.rb index f5a88a20aaa..3829c193246 100644 --- a/spec/workers/send_eoc_deadline_reminder_email_to_candidates_worker_spec.rb +++ b/spec/workers/send_eoc_deadline_reminder_email_to_candidates_worker_spec.rb @@ -21,6 +21,19 @@ end end + it 'does not return an application where the candidate account is locked' do + allow(CycleTimetable).to receive(:need_to_send_deadline_reminder?).and_return(true) + + unsubscribed_candidate = create(:candidate, account_locked: true) + create(:application_form, candidate: unsubscribed_candidate) + + described_class.new.perform + + email_for_candidate = email_for_candidate(unsubscribed_candidate) + + expect(email_for_candidate).not_to be_present + end + it 'does not return an application where the candidate is unsubscribed' do allow(CycleTimetable).to receive(:need_to_send_deadline_reminder?).and_return(true) @@ -34,6 +47,19 @@ expect(email_for_candidate).not_to be_present end + it 'does not return an application where the candidate submission is blocked' do + allow(CycleTimetable).to receive(:need_to_send_deadline_reminder?).and_return(true) + + unsubscribed_candidate = create(:candidate, submission_blocked: true) + create(:application_form, candidate: unsubscribed_candidate) + + described_class.new.perform + + email_for_candidate = email_for_candidate(unsubscribed_candidate) + + expect(email_for_candidate).not_to be_present + end + it 'returns an application when the deadline is 1 month away' do travel_temporarily_to(CycleTimetable.apply_deadline_second_reminder) do candidate = create(:candidate)