diff --git a/app/lib/mavis_cli.rb b/app/lib/mavis_cli.rb index 3fde173c8b..ea014c9b73 100644 --- a/app/lib/mavis_cli.rb +++ b/app/lib/mavis_cli.rb @@ -29,15 +29,16 @@ def self.progress_bar(total) require_relative "mavis_cli/gias/download" require_relative "mavis_cli/gias/import" require_relative "mavis_cli/gp_practices/import" -require_relative "mavis_cli/nhs_api/access_token" -require_relative "mavis_cli/pds/get" -require_relative "mavis_cli/pds/search" require_relative "mavis_cli/local_authorities/download" require_relative "mavis_cli/local_authorities/download_gias_codes" +require_relative "mavis_cli/local_authorities/download_postcode_mappings" require_relative "mavis_cli/local_authorities/import" require_relative "mavis_cli/local_authorities/import_gias_codes" -require_relative "mavis_cli/local_authorities/download_postcode_mappings" require_relative "mavis_cli/local_authorities/import_postcode_mappings" +require_relative "mavis_cli/nhs_api/access_token" +require_relative "mavis_cli/pds/get" +require_relative "mavis_cli/pds/search" +require_relative "mavis_cli/reporting_api/update_vaccination_events" require_relative "mavis_cli/schools/add_programme_year_group" require_relative "mavis_cli/schools/add_to_team" require_relative "mavis_cli/schools/create" @@ -53,7 +54,7 @@ def self.progress_bar(total) require_relative "mavis_cli/teams/list" require_relative "mavis_cli/teams/onboard" require_relative "mavis_cli/users/create" -require_relative "mavis_cli/vaccination_records/generate_fhir" -require_relative "mavis_cli/vaccination_records/sync" require_relative "mavis_cli/vaccination_records/create_from_fhir" +require_relative "mavis_cli/vaccination_records/generate_fhir" require_relative "mavis_cli/vaccination_records/search_imms_api" +require_relative "mavis_cli/vaccination_records/sync" diff --git a/app/lib/mavis_cli/reporting_api/update_vaccination_events.rb b/app/lib/mavis_cli/reporting_api/update_vaccination_events.rb new file mode 100644 index 0000000000..9bf7b0ecb2 --- /dev/null +++ b/app/lib/mavis_cli/reporting_api/update_vaccination_events.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module MavisCLI + module ReportingAPI + class UpdateVaccinationEvents < Dry::CLI::Command + desc <<-DESC + Update ReportingAPI Vaccination Events for all VaccinationRecords created after a given DateTime. + DateTimes can be given in any format parsable by Time.parse. + Defaults to one year ago. + DESC + + option :from, + desc: "Only consider vaccination records created after this time", + type: :string, + optional: true, + aliases: %w[--from], + default: nil + + def call(from: nil, **) + MavisCLI.load_rails + + min_datetime = from ? Time.zone.parse(from) : (Time.zone.now - 1.year) + + vaccination_records = + VaccinationRecord.where("created_at > ?", min_datetime) + puts "#{vaccination_records.count} VaccinationRecords created since #{min_datetime.iso8601}" + if vaccination_records.exists? + puts "Updating VaccinationEvents" + progress_bar = MavisCLI.progress_bar(vaccination_records.count + 1) + + vaccination_records.find_each do |vaccination_record| + vaccination_record.create_or_update_reporting_api_vaccination_event! + progress_bar.increment + end + else + puts "Nothing to do" + end + end + end + end + + register "reporting-api" do |prefix| + prefix.register "update-vaccination-events", + ReportingAPI::UpdateVaccinationEvents + end +end diff --git a/app/models/vaccination_record.rb b/app/models/vaccination_record.rb index 46b8074f57..35517aaa54 100644 --- a/app/models/vaccination_record.rb +++ b/app/models/vaccination_record.rb @@ -230,6 +230,34 @@ def snomed_procedure_code = vaccine&.snomed_procedure_code(dose_sequence:) delegate :snomed_procedure_term, to: :vaccine, allow_nil: true + def create_or_update_reporting_api_vaccination_event! + re = + ReportingAPI::VaccinationEvent.find_or_initialize_by( + source_id: id, + source_type: self.class.name + ) + re.event_timestamp = performed_at + re.event_type = outcome + + re.copy_attributes_from_references( + patient: patient.reload, + patient_local_authority_from_postcode: + patient.local_authority_from_postcode, + patient_school: patient.school, + patient_school_local_authority: patient.school&.local_authority, + location:, + location_local_authority: location&.local_authority, + vaccination_record: self, + vaccine:, + team:, + organisation: team.organisation, + programme: + ) + + re.save! + re + end + private def requires_location_name? = location.nil? diff --git a/spec/features/cli_reporting_api_update_vaccination_events_spec.rb b/spec/features/cli_reporting_api_update_vaccination_events_spec.rb new file mode 100644 index 0000000000..f38a8dac35 --- /dev/null +++ b/spec/features/cli_reporting_api_update_vaccination_events_spec.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +require_relative "../../app/lib/mavis_cli" + +describe "mavis reporting-api update-vaccination-events" do + it "generates Reporting API Vaccination Event records" do + given_a_team_exists + and_there_is_a_patient_in_a_session + and_there_are_some_vaccination_records + when_i_run_the_reporting_api_update_vaccination_events_command + then_reporting_api_vaccination_events_are_created + end + + it "updates existing Reporting API Vaccination Event records" do + given_a_team_exists + and_there_is_a_patient_in_a_session + and_there_are_some_vaccination_records + and_the_vaccination_records_already_have_reporting_api_vaccination_events + when_i_run_the_reporting_api_update_vaccination_events_command + then_it_does_not_generate_new_vaccination_event_records + and_it_updates_the_existing_vaccination_event_records + end + + it "only affects Vaccination Records created after the given from_datetime" do + given_a_team_exists + and_there_is_a_patient_in_a_session + and_there_are_some_vaccination_records + when_i_run_the_reporting_api_update_vaccination_events_command_with_a_from_datetime + then_event_records_are_only_created_for_vaccination_records_created_after_the_given_from_datetime + end + + def given_a_team_exists + @programme = + Programme.find_by(type: "hpv") || create(:programme, type: "hpv") + @team = create(:team, programmes: [@programme]) + end + + def nurse + User.find_by(fallback_role: :nurse) || create(:nurse) + end + + def and_there_is_a_patient_in_a_session + subteam = create(:subteam, team: @team) + location = create(:generic_clinic, subteam:) + @session = + create(:session, team: @team, programmes: [@programme], location:) + parent = create(:parent) + @patient = + create( + :patient, + :consent_given_triage_not_needed, + team: @team, + session: @session, + programmes: [@programme], + parents: [parent] + ) + @other_patient = + create( + :patient, + :consent_given_triage_not_needed, + team: @team, + session: @session, + programmes: [@programme], + parents: [parent] + ) + end + + def and_there_are_some_vaccination_records + @vr1 = + create( + :vaccination_record, + patient: @patient, + session: @session, + location: @session.location, + performed_by: nurse, + programme: @programme, + created_at: Time.current - 1.day, + updated_at: Time.current - 1.day + ) + @vr2 = + create( + :vaccination_record, + patient: @other_patient, + session: @session, + location: @session.location, + performed_by: nurse, + programme: @programme, + created_at: Time.current - 4.hours, + updated_at: Time.current - 4.hours + ) + end + + def and_the_vaccination_records_already_have_reporting_api_vaccination_events + VaccinationRecord.all.find_each( + &:create_or_update_reporting_api_vaccination_event! + ) + end + + def when_i_run_the_reporting_api_update_vaccination_events_command + @vaccination_events_count_before = ReportingAPI::VaccinationEvent.count + + MavisCLI.instance_variable_set(:@progress_bar, nil) + + @output = + capture_output do + Dry::CLI.new(MavisCLI).call( + arguments: %w[reporting-api update-vaccination-events] + ) + end + end + + def when_i_run_the_reporting_api_update_vaccination_events_command_with_a_from_datetime + @vaccination_events_count_before = ReportingAPI::VaccinationEvent.count + + MavisCLI.instance_variable_set(:@progress_bar, nil) + + @output = + capture_output do + Dry::CLI.new(MavisCLI).call( + arguments: [ + "reporting-api", + "update-vaccination-events", + "--from", + (Time.current - 8.hours).iso8601.to_s + ] + ) + end + end + + def then_reporting_api_vaccination_events_are_created + expect(ReportingAPI::VaccinationEvent.count).to be > + @vaccination_events_count_before + end + + def then_event_records_are_only_created_for_vaccination_records_created_after_the_given_from_datetime + expect(ReportingAPI::VaccinationEvent.count).to eq(1) + expect(ReportingAPI::VaccinationEvent.last.source_id).to eq(@vr2.id) + end + + def then_it_does_not_generate_new_vaccination_event_records + expect( + ReportingAPI::VaccinationEvent.count + ).to eq @vaccination_events_count_before + end + + def and_it_updates_the_existing_vaccination_event_records + expect(ReportingAPI::VaccinationEvent.pluck(:updated_at)).to all be_within( + 1.second + ).of(Time.current) + end +end diff --git a/spec/models/vaccination_record_spec.rb b/spec/models/vaccination_record_spec.rb index d700bdc15e..6e690940e8 100644 --- a/spec/models/vaccination_record_spec.rb +++ b/spec/models/vaccination_record_spec.rb @@ -460,4 +460,189 @@ end end end + + describe "#create_or_update_reporting_api_vaccination_event" do + subject(:vaccination_record) do + create( + :vaccination_record, + programme: flu, + outcome: :not_well, + session: session, + location: school, + patient: patient + ) + end + + let(:flu) do + Programme.find_by_type("flu") || create(:programme, type: "flu") + end + let(:programmes) { [flu] } + let(:team) { create(:team, programmes:) } + let(:session) { create(:session, team:, programmes:) } + let(:school) { create(:school, gias_local_authority_code: 999_999) } + let(:patient) do + create(:patient, address_postcode: "DH1 3LH", school: school) + end + let(:local_authority) do + create(:local_authority, gss_code: "D000000001", gias_code: 999_999) + end + + before do + create( + :local_authority_postcode, + gss_code: local_authority.gss_code, + value: "DH1 3LH" + ) + end + + context "when a reporting_api_vaccination_event already exists with this vaccination_record as source" do + let!(:existing_event) do + create( + :reporting_api_vaccination_event, + source: vaccination_record, + outcome: "not_well" + ) + end + + before { vaccination_record.update!(outcome: :administered) } + + it "does not create a new reporting_api_vaccination_event" do + expect { + vaccination_record.create_or_update_reporting_api_vaccination_event! + }.not_to change(ReportingAPI::VaccinationEvent, :count) + end + + it "updates the existing reporting_api_vaccination_event" do + vaccination_record.create_or_update_reporting_api_vaccination_event! + + expect(existing_event.reload.vaccination_record_outcome).to eq( + "administered" + ) + end + end + + context "when no reporting_api_vaccination_event exists with this vaccination_record as source" do + it "creates a new reporting_api_vaccination_event" do + expect { + vaccination_record.create_or_update_reporting_api_vaccination_event! + }.to change(ReportingAPI::VaccinationEvent, :count).by(1) + end + + describe "the created VaccinationEvent" do + before do + vaccination_record.create_or_update_reporting_api_vaccination_event! + end + + let(:event) do + ReportingAPI::VaccinationEvent.find_by( + source_id: vaccination_record.id + ) + end + + it "has this vaccination_record as source" do + expect(event.source).to eq(vaccination_record) + end + + it "has the outcome as event_type" do + expect(event.event_type).to eq(vaccination_record.outcome) + end + + it "has performed_at as event_timestamp" do + expect(event.event_timestamp).to eq(vaccination_record.performed_at) + end + + describe "denormalized relation properties" do + it "has patient_ attributes set" do + expect(event).to have_attributes( + patient_id: patient.id, + patient_address_town: patient.address_town, + patient_address_postcode: patient.address_postcode, + patient_gender_code: patient.gender_code, + patient_home_educated: patient.home_educated, + patient_date_of_death: patient.date_of_death, + patient_birth_academic_year: patient.birth_academic_year + ) + end + + it "has patient_school_ attributes set" do + expect(event).to have_attributes( + patient_school_id: school.id, + patient_school_address_town: school.address_town, + patient_school_address_postcode: school.address_postcode, + patient_school_name: school.name, + patient_school_gias_local_authority_code: + school.gias_local_authority_code, + patient_school_type: school.type + ) + end + + it "has patient_school_local_authority_ attributes set" do + expect(event).to have_attributes( + patient_school_local_authority_short_name: + local_authority.short_name, + patient_school_local_authority_mhclg_code: + local_authority.mhclg_code + ) + end + + it "has patient_local_authority_from_postcode_ attributes set" do + expect(event).to have_attributes( + patient_local_authority_from_postcode_short_name: + patient.local_authority_from_postcode.short_name, + patient_local_authority_from_postcode_mhclg_code: + patient.local_authority_from_postcode.mhclg_code + ) + end + + it "has location_ attributes set" do + expect(event).to have_attributes( + location_id: school.id, + location_address_town: school.address_town, + location_address_postcode: school.address_postcode, + location_name: school.name, + location_type: school.type + ) + end + + it "has location_local_authority_ attributes set" do + expect(event).to have_attributes( + location_local_authority_short_name: local_authority.short_name, + location_local_authority_mhclg_code: local_authority.mhclg_code + ) + end + + it "has team_ attributes set" do + expect(event).to have_attributes( + team_id: team&.id, + team_name: team&.name + ) + end + + it "has organisation_ attributes set" do + expect(event).to have_attributes( + organisation_id: team.organisation.id, + organisation_ods_code: team.organisation.ods_code + ) + end + + it "has programme_ attributes set" do + expect(event).to have_attributes( + programme_id: flu.id, + programme_type: flu.type + ) + end + + it "has vaccination_record_ attributes set" do + expect(event).to have_attributes( + vaccination_record_outcome: vaccination_record.outcome, + vaccination_record_uuid: vaccination_record.uuid, + vaccination_record_performed_at: vaccination_record.performed_at, + vaccination_record_programme_id: vaccination_record.programme_id, + vaccination_record_session_id: vaccination_record.session_id + ) + end + end + end + end + end end