From a66572c0349cd69d716236dd6994cc84daf4a616 Mon Sep 17 00:00:00 2001 From: Tony Headford Date: Mon, 29 Jan 2024 14:30:26 +0000 Subject: [PATCH 1/3] update comms service and specs, add queries --- app/models/induction_record.rb | 6 +- ...appropriate_body_and_unregistered_query.rb | 32 ++++ ...appropriate_body_and_unregistered_query.rb | 33 ++++ .../bulk_mailers/school_reminder_comms.rb | 53 +++++++ .../school_reminder_comms_spec.rb | 144 +++++++++++++++++- 5 files changed, 265 insertions(+), 3 deletions(-) create mode 100644 app/queries/ects/with_an_appropriate_body_and_unregistered_query.rb create mode 100644 app/queries/ects/without_an_appropriate_body_and_unregistered_query.rb diff --git a/app/models/induction_record.rb b/app/models/induction_record.rb index 493d678d40..49fa8533cf 100644 --- a/app/models/induction_record.rb +++ b/app/models/induction_record.rb @@ -92,8 +92,10 @@ def self.ransackable_associations(_auth_object = nil) end # Instance Methods - # appropriate_body_name - delegate :name, to: :appropriate_body, allow_nil: true, prefix: true + + def appropriate_body_name + appropriate_body&.name || school_cohort.appropriate_body&.name + end def active? active_induction_status? && (end_unknown? || end_date.future?) && !transferring_in? diff --git a/app/queries/ects/with_an_appropriate_body_and_unregistered_query.rb b/app/queries/ects/with_an_appropriate_body_and_unregistered_query.rb new file mode 100644 index 0000000000..5805ceee87 --- /dev/null +++ b/app/queries/ects/with_an_appropriate_body_and_unregistered_query.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Ects + class WithAnAppropriateBodyAndUnregisteredQuery < BaseService + def call + InductionRecord + .active + .training_status_active + .joins(:participant_profile) + .where(participant_profile: { induction_start_date: nil, type: "ParticipantProfile::ECT" }) + .joins(induction_programme: :school_cohort) + .where(induction_programme: { training_programme: training_programme_types }) + .where("induction_records.appropriate_body_id IS NOT NULL OR school_cohorts.appropriate_body_id IS NOT NULL") + end + + private + + attr_reader :include_fip, :include_cip + + def initialize(include_fip: true, include_cip: true) + @include_fip = include_fip + @include_cip = include_cip + end + + def training_programme_types + training_programmes = [] + training_programmes << "full_induction_programme" if include_fip + training_programmes << "core_induction_programme" if include_cip + training_programmes + end + end +end diff --git a/app/queries/ects/without_an_appropriate_body_and_unregistered_query.rb b/app/queries/ects/without_an_appropriate_body_and_unregistered_query.rb new file mode 100644 index 0000000000..1ede4e3447 --- /dev/null +++ b/app/queries/ects/without_an_appropriate_body_and_unregistered_query.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Ects + class WithoutAnAppropriateBodyAndUnregisteredQuery < BaseService + def call + InductionRecord + .active + .training_status_active + .joins(:participant_profile) + .where(participant_profile: { induction_start_date: nil, type: "ParticipantProfile::ECT" }) + .joins(induction_programme: :school_cohort) + .where(induction_programme: { training_programme: training_programme_types }) + .where(appropriate_body_id: nil) + .where(school_cohort: { appropriate_body_id: nil }) + end + + private + + attr_reader :include_fip, :include_cip + + def initialize(include_fip: true, include_cip: true) + @include_fip = include_fip + @include_cip = include_cip + end + + def training_programme_types + training_programmes = [] + training_programmes << "full_induction_programme" if include_fip + training_programmes << "core_induction_programme" if include_cip + training_programmes + end + end +end diff --git a/app/services/bulk_mailers/school_reminder_comms.rb b/app/services/bulk_mailers/school_reminder_comms.rb index bf5fb6b8c0..06355c7acb 100644 --- a/app/services/bulk_mailers/school_reminder_comms.rb +++ b/app/services/bulk_mailers/school_reminder_comms.rb @@ -12,6 +12,59 @@ def initialize(cohort:, dry_run: false) @dry_run = dry_run end + def contact_sits_that_need_to_chase_their_ab_to_register_ects + email_count = 0 + + Ects::WithAnAppropriateBodyAndUnregisteredQuery + .call(include_cip: false) + .includes(:user, :school) + .find_each do |induction_record| + ect_name = induction_record.user.full_name + school = induction_record.school + appropriate_body_name = induction_record.appropriate_body_name + lead_provider_name = induction_record.lead_provider_name + delivery_partner_name = induction_record.delivery_partner_name + + school.induction_coordinator_profiles.each do |induction_coordinator| + email_count += 1 + next if dry_run + + SchoolMailer + .with(school:, induction_coordinator:, ect_name:, appropriate_body_name:, lead_provider_name:, delivery_partner_name:) + .remind_sit_that_ab_has_not_registered_ect + .deliver_later + end + end + + email_count + end + + def contact_sits_that_need_to_appoint_an_ab_for_unregistered_ects + email_count = 0 + + Ects::WithoutAnAppropriateBodyAndUnregisteredQuery + .call(include_cip: false) + .includes(:user) + .find_each do |induction_record| + ect_name = induction_record.user.full_name + school = induction_record.school + lead_provider_name = induction_record.lead_provider_name + delivery_partner_name = induction_record.delivery_partner_name + + school.induction_coordinator_profiles.each do |induction_coordinator| + email_count += 1 + next if dry_run + + SchoolMailer + .with(school:, induction_coordinator:, ect_name:, lead_provider_name:, delivery_partner_name:) + .remind_sit_to_appoint_ab_for_unregistered_ect + .deliver_later + end + end + + email_count + end + def contact_sits_that_need_to_assign_mentors email_count = 0 diff --git a/spec/services/bulk_mailers/school_reminder_comms_spec.rb b/spec/services/bulk_mailers/school_reminder_comms_spec.rb index c2a85834c3..b5ade0f5e4 100644 --- a/spec/services/bulk_mailers/school_reminder_comms_spec.rb +++ b/spec/services/bulk_mailers/school_reminder_comms_spec.rb @@ -8,8 +8,11 @@ let(:dry_run) { false } let(:school_cohort) { create(:seed_school_cohort, :fip, :with_school, cohort:) } - let(:induction_programme) { create(:seed_induction_programme, :fip, school_cohort:) } let(:school) { school_cohort.school } + let(:lead_provider) { create(:seed_lead_provider, :valid) } + let(:delivery_partner) { create(:seed_delivery_partner, :valid) } + let(:partnership) { create(:seed_partnership, lead_provider:, delivery_partner:, cohort:, school:) } + let(:induction_programme) { create(:seed_induction_programme, :fip, partnership:, school_cohort:) } let!(:sit_profile) { create(:seed_induction_coordinator_profiles_school, :valid, school:).induction_coordinator_profile } subject(:service) { described_class.new(cohort: query_cohort, dry_run:) } @@ -18,6 +21,145 @@ school.update!(school_type_code: 1) end + describe "#contact_sits_that_need_to_chase_their_ab_to_register_ects" do + let(:participant_profile) { create(:seed_ect_participant_profile, :valid, school_cohort:) } + let(:ect_name) { participant_profile.user.full_name } + + let(:ect_appropriate_body) { nil } + let(:school_appropriate_body) { nil } + let!(:induction_record) { create(:seed_induction_record, :valid, participant_profile:, induction_programme:, appropriate_body: ect_appropriate_body) } + + before do + school_cohort.update!(appropriate_body: school_appropriate_body) + end + + context "when a school has ECTs without an induction start date" do + context "when there is an AB appointed" do + let(:school_appropriate_body) { create(:seed_appropriate_body, :valid) } + + it "mails the induction coordinator" do + expect { + service.contact_sits_that_need_to_chase_their_ab_to_register_ects + }.to have_enqueued_mail(SchoolMailer, :remind_sit_that_ab_has_not_registered_ect) + .with(params: { school:, induction_coordinator: sit_profile, ect_name:, appropriate_body_name: school_appropriate_body.name, lead_provider_name: lead_provider.name, delivery_partner_name: delivery_partner.name }, args: []) + end + + it "returns the count of emails sent" do + expect(service.contact_sits_that_need_to_chase_their_ab_to_register_ects).to eq 1 + end + + context "when the AB is set at the participant level" do + let(:ect_appropriate_body) { create(:seed_appropriate_body, :valid) } + + it "mails the induction coordinator using the participant-level AB" do + expect { + service.contact_sits_that_need_to_chase_their_ab_to_register_ects + }.to have_enqueued_mail(SchoolMailer, :remind_sit_that_ab_has_not_registered_ect) + .with(params: { school:, induction_coordinator: sit_profile, ect_name:, appropriate_body_name: ect_appropriate_body.name, lead_provider_name: lead_provider.name, delivery_partner_name: delivery_partner.name }, args: []) + end + + it "returns the count of emails sent" do + expect(service.contact_sits_that_need_to_chase_their_ab_to_register_ects).to eq 1 + end + end + + context "when the dry_run flag is set" do + let(:dry_run) { true } + + it "does not mail the induction coordinator" do + expect { + service.contact_sits_that_need_to_chase_their_ab_to_register_ects + }.not_to have_enqueued_mail + end + + it "returns the count of emails that would have been sent" do + expect(service.contact_sits_that_need_to_chase_their_ab_to_register_ects).to eq 1 + end + end + end + + context "when no AB has been appointed" do + it "does not mail the induction coordinator" do + expect { + service.contact_sits_that_need_to_chase_their_ab_to_register_ects + }.not_to have_enqueued_mail(SchoolMailer, :remind_sit_that_ab_has_not_registered_ect) + end + + it "returns the count of emails sent" do + expect(service.contact_sits_that_need_to_chase_their_ab_to_register_ects).to eq 0 + end + end + end + end + + describe "#contact_sits_that_need_to_appoint_an_ab_for_unregistered_ects" do + let(:participant_profile) { create(:seed_ect_participant_profile, :valid, school_cohort:) } + let(:ect_name) { participant_profile.user.full_name } + + let(:ect_appropriate_body) { nil } + let(:school_appropriate_body) { nil } + let!(:induction_record) { create(:seed_induction_record, :valid, participant_profile:, induction_programme:, appropriate_body: ect_appropriate_body) } + + before do + school_cohort.update!(appropriate_body: school_appropriate_body) + end + + context "when a school has ECTs without an AB and induction start date" do + it "mails the induction coordinator" do + expect { + service.contact_sits_that_need_to_appoint_an_ab_for_unregistered_ects + }.to have_enqueued_mail(SchoolMailer, :remind_sit_to_appoint_ab_for_unregistered_ect) + .with(params: { school:, induction_coordinator: sit_profile, ect_name:, lead_provider_name: lead_provider.name, delivery_partner_name: delivery_partner.name }, args: []) + end + + it "returns the count of emails sent" do + expect(service.contact_sits_that_need_to_appoint_an_ab_for_unregistered_ects).to eq 1 + end + + context "when the AB is set at the school level" do + let(:school_appropriate_body) { create(:seed_appropriate_body, :valid) } + + it "does not mail the induction coordinator" do + expect { + service.contact_sits_that_need_to_appoint_an_ab_for_unregistered_ects + }.not_to have_enqueued_mail(SchoolMailer, :remind_sit_to_appoint_ab_for_unregistered_ect) + end + + it "returns the count of emails sent" do + expect(service.contact_sits_that_need_to_appoint_an_ab_for_unregistered_ects).to eq 0 + end + end + + context "when the AB is set at the participant level" do + let(:school_appropriate_body) { create(:seed_appropriate_body, :valid) } + + it "does not mail the induction coordinator" do + expect { + service.contact_sits_that_need_to_appoint_an_ab_for_unregistered_ects + }.not_to have_enqueued_mail(SchoolMailer, :remind_sit_to_appoint_ab_for_unregistered_ect) + end + + it "returns the count of emails sent" do + expect(service.contact_sits_that_need_to_appoint_an_ab_for_unregistered_ects).to eq 0 + end + end + + context "when the dry_run flag is set" do + let(:dry_run) { true } + + it "does not mail the induction coordinator" do + expect { + service.contact_sits_that_need_to_appoint_an_ab_for_unregistered_ects + }.not_to have_enqueued_mail(SchoolMailer, :remind_sit_to_appoint_ab_for_unregistered_ect) + end + + it "returns the count of emails that would have been sent" do + expect(service.contact_sits_that_need_to_appoint_an_ab_for_unregistered_ects).to eq 1 + end + end + end + end + describe "#contact_sits_that_need_to_assign_mentors" do let(:participant_profile) { create(:seed_ect_participant_profile, :valid, school_cohort:) } let!(:eligibility) { create(:seed_ecf_participant_eligibility, participant_profile:) } From 2e451a0ec42d8a1630060b616e292d800b87a853 Mon Sep 17 00:00:00 2001 From: Tony Headford Date: Mon, 29 Jan 2024 16:05:19 +0000 Subject: [PATCH 2/3] add specs for queries --- ...priate_body_and_unregistered_query_spec.rb | 102 ++++++++++++++++++ ...priate_body_and_unregistered_query_spec.rb | 82 ++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 spec/queries/ects/with_an_appropriate_body_and_unregistered_query_spec.rb create mode 100644 spec/queries/ects/without_an_appropriate_body_and_unregistered_query_spec.rb diff --git a/spec/queries/ects/with_an_appropriate_body_and_unregistered_query_spec.rb b/spec/queries/ects/with_an_appropriate_body_and_unregistered_query_spec.rb new file mode 100644 index 0000000000..17c2f3cfe6 --- /dev/null +++ b/spec/queries/ects/with_an_appropriate_body_and_unregistered_query_spec.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Ects::WithAnAppropriateBodyAndUnregisteredQuery do + describe "#call" do + let(:fip_ab) { create(:seed_appropriate_body, :valid) } + let(:cip_ab) { create(:seed_appropriate_body, :valid) } + + let(:fip_school_cohort) { create(:seed_school_cohort, :fip, :valid, appropriate_body: fip_ab) } + let(:cip_school_cohort) { create(:seed_school_cohort, :cip, :valid, appropriate_body: cip_ab) } + let(:fip_induction_programme) { create(:seed_induction_programme, :fip, school_cohort: fip_school_cohort) } + let(:cip_induction_programme) { create(:seed_induction_programme, :cip, school_cohort: cip_school_cohort) } + + let(:fip_participant_profile) { create(:seed_ect_participant_profile, :valid, school_cohort: fip_school_cohort) } + let(:cip_participant_profile) { create(:seed_ect_participant_profile, :valid, school_cohort: cip_school_cohort) } + + let(:include_fip) { true } + let(:include_cip) { true } + + let!(:fip_induction_record) do + create(:seed_induction_record, :valid, participant_profile: fip_participant_profile, induction_programme: fip_induction_programme) + end + + let!(:cip_induction_record) do + create(:seed_induction_record, :valid, participant_profile: cip_participant_profile, induction_programme: cip_induction_programme) + end + + subject(:query_result) { described_class.call(include_fip:, include_cip:) } + + context "when there are ECTs without induction start dates" do + context "when the school has an AB selected" do + it "returns the induction records for the participants without induction start dates" do + expect(query_result).to match_array [fip_induction_record, cip_induction_record] + end + + context "when CIP is not included" do + let(:include_cip) { false } + + it "does not return CIP participants" do + expect(query_result).to match_array [fip_induction_record] + end + end + + context "when FIP is not included" do + let(:include_fip) { false } + + it "does not return FIP participants" do + expect(query_result).to match_array [cip_induction_record] + end + end + end + + context "when the participant has an AB selected" do + before do + fip_induction_record.update!(appropriate_body: fip_ab) + cip_induction_record.update!(appropriate_body: cip_ab) + fip_school_cohort.update!(appropriate_body: nil) + cip_school_cohort.update!(appropriate_body: nil) + end + + it "returns the induction records for the participants without induction start dates" do + expect(query_result).to match_array [fip_induction_record, cip_induction_record] + end + + context "when CIP is not included" do + let(:include_cip) { false } + + it "does not return CIP participants" do + expect(query_result).to match_array [fip_induction_record] + end + end + + context "when FIP is not included" do + let(:include_fip) { false } + + it "does not return FIP participants" do + expect(query_result).to match_array [cip_induction_record] + end + end + end + + context "when an AB is not selected" do + let(:fip_ab) { nil } + + it "does not return participants" do + expect(query_result).to match_array [cip_induction_record] + end + end + end + + context "when there are ECTs with induction start dates" do + before do + cip_participant_profile.update!(induction_start_date: 1.week.ago) + end + + it "does not return those participants" do + expect(query_result).to match_array [fip_induction_record] + end + end + end +end diff --git a/spec/queries/ects/without_an_appropriate_body_and_unregistered_query_spec.rb b/spec/queries/ects/without_an_appropriate_body_and_unregistered_query_spec.rb new file mode 100644 index 0000000000..9460ba70d8 --- /dev/null +++ b/spec/queries/ects/without_an_appropriate_body_and_unregistered_query_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Ects::WithoutAnAppropriateBodyAndUnregisteredQuery do + describe "#call" do + let(:fip_school_cohort) { create(:seed_school_cohort, :fip, :valid) } + let(:cip_school_cohort) { create(:seed_school_cohort, :cip, :valid) } + let(:fip_induction_programme) { create(:seed_induction_programme, :fip, school_cohort: fip_school_cohort) } + let(:cip_induction_programme) { create(:seed_induction_programme, :cip, school_cohort: cip_school_cohort) } + + let(:fip_participant_profile) { create(:seed_ect_participant_profile, :valid, school_cohort: fip_school_cohort) } + let(:cip_participant_profile) { create(:seed_ect_participant_profile, :valid, school_cohort: cip_school_cohort) } + + let(:include_fip) { true } + let(:include_cip) { true } + + let!(:fip_induction_record) do + create(:seed_induction_record, :valid, participant_profile: fip_participant_profile, induction_programme: fip_induction_programme) + end + + let!(:cip_induction_record) do + create(:seed_induction_record, :valid, participant_profile: cip_participant_profile, induction_programme: cip_induction_programme) + end + + subject(:query_result) { described_class.call(include_fip:, include_cip:) } + + context "when there are ECTs without induction start dates" do + context "when no AB is selected" do + it "returns the induction records for the participants without induction start dates" do + expect(query_result).to match_array [fip_induction_record, cip_induction_record] + end + + context "when CIP is not included" do + let(:include_cip) { false } + + it "does not return CIP participants" do + expect(query_result).to match_array [fip_induction_record] + end + end + + context "when FIP is not included" do + let(:include_fip) { false } + + it "does not return FIP participants" do + expect(query_result).to match_array [cip_induction_record] + end + end + end + + context "when the school has an AB" do + before do + fip_school_cohort.update!(appropriate_body: create(:seed_appropriate_body, :valid)) + end + + it "does not return those participants" do + expect(query_result).to match_array [cip_induction_record] + end + end + + context "when the participant has an AB" do + before do + cip_induction_record.update!(appropriate_body: create(:seed_appropriate_body, :valid)) + end + + it "does not return those participants" do + expect(query_result).to match_array [fip_induction_record] + end + end + end + + context "when there are ECTs with induction start dates" do + before do + cip_participant_profile.update!(induction_start_date: 1.week.ago) + end + + it "does not return those participants" do + expect(query_result).to match_array [fip_induction_record] + end + end + end +end From 5b7df349a7a6144ef7acf014c14d78bfda70c5d1 Mon Sep 17 00:00:00 2001 From: Tony Headford Date: Tue, 30 Jan 2024 10:49:10 +0000 Subject: [PATCH 3/3] restore delegate to induction record and move code to service --- app/models/induction_record.rb | 6 ++---- app/services/bulk_mailers/school_reminder_comms.rb | 6 +++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/models/induction_record.rb b/app/models/induction_record.rb index 49fa8533cf..493d678d40 100644 --- a/app/models/induction_record.rb +++ b/app/models/induction_record.rb @@ -92,10 +92,8 @@ def self.ransackable_associations(_auth_object = nil) end # Instance Methods - - def appropriate_body_name - appropriate_body&.name || school_cohort.appropriate_body&.name - end + # appropriate_body_name + delegate :name, to: :appropriate_body, allow_nil: true, prefix: true def active? active_induction_status? && (end_unknown? || end_date.future?) && !transferring_in? diff --git a/app/services/bulk_mailers/school_reminder_comms.rb b/app/services/bulk_mailers/school_reminder_comms.rb index 06355c7acb..e722d438b6 100644 --- a/app/services/bulk_mailers/school_reminder_comms.rb +++ b/app/services/bulk_mailers/school_reminder_comms.rb @@ -21,7 +21,7 @@ def contact_sits_that_need_to_chase_their_ab_to_register_ects .find_each do |induction_record| ect_name = induction_record.user.full_name school = induction_record.school - appropriate_body_name = induction_record.appropriate_body_name + appropriate_body_name = appropriate_body_name(induction_record) lead_provider_name = induction_record.lead_provider_name delivery_partner_name = induction_record.delivery_partner_name @@ -205,5 +205,9 @@ def opt_in_out_url(email:, school:) Rails.application.routes.url_helpers.choose_how_to_continue_url(token: nomination_token(email:, school:), host: Rails.application.config.domain) end + + def appropriate_body_name(induction_record) + induction_record.appropriate_body&.name || induction_record.school_cohort.appropriate_body&.name + end end end