From f45cc8c765d5193bad540009d6a493f703c797aa Mon Sep 17 00:00:00 2001 From: Thomas Leese Date: Mon, 8 Sep 2025 10:16:40 +0100 Subject: [PATCH 1/3] Rename `PatientSession` This renames the `PatientSession` model to `PatientLocation` in preparation for the next commit which will restructure the model to remove the association to the session and instead link to a location and an academic year. Jira-Issue: MAV-1822 --- app/components/app_activity_log_component.rb | 14 +++--- .../app_patient_programmes_table_component.rb | 3 +- .../app_programme_session_table_component.rb | 14 +++--- .../app_session_actions_component.rb | 20 ++++---- .../app_session_details_summary_component.rb | 8 ++-- ..._session_needs_review_warning_component.rb | 6 +-- .../api/testing/teams_controller.rb | 4 +- .../patient_sessions/base_controller.rb | 8 ++-- app/controllers/patients_controller.rb | 2 +- app/controllers/programmes/base_controller.rb | 2 +- .../sessions/consent_controller.rb | 6 +-- .../sessions/invite_to_clinic_controller.rb | 6 +-- .../patient_specific_directions_controller.rb | 20 ++++---- .../sessions/patients_controller.rb | 6 +-- app/controllers/sessions/record_controller.rb | 6 +-- .../sessions/register_controller.rb | 6 +-- app/controllers/sessions/triage_controller.rb | 6 +-- app/controllers/sessions_controller.rb | 2 +- .../concerns/patient_merge_form_concern.rb | 2 +- ...end_school_consent_notification_concern.rb | 6 +-- .../enqueue_vaccinations_search_in_nhs_job.rb | 6 +-- app/jobs/send_school_session_reminders_job.rb | 2 +- ...rb => clinic_patient_locations_factory.rb} | 12 ++--- app/lib/generate/vaccination_records.rb | 24 +++++----- app/lib/location_sessions_factory.rb | 2 +- app/lib/mavis_cli/schools/move_patients.rb | 2 +- app/lib/patient_merger.rb | 10 ++-- app/lib/stats/consents_by_school.rb | 6 +-- app/lib/status_updater.rb | 24 +++++----- app/lib/team_sessions_factory.rb | 2 +- app/models/class_import.rb | 2 +- app/models/immunisation_import.rb | 14 +++--- app/models/immunisation_import_row.rb | 4 +- app/models/patient.rb | 16 +++---- app/models/patient/vaccination_status.rb | 2 +- app/models/patient_changeset.rb | 4 +- app/models/patient_import_row.rb | 4 +- ...patient_session.rb => patient_location.rb} | 34 +++++++------- app/models/school_move.rb | 4 +- app/models/session.rb | 6 +-- app/models/team.rb | 4 +- app/policies/patient_policy.rb | 4 +- app/policies/school_move_policy.rb | 2 +- app/policies/vaccination_record_policy.rb | 3 +- app/views/sessions/consent/show.html.erb | 6 +-- .../patient_specific_directions/show.html.erb | 6 +-- app/views/sessions/patients/show.html.erb | 6 +-- app/views/sessions/record/show.html.erb | 6 +-- app/views/sessions/register/show.html.erb | 6 +-- app/views/sessions/triage/show.html.erb | 6 +-- .../20250909080957_rename_patient_session.rb | 13 ++++++ db/schema.rb | 32 ++++++------- db/seeds.rb | 4 +- docs/ops-tasks.md | 8 ++-- script/generate_model_office_data.rb | 2 +- .../app_activity_log_component_spec.rb | 2 +- ...pp_patient_session_table_component_spec.rb | 2 +- .../app_patient_table_component_spec.rb | 2 +- ...ion_needs_review_warning_component_spec.rb | 2 +- ...vaccination_record_table_component_spec.rb | 2 +- .../api/testing/teams_controller_spec.rb | 4 +- ...tient_sessions.rb => patient_locations.rb} | 8 ++-- spec/factories/patients.rb | 2 +- .../archive_vaccination_record_spec.rb | 4 +- spec/features/cli_generate_consents_spec.rb | 2 +- .../cli_schools_move_patients_spec.rb | 4 +- .../cli_stats_consents_by_school_spec.rb | 6 +-- spec/features/cli_stats_organisations_spec.rb | 4 +- .../download_vaccination_reports_spec.rb | 8 ++-- ...accination_records_with_duplicates_spec.rb | 8 ++-- spec/features/programme_cohorts_spec.rb | 2 +- spec/forms/patient_search_form_spec.rb | 46 ++++++++++--------- ...eue_clinic_session_invitations_job_spec.rb | 2 +- .../patient_nhs_number_lookup_job_spec.rb | 10 ++-- ...matic_school_consent_reminders_job_spec.rb | 2 +- ...anual_school_consent_reminders_job_spec.rb | 2 +- .../send_school_consent_requests_job_spec.rb | 2 +- .../send_school_session_reminders_job_spec.rb | 2 +- spec/lib/patient_archiver_spec.rb | 2 +- spec/lib/patient_merger_spec.rb | 6 +-- .../reports/offline_session_exporter_spec.rb | 16 +++---- .../programme_vaccinations_exporter_spec.rb | 6 +-- spec/lib/stats/consents_by_school_spec.rb | 10 ++-- .../lib/status_generator/registration_spec.rb | 2 +- spec/lib/status_generator/session_spec.rb | 14 +++--- spec/lib/status_updater_spec.rb | 2 +- spec/lib/tasks/status_update_spec.rb | 6 +-- spec/models/cohort_import_spec.rb | 2 +- spec/models/immunisation_import_spec.rb | 12 ++--- .../models/patient/vaccination_status_spec.rb | 2 +- ...ssion_spec.rb => patient_location_spec.rb} | 32 ++++++------- spec/models/patient_spec.rb | 24 +++++----- spec/policies/patient_policy_spec.rb | 4 +- 93 files changed, 359 insertions(+), 344 deletions(-) rename app/lib/{clinic_patient_sessions_factory.rb => clinic_patient_locations_factory.rb} (79%) rename app/models/{patient_session.rb => patient_location.rb} (88%) create mode 100644 db/migrate/20250909080957_rename_patient_session.rb rename spec/factories/{patient_sessions.rb => patient_locations.rb} (71%) rename spec/models/{patient_session_spec.rb => patient_location_spec.rb} (82%) diff --git a/app/components/app_activity_log_component.rb b/app/components/app_activity_log_component.rb index 247e6cf816..72b7b751b0 100644 --- a/app/components/app_activity_log_component.rb +++ b/app/components/app_activity_log_component.rb @@ -63,9 +63,9 @@ def initialize(team:, patient:, session: nil) session ? scope.where(programme_ids: session.programmes.ids) : scope end - @patient_sessions = + @patient_locations = @patient - .patient_sessions + .patient_locations .includes_programmes .includes(session: :location) .then { |scope| session ? scope.where(session:) : scope } @@ -105,7 +105,7 @@ def initialize(team:, patient:, session: nil) :notes, :notify_log_entries, :patient, - :patient_sessions, + :patient_locations, :patient_specific_directions, :pre_screenings, :attendance_records, @@ -346,14 +346,14 @@ def pre_screening_events end def session_events - patient_sessions.map do |patient_session| - patient = patient_session.patient - session = patient_session.session + patient_locations.map do |patient_location| + patient = patient_location.patient + session = patient_location.session [ { title: "Added to the session at #{session.location.name}", - at: patient_session.created_at, + at: patient_location.created_at, programmes: session.programmes_for(patient:) } ] diff --git a/app/components/app_patient_programmes_table_component.rb b/app/components/app_patient_programmes_table_component.rb index 7464847b37..6c011b5b98 100644 --- a/app/components/app_patient_programmes_table_component.rb +++ b/app/components/app_patient_programmes_table_component.rb @@ -258,7 +258,8 @@ def vaccination_records_for(programme:, academic_year: nil) end def eligible_year_groups_for(programme:) - location_ids = patient.patient_sessions.joins(:session).select(:location_id) + location_ids = + patient.patient_locations.joins(:session).select(:location_id) LocationProgrammeYearGroup .where(location_id: location_ids) diff --git a/app/components/app_programme_session_table_component.rb b/app/components/app_programme_session_table_component.rb index dd6c343b6d..ec0b252b23 100644 --- a/app/components/app_programme_session_table_component.rb +++ b/app/components/app_programme_session_table_component.rb @@ -13,11 +13,11 @@ def initialize(sessions, programme:) delegate :govuk_table, to: :helpers def cohort_count(session:) - format_number(patient_sessions(session:).count) + format_number(patient_locations(session:).count) end def no_response_scope(session:) - patient_sessions(session:).has_consent_status(:no_response, programme:) + patient_locations(session:).has_consent_status(:no_response, programme:) end def no_response_count(session:) @@ -27,13 +27,13 @@ def no_response_count(session:) def no_response_percentage(session:) format_percentage( no_response_scope(session:).count, - patient_sessions(session:).count + patient_locations(session:).count ) end def triage_needed_count(session:) format_number( - patient_sessions(session:).has_triage_status(:required, programme:).count + patient_locations(session:).has_triage_status(:required, programme:).count ) end @@ -48,13 +48,13 @@ def vaccinated_count(session:) def vaccinated_percentage(session:) format_percentage( vaccinated_scope(session:).count, - patient_sessions(session:).count + patient_locations(session:).count ) end - def patient_sessions(session:) + def patient_locations(session:) session - .patient_sessions + .patient_locations .joins(:patient, :session) .appear_in_programmes([programme]) end diff --git a/app/components/app_session_actions_component.rb b/app/components/app_session_actions_component.rb index 98f1d10fc7..3af1f7eec2 100644 --- a/app/components/app_session_actions_component.rb +++ b/app/components/app_session_actions_component.rb @@ -19,9 +19,9 @@ def render? = rows.any? delegate :govuk_summary_list, to: :helpers delegate :academic_year, :programmes, to: :session - def patient_sessions + def patient_locations session - .patient_sessions + .patient_locations .joins(:patient, :session) .appear_in_programmes(programmes) end @@ -38,7 +38,7 @@ def rows end def no_nhs_number_row - count = patient_sessions.merge(Patient.without_nhs_number).count + count = patient_locations.merge(Patient.without_nhs_number).count href = session_patients_path(session, missing_nhs_number: true) generate_row(:children_without_nhs_number, count:, href:) @@ -47,7 +47,7 @@ def no_nhs_number_row def no_consent_response_row status = "no_response" count = - patient_sessions.has_consent_status(status, programme: programmes).count + patient_locations.has_consent_status(status, programme: programmes).count href = session_consent_path(session, consent_statuses: [status]) actions = [ { @@ -61,7 +61,7 @@ def no_consent_response_row def conflicting_consent_row status = "conflicts" count = - patient_sessions.has_consent_status(status, programme: programmes).count + patient_locations.has_consent_status(status, programme: programmes).count href = session_consent_path(session, consent_statuses: [status]) generate_row(:children_with_conflicting_consent_response, count:, href:) @@ -70,7 +70,7 @@ def conflicting_consent_row def triage_required_row status = "required" count = - patient_sessions.has_triage_status(status, programme: programmes).count + patient_locations.has_triage_status(status, programme: programmes).count href = session_triage_path(session, triage_status: status) generate_row(:children_requiring_triage, count:, href:) @@ -80,7 +80,7 @@ def register_attendance_row return nil unless session.requires_registration? && session.today? status = "unknown" - count = patient_sessions.has_registration_status(status).count + count = patient_locations.has_registration_status(status).count href = session_register_path(session, register_status: status) generate_row(:children_to_register, count:, href:) @@ -91,13 +91,13 @@ def ready_for_vaccinator_row counts_by_programme = session.programmes.index_with do |programme| - patient_sessions + patient_locations .has_registration_status(%w[attending completed]) .includes( patient: %i[consent_statuses triage_statuses vaccination_statuses] ) - .count do |patient_session| - patient_session.patient.consent_given_and_safe_to_vaccinate?( + .count do |patient_location| + patient_location.patient.consent_given_and_safe_to_vaccinate?( programme:, academic_year: ) diff --git a/app/components/app_session_details_summary_component.rb b/app/components/app_session_details_summary_component.rb index 5abb39c2ad..e776f9dbf4 100644 --- a/app/components/app_session_details_summary_component.rb +++ b/app/components/app_session_details_summary_component.rb @@ -16,15 +16,15 @@ def call delegate :govuk_summary_list, to: :helpers delegate :programmes, to: :session - def patient_sessions + def patient_locations session - .patient_sessions + .patient_locations .joins(:patient, :session) .appear_in_programmes(programmes) end def cohort_row - count = patient_sessions.count + count = patient_locations.count { key: { text: "Cohort" }, value: { text: I18n.t("children", count:) } } end @@ -33,7 +33,7 @@ def consent_refused_row status = "refused" count = - patient_sessions.has_consent_status(status, programme: programmes).count + patient_locations.has_consent_status(status, programme: programmes).count href = session_consent_path(session, consent_statuses: [status]) diff --git a/app/components/app_session_needs_review_warning_component.rb b/app/components/app_session_needs_review_warning_component.rb index 14c4ee1ac1..2b5066db8b 100644 --- a/app/components/app_session_needs_review_warning_component.rb +++ b/app/components/app_session_needs_review_warning_component.rb @@ -33,7 +33,7 @@ def warning_href def warning_counts @warning_counts ||= { children_without_nhs_number: - patient_sessions.merge(Patient.without_nhs_number).count + patient_locations.merge(Patient.without_nhs_number).count } end @@ -42,9 +42,9 @@ def make_row_from_warning(warning) link_to(t(warning, count: warning_counts[warning]), warning_href[warning]) end - def patient_sessions + def patient_locations @session - .patient_sessions + .patient_locations .joins(:patient, :session) .appear_in_programmes(@session.programmes) end diff --git a/app/controllers/api/testing/teams_controller.rb b/app/controllers/api/testing/teams_controller.rb index 98ab0b3eab..9e91ecc6c4 100644 --- a/app/controllers/api/testing/teams_controller.rb +++ b/app/controllers/api/testing/teams_controller.rb @@ -28,7 +28,7 @@ def destroy patient_ids = team.patients.pluck(:id) consent_form_ids = team.consent_forms.pluck(:id) - log_destroy(PatientSession.where(session: sessions)) + log_destroy(PatientLocation.where(session: sessions)) log_destroy(AccessLogEntry.where(patient_id: patient_ids)) log_destroy(ArchiveReason.where(patient_id: patient_ids)) @@ -41,7 +41,7 @@ def destroy log_destroy(NotifyLogEntry.where(patient_id: patient_ids)) log_destroy(NotifyLogEntry.where(consent_form_id: consent_form_ids)) log_destroy(PatientChangeset.where(patient_id: patient_ids)) - log_destroy(PatientSession.where(patient_id: patient_ids)) + log_destroy(PatientLocation.where(patient_id: patient_ids)) log_destroy(PatientSpecificDirection.where(patient_id: patient_ids)) log_destroy(PDSSearchResult.where(patient_id: patient_ids)) log_destroy(PreScreening.where(patient_id: patient_ids)) diff --git a/app/controllers/patient_sessions/base_controller.rb b/app/controllers/patient_sessions/base_controller.rb index dde01ee584..e247c8b6fd 100644 --- a/app/controllers/patient_sessions/base_controller.rb +++ b/app/controllers/patient_sessions/base_controller.rb @@ -4,7 +4,7 @@ class PatientSessions::BaseController < ApplicationController before_action :set_session before_action :set_academic_year before_action :set_patient - before_action :set_patient_session + before_action :set_patient_location before_action :set_programme before_action :set_breadcrumb_item @@ -34,9 +34,9 @@ def set_patient ) end - def set_patient_session - @patient_session = - PatientSession.find_by!(patient: @patient, session: @session) + def set_patient_location + @patient_location = + PatientLocation.find_by!(patient: @patient, session: @session) end def set_programme diff --git a/app/controllers/patients_controller.rb b/app/controllers/patients_controller.rb index b30d4465e0..850efd6827 100644 --- a/app/controllers/patients_controller.rb +++ b/app/controllers/patients_controller.rb @@ -29,7 +29,7 @@ def invite_to_clinic session = current_team.generic_clinic_session(academic_year: AcademicYear.pending) - PatientSession.find_or_create_by!(patient: @patient, session:) + PatientLocation.find_or_create_by!(patient: @patient, session:) redirect_to patient_path(@patient), flash: { diff --git a/app/controllers/programmes/base_controller.rb b/app/controllers/programmes/base_controller.rb index 2b88881161..0aacc493c4 100644 --- a/app/controllers/programmes/base_controller.rb +++ b/app/controllers/programmes/base_controller.rb @@ -22,7 +22,7 @@ def set_academic_year def patient_ids @patient_ids ||= - PatientSession + PatientLocation .distinct .joins(:patient, :session) .where(session_id: session_ids) diff --git a/app/controllers/sessions/consent_controller.rb b/app/controllers/sessions/consent_controller.rb index 10bf452bb1..4dbf089c96 100644 --- a/app/controllers/sessions/consent_controller.rb +++ b/app/controllers/sessions/consent_controller.rb @@ -27,7 +27,7 @@ def show scope = @session - .patient_sessions + .patient_locations .includes_programmes .includes(patient: [:consent_statuses, { notes: :created_by }]) .has_consent_status( @@ -35,8 +35,8 @@ def show programme: @form.programmes ) - patient_sessions = @form.apply(scope) - @pagy, @patient_sessions = pagy(patient_sessions) + patient_locations = @form.apply(scope) + @pagy, @patient_locations = pagy(patient_locations) end private diff --git a/app/controllers/sessions/invite_to_clinic_controller.rb b/app/controllers/sessions/invite_to_clinic_controller.rb index 55f00e84ec..1d920580b3 100644 --- a/app/controllers/sessions/invite_to_clinic_controller.rb +++ b/app/controllers/sessions/invite_to_clinic_controller.rb @@ -14,7 +14,7 @@ def edit def update if @session.school? - factory.create_patient_sessions! + factory.create_patient_locations! flash[ :success @@ -54,7 +54,7 @@ def set_generic_clinic_session def set_patients_to_invite @patients_to_invite = if @session.school? - factory.patient_sessions_to_create.map(&:patient) + factory.patient_locations_to_create.map(&:patient) else session_date = @generic_clinic_session.next_date(include_today: true) SendClinicSubsequentInvitationsJob.new.patients(@session, session_date:) @@ -68,7 +68,7 @@ def set_invitations_to_send def factory @factory ||= if @session.school? - ClinicPatientSessionsFactory.new( + ClinicPatientLocationsFactory.new( school_session: @session, generic_clinic_session: @generic_clinic_session ) diff --git a/app/controllers/sessions/patient_specific_directions_controller.rb b/app/controllers/sessions/patient_specific_directions_controller.rb index 90507b7886..b6da1d0471 100644 --- a/app/controllers/sessions/patient_specific_directions_controller.rb +++ b/app/controllers/sessions/patient_specific_directions_controller.rb @@ -10,20 +10,20 @@ class Sessions::PatientSpecificDirectionsController < ApplicationController def show scope = - @session.patient_sessions.includes_programmes.includes( + @session.patient_locations.includes_programmes.includes( patient: { patient_specific_directions: :programme } ) - @eligible_for_bulk_psd_count = patient_sessions_allowed_psd.count - patient_sessions = @form.apply(scope) - @pagy, @patient_sessions = pagy(patient_sessions) + @eligible_for_bulk_psd_count = patient_locations_allowed_psd.count + patient_locations = @form.apply(scope) + @pagy, @patient_locations = pagy(patient_locations) render layout: "full" end def new - @eligible_for_bulk_psd_count = patient_sessions_allowed_psd.count + @eligible_for_bulk_psd_count = patient_locations_allowed_psd.count end def create @@ -56,12 +56,12 @@ def set_vaccine end def psds_to_create - patient_sessions_allowed_psd.map do |patient_session| + patient_locations_allowed_psd.map do |patient_location| PatientSpecificDirection.new( academic_year: @session.academic_year, created_by: current_user, delivery_site: "nose", - patient_id: patient_session.patient_id, + patient_id: patient_location.patient_id, programme: @programme, team: current_team, vaccine: @vaccine, @@ -70,10 +70,10 @@ def psds_to_create end end - def patient_sessions_allowed_psd - @patient_sessions_allowed_psd ||= + def patient_locations_allowed_psd + @patient_locations_allowed_psd ||= @session - .patient_sessions + .patient_locations .has_consent_status( "given", programme: @programme, diff --git a/app/controllers/sessions/patients_controller.rb b/app/controllers/sessions/patients_controller.rb index 87f0390318..538063ac00 100644 --- a/app/controllers/sessions/patients_controller.rb +++ b/app/controllers/sessions/patients_controller.rb @@ -12,12 +12,12 @@ def show @statuses = Patient::VaccinationStatus.statuses.keys scope = - @session.patient_sessions.includes_programmes.includes( + @session.patient_locations.includes_programmes.includes( patient: [:vaccination_statuses, { notes: :created_by }] ) - patient_sessions = @form.apply(scope) - @pagy, @patient_sessions = pagy(patient_sessions) + patient_locations = @form.apply(scope) + @pagy, @patient_locations = pagy(patient_locations) end private diff --git a/app/controllers/sessions/record_controller.rb b/app/controllers/sessions/record_controller.rb index 0655f461d4..3f99f7c5fd 100644 --- a/app/controllers/sessions/record_controller.rb +++ b/app/controllers/sessions/record_controller.rb @@ -15,7 +15,7 @@ class Sessions::RecordController < ApplicationController def show scope = - @session.patient_sessions.includes( + @session.patient_locations.includes( patient: [ :consent_statuses, :triage_statuses, @@ -28,7 +28,7 @@ def show scope = scope.has_registration_status(%w[attending completed]) end - patient_sessions = + patient_locations = filter_on_vaccine_method_or_patient_specific_direction( @form.apply(scope) ).consent_given_and_ready_to_vaccinate( @@ -36,7 +36,7 @@ def show vaccine_method: @form.vaccine_method.presence ) - @pagy, @patient_sessions = pagy_array(patient_sessions) + @pagy, @patient_locations = pagy_array(patient_locations) render layout: "full" end diff --git a/app/controllers/sessions/register_controller.rb b/app/controllers/sessions/register_controller.rb index dc5d30c1c0..1e473d006f 100644 --- a/app/controllers/sessions/register_controller.rb +++ b/app/controllers/sessions/register_controller.rb @@ -14,7 +14,7 @@ def show @statuses = Patient::RegistrationStatus.statuses.keys scope = - @session.patient_sessions.includes_programmes.includes( + @session.patient_locations.includes_programmes.includes( patient: [ :consent_statuses, :registration_statuses, @@ -24,8 +24,8 @@ def show ] ) - patient_sessions = @form.apply(scope) - @pagy, @patient_sessions = pagy(patient_sessions) + patient_locations = @form.apply(scope) + @pagy, @patient_locations = pagy(patient_locations) end def create diff --git a/app/controllers/sessions/triage_controller.rb b/app/controllers/sessions/triage_controller.rb index 206e8528c0..c37eb46705 100644 --- a/app/controllers/sessions/triage_controller.rb +++ b/app/controllers/sessions/triage_controller.rb @@ -13,13 +13,13 @@ def show scope = @session - .patient_sessions + .patient_locations .includes_programmes .includes(patient: [:triage_statuses, { notes: :created_by }]) .has_triage_status(@statuses, programme: @form.programmes) - patient_sessions = @form.apply(scope) - @pagy, @patient_sessions = pagy(patient_sessions) + patient_locations = @form.apply(scope) + @pagy, @patient_locations = pagy(patient_locations) end private diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 0c2c07519e..92960ffbdb 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -17,7 +17,7 @@ def index sessions = @form.apply(scope) @patient_count_by_session_id = - PatientSession + PatientLocation .where(session_id: sessions.map(&:id)) .joins(:patient, :session) .appear_in_programmes(@programmes) diff --git a/app/forms/concerns/patient_merge_form_concern.rb b/app/forms/concerns/patient_merge_form_concern.rb index 9f25e6f0fd..29ba0245df 100644 --- a/app/forms/concerns/patient_merge_form_concern.rb +++ b/app/forms/concerns/patient_merge_form_concern.rb @@ -20,7 +20,7 @@ def existing_patient ) || Patient .where - .missing(:patient_sessions) + .missing(:patient_locations) .includes(vaccination_records: :programme) .find_by(nhs_number:) end diff --git a/app/jobs/concerns/send_school_consent_notification_concern.rb b/app/jobs/concerns/send_school_consent_notification_concern.rb index c183e1d240..caaecf6584 100644 --- a/app/jobs/concerns/send_school_consent_notification_concern.rb +++ b/app/jobs/concerns/send_school_consent_notification_concern.rb @@ -9,11 +9,11 @@ def patient_programmes_eligible_for_notification(session:) return unless session.school? && session.open_for_consent? session - .patient_sessions + .patient_locations .includes_programmes .includes(patient: %i[consent_notifications consents vaccination_records]) - .find_each do |patient_session| - patient = patient_session.patient + .find_each do |patient_location| + patient = patient_location.patient next unless patient.send_notifications? ProgrammeGrouper diff --git a/app/jobs/enqueue_vaccinations_search_in_nhs_job.rb b/app/jobs/enqueue_vaccinations_search_in_nhs_job.rb index d1ff147b06..5fbba861fa 100644 --- a/app/jobs/enqueue_vaccinations_search_in_nhs_job.rb +++ b/app/jobs/enqueue_vaccinations_search_in_nhs_job.rb @@ -15,11 +15,7 @@ def perform(sessions = nil) .references(:session_dates) end - patient_ids = - Patient - .includes(:sessions) - .where(patient_sessions: { session: sessions.pluck(:id) }) - .ids + patient_ids = PatientLocation.where(session: sessions).pluck(:patient_id) return if patient_ids.empty? diff --git a/app/jobs/send_school_session_reminders_job.rb b/app/jobs/send_school_session_reminders_job.rb index 3a1c248382..0de6aa9071 100644 --- a/app/jobs/send_school_session_reminders_job.rb +++ b/app/jobs/send_school_session_reminders_job.rb @@ -22,7 +22,7 @@ def perform SessionNotification .where(session:) .where( - "session_notifications.patient_id = patient_sessions.patient_id" + "session_notifications.patient_id = patient_locations.patient_id" ) .where(session_date: date) .arel diff --git a/app/lib/clinic_patient_sessions_factory.rb b/app/lib/clinic_patient_locations_factory.rb similarity index 79% rename from app/lib/clinic_patient_sessions_factory.rb rename to app/lib/clinic_patient_locations_factory.rb index e3f38fd91e..e32f1ea434 100644 --- a/app/lib/clinic_patient_sessions_factory.rb +++ b/app/lib/clinic_patient_locations_factory.rb @@ -1,19 +1,19 @@ # frozen_string_literal: true -class ClinicPatientSessionsFactory +class ClinicPatientLocationsFactory def initialize(school_session:, generic_clinic_session:) @school_session = school_session @generic_clinic_session = generic_clinic_session end - def create_patient_sessions! - PatientSession.import!( - patient_sessions_to_create, + def create_patient_locations! + PatientLocation.import!( + patient_locations_to_create, on_duplicate_key_ignore: true ) end - def patient_sessions_to_create + def patient_locations_to_create patients_in_school.filter_map do |patient| if SendClinicInitialInvitationsJob.new.should_send_notification?( patient:, @@ -21,7 +21,7 @@ def patient_sessions_to_create programmes:, session_date: ) - PatientSession.includes(:session_notifications).find_or_initialize_by( + PatientLocation.includes(:session_notifications).find_or_initialize_by( patient:, session: generic_clinic_session ) diff --git a/app/lib/generate/vaccination_records.rb b/app/lib/generate/vaccination_records.rb index 65f5f6550b..bac14d456a 100644 --- a/app/lib/generate/vaccination_records.rb +++ b/app/lib/generate/vaccination_records.rb @@ -22,9 +22,9 @@ def create_vaccinations attendance_records = [] vaccination_records = [] - random_patient_sessions.each do |patient_session| - patient = patient_session.patient - session = patient_session.session + random_patient_locations.each do |patient_location| + patient = patient_location.patient + session = patient_location.session unless AttendanceRecord.exists?(patient:, location: session.location) attendance_records << FactoryBot.build( @@ -36,16 +36,16 @@ def create_vaccinations end location_name = - patient_session.location.name if patient_session.session.clinic? + patient_location.location.name if patient_location.session.clinic? vaccination_records << FactoryBot.build( :vaccination_record, :administered, - patient: patient_session.patient, + patient: patient_location.patient, programme:, team:, performed_by:, - session: patient_session.session, + session: patient_location.session, vaccine:, batch:, location_name: @@ -58,25 +58,25 @@ def create_vaccinations StatusUpdater.call(patient: vaccination_records.map(&:patient)) end - def random_patient_sessions + def random_patient_locations if administered&.positive? - patient_sessions + patient_locations .sample(administered) .tap do |selected| if selected.size < administered info = - "#{selected.size} (patient_sessions) < #{administered} (administered)" + "#{selected.size} (patient_locations) < #{administered} (administered)" raise "Not enough patients to generate vaccinations: #{info}" end end else - patient_sessions + patient_locations end end - def patient_sessions + def patient_locations (session.presence || team) - .patient_sessions + .patient_locations .joins(:patient) .includes( :session, diff --git a/app/lib/location_sessions_factory.rb b/app/lib/location_sessions_factory.rb index e8be90a105..97210c92c0 100644 --- a/app/lib/location_sessions_factory.rb +++ b/app/lib/location_sessions_factory.rb @@ -56,7 +56,7 @@ def find_or_create_session!(programmes:) end def add_patients!(session:) - PatientSession.import!( + PatientLocation.import!( %i[patient_id session_id], patient_ids.map { [it, session.id] }, on_duplicate_key_ignore: true diff --git a/app/lib/mavis_cli/schools/move_patients.rb b/app/lib/mavis_cli/schools/move_patients.rb index 870c38d4b9..43312fdda4 100644 --- a/app/lib/mavis_cli/schools/move_patients.rb +++ b/app/lib/mavis_cli/schools/move_patients.rb @@ -19,7 +19,7 @@ def call(source_urn:, target_urn:) return end - unless PatientSession + unless PatientLocation .joins(:patient, :session) .where( patient: { diff --git a/app/lib/patient_merger.rb b/app/lib/patient_merger.rb index 31e4fdc72c..ae96097a5a 100644 --- a/app/lib/patient_merger.rb +++ b/app/lib/patient_merger.rb @@ -100,16 +100,16 @@ def call end end - patient_to_destroy.patient_sessions.each do |patient_session| - if patient_to_keep.patient_sessions.exists?( - session_id: patient_session.session_id + patient_to_destroy.patient_locations.each do |patient_location| + if patient_to_keep.patient_locations.exists?( + session_id: patient_location.session_id ) next end - patient_session.update_column(:patient_id, patient_to_keep.id) + patient_location.update_column(:patient_id, patient_to_keep.id) end - PatientSession.where(patient: patient_to_destroy).destroy_all + PatientLocation.where(patient: patient_to_destroy).destroy_all patient_to_destroy.changesets.update_all(patient_id: nil) diff --git a/app/lib/stats/consents_by_school.rb b/app/lib/stats/consents_by_school.rb index f037d2e8ab..a7ca0e9d47 100644 --- a/app/lib/stats/consents_by_school.rb +++ b/app/lib/stats/consents_by_school.rb @@ -47,13 +47,13 @@ def collect_data sessions.find_each do |session| session - .patient_sessions + .patient_locations .includes(patient: { consents: %i[consent_form parent] }) - .find_each do |patient_session| + .find_each do |patient_location| grouped_consents = @programmes.map do |programme| ConsentGrouper.call( - patient_session.patient.consents, + patient_location.patient.consents, programme_id: programme.id, academic_year: @academic_year )&.min_by(&:created_at) diff --git a/app/lib/status_updater.rb b/app/lib/status_updater.rb index c1af25ff92..b85c0625dd 100644 --- a/app/lib/status_updater.rb +++ b/app/lib/status_updater.rb @@ -2,12 +2,12 @@ class StatusUpdater def initialize(patient: nil, session: nil) - scope = PatientSession + scope = PatientLocation scope = scope.where(patient:) if patient scope = scope.where(session:) if session - @patient_sessions = scope + @patient_locations = scope end def call @@ -23,7 +23,7 @@ def self.call(...) = new(...).call private - attr_reader :patient_sessions + attr_reader :patient_locations def update_consent_statuses! Patient::ConsentStatus.import!( @@ -33,7 +33,7 @@ def update_consent_statuses! ) Patient::ConsentStatus - .where(patient: patient_sessions.select(:patient_id)) + .where(patient: patient_locations.select(:patient_id)) .includes(:consents, :patient, :programme, :vaccination_records) .find_in_batches(batch_size: 10_000) do |batch| batch.each(&:assign_status) @@ -51,14 +51,14 @@ def update_consent_statuses! def update_registration_statuses! Patient::RegistrationStatus.import!( %i[patient_id session_id], - patient_session_statuses_to_import, + patient_location_statuses_to_import, on_duplicate_key_ignore: true ) Patient::RegistrationStatus .where( - patient: patient_sessions.select(:patient_id), - session: patient_sessions.select(:session_id) + patient: patient_locations.select(:patient_id), + session: patient_locations.select(:session_id) ) .includes( :patient, @@ -87,7 +87,7 @@ def update_triage_statuses! ) Patient::TriageStatus - .where(patient: patient_sessions.select(:patient_id)) + .where(patient: patient_locations.select(:patient_id)) .includes(:patient, :programme, :consents, :triages, :vaccination_records) .find_in_batches(batch_size: 10_000) do |batch| batch.each(&:assign_status) @@ -110,7 +110,7 @@ def update_vaccination_statuses! ) Patient::VaccinationStatus - .where(patient: patient_sessions.select(:patient_id)) + .where(patient: patient_locations.select(:patient_id)) .includes( :patient, :programme, @@ -138,7 +138,7 @@ def academic_years def patient_statuses_to_import @patient_statuses_to_import ||= - patient_sessions + patient_locations .joins(:patient) .pluck(:patient_id, :"patients.birth_academic_year") .uniq @@ -164,8 +164,8 @@ def vaccination_statuses_to_import end end - def patient_session_statuses_to_import - patient_sessions + def patient_location_statuses_to_import + patient_locations .joins(:patient, :session) .pluck( :"patients.id", diff --git a/app/lib/team_sessions_factory.rb b/app/lib/team_sessions_factory.rb index 465a0ff198..9b5d0e33b6 100644 --- a/app/lib/team_sessions_factory.rb +++ b/app/lib/team_sessions_factory.rb @@ -37,7 +37,7 @@ def destroy_orphaned_sessions! .where(academic_year:) .where.not(location: team.locations) .where - .missing(:patient_sessions) + .missing(:patient_locations) .destroy_all end end diff --git a/app/models/class_import.rb b/app/models/class_import.rb index 9a2cadc313..834fb3adfb 100644 --- a/app/models/class_import.rb +++ b/app/models/class_import.rb @@ -56,7 +56,7 @@ def postprocess_rows! existing_patients = Patient.where(birth_academic_year: birth_academic_years).where( - PatientSession + PatientLocation .joins(session: :location) .where("patient_id = patients.id") .where(session: { academic_year:, location: }) diff --git a/app/models/immunisation_import.rb b/app/models/immunisation_import.rb index 619562805d..192f7b26d8 100644 --- a/app/models/immunisation_import.rb +++ b/app/models/immunisation_import.rb @@ -34,7 +34,7 @@ class ImmunisationImport < ApplicationRecord include CSVImportable has_and_belongs_to_many :batches - has_and_belongs_to_many :patient_sessions + has_and_belongs_to_many :patient_locations has_and_belongs_to_many :sessions has_and_belongs_to_many :vaccination_records @@ -57,7 +57,7 @@ def process_row(row) @vaccination_records_batch ||= Set.new @batches_batch ||= Set.new @patients_batch ||= Set.new - @patient_sessions_batch ||= Set.new + @patient_locations_batch ||= Set.new @vaccination_records_batch.add(vaccination_record) if (batch = vaccination_record.batch) @@ -65,8 +65,8 @@ def process_row(row) end @patients_batch.add(vaccination_record.patient) - if (patient_session = row.to_patient_session) - @patient_sessions_batch.add(patient_session) + if (patient_location = row.to_patient_location) + @patient_locations_batch.add(patient_location) end count_column_to_increment @@ -78,16 +78,16 @@ def bulk_import(rows: 100) # We need to convert the batch to an array as `import` modifies the # objects to add IDs to any new records. vaccination_records = @vaccination_records_batch.to_a - patient_sessions = @patient_sessions_batch.to_a + patient_locations = @patient_locations_batch.to_a VaccinationRecord.import(vaccination_records, on_duplicate_key_update: :all) - PatientSession.import(patient_sessions, on_duplicate_key_ignore: :all) + PatientLocation.import(patient_locations, on_duplicate_key_ignore: :all) [ [:vaccination_records, vaccination_records], [:batches, @batches_batch], [:patients, @patients_batch], - [:patient_sessions, patient_sessions.select { it.id.present? }] + [:patient_locations, patient_locations.select { it.id.present? }] ].each do |association, collection| link_records_by_type(association, collection) collection.clear diff --git a/app/models/immunisation_import_row.rb b/app/models/immunisation_import_row.rb index c358410632..6a3e902cd7 100644 --- a/app/models/immunisation_import_row.rb +++ b/app/models/immunisation_import_row.rb @@ -156,8 +156,8 @@ def to_vaccination_record vaccination_record end - def to_patient_session - PatientSession.new(patient:, session:) if patient && session + def to_patient_location + PatientLocation.new(patient:, session:) if patient && session end def batch_expiry = @data[:batch_expiry_date] diff --git a/app/models/patient.rb b/app/models/patient.rb index 0e8389dbfd..35db2e22c4 100644 --- a/app/models/patient.rb +++ b/app/models/patient.rb @@ -71,7 +71,7 @@ class Patient < ApplicationRecord has_many :notes has_many :notify_log_entries has_many :parent_relationships, -> { order(:created_at) } - has_many :patient_sessions + has_many :patient_locations has_many :pds_search_results has_many :pre_screenings has_many :registration_statuses @@ -86,12 +86,12 @@ class Patient < ApplicationRecord has_many :parents, through: :parent_relationships has_many :patient_specific_directions - has_many :sessions, through: :patient_sessions + has_many :sessions, through: :patient_locations has_many :teams, -> { distinct }, through: :sessions has_many :pending_sessions, -> { where(academic_year: AcademicYear.pending) }, - through: :patient_sessions, + through: :patient_locations, source: :session has_and_belongs_to_many :class_imports @@ -142,7 +142,7 @@ class Patient < ApplicationRecord scope :appear_in_programmes, ->(programmes, academic_year:) do where( - PatientSession + PatientLocation .joins(:session) .where(sessions: { academic_year: }) .where("patient_id = patients.id") @@ -155,7 +155,7 @@ class Patient < ApplicationRecord scope :not_appear_in_programmes, ->(programmes, academic_year:) do where.not( - PatientSession + PatientLocation .joins(:session) .where(sessions: { academic_year: }) .where("patient_id = patients.id") @@ -503,7 +503,7 @@ def invalidate! end def not_in_team?(team:, academic_year:) - patient_sessions + patient_locations .joins(:session) .where(session: { academic_year:, team: }) .empty? @@ -514,7 +514,7 @@ def dup_for_pending_changes new_patient.nhs_number = nil pending_sessions.each do |session| - new_patient.patient_sessions.build(session:) + new_patient.patient_locations.build(session:) end school_moves.each do |school_move| @@ -534,7 +534,7 @@ def clear_pending_sessions!(team: nil) sessions = sessions.where(team_id: team.id) unless team.nil? - patient_sessions.where(session: sessions).destroy_all_if_safe + patient_locations.where(session: sessions).destroy_all_if_safe end def self.from_consent_form(consent_form) diff --git a/app/models/patient/vaccination_status.rb b/app/models/patient/vaccination_status.rb index 3454fc8cbe..b174b33657 100644 --- a/app/models/patient/vaccination_status.rb +++ b/app/models/patient/vaccination_status.rb @@ -38,7 +38,7 @@ class Patient::VaccinationStatus < ApplicationRecord -> { kept.order(performed_at: :desc) }, through: :patient - has_one :patient_session + has_one :patient_location has_one :attendance_record, -> { today }, diff --git a/app/models/patient_changeset.rb b/app/models/patient_changeset.rb index 577967cae3..5a230173a2 100644 --- a/app/models/patient_changeset.rb +++ b/app/models/patient_changeset.rb @@ -114,7 +114,7 @@ def patient Patient.new( child_attributes.merge( "home_educated" => false, - "patient_sessions" => [] + "patient_locations" => [] ) ) end @@ -209,7 +209,7 @@ def existing_patients end matches = - Patient.includes(:patient_sessions).match_existing( + Patient.includes(:patient_locations).match_existing( nhs_number: child_attributes["nhs_number"], given_name: child_attributes["given_name"], family_name: child_attributes["family_name"], diff --git a/app/models/patient_import_row.rb b/app/models/patient_import_row.rb index cd0dd2ea99..e0a42dd291 100644 --- a/app/models/patient_import_row.rb +++ b/app/models/patient_import_row.rb @@ -51,7 +51,7 @@ def to_patient prepare_patient_changes(existing_patient, import_attributes) else Patient.new( - import_attributes.merge(home_educated: false, patient_sessions: []) + import_attributes.merge(home_educated: false, patient_locations: []) ) end end @@ -346,7 +346,7 @@ def existing_patients return end - Patient.includes(:patient_sessions).match_existing( + Patient.includes(:patient_locations).match_existing( nhs_number: nhs_number_value, given_name: first_name.to_s, family_name: last_name.to_s, diff --git a/app/models/patient_session.rb b/app/models/patient_location.rb similarity index 88% rename from app/models/patient_session.rb rename to app/models/patient_location.rb index 89b1e8fe94..8480a142dc 100644 --- a/app/models/patient_session.rb +++ b/app/models/patient_location.rb @@ -2,7 +2,7 @@ # == Schema Information # -# Table name: patient_sessions +# Table name: patient_locations # # id :bigint not null, primary key # created_at :datetime not null @@ -12,8 +12,8 @@ # # Indexes # -# index_patient_sessions_on_patient_id_and_session_id (patient_id,session_id) UNIQUE -# index_patient_sessions_on_session_id (session_id) +# index_patient_locations_on_patient_id_and_session_id (patient_id,session_id) UNIQUE +# index_patient_locations_on_session_id (session_id) # # Foreign Keys # @@ -37,7 +37,7 @@ # that case, the list of programmes they appear in won't be the same as the # complete list of programmes administered in the session. -class PatientSession < ApplicationRecord +class PatientLocation < ApplicationRecord audited associated_with: :patient has_associated_audits @@ -143,7 +143,7 @@ class PatientSession < ApplicationRecord ->(status, programme:, vaccine_method: nil) do consent_status_scope = Patient::ConsentStatus - .where("patient_id = patient_sessions.patient_id") + .where("patient_id = patient_locations.patient_id") .where("academic_year = sessions.academic_year") .where(status:, programme:) @@ -159,8 +159,8 @@ class PatientSession < ApplicationRecord ->(status) do where( Patient::RegistrationStatus - .where("patient_id = patient_sessions.patient_id") - .where("session_id = patient_sessions.session_id") + .where("patient_id = patient_locations.patient_id") + .where("session_id = patient_locations.session_id") .where(status:) .arel .exists @@ -171,7 +171,7 @@ class PatientSession < ApplicationRecord ->(status, programme:) do joins(:session).where( Patient::TriageStatus - .where("patient_id = patient_sessions.patient_id") + .where("patient_id = patient_locations.patient_id") .where("academic_year = sessions.academic_year") .where(status:, programme:) .arel @@ -183,7 +183,7 @@ class PatientSession < ApplicationRecord ->(status, programme:) do joins(:session).where( Patient::VaccinationStatus - .where("patient_id = patient_sessions.patient_id") + .where("patient_id = patient_locations.patient_id") .where("academic_year = sessions.academic_year") .where(status:, programme:) .arel @@ -195,7 +195,7 @@ class PatientSession < ApplicationRecord ->(vaccine_method, programme:) do joins(:session).where( Patient::TriageStatus - .where("patient_id = patient_sessions.patient_id") + .where("patient_id = patient_locations.patient_id") .where("academic_year = sessions.academic_year") .where(vaccine_method:, programme:) .arel @@ -203,14 +203,14 @@ class PatientSession < ApplicationRecord ).or( joins(:session).where( Patient::TriageStatus - .where("patient_id = patient_sessions.patient_id") + .where("patient_id = patient_locations.patient_id") .where("academic_year = sessions.academic_year") .where(status: "not_required", programme:) .arel .exists ).where( Patient::ConsentStatus - .where("patient_id = patient_sessions.patient_id") + .where("patient_id = patient_locations.patient_id") .where("academic_year = sessions.academic_year") .where(programme:) .has_vaccine_method(vaccine_method) @@ -222,9 +222,9 @@ class PatientSession < ApplicationRecord scope :consent_given_and_ready_to_vaccinate, ->(programmes:, vaccine_method:) do - select do |patient_session| - patient = patient_session.patient - session = patient_session.session + select do |patient_location| + patient = patient_location.patient + session = patient_location.session programmes.any? do |programme| patient.consent_given_and_safe_to_vaccinate?( @@ -240,7 +240,7 @@ class PatientSession < ApplicationRecord ->(programme:, team:) do joins(:session).where.not( PatientSpecificDirection - .where("patient_id = patient_sessions.patient_id") + .where("patient_id = patient_locations.patient_id") .where("academic_year = sessions.academic_year") .where(programme:, team:) .not_invalidated @@ -253,7 +253,7 @@ class PatientSession < ApplicationRecord ->(programme:, team:) do joins(:session).where( PatientSpecificDirection - .where("patient_id = patient_sessions.patient_id") + .where("patient_id = patient_locations.patient_id") .where("academic_year = sessions.academic_year") .where(programme:, team:) .not_invalidated diff --git a/app/models/school_move.rb b/app/models/school_move.rb index e16187e993..179dfbee88 100644 --- a/app/models/school_move.rb +++ b/app/models/school_move.rb @@ -98,13 +98,13 @@ def update_archive_reasons!(user:) def update_sessions! patient - .patient_sessions + .patient_locations .joins(:session) .where("academic_year >= ?", academic_year) .destroy_all_if_safe sessions_to_add.find_each do |session| - PatientSession.find_or_create_by!(patient:, session:) + PatientLocation.find_or_create_by!(patient:, session:) end StatusUpdater.call(patient:) diff --git a/app/models/session.rb b/app/models/session.rb index b23129714a..a9a25c319b 100644 --- a/app/models/session.rb +++ b/app/models/session.rb @@ -39,7 +39,7 @@ class Session < ApplicationRecord has_many :consent_notifications has_many :notes - has_many :patient_sessions + has_many :patient_locations has_many :session_dates, -> { order(:value) } has_many :session_notifications has_many :session_programmes, @@ -54,7 +54,7 @@ class Session < ApplicationRecord has_many :pre_screenings, through: :session_dates has_many :programmes, through: :session_programmes has_many :gillick_assessments, through: :session_dates - has_many :patients, through: :patient_sessions + has_many :patients, through: :patient_locations has_many :vaccines, through: :programmes has_many :location_programme_year_groups, @@ -301,7 +301,7 @@ def open_for_consent? def next_reminder_date = next_reminder_dates.first def patients_with_no_consent_response_count - patient_sessions.has_consent_status( + patient_locations.has_consent_status( "no_response", programme: programmes ).count diff --git a/app/models/team.rb b/app/models/team.rb index 0c4e1614d9..c58316cf98 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -52,8 +52,8 @@ class Team < ApplicationRecord has_many :community_clinics, through: :subteams has_many :locations, through: :subteams - has_many :patient_sessions, through: :sessions - has_many :patients, -> { distinct }, through: :patient_sessions + has_many :patient_locations, through: :sessions + has_many :patients, -> { distinct }, through: :patient_locations has_many :programmes, through: :team_programmes has_many :schools, through: :subteams has_many :vaccination_records, through: :sessions diff --git a/app/policies/patient_policy.rb b/app/policies/patient_policy.rb index e7f2ca1d1d..0c84d437f9 100644 --- a/app/policies/patient_policy.rb +++ b/app/policies/patient_policy.rb @@ -9,10 +9,10 @@ def resolve return scope.none if team.nil? existence_criteria = [ - PatientSession + PatientLocation .select("1") .joins(:session) - .where("patient_sessions.patient_id = patients.id") + .where("patient_locations.patient_id = patients.id") .where(sessions: { team_id: team.id }) .arel, ArchiveReason diff --git a/app/policies/school_move_policy.rb b/app/policies/school_move_policy.rb index a1c15ef832..078f26f3c9 100644 --- a/app/policies/school_move_policy.rb +++ b/app/policies/school_move_policy.rb @@ -8,7 +8,7 @@ def resolve patient_subquery = Patient - .joins(patient_sessions: :session) + .joins(patient_locations: :session) .select(:id) .distinct .where(sessions: { team_id: team.id }) diff --git a/app/policies/vaccination_record_policy.rb b/app/policies/vaccination_record_policy.rb index b6b8ed8a19..f8556cfe2f 100644 --- a/app/policies/vaccination_record_policy.rb +++ b/app/policies/vaccination_record_policy.rb @@ -70,10 +70,11 @@ def resolve relevant_patients = Patient .select("1") - .joins(patient_sessions: :session) + .joins(patient_locations: :session) .where("patients.id = vaccination_records.patient_id") .where(sessions: { team_id: team.id }) .arel + scope .kept .where(relevant_patients.exists) diff --git a/app/views/sessions/consent/show.html.erb b/app/views/sessions/consent/show.html.erb index 72cfa112ea..b3ca8aa8e1 100644 --- a/app/views/sessions/consent/show.html.erb +++ b/app/views/sessions/consent/show.html.erb @@ -22,10 +22,10 @@
<%= render AppSessionNeedsReviewWarningComponent.new(session: @session) %> <%= render AppSearchResultsComponent.new(@pagy, label: "children") do %> - <% @patient_sessions.each do |patient_session| %> + <% @patient_locations.each do |patient_location| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_session.patient, - session: patient_session.session, + patient: patient_location.patient, + session: patient_location.session, context: :consent, programmes: @form.programmes, ) %> diff --git a/app/views/sessions/patient_specific_directions/show.html.erb b/app/views/sessions/patient_specific_directions/show.html.erb index 1f88295d09..4aa9d0b076 100644 --- a/app/views/sessions/patient_specific_directions/show.html.erb +++ b/app/views/sessions/patient_specific_directions/show.html.erb @@ -38,10 +38,10 @@ <%= render AppSessionNeedsReviewWarningComponent.new(session: @session) %> <%= render AppSearchResultsComponent.new(@pagy, label: "children", heading: "Review PSDs") do %> - <% @patient_sessions.each do |patient_session| %> + <% @patient_locations.each do |patient_location| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_session.patient, - session: patient_session.session, + patient: patient_location.patient, + session: patient_location.session, context: :patient_specific_direction, ) %> <% end %> diff --git a/app/views/sessions/patients/show.html.erb b/app/views/sessions/patients/show.html.erb index b4c570e1f2..d4ea5a8005 100644 --- a/app/views/sessions/patients/show.html.erb +++ b/app/views/sessions/patients/show.html.erb @@ -22,10 +22,10 @@
<%= render AppSessionNeedsReviewWarningComponent.new(session: @session) %> <%= render AppSearchResultsComponent.new(@pagy, label: "children") do %> - <% @patient_sessions.each do |patient_session| %> + <% @patient_locations.each do |patient_location| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_session.patient, - session: patient_session.session, + patient: patient_location.patient, + session: patient_location.session, context: :patients, programmes: @form.programmes, ) %> diff --git a/app/views/sessions/record/show.html.erb b/app/views/sessions/record/show.html.erb index a84035a2c0..ff95b77896 100644 --- a/app/views/sessions/record/show.html.erb +++ b/app/views/sessions/record/show.html.erb @@ -24,10 +24,10 @@ <%= render AppSessionNeedsReviewWarningComponent.new(session: @session) %> <%= render AppVaccinationsSummaryTableComponent.new(current_user:, session: @session, request_session: session) %> <%= render AppSearchResultsComponent.new(@pagy, label: "children") do %> - <% @patient_sessions.each do |patient_session| %> + <% @patient_locations.each do |patient_location| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_session.patient, - session: patient_session.session, + patient: patient_location.patient, + session: patient_location.session, context: :record, programmes: @form.programmes, ) %> diff --git a/app/views/sessions/register/show.html.erb b/app/views/sessions/register/show.html.erb index 2e61205792..ad98be5335 100644 --- a/app/views/sessions/register/show.html.erb +++ b/app/views/sessions/register/show.html.erb @@ -24,10 +24,10 @@
<%= render AppSessionNeedsReviewWarningComponent.new(session: @session) %> <%= render AppSearchResultsComponent.new(@pagy, label: "children") do %> - <% @patient_sessions.each do |patient_session| %> + <% @patient_locations.each do |patient_location| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_session.patient, - session: patient_session.session, + patient: patient_location.patient, + session: patient_location.session, context: :register, programmes: @form.programmes, ) %> diff --git a/app/views/sessions/triage/show.html.erb b/app/views/sessions/triage/show.html.erb index 9676d9a85f..a88df021fa 100644 --- a/app/views/sessions/triage/show.html.erb +++ b/app/views/sessions/triage/show.html.erb @@ -22,10 +22,10 @@
<%= render AppSessionNeedsReviewWarningComponent.new(session: @session) %> <%= render AppSearchResultsComponent.new(@pagy, label: "children") do %> - <% @patient_sessions.each do |patient_session| %> + <% @patient_locations.each do |patient_location| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_session.patient, - session: patient_session.session, + patient: patient_location.patient, + session: patient_location.session, context: :triage, programmes: @form.programmes, ) %> diff --git a/db/migrate/20250909080957_rename_patient_session.rb b/db/migrate/20250909080957_rename_patient_session.rb new file mode 100644 index 0000000000..1d2cdf1be9 --- /dev/null +++ b/db/migrate/20250909080957_rename_patient_session.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class RenamePatientSession < ActiveRecord::Migration[8.0] + def change + rename_table :patient_sessions, :patient_locations + + rename_table :immunisation_imports_patient_sessions, + :immunisation_imports_patient_locations + rename_column :immunisation_imports_patient_locations, + :patient_session_id, + :patient_location_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 5af036ab1e..9a601887d7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -358,10 +358,10 @@ t.index ["uploaded_by_user_id"], name: "index_immunisation_imports_on_uploaded_by_user_id" end - create_table "immunisation_imports_patient_sessions", id: false, force: :cascade do |t| + create_table "immunisation_imports_patient_locations", id: false, force: :cascade do |t| t.bigint "immunisation_import_id", null: false - t.bigint "patient_session_id", null: false - t.index ["immunisation_import_id", "patient_session_id"], name: "idx_on_immunisation_import_id_patient_session_id_b5003c646e", unique: true + t.bigint "patient_location_id", null: false + t.index ["immunisation_import_id", "patient_location_id"], name: "idx_on_immunisation_import_id_patient_location_id_97ddfb7192", unique: true end create_table "immunisation_imports_patients", id: false, force: :cascade do |t| @@ -536,6 +536,15 @@ t.index ["status"], name: "index_patient_consent_statuses_on_status" end + create_table "patient_locations", force: :cascade do |t| + t.bigint "session_id", null: false + t.bigint "patient_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["patient_id", "session_id"], name: "index_patient_locations_on_patient_id_and_session_id", unique: true + t.index ["session_id"], name: "index_patient_locations_on_session_id" + end + create_table "patient_registration_statuses", force: :cascade do |t| t.integer "status", default: 0, null: false t.bigint "patient_id", null: false @@ -546,15 +555,6 @@ t.index ["status"], name: "index_patient_registration_statuses_on_status" end - create_table "patient_sessions", force: :cascade do |t| - t.bigint "session_id", null: false - t.bigint "patient_id", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["patient_id", "session_id"], name: "index_patient_sessions_on_patient_id_and_session_id", unique: true - t.index ["session_id"], name: "index_patient_sessions_on_session_id" - end - create_table "patient_specific_directions", force: :cascade do |t| t.bigint "created_by_user_id", null: false t.bigint "patient_id", null: false @@ -1013,8 +1013,8 @@ add_foreign_key "identity_checks", "vaccination_records", on_delete: :cascade add_foreign_key "immunisation_imports", "teams" add_foreign_key "immunisation_imports", "users", column: "uploaded_by_user_id" - add_foreign_key "immunisation_imports_patient_sessions", "immunisation_imports" - add_foreign_key "immunisation_imports_patient_sessions", "patient_sessions" + add_foreign_key "immunisation_imports_patient_locations", "immunisation_imports" + add_foreign_key "immunisation_imports_patient_locations", "patient_locations" add_foreign_key "immunisation_imports_patients", "immunisation_imports" add_foreign_key "immunisation_imports_patients", "patients" add_foreign_key "immunisation_imports_sessions", "immunisation_imports" @@ -1037,10 +1037,10 @@ add_foreign_key "patient_changesets", "patients" add_foreign_key "patient_consent_statuses", "patients", on_delete: :cascade add_foreign_key "patient_consent_statuses", "programmes" + add_foreign_key "patient_locations", "patients" + add_foreign_key "patient_locations", "sessions" add_foreign_key "patient_registration_statuses", "patients", on_delete: :cascade add_foreign_key "patient_registration_statuses", "sessions", on_delete: :cascade - add_foreign_key "patient_sessions", "patients" - add_foreign_key "patient_sessions", "sessions" add_foreign_key "patient_specific_directions", "patients" add_foreign_key "patient_specific_directions", "programmes" add_foreign_key "patient_specific_directions", "teams" diff --git a/db/seeds.rb b/db/seeds.rb index 47da84f57f..d5c481705b 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -170,9 +170,9 @@ def setup_clinic(team) # All patients belong to the community clinic. This is normally # handled by school moves, but here we need to do it manually. - PatientSession.import( + PatientLocation.import( team.patients.map do - PatientSession.new(patient: it, session: clinic_session) + PatientLocation.new(patient: it, session: clinic_session) end, on_duplicate_key_ignore: :all ) diff --git a/docs/ops-tasks.md b/docs/ops-tasks.md index 758f648557..9e420ad3ae 100644 --- a/docs/ops-tasks.md +++ b/docs/ops-tasks.md @@ -13,7 +13,7 @@ session = org.sessions.find_by(location:) session.patients.count # get the number of patients # check all the patients can be safely removed from the session -session.patient_sessions.all?(&:safe_to_destroy?) +session.patient_locations.all?(&:safe_to_destroy?) # update all the patients to unknown school session.patients.update_all( @@ -23,7 +23,7 @@ session.patients.update_all( ) # removes all patients from the session -session.patient_sessions.destroy_all +session.patient_locations.destroy_all ``` ## Add a patient from community clinic to school session @@ -143,9 +143,9 @@ This can be done manually, and doesn't need a `SchoolMove`: ```rb # Check if the patient session in question is safe to be destroyed. -patient.patient_sessions.first.safe_to_destroy? +patient.patient_locations.first.safe_to_destroy? -patient.patient_sessions.first.destroy +patient.patient_locations.first.destroy patient.sessions << clinic_session ``` diff --git a/script/generate_model_office_data.rb b/script/generate_model_office_data.rb index 0f60428b85..9836532a37 100644 --- a/script/generate_model_office_data.rb +++ b/script/generate_model_office_data.rb @@ -262,7 +262,7 @@ def create_students_and_vaccinations_for(school:, team:, year_size_estimate:) session_participants = [dose_1_cohort, dose_2_cohort].flatten.compact session_participants.filter_map do |student| - patient_session = PatientSession.create!(patient: student, session:) + patient_session = PatientLocation.create!(patient: student, session:) next if rand < 0.1 # assume 90% uptake diff --git a/spec/components/app_activity_log_component_spec.rb b/spec/components/app_activity_log_component_spec.rb index c63ec1cf6d..8e2194d1d6 100644 --- a/spec/components/app_activity_log_component_spec.rb +++ b/spec/components/app_activity_log_component_spec.rb @@ -35,7 +35,7 @@ create(:parent_relationship, :father, parent: dad, patient:) create( - :patient_session, + :patient_location, patient:, session:, created_at: Time.zone.parse("2025-05-29 12:00") diff --git a/spec/components/app_patient_session_table_component_spec.rb b/spec/components/app_patient_session_table_component_spec.rb index e8d8cab403..80ed27f4ab 100644 --- a/spec/components/app_patient_session_table_component_spec.rb +++ b/spec/components/app_patient_session_table_component_spec.rb @@ -31,7 +31,7 @@ # relative to the current academic year. let(:patient) { create(:patient, date_of_birth: Date.new(2011, 9, 1)) } - before { create_list(:patient_session, 1, patient:, session:) } + before { create_list(:patient_location, 1, patient:, session:) } it { should have_content("Location") } it { should have_content("Session dates") } diff --git a/spec/components/app_patient_table_component_spec.rb b/spec/components/app_patient_table_component_spec.rb index 35dfc988dd..f62862b78c 100644 --- a/spec/components/app_patient_table_component_spec.rb +++ b/spec/components/app_patient_table_component_spec.rb @@ -77,7 +77,7 @@ let(:team) { current_user.selected_team } let(:session) { create(:session, team:) } - before { create(:patient_session, patient: patients.first, session:) } + before { create(:patient_location, patient: patients.first, session:) } it "renders links" do expect(rendered).to have_link("SMITH, John") diff --git a/spec/components/app_session_needs_review_warning_component_spec.rb b/spec/components/app_session_needs_review_warning_component_spec.rb index 6cd7a64a6e..fc20e9ce5b 100644 --- a/spec/components/app_session_needs_review_warning_component_spec.rb +++ b/spec/components/app_session_needs_review_warning_component_spec.rb @@ -26,7 +26,7 @@ create( :patient, nhs_number: nil, - patient_sessions: [build(:patient_session, session:)], + patient_locations: [build(:patient_location, session:)], year_group: session.programmes.sample.default_year_groups.sample ) end diff --git a/spec/components/app_vaccination_record_table_component_spec.rb b/spec/components/app_vaccination_record_table_component_spec.rb index 0fed305ebf..c1f4e1fee7 100644 --- a/spec/components/app_vaccination_record_table_component_spec.rb +++ b/spec/components/app_vaccination_record_table_component_spec.rb @@ -66,7 +66,7 @@ context "with a vaccination record not performed by the team" do before do - vaccination_records.first.patient.patient_sessions.destroy_all + vaccination_records.first.patient.patient_locations.destroy_all vaccination_records.first.update!( session: nil, source: "historical_upload", diff --git a/spec/controllers/api/testing/teams_controller_spec.rb b/spec/controllers/api/testing/teams_controller_spec.rb index b7eb5b878a..e071ccc802 100644 --- a/spec/controllers/api/testing/teams_controller_spec.rb +++ b/spec/controllers/api/testing/teams_controller_spec.rb @@ -85,7 +85,7 @@ .and(change(NotifyLogEntry, :count).by(-3)) .and(change(Parent, :count).by(-4)) .and(change(Patient, :count).by(-3)) - .and(change(PatientSession, :count).by(-3)) + .and(change(PatientLocation, :count).by(-3)) .and(change(VaccinationRecord, :count).by(-9)) .and(change(SessionDate, :count).by(-1)) ) @@ -106,7 +106,7 @@ .and(change(NotifyLogEntry, :count).by(-3)) .and(change(Parent, :count).by(-4)) .and(change(Patient, :count).by(-3)) - .and(change(PatientSession, :count).by(-3)) + .and(change(PatientLocation, :count).by(-3)) .and(change(VaccinationRecord, :count).by(-9)) .and(change(SessionDate, :count).by(-1)) ) diff --git a/spec/factories/patient_sessions.rb b/spec/factories/patient_locations.rb similarity index 71% rename from spec/factories/patient_sessions.rb rename to spec/factories/patient_locations.rb index 7f791bc095..b93048c674 100644 --- a/spec/factories/patient_sessions.rb +++ b/spec/factories/patient_locations.rb @@ -2,7 +2,7 @@ # == Schema Information # -# Table name: patient_sessions +# Table name: patient_locations # # id :bigint not null, primary key # created_at :datetime not null @@ -12,8 +12,8 @@ # # Indexes # -# index_patient_sessions_on_patient_id_and_session_id (patient_id,session_id) UNIQUE -# index_patient_sessions_on_session_id (session_id) +# index_patient_locations_on_patient_id_and_session_id (patient_id,session_id) UNIQUE +# index_patient_locations_on_session_id (session_id) # # Foreign Keys # @@ -21,7 +21,7 @@ # fk_rails_... (session_id => sessions.id) # FactoryBot.define do - factory :patient_session do + factory :patient_location do transient { programmes { [association(:programme)] } } patient diff --git a/spec/factories/patients.rb b/spec/factories/patients.rb index f38f95fbba..97c75f6844 100644 --- a/spec/factories/patients.rb +++ b/spec/factories/patients.rb @@ -125,7 +125,7 @@ after(:create) do |patient, evaluator| if evaluator.session - PatientSession.find_or_create_by!(patient:, session: evaluator.session) + PatientLocation.find_or_create_by!(patient:, session: evaluator.session) end end diff --git a/spec/features/archive_vaccination_record_spec.rb b/spec/features/archive_vaccination_record_spec.rb index f9eb2d0c2d..208702684a 100644 --- a/spec/features/archive_vaccination_record_spec.rb +++ b/spec/features/archive_vaccination_record_spec.rb @@ -136,8 +136,8 @@ def given_an_hpv_programme_is_underway team: @team ) - @patient_session = - create(:patient_session, patient: @patient, session: @session) + @patient_location = + create(:patient_location, patient: @patient, session: @session) end def and_an_administered_vaccination_record_exists diff --git a/spec/features/cli_generate_consents_spec.rb b/spec/features/cli_generate_consents_spec.rb index f25e0ceb9c..42f330495c 100644 --- a/spec/features/cli_generate_consents_spec.rb +++ b/spec/features/cli_generate_consents_spec.rb @@ -56,7 +56,7 @@ def then_consents_are_created_with_the_given_statuses expect( @team - .patient_sessions + .patient_locations .has_consent_status(:given, programme: @programme) .has_triage_status(:not_required, programme: @programme) .count diff --git a/spec/features/cli_schools_move_patients_spec.rb b/spec/features/cli_schools_move_patients_spec.rb index 1d965f62a9..8ddb017163 100644 --- a/spec/features/cli_schools_move_patients_spec.rb +++ b/spec/features/cli_schools_move_patients_spec.rb @@ -52,11 +52,11 @@ end context "when some patient sessions are not safe to destroy" do - let!(:patient_session) { create(:patient_session, patient:, session:) } # rubocop:disable RSpec/LetSetup + let!(:patient_location) { create(:patient_location, patient:, session:) } # rubocop:disable RSpec/LetSetup before do # rubocop:disable RSpec/AnyInstance - allow_any_instance_of(PatientSession).to receive( + allow_any_instance_of(PatientLocation).to receive( :safe_to_destroy? ).and_return(false) # rubocop:enable RSpec/AnyInstance diff --git a/spec/features/cli_stats_consents_by_school_spec.rb b/spec/features/cli_stats_consents_by_school_spec.rb index c0e0cfe06f..2b7f0b5a79 100644 --- a/spec/features/cli_stats_consents_by_school_spec.rb +++ b/spec/features/cli_stats_consents_by_school_spec.rb @@ -111,9 +111,9 @@ def given_organisation_has_consent_data session: session_prev_year ) - create(:patient_session, session: session1, patient: patient1) - create(:patient_session, session: session2, patient: patient2) - create(:patient_session, session: session3, patient: patient3) + create(:patient_location, session: session1, patient: patient1) + create(:patient_location, session: session2, patient: patient2) + create(:patient_location, session: session3, patient: patient3) create( :consent, diff --git a/spec/features/cli_stats_organisations_spec.rb b/spec/features/cli_stats_organisations_spec.rb index 2b34480558..1f450d4a9a 100644 --- a/spec/features/cli_stats_organisations_spec.rb +++ b/spec/features/cli_stats_organisations_spec.rb @@ -145,7 +145,7 @@ def given_organisation_has_complete_data_with_filters ) create(:patient, :consent_no_response, year_group: 11, session: session1) - create(:patient_session, patient: patient_year_9, session: session1) + create(:patient_location, patient: patient_year_9, session: session1) create( :consent, :refused, @@ -154,7 +154,7 @@ def given_organisation_has_complete_data_with_filters ) create( - :patient_session, + :patient_location, patient: patient_year_8, session: session_last_year ) diff --git a/spec/features/download_vaccination_reports_spec.rb b/spec/features/download_vaccination_reports_spec.rb index 9ac6e35e5e..7b81f6ed44 100644 --- a/spec/features/download_vaccination_reports_spec.rb +++ b/spec/features/download_vaccination_reports_spec.rb @@ -75,8 +75,8 @@ def given_an_hpv_programme_is_underway team: @team ) - @patient_session = - create(:patient_session, patient: @patient, session: @session) + @patient_location = + create(:patient_location, patient: @patient, session: @session) end def given_a_menacwy_programme_is_underway @@ -95,8 +95,8 @@ def given_a_menacwy_programme_is_underway team: @team ) - @patient_session = - create(:patient_session, patient: @patient, session: @session) + @patient_location = + create(:patient_location, patient: @patient, session: @session) end def and_an_administered_vaccination_record_exists diff --git a/spec/features/import_vaccination_records_with_duplicates_spec.rb b/spec/features/import_vaccination_records_with_duplicates_spec.rb index 88e35808c5..c8a1a8d642 100644 --- a/spec/features/import_vaccination_records_with_duplicates_spec.rb +++ b/spec/features/import_vaccination_records_with_duplicates_spec.rb @@ -85,14 +85,14 @@ def and_an_existing_patient_record_exists address_postcode: "LE8 2DA", school: @location ) - @patient_session = + @patient_location = create( - :patient_session, + :patient_location, patient: @already_vaccinated_patient, session: @session ) - @third_patient_session = - create(:patient_session, patient: @third_patient, session: @session) + @third_patient_location = + create(:patient_location, patient: @third_patient, session: @session) @vaccine = @programme.vaccines.find_by(nivs_name: "Gardasil9") @other_vaccine = @programme.vaccines.find_by(nivs_name: "Cervarix") @batch = create(:batch, vaccine: @vaccine, name: "SomethingElse") diff --git a/spec/features/programme_cohorts_spec.rb b/spec/features/programme_cohorts_spec.rb index 635e3f3ac0..0782a4ad95 100644 --- a/spec/features/programme_cohorts_spec.rb +++ b/spec/features/programme_cohorts_spec.rb @@ -41,7 +41,7 @@ def and_there_are_patients_in_different_year_groups # To make it realistic we'll also add patients to clinics. Patient.find_each do |patient| create( - :patient_session, + :patient_location, patient:, session: @team.generic_clinic_session(academic_year: AcademicYear.current) diff --git a/spec/forms/patient_search_form_spec.rb b/spec/forms/patient_search_form_spec.rb index ead80a660b..c8485df99e 100644 --- a/spec/forms/patient_search_form_spec.rb +++ b/spec/forms/patient_search_form_spec.rb @@ -350,7 +350,7 @@ end context "for patient sessions" do - let(:scope) { PatientSession.all } + let(:scope) { PatientLocation.all } let(:session) { create(:session, programmes:, team:) } @@ -376,20 +376,20 @@ context "with a patient session eligible for the programme" do let(:patient) { create(:patient, year_group: 9) } - let(:patient_session) { create(:patient_session, patient:, session:) } + let(:patient_location) { create(:patient_location, patient:, session:) } it "is included" do - expect(form.apply(scope)).to include(patient_session) + expect(form.apply(scope)).to include(patient_location) end end context "with a patient session not eligible for the programme" do let(:patient) { create(:patient, year_group: 8) } - let(:patient_session) { create(:patient_session, patient:, session:) } + let(:patient_location) { create(:patient_location, patient:, session:) } it "is not included" do - expect(form.apply(scope)).not_to include(patient_session) + expect(form.apply(scope)).not_to include(patient_location) end end end @@ -414,8 +414,8 @@ patient_refused = create(:patient, :consent_refused, session:) expect(form.apply(scope)).to contain_exactly( - patient_given.patient_sessions.first, - patient_refused.patient_sessions.first + patient_given.patient_locations.first, + patient_refused.patient_locations.first ) end @@ -423,12 +423,12 @@ let(:consent_statuses) { %w[given_nasal] } it "filters on consent status" do - patient_session_given_nasal = + patient_location_given_nasal = create( :patient, :consent_given_nasal_only_triage_not_needed, session: - ).patient_sessions.first + ).patient_locations.first create( :patient, @@ -437,7 +437,7 @@ ) expect(form.apply(scope)).to contain_exactly( - patient_session_given_nasal + patient_location_given_nasal ) end end @@ -457,8 +457,8 @@ it "filters on register status" do patient = create(:patient, :in_attendance, session:) - patient_session = patient.patient_sessions.first - expect(form.apply(scope)).to include(patient_session) + patient_location = patient.patient_locations.first + expect(form.apply(scope)).to include(patient_location) end end @@ -477,9 +477,9 @@ it "filters on triage status" do patient = create(:patient, :consent_given_triage_needed, session:) - patient_session = patient.patient_sessions.first + patient_location = patient.patient_locations.first - expect(form.apply(scope)).to include(patient_session) + expect(form.apply(scope)).to include(patient_location) end end @@ -496,24 +496,28 @@ let(:triage_status) { nil } let(:year_groups) { nil } - let!(:patient_session_with_psd) do - create(:patient_session, session:).tap do |patient_session| + let!(:patient_location_with_psd) do + create(:patient_location, session:).tap do |patient_location| create( :patient_specific_direction, - patient: patient_session.patient, + patient: patient_location.patient, programme: programmes.first, team: ) end end - let!(:patient_session_without_psd) { create(:patient_session, session:) } + let!(:patient_location_without_psd) do + create(:patient_location, session:) + end context "when status is 'added'" do let(:patient_specific_direction_status) { "added" } it "finds the patient with the PSD" do - expect(form.apply(scope)).to contain_exactly(patient_session_with_psd) + expect(form.apply(scope)).to contain_exactly( + patient_location_with_psd + ) end end @@ -522,7 +526,7 @@ it "finds the patient that has no PSD" do expect(form.apply(scope)).to contain_exactly( - patient_session_without_psd + patient_location_without_psd ) end end @@ -570,7 +574,7 @@ ) expect(form.apply(scope)).to contain_exactly( - nasal_patient.patient_sessions.first + nasal_patient.patient_locations.first ) end end diff --git a/spec/jobs/enqueue_clinic_session_invitations_job_spec.rb b/spec/jobs/enqueue_clinic_session_invitations_job_spec.rb index 63fc459314..0a0971513a 100644 --- a/spec/jobs/enqueue_clinic_session_invitations_job_spec.rb +++ b/spec/jobs/enqueue_clinic_session_invitations_job_spec.rb @@ -11,7 +11,7 @@ let(:patient) { create(:patient, parents:, year_group: 8) } let(:location) { create(:generic_clinic, team:) } - before { create(:patient_session, patient:, session:) } + before { create(:patient_location, patient:, session:) } context "for a scheduled clinic session in 3 weeks" do let(:date) { 3.weeks.from_now.to_date } diff --git a/spec/jobs/patient_nhs_number_lookup_job_spec.rb b/spec/jobs/patient_nhs_number_lookup_job_spec.rb index 92607ba75b..3e795cffbc 100644 --- a/spec/jobs/patient_nhs_number_lookup_job_spec.rb +++ b/spec/jobs/patient_nhs_number_lookup_job_spec.rb @@ -74,10 +74,10 @@ let!(:existing_patient) { create(:patient, nhs_number: "9449306168") } - let(:patient_session) do - create(:patient_session, patient:, programmes: [programme]) + let(:patient_location) do + create(:patient_location, patient:, programmes: [programme]) end - let(:session) { patient_session.session } + let(:session) { patient_location.session } let(:gillick_assessment) do create(:gillick_assessment, :competent, patient:, session:) end @@ -114,9 +114,9 @@ context "when the existing patient is already in the session" do before do create( - :patient_session, + :patient_location, patient: existing_patient, - session: patient_session.session + session: patient_location.session ) end diff --git a/spec/jobs/send_automatic_school_consent_reminders_job_spec.rb b/spec/jobs/send_automatic_school_consent_reminders_job_spec.rb index 894f80aa9e..5d0ba42cde 100644 --- a/spec/jobs/send_automatic_school_consent_reminders_job_spec.rb +++ b/spec/jobs/send_automatic_school_consent_reminders_job_spec.rb @@ -69,7 +69,7 @@ end before do - patients.each { |patient| create(:patient_session, patient:, session:) } + patients.each { |patient| create(:patient_location, patient:, session:) } ConsentNotification.request.update_all(sent_at: dates.first - 1.week) ConsentNotification.reminder.update_all(sent_at: dates.first) diff --git a/spec/jobs/send_manual_school_consent_reminders_job_spec.rb b/spec/jobs/send_manual_school_consent_reminders_job_spec.rb index 372268d858..4be5a21d7a 100644 --- a/spec/jobs/send_manual_school_consent_reminders_job_spec.rb +++ b/spec/jobs/send_manual_school_consent_reminders_job_spec.rb @@ -42,7 +42,7 @@ before do create(:parent_relationship, patient:, parent:) - create(:patient_session, patient:, session:, programmes:) + create(:patient_location, patient:, session:, programmes:) patient.reload end diff --git a/spec/jobs/send_school_consent_requests_job_spec.rb b/spec/jobs/send_school_consent_requests_job_spec.rb index 6cd7656ccc..a422973356 100644 --- a/spec/jobs/send_school_consent_requests_job_spec.rb +++ b/spec/jobs/send_school_consent_requests_job_spec.rb @@ -28,7 +28,7 @@ end before do - patients.each { |patient| create(:patient_session, patient:, session:) } + patients.each { |patient| create(:patient_location, patient:, session:) } end around { |example| travel_to(today) { example.run } } diff --git a/spec/jobs/send_school_session_reminders_job_spec.rb b/spec/jobs/send_school_session_reminders_job_spec.rb index 873786af83..ccff0b242c 100644 --- a/spec/jobs/send_school_session_reminders_job_spec.rb +++ b/spec/jobs/send_school_session_reminders_job_spec.rb @@ -9,7 +9,7 @@ create(:patient, :consent_given_triage_not_needed, parents:, programmes:) end - before { create(:patient_session, patient:, session:) } + before { create(:patient_location, patient:, session:) } context "for an active session tomorrow" do let(:session) { create(:session, :tomorrow, programmes:) } diff --git a/spec/lib/patient_archiver_spec.rb b/spec/lib/patient_archiver_spec.rb index 046ad667d0..965f0466a0 100644 --- a/spec/lib/patient_archiver_spec.rb +++ b/spec/lib/patient_archiver_spec.rb @@ -22,7 +22,7 @@ context "when in upcoming sessions" do let(:session) { create(:session, :tomorrow, team:) } - before { create(:patient_session, patient:, session:) } + before { create(:patient_location, patient:, session:) } it "removes the patient from the sessions" do expect(patient.sessions).to include(session) diff --git a/spec/lib/patient_merger_spec.rb b/spec/lib/patient_merger_spec.rb index 41d426435b..2cefe5f772 100644 --- a/spec/lib/patient_merger_spec.rb +++ b/spec/lib/patient_merger_spec.rb @@ -58,8 +58,8 @@ let(:parent_relationship) do create(:parent_relationship, patient: patient_to_destroy) end - let(:patient_session) do - create(:patient_session, session:, patient: patient_to_destroy) + let(:patient_location) do + create(:patient_location, session:, patient: patient_to_destroy) end let(:patient_specific_direction) do create( @@ -162,7 +162,7 @@ end it "moves patient sessions" do - expect { call }.to change { patient_session.reload.patient }.to( + expect { call }.to change { patient_location.reload.patient }.to( patient_to_keep ) end diff --git a/spec/lib/reports/offline_session_exporter_spec.rb b/spec/lib/reports/offline_session_exporter_spec.rb index 251ef42c28..20a3de2efa 100644 --- a/spec/lib/reports/offline_session_exporter_spec.rb +++ b/spec/lib/reports/offline_session_exporter_spec.rb @@ -119,7 +119,7 @@ def validation_formula(worksheet:, column_name:, row: 1) let(:batch) do create(:batch, :not_expired, vaccine: programme.vaccines.active.first) end - let(:patient_session) { create(:patient_session, patient:, session:) } + let(:patient_location) { create(:patient_location, patient:, session:) } let(:patient) do create(:patient, year_group: programme.default_year_groups.first) end @@ -225,7 +225,7 @@ def validation_formula(worksheet:, column_name:, row: 1) end context "with a vaccinated patient" do - before { create(:patient_session, patient:, session:) } + before { create(:patient_location, patient:, session:) } let!(:vaccination_record) do create( @@ -340,7 +340,7 @@ def validation_formula(worksheet:, column_name:, row: 1) end context "with a vaccinated patient outside the session" do - before { create(:patient_session, patient:, session:) } + before { create(:patient_location, patient:, session:) } let!(:vaccination_record) do create( @@ -430,8 +430,8 @@ def validation_formula(worksheet:, column_name:, row: 1) end before do - create(:patient_session, patient:, session:) - create(:patient_session, patient:, session: clinic_session) + create(:patient_location, patient:, session:) + create(:patient_location, patient:, session: clinic_session) end it "adds a row with the vaccination details" do @@ -493,7 +493,7 @@ def validation_formula(worksheet:, column_name:, row: 1) context "with a vaccinated patient for a different programme" do before do - create(:patient_session, patient:, session:) + create(:patient_location, patient:, session:) other_programme = (Programme.types.keys - [programme.type]).sample create( @@ -558,7 +558,7 @@ def validation_formula(worksheet:, column_name:, row: 1) end context "with a patient who couldn't be vaccinated" do - before { create(:patient_session, patient:, session:) } + before { create(:patient_location, patient:, session:) } let!(:vaccination_record) do create( @@ -875,7 +875,7 @@ def validation_formula(worksheet:, column_name:, row: 1) ) end - before { create(:patient_session, patient:, session:) } + before { create(:patient_location, patient:, session:) } it "adds a row to fill in" do expect(rows.count).to eq(1) diff --git a/spec/lib/reports/programme_vaccinations_exporter_spec.rb b/spec/lib/reports/programme_vaccinations_exporter_spec.rb index 996b83a4ec..dc80368500 100644 --- a/spec/lib/reports/programme_vaccinations_exporter_spec.rb +++ b/spec/lib/reports/programme_vaccinations_exporter_spec.rb @@ -211,7 +211,7 @@ end context "with a vaccinated patient outside the date range" do - let(:patient) { create(:patient_session, session:).patient } + let(:patient) { create(:patient_location, session:).patient } let(:start_date) { Date.current } before do @@ -230,7 +230,7 @@ end context "with a vaccination for a different programme" do - let(:patient) { create(:patient_session, session:).patient } + let(:patient) { create(:patient_location, session:).patient } let(:other_programme) do create( @@ -252,7 +252,7 @@ end context "with a vaccinated patient that was updated in the date range" do - let(:patient) { create(:patient_session, session:).patient } + let(:patient) { create(:patient_location, session:).patient } let(:start_date) { 1.day.ago } before do diff --git a/spec/lib/stats/consents_by_school_spec.rb b/spec/lib/stats/consents_by_school_spec.rb index 862cb89968..0f2c4a14dd 100644 --- a/spec/lib/stats/consents_by_school_spec.rb +++ b/spec/lib/stats/consents_by_school_spec.rb @@ -21,7 +21,7 @@ context "when there are consent responses" do before do - patient_session + patient_location consent end @@ -37,7 +37,7 @@ end let!(:patient) { create(:patient, team:) } - let(:patient_session) { create(:patient_session, session:, patient:) } + let(:patient_location) { create(:patient_location, session:, patient:) } let(:consent) do create( @@ -169,7 +169,7 @@ end context "when there are multiple consents for a patient" do - before { patient_session } + before { patient_location } let!(:session) do create( @@ -183,8 +183,8 @@ end let!(:patient) { create(:patient, team: team) } - let(:patient_session) do - create(:patient_session, session: session, patient: patient) + let(:patient_location) do + create(:patient_location, session: session, patient: patient) end let!(:flu_consent) do diff --git a/spec/lib/status_generator/registration_spec.rb b/spec/lib/status_generator/registration_spec.rb index 16e653fddb..242087a091 100644 --- a/spec/lib/status_generator/registration_spec.rb +++ b/spec/lib/status_generator/registration_spec.rb @@ -20,7 +20,7 @@ let(:session) do create(:session, dates: [Date.yesterday, Date.current], programmes:) end - let(:patient_session) { create(:patient_session, patient:, session:) } + let(:patient_location) { create(:patient_location, patient:, session:) } describe "#status" do subject { generator.status } diff --git a/spec/lib/status_generator/session_spec.rb b/spec/lib/status_generator/session_spec.rb index d70f37da5e..e68fb4d5b5 100644 --- a/spec/lib/status_generator/session_spec.rb +++ b/spec/lib/status_generator/session_spec.rb @@ -3,8 +3,8 @@ describe StatusGenerator::Session do subject(:generator) do described_class.new( - session_id: patient_session.session_id, - academic_year: patient_session.academic_year, + session_id: patient_location.session_id, + academic_year: patient_location.academic_year, attendance_record: patient.attendance_records.last, programme:, patient:, @@ -14,14 +14,14 @@ ) end - let(:patient_session) { create(:patient_session, programmes: [programme]) } + let(:patient_location) { create(:patient_location, programmes: [programme]) } let(:programme) { create(:programme) } describe "#status" do subject(:status) { generator.status } - let(:patient) { patient_session.patient } - let(:session) { patient_session.session } + let(:patient) { patient_location.patient } + let(:session) { patient_location.session } context "with no vaccination record" do it { should be(:none_yet) } @@ -90,8 +90,8 @@ around { |example| travel_to(Time.zone.now) { example.run } } - let(:patient) { patient_session.patient } - let(:session) { patient_session.session } + let(:patient) { patient_location.patient } + let(:session) { patient_location.session } let(:performed_at) { 1.day.ago.beginning_of_minute } let(:created_at) { 2.days.ago.midday } diff --git a/spec/lib/status_updater_spec.rb b/spec/lib/status_updater_spec.rb index cb3ed7c6a4..16ff077716 100644 --- a/spec/lib/status_updater_spec.rb +++ b/spec/lib/status_updater_spec.rb @@ -5,7 +5,7 @@ around { |example| travel_to(Date.new(2025, 7, 31)) { example.run } } - before { create(:patient_session, patient:, programmes:) } + before { create(:patient_location, patient:, programmes:) } context "with an HPV session and ineligible patient" do let(:programmes) { [create(:programme, :hpv)] } diff --git a/spec/lib/tasks/status_update_spec.rb b/spec/lib/tasks/status_update_spec.rb index 7ae92df287..0cea9d343d 100644 --- a/spec/lib/tasks/status_update_spec.rb +++ b/spec/lib/tasks/status_update_spec.rb @@ -4,7 +4,7 @@ context "with all patients" do subject(:invoke) { Rake::Task["status:update:all"].invoke } - before { create(:patient_session) } + before { create(:patient_location) } after { Rake.application["status:update:all"].reenable } @@ -25,7 +25,7 @@ let(:patient) { create(:patient) } - before { create(:patient_session, patient:) } + before { create(:patient_location, patient:) } it "doesn't raise an error" do expect { invoke }.not_to raise_error @@ -46,7 +46,7 @@ let(:session) { create(:session) } - before { create(:patient_session, session:) } + before { create(:patient_location, session:) } it "doesn't raise an error" do expect { invoke }.not_to raise_error diff --git a/spec/models/cohort_import_spec.rb b/spec/models/cohort_import_spec.rb index a5403ef090..749de3a29b 100644 --- a/spec/models/cohort_import_spec.rb +++ b/spec/models/cohort_import_spec.rb @@ -403,7 +403,7 @@ before do team.sessions.each do |session| - create(:patient_session, session:, patient: existing_patient) + create(:patient_location, session:, patient: existing_patient) end end diff --git a/spec/models/immunisation_import_spec.rb b/spec/models/immunisation_import_spec.rb index 6606c24455..7b18cb7e5b 100644 --- a/spec/models/immunisation_import_spec.rb +++ b/spec/models/immunisation_import_spec.rb @@ -143,7 +143,7 @@ .and change(immunisation_import.vaccination_records, :count).by(11) .and change(immunisation_import.patients, :count).by(11) .and change(immunisation_import.batches, :count).by(4) - .and not_change(immunisation_import.patient_sessions, :count) + .and not_change(immunisation_import.patient_locations, :count) # Second import should not duplicate the vaccination records if they're # identical. @@ -153,7 +153,7 @@ .to not_change(immunisation_import, :processed_at) .and not_change(VaccinationRecord, :count) .and not_change(Patient, :count) - .and not_change(PatientSession, :count) + .and not_change(PatientLocation, :count) .and not_change(Batch, :count) end @@ -197,7 +197,7 @@ .and change(immunisation_import.vaccination_records, :count).by(11) .and change(immunisation_import.patients, :count).by(10) .and change(immunisation_import.batches, :count).by(8) - .and not_change(immunisation_import.patient_sessions, :count) + .and not_change(immunisation_import.patient_locations, :count) # Second import should not duplicate the vaccination records if they're # identical. @@ -207,7 +207,7 @@ .to not_change(immunisation_import, :processed_at) .and not_change(VaccinationRecord, :count) .and not_change(Patient, :count) - .and not_change(PatientSession, :count) + .and not_change(PatientLocation, :count) .and not_change(Batch, :count) end @@ -257,7 +257,7 @@ .and change(immunisation_import.vaccination_records, :count).by(4) .and change(immunisation_import.patients, :count).by(4) .and change(immunisation_import.batches, :count).by(1) - .and not_change(immunisation_import.patient_sessions, :count) + .and not_change(immunisation_import.patient_locations, :count) # Second import should not duplicate the vaccination records if they're # identical. @@ -267,7 +267,7 @@ .to not_change(immunisation_import, :processed_at) .and not_change(VaccinationRecord, :count) .and not_change(Patient, :count) - .and not_change(PatientSession, :count) + .and not_change(PatientLocation, :count) .and not_change(Batch, :count) end end diff --git a/spec/models/patient/vaccination_status_spec.rb b/spec/models/patient/vaccination_status_spec.rb index 1269e493df..b0bfb62f42 100644 --- a/spec/models/patient/vaccination_status_spec.rb +++ b/spec/models/patient/vaccination_status_spec.rb @@ -105,7 +105,7 @@ before do patient.strict_loading!(false) - create(:patient_session, patient:, session:) + create(:patient_location, patient:, session:) end let(:session) { create(:session, programmes: [programme]) } diff --git a/spec/models/patient_session_spec.rb b/spec/models/patient_location_spec.rb similarity index 82% rename from spec/models/patient_session_spec.rb rename to spec/models/patient_location_spec.rb index 10c12f8005..a255be577e 100644 --- a/spec/models/patient_session_spec.rb +++ b/spec/models/patient_location_spec.rb @@ -2,7 +2,7 @@ # == Schema Information # -# Table name: patient_sessions +# Table name: patient_locations # # id :bigint not null, primary key # created_at :datetime not null @@ -12,8 +12,8 @@ # # Indexes # -# index_patient_sessions_on_patient_id_and_session_id (patient_id,session_id) UNIQUE -# index_patient_sessions_on_session_id (session_id) +# index_patient_locations_on_patient_id_and_session_id (patient_id,session_id) UNIQUE +# index_patient_locations_on_session_id (session_id) # # Foreign Keys # @@ -21,8 +21,8 @@ # fk_rails_... (session_id => sessions.id) # -describe PatientSession do - subject(:patient_session) { create(:patient_session, session:) } +describe PatientLocation do + subject(:patient_location) { create(:patient_location, session:) } let(:programme) { create(:programme) } let(:session) { create(:session, programmes: [programme]) } @@ -43,20 +43,20 @@ let(:programmes) { create_list(:programme, 1, :td_ipv) } let(:session) { create(:session, programmes:) } - let(:patient_session) { create(:patient_session, patient:, session:) } + let(:patient_location) { create(:patient_location, patient:, session:) } it { should be_empty } context "in a session with the right year group" do let(:patient) { create(:patient, year_group: 9) } - it { should include(patient_session) } + it { should include(patient_location) } end context "in a session but the wrong year group" do let(:patient) { create(:patient, year_group: 8) } - it { should_not include(patient_session) } + it { should_not include(patient_location) } end context "in a session with the right year group for the programme but not the location" do @@ -75,7 +75,7 @@ end end - it { should_not include(patient_session) } + it { should_not include(patient_location) } end end @@ -91,7 +91,7 @@ let(:session) { create(:session, programmes:) } let(:academic_year) { Date.current.academic_year } let(:vaccine_method) { nil } - let(:patient_session) { patient.patient_sessions.first } + let(:patient_location) { patient.patient_locations.first } it { should be_empty } @@ -100,7 +100,7 @@ create(:patient, :consent_given_triage_not_needed, session:) end - it { should include(patient_session) } + it { should include(patient_location) } end context "when filtering on nasal spray" do @@ -118,7 +118,7 @@ ).update!(vaccine_methods: %w[nasal injection]) end - it { should include(patient_session) } + it { should include(patient_location) } context "when the patient has been vaccinated for flu" do before do @@ -131,7 +131,7 @@ StatusUpdater.call(session:, patient:) end - it { should_not include(patient_session) } + it { should_not include(patient_location) } end end end @@ -139,10 +139,10 @@ end describe "#safe_to_destroy?" do - subject(:safe_to_destroy?) { patient_session.safe_to_destroy? } + subject(:safe_to_destroy?) { patient_location.safe_to_destroy? } - let(:patient_session) { create(:patient_session, session:) } - let(:patient) { patient_session.patient } + let(:patient_location) { create(:patient_location, session:) } + let(:patient) { patient_location.patient } context "when safe to destroy" do it { should be true } diff --git a/spec/models/patient_spec.rb b/spec/models/patient_spec.rb index 2f71102d67..f97c7519fc 100644 --- a/spec/models/patient_spec.rb +++ b/spec/models/patient_spec.rb @@ -173,16 +173,16 @@ end before do - create(:patient_session, patient:, session: flu_session) - create(:patient_session, patient:, session: hpv_session) + create(:patient_location, patient:, session: flu_session) + create(:patient_location, patient:, session: hpv_session) create( - :patient_session, + :patient_location, patient: another_patient, session: flu_session ) create( - :patient_session, + :patient_location, patient: another_patient, session: hpv_session ) @@ -275,16 +275,16 @@ end before do - create(:patient_session, patient:, session: flu_session) - create(:patient_session, patient:, session: hpv_session) + create(:patient_location, patient:, session: flu_session) + create(:patient_location, patient:, session: hpv_session) create( - :patient_session, + :patient_location, patient: another_patient, session: flu_session ) create( - :patient_session, + :patient_location, patient: another_patient, session: hpv_session ) @@ -896,7 +896,7 @@ ) end - before { create(:patient_session, patient:, session:) } + before { create(:patient_location, patient:, session:) } it "removes the patient from the session" do expect(session.patients).to include(patient) @@ -1134,11 +1134,11 @@ ) end - before { create(:patient_session, patient: old_patient, session:) } + before { create(:patient_location, patient: old_patient, session:) } it "adds the new patient to any upcoming sessions" do - expect(new_patient.patient_sessions.size).to eq(1) - expect(new_patient.patient_sessions.first.session).to eq(session) + expect(new_patient.patient_locations.size).to eq(1) + expect(new_patient.patient_locations.first.session).to eq(session) end end diff --git a/spec/policies/patient_policy_spec.rb b/spec/policies/patient_policy_spec.rb index ed4e184f34..a3c4d7a8cc 100644 --- a/spec/policies/patient_policy_spec.rb +++ b/spec/policies/patient_policy_spec.rb @@ -38,12 +38,12 @@ before do create( - :patient_session, + :patient_location, patient: patient_in_session, session: create(:session, team:, programmes:) ) create( - :patient_session, + :patient_location, patient: patient_not_in_session, session: create(:session, team: another_team, programmes:) ) From 3ab34838e3ffa8979c0343c80233466d7d74d5fe Mon Sep 17 00:00:00 2001 From: Thomas Leese Date: Wed, 10 Sep 2025 07:50:21 +0100 Subject: [PATCH 2/3] Remove session association on `PatientSession` This reworks the `PatientLocation` model to no longer have a direct association with a session and instead have a separate `location_id` and `academic_year`, which represents the equivelant information since that's all that is needed to get the sessions for the patient. Jira-Issue: MAV-1822 --- app/components/app_activity_log_component.rb | 15 +- ...patient_pds_discrepancy_table_component.rb | 4 +- .../app_patient_programmes_table_component.rb | 3 +- .../app_programme_session_table_component.rb | 5 +- .../app_session_actions_component.rb | 9 +- .../app_session_details_summary_component.rb | 5 +- ..._session_needs_review_warning_component.rb | 2 +- .../api/testing/teams_controller.rb | 4 +- app/controllers/consent_forms_controller.rb | 6 +- app/controllers/imports/issues_controller.rb | 1 - .../patient_sessions/base_controller.rb | 6 +- app/controllers/patients_controller.rb | 18 ++- app/controllers/programmes/base_controller.rb | 5 +- .../sessions/consent_controller.rb | 1 - .../patient_specific_directions_controller.rb | 2 +- .../sessions/patients_controller.rb | 2 +- app/controllers/sessions/record_controller.rb | 7 +- .../sessions/register_controller.rb | 2 +- app/controllers/sessions/triage_controller.rb | 1 - app/controllers/sessions_controller.rb | 7 +- app/forms/patient_search_form.rb | 4 +- ...end_school_consent_notification_concern.rb | 1 - .../enqueue_vaccinations_search_in_nhs_job.rb | 15 +- app/lib/clinic_patient_locations_factory.rb | 5 +- app/lib/generate/vaccination_records.rb | 90 ++++++----- app/lib/location_sessions_factory.rb | 13 +- app/lib/mavis_cli/schools/move_patients.rb | 13 +- app/lib/patient_merger.rb | 3 +- app/lib/stats/organisations.rb | 6 +- app/lib/status_updater.rb | 25 ++-- app/lib/team_sessions_factory.rb | 4 +- app/models/class_import.rb | 4 +- app/models/immunisation_import_row.rb | 4 +- app/models/location.rb | 1 + app/models/patient.rb | 54 ++++--- app/models/patient_location.rb | 140 +++++++++--------- app/models/school_move.rb | 24 +-- app/models/session.rb | 30 ++-- app/models/team.rb | 12 +- app/policies/patient_policy.rb | 4 +- app/policies/school_move_policy.rb | 23 +-- app/policies/vaccination_record_policy.rb | 11 +- app/views/sessions/consent/show.html.erb | 2 +- .../patient_specific_directions/show.html.erb | 2 +- app/views/sessions/patients/show.html.erb | 2 +- app/views/sessions/record/show.html.erb | 2 +- app/views/sessions/register/show.html.erb | 2 +- app/views/sessions/triage/show.html.erb | 2 +- ...e_patient_session_session_with_location.rb | 41 +++++ db/schema.rb | 11 +- db/seeds.rb | 17 ++- spec/factories/patient_locations.rb | 23 +-- spec/factories/patients.rb | 10 +- spec/factories/sessions.rb | 7 +- spec/features/cli_generate_consents_spec.rb | 14 +- ...port_child_pds_lookup_extravaganza_spec.rb | 9 +- spec/features/manage_children_spec.rb | 2 + ...eue_vaccinations_search_in_nhs_job_spec.rb | 5 +- .../patient_nhs_number_lookup_job_spec.rb | 9 +- ...anual_school_consent_reminders_job_spec.rb | 2 +- spec/lib/generate/vaccination_records_spec.rb | 2 + spec/lib/status_generator/session_spec.rb | 20 +-- spec/lib/status_updater_spec.rb | 4 +- spec/models/cohort_import_spec.rb | 12 +- spec/models/patient_location_spec.rb | 25 ++-- spec/models/patient_spec.rb | 53 +++---- spec/models/session_spec.rb | 7 +- spec/policies/school_move_policy_spec.rb | 34 +++++ 68 files changed, 500 insertions(+), 410 deletions(-) create mode 100644 db/migrate/20250909145402_replace_patient_session_session_with_location.rb create mode 100644 spec/policies/school_move_policy_spec.rb diff --git a/app/components/app_activity_log_component.rb b/app/components/app_activity_log_component.rb index 72b7b751b0..e0a914c2c0 100644 --- a/app/components/app_activity_log_component.rb +++ b/app/components/app_activity_log_component.rb @@ -66,9 +66,10 @@ def initialize(team:, patient:, session: nil) @patient_locations = @patient .patient_locations - .includes_programmes - .includes(session: :location) - .then { |scope| session ? scope.where(session:) : scope } + .includes(:location) + .then do |scope| + session ? scope.where(location: session.location) : scope + end @patient_specific_directions = @patient @@ -347,14 +348,10 @@ def pre_screening_events def session_events patient_locations.map do |patient_location| - patient = patient_location.patient - session = patient_location.session - [ { - title: "Added to the session at #{session.location.name}", - at: patient_location.created_at, - programmes: session.programmes_for(patient:) + title: "Added to the session at #{patient_location.location.name}", + at: patient_location.created_at } ] end diff --git a/app/components/app_patient_pds_discrepancy_table_component.rb b/app/components/app_patient_pds_discrepancy_table_component.rb index d8d9738d8e..247fb2d9a5 100644 --- a/app/components/app_patient_pds_discrepancy_table_component.rb +++ b/app/components/app_patient_pds_discrepancy_table_component.rb @@ -12,9 +12,7 @@ def initialize(discrepancies:, current_user:) delegate :format_nhs_number, :govuk_table, to: :helpers - def can_link_to?(record) - allowed_ids.include?(record.id) - end + def can_link_to?(record) = allowed_ids.include?(record.id) def allowed_ids @allowed_ids ||= PatientPolicy::Scope.new(current_user, Patient).resolve.ids diff --git a/app/components/app_patient_programmes_table_component.rb b/app/components/app_patient_programmes_table_component.rb index 6c011b5b98..e2e8ccf29d 100644 --- a/app/components/app_patient_programmes_table_component.rb +++ b/app/components/app_patient_programmes_table_component.rb @@ -258,8 +258,7 @@ def vaccination_records_for(programme:, academic_year: nil) end def eligible_year_groups_for(programme:) - location_ids = - patient.patient_locations.joins(:session).select(:location_id) + location_ids = patient.patient_locations.select(:location_id) LocationProgrammeYearGroup .where(location_id: location_ids) diff --git a/app/components/app_programme_session_table_component.rb b/app/components/app_programme_session_table_component.rb index ec0b252b23..8ecb7aa4cc 100644 --- a/app/components/app_programme_session_table_component.rb +++ b/app/components/app_programme_session_table_component.rb @@ -53,10 +53,7 @@ def vaccinated_percentage(session:) end def patient_locations(session:) - session - .patient_locations - .joins(:patient, :session) - .appear_in_programmes([programme]) + session.patient_locations.joins(:patient).appear_in_programmes([programme]) end def format_number(count) = count.to_s diff --git a/app/components/app_session_actions_component.rb b/app/components/app_session_actions_component.rb index 3af1f7eec2..c79a3d52cf 100644 --- a/app/components/app_session_actions_component.rb +++ b/app/components/app_session_actions_component.rb @@ -20,10 +20,7 @@ def render? = rows.any? delegate :academic_year, :programmes, to: :session def patient_locations - session - .patient_locations - .joins(:patient, :session) - .appear_in_programmes(programmes) + session.patient_locations.joins(:patient).appear_in_programmes(programmes) end def rows @@ -80,7 +77,7 @@ def register_attendance_row return nil unless session.requires_registration? && session.today? status = "unknown" - count = patient_locations.has_registration_status(status).count + count = patient_locations.has_registration_status(status, session:).count href = session_register_path(session, register_status: status) generate_row(:children_to_register, count:, href:) @@ -92,7 +89,7 @@ def ready_for_vaccinator_row counts_by_programme = session.programmes.index_with do |programme| patient_locations - .has_registration_status(%w[attending completed]) + .has_registration_status(%w[attending completed], session:) .includes( patient: %i[consent_statuses triage_statuses vaccination_statuses] ) diff --git a/app/components/app_session_details_summary_component.rb b/app/components/app_session_details_summary_component.rb index e776f9dbf4..f1f176667b 100644 --- a/app/components/app_session_details_summary_component.rb +++ b/app/components/app_session_details_summary_component.rb @@ -17,10 +17,7 @@ def call delegate :programmes, to: :session def patient_locations - session - .patient_locations - .joins(:patient, :session) - .appear_in_programmes(programmes) + session.patient_locations.joins(:patient).appear_in_programmes(programmes) end def cohort_row diff --git a/app/components/app_session_needs_review_warning_component.rb b/app/components/app_session_needs_review_warning_component.rb index 2b5066db8b..7aae02f179 100644 --- a/app/components/app_session_needs_review_warning_component.rb +++ b/app/components/app_session_needs_review_warning_component.rb @@ -45,7 +45,7 @@ def make_row_from_warning(warning) def patient_locations @session .patient_locations - .joins(:patient, :session) + .joins(:patient) .appear_in_programmes(@session.programmes) end end diff --git a/app/controllers/api/testing/teams_controller.rb b/app/controllers/api/testing/teams_controller.rb index 9e91ecc6c4..3041075d73 100644 --- a/app/controllers/api/testing/teams_controller.rb +++ b/app/controllers/api/testing/teams_controller.rb @@ -28,7 +28,9 @@ def destroy patient_ids = team.patients.pluck(:id) consent_form_ids = team.consent_forms.pluck(:id) - log_destroy(PatientLocation.where(session: sessions)) + log_destroy( + PatientLocation.where(location_id: sessions.select(:location_id)) + ) log_destroy(AccessLogEntry.where(patient_id: patient_ids)) log_destroy(ArchiveReason.where(patient_id: patient_ids)) diff --git a/app/controllers/consent_forms_controller.rb b/app/controllers/consent_forms_controller.rb index 85dbb28b3f..6f50912688 100644 --- a/app/controllers/consent_forms_controller.rb +++ b/app/controllers/consent_forms_controller.rb @@ -37,10 +37,11 @@ def update_match session = @patient - .pending_sessions + .sessions .includes(:location_programme_year_groups, :programmes) .has_programmes(@consent_form.programmes) - .first || @consent_form.original_session + .find_by(academic_year: AcademicYear.pending) || + @consent_form.original_session programme = session.programmes_for(patient: @patient).first @@ -136,7 +137,6 @@ def set_patient @patient = policy_scope(Patient).includes( parent_relationships: :parent, - pending_sessions: :programmes, vaccination_records: :programme ).find(params[:patient_id]) end diff --git a/app/controllers/imports/issues_controller.rb b/app/controllers/imports/issues_controller.rb index c768c964da..684ba85de6 100644 --- a/app/controllers/imports/issues_controller.rb +++ b/app/controllers/imports/issues_controller.rb @@ -40,7 +40,6 @@ def set_import_issues @patients = policy_scope(Patient).with_pending_changes.includes( :gp_practice, - :pending_sessions, :school, :school_moves ) diff --git a/app/controllers/patient_sessions/base_controller.rb b/app/controllers/patient_sessions/base_controller.rb index e247c8b6fd..35d4e04adb 100644 --- a/app/controllers/patient_sessions/base_controller.rb +++ b/app/controllers/patient_sessions/base_controller.rb @@ -36,7 +36,11 @@ def set_patient def set_patient_location @patient_location = - PatientLocation.find_by!(patient: @patient, session: @session) + PatientLocation.find_by!( + patient: @patient, + location: @session.location, + academic_year: @session.academic_year + ) end def set_programme diff --git a/app/controllers/patients_controller.rb b/app/controllers/patients_controller.rb index 850efd6827..41ace8abdd 100644 --- a/app/controllers/patients_controller.rb +++ b/app/controllers/patients_controller.rb @@ -26,10 +26,11 @@ def edit end def invite_to_clinic - session = - current_team.generic_clinic_session(academic_year: AcademicYear.pending) - - PatientLocation.find_or_create_by!(patient: @patient, session:) + PatientLocation.find_or_create_by!( + patient: @patient, + location: current_team.generic_clinic, + academic_year: AcademicYear.pending + ) redirect_to patient_path(@patient), flash: { @@ -46,15 +47,16 @@ def set_patient :school, consents: %i[parent patient], parent_relationships: :parent, - sessions: :location, vaccination_records: :programme ).find(params[:id]) end def set_in_generic_clinic - generic_clinic_session = - current_team.generic_clinic_session(academic_year: AcademicYear.pending) - @in_generic_clinic = @patient.sessions.include?(generic_clinic_session) + @in_generic_clinic = + @patient.patient_locations.exists?( + location: current_team.generic_clinic, + academic_year: AcademicYear.pending + ) end def record_access_log_entry diff --git a/app/controllers/programmes/base_controller.rb b/app/controllers/programmes/base_controller.rb index 0aacc493c4..0e73d2fcf8 100644 --- a/app/controllers/programmes/base_controller.rb +++ b/app/controllers/programmes/base_controller.rb @@ -24,8 +24,9 @@ def patient_ids @patient_ids ||= PatientLocation .distinct - .joins(:patient, :session) - .where(session_id: session_ids) + .joins(:patient) + .joins_sessions + .where("sessions.id IN (?)", session_ids) .appear_in_programmes([@programme]) .not_archived(team: current_team) .pluck(:patient_id) diff --git a/app/controllers/sessions/consent_controller.rb b/app/controllers/sessions/consent_controller.rb index 4dbf089c96..83fe3e1aad 100644 --- a/app/controllers/sessions/consent_controller.rb +++ b/app/controllers/sessions/consent_controller.rb @@ -28,7 +28,6 @@ def show scope = @session .patient_locations - .includes_programmes .includes(patient: [:consent_statuses, { notes: :created_by }]) .has_consent_status( statuses_except_not_required, diff --git a/app/controllers/sessions/patient_specific_directions_controller.rb b/app/controllers/sessions/patient_specific_directions_controller.rb index b6da1d0471..1347a71773 100644 --- a/app/controllers/sessions/patient_specific_directions_controller.rb +++ b/app/controllers/sessions/patient_specific_directions_controller.rb @@ -10,7 +10,7 @@ class Sessions::PatientSpecificDirectionsController < ApplicationController def show scope = - @session.patient_locations.includes_programmes.includes( + @session.patient_locations.includes( patient: { patient_specific_directions: :programme } diff --git a/app/controllers/sessions/patients_controller.rb b/app/controllers/sessions/patients_controller.rb index 538063ac00..3b41de97ef 100644 --- a/app/controllers/sessions/patients_controller.rb +++ b/app/controllers/sessions/patients_controller.rb @@ -12,7 +12,7 @@ def show @statuses = Patient::VaccinationStatus.statuses.keys scope = - @session.patient_locations.includes_programmes.includes( + @session.patient_locations.includes( patient: [:vaccination_statuses, { notes: :created_by }] ) diff --git a/app/controllers/sessions/record_controller.rb b/app/controllers/sessions/record_controller.rb index 3f99f7c5fd..263ca42d61 100644 --- a/app/controllers/sessions/record_controller.rb +++ b/app/controllers/sessions/record_controller.rb @@ -25,7 +25,11 @@ def show ) if @session.requires_registration? - scope = scope.has_registration_status(%w[attending completed]) + scope = + scope.has_registration_status( + %w[attending completed], + session: @session + ) end patient_locations = @@ -33,6 +37,7 @@ def show @form.apply(scope) ).consent_given_and_ready_to_vaccinate( programmes: @form.programmes, + academic_year: @session.academic_year, vaccine_method: @form.vaccine_method.presence ) diff --git a/app/controllers/sessions/register_controller.rb b/app/controllers/sessions/register_controller.rb index 1e473d006f..3c86885616 100644 --- a/app/controllers/sessions/register_controller.rb +++ b/app/controllers/sessions/register_controller.rb @@ -14,7 +14,7 @@ def show @statuses = Patient::RegistrationStatus.statuses.keys scope = - @session.patient_locations.includes_programmes.includes( + @session.patient_locations.includes( patient: [ :consent_statuses, :registration_statuses, diff --git a/app/controllers/sessions/triage_controller.rb b/app/controllers/sessions/triage_controller.rb index c37eb46705..959bc497a1 100644 --- a/app/controllers/sessions/triage_controller.rb +++ b/app/controllers/sessions/triage_controller.rb @@ -14,7 +14,6 @@ def show scope = @session .patient_locations - .includes_programmes .includes(patient: [:triage_statuses, { notes: :created_by }]) .has_triage_status(@statuses, programme: @form.programmes) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 92960ffbdb..81f0f532e0 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -18,10 +18,11 @@ def index @patient_count_by_session_id = PatientLocation - .where(session_id: sessions.map(&:id)) - .joins(:patient, :session) + .joins_sessions + .where("sessions.id IN (?)", sessions.pluck(:id)) + .joins(:patient) .appear_in_programmes(@programmes) - .group(:session_id) + .group("sessions.id") .count @pagy, @sessions = pagy_array(sessions) diff --git a/app/forms/patient_search_form.rb b/app/forms/patient_search_form.rb index 25a22b7301..b5ab4586dd 100644 --- a/app/forms/patient_search_form.rb +++ b/app/forms/patient_search_form.rb @@ -129,7 +129,7 @@ def filter_nhs_number(scope) def filter_programmes(scope) if programmes.present? if @session - scope.joins(:patient, :session).appear_in_programmes(programmes) + scope.joins(:patient).appear_in_programmes(programmes) else scope.appear_in_programmes(programmes, academic_year:) end @@ -218,7 +218,7 @@ def filter_patient_specific_direction_status(scope) def filter_register_status(scope) if (status = register_status&.to_sym).present? - scope.has_registration_status(status) + scope.has_registration_status(status, session: @session) else scope end diff --git a/app/jobs/concerns/send_school_consent_notification_concern.rb b/app/jobs/concerns/send_school_consent_notification_concern.rb index caaecf6584..c297e1985d 100644 --- a/app/jobs/concerns/send_school_consent_notification_concern.rb +++ b/app/jobs/concerns/send_school_consent_notification_concern.rb @@ -10,7 +10,6 @@ def patient_programmes_eligible_for_notification(session:) session .patient_locations - .includes_programmes .includes(patient: %i[consent_notifications consents vaccination_records]) .find_each do |patient_location| patient = patient_location.patient diff --git a/app/jobs/enqueue_vaccinations_search_in_nhs_job.rb b/app/jobs/enqueue_vaccinations_search_in_nhs_job.rb index 5fbba861fa..e52c3937d5 100644 --- a/app/jobs/enqueue_vaccinations_search_in_nhs_job.rb +++ b/app/jobs/enqueue_vaccinations_search_in_nhs_job.rb @@ -4,8 +4,10 @@ class EnqueueVaccinationsSearchInNHSJob < ApplicationJob queue_as :immunisations_api def perform(sessions = nil) - sessions ||= - begin + scope = + if sessions + Session.where(id: sessions.map(&:id)) + else flu = Programme.flu.sole Session .includes(:session_dates) @@ -15,10 +17,11 @@ def perform(sessions = nil) .references(:session_dates) end - patient_ids = PatientLocation.where(session: sessions).pluck(:patient_id) + scope.find_each do |session| + ids = session.patients.pluck(:id) + next if ids.empty? - return if patient_ids.empty? - - SearchVaccinationRecordsInNHSJob.perform_bulk(patient_ids.zip) + SearchVaccinationRecordsInNHSJob.perform_bulk(ids.zip) + end end end diff --git a/app/lib/clinic_patient_locations_factory.rb b/app/lib/clinic_patient_locations_factory.rb index e32f1ea434..ef81d452e9 100644 --- a/app/lib/clinic_patient_locations_factory.rb +++ b/app/lib/clinic_patient_locations_factory.rb @@ -21,9 +21,10 @@ def patient_locations_to_create programmes:, session_date: ) - PatientLocation.includes(:session_notifications).find_or_initialize_by( + PatientLocation.new( patient:, - session: generic_clinic_session + academic_year: generic_clinic_session.academic_year, + location: generic_clinic_session.location ) end end diff --git a/app/lib/generate/vaccination_records.rb b/app/lib/generate/vaccination_records.rb index bac14d456a..f806cd99ce 100644 --- a/app/lib/generate/vaccination_records.rb +++ b/app/lib/generate/vaccination_records.rb @@ -8,9 +8,7 @@ def initialize(team:, programme: nil, session: nil, administered: nil) @administered = administered end - def call - create_vaccinations - end + def call = create_vaccinations def self.call(...) = new(...).call @@ -22,34 +20,34 @@ def create_vaccinations attendance_records = [] vaccination_records = [] - random_patient_locations.each do |patient_location| - patient = patient_location.patient - session = patient_location.session + sessions.each do |session| + location = session.location + + random_patients_for(session:).each do |patient| + unless AttendanceRecord.exists?(patient:, location:) + attendance_records << FactoryBot.build( + :attendance_record, + :present, + patient:, + session: + ) + end + + location_name = location.name if session.clinic? - unless AttendanceRecord.exists?(patient:, location: session.location) - attendance_records << FactoryBot.build( - :attendance_record, - :present, + vaccination_records << FactoryBot.build( + :vaccination_record, + :administered, patient:, - session: + programme:, + team:, + performed_by:, + session:, + vaccine:, + batch:, + location_name: ) end - - location_name = - patient_location.location.name if patient_location.session.clinic? - - vaccination_records << FactoryBot.build( - :vaccination_record, - :administered, - patient: patient_location.patient, - programme:, - team:, - performed_by:, - session: patient_location.session, - vaccine:, - batch:, - location_name: - ) end AttendanceRecord.import!(attendance_records) @@ -58,9 +56,9 @@ def create_vaccinations StatusUpdater.call(patient: vaccination_records.map(&:patient)) end - def random_patient_locations + def random_patients_for(session:) if administered&.positive? - patient_locations + patients_for(session:) .sample(administered) .tap do |selected| if selected.size < administered @@ -70,27 +68,27 @@ def random_patient_locations end end else - patient_locations + patients_for(session:) end end - def patient_locations - (session.presence || team) - .patient_locations - .joins(:patient) - .includes( - :session, - :location, - session: :session_dates, - patient: %i[consent_statuses vaccination_statuses triage_statuses] - ) - .appear_in_programmes([programme]) - .has_consent_status("given", programme:) + def sessions + ( + @sessions ||= + session ? [session] : team.sessions.includes(:location, :session_dates) + ) + end + + def patients_for(session:) + academic_year = session.academic_year + + session + .patients + .includes(:consent_statuses, :vaccination_statuses, :triage_statuses) + .appear_in_programmes([programme], academic_year:) + .has_consent_status("given", programme:, academic_year:) .select do - it.patient.consent_given_and_safe_to_vaccinate?( - programme:, - academic_year: it.session.academic_year - ) + it.consent_given_and_safe_to_vaccinate?(programme:, academic_year:) end end diff --git a/app/lib/location_sessions_factory.rb b/app/lib/location_sessions_factory.rb index 97210c92c0..483a17d573 100644 --- a/app/lib/location_sessions_factory.rb +++ b/app/lib/location_sessions_factory.rb @@ -9,17 +9,16 @@ def initialize(location, academic_year:) def call ActiveRecord::Base.transaction do if location.generic_clinic? - add_patients!( - session: find_or_create_session!(programmes: location.programmes) - ) + find_or_create_session!(programmes: location.programmes) else ProgrammeGrouper .call(location.programmes) .values .reject { |programmes| already_exists?(programmes:) } .map { |programmes| create_session!(programmes:) } - .each { |session| add_patients!(session:) } end + + add_patients! end end @@ -55,10 +54,10 @@ def find_or_create_session!(programmes:) end end - def add_patients!(session:) + def add_patients! PatientLocation.import!( - %i[patient_id session_id], - patient_ids.map { [it, session.id] }, + %i[patient_id location_id academic_year], + patient_ids.map { [it, location.id, academic_year] }, on_duplicate_key_ignore: true ) end diff --git a/app/lib/mavis_cli/schools/move_patients.rb b/app/lib/mavis_cli/schools/move_patients.rb index 43312fdda4..c8289cb5e4 100644 --- a/app/lib/mavis_cli/schools/move_patients.rb +++ b/app/lib/mavis_cli/schools/move_patients.rb @@ -11,6 +11,8 @@ class MovePatients < Dry::CLI::Command def call(source_urn:, target_urn:) MavisCLI.load_rails + academic_year = AcademicYear.pending + old_loc = Location.school.find_by_urn_and_site(source_urn) new_loc = Location.school.find_by_urn_and_site(target_urn) @@ -20,14 +22,13 @@ def call(source_urn:, target_urn:) end unless PatientLocation - .joins(:patient, :session) + .joins(:patient) .where( patient: { school: old_loc }, - session: { - location: old_loc - } + academic_year:, + location: old_loc ) .all?(&:safe_to_destroy?) raise "Some patient sessions at #{old_loc.urn} are not safe to destroy. Cannot complete transfer." @@ -42,6 +43,10 @@ def call(source_urn:, target_urn:) location_id: new_loc.id ) Patient.where(school_id: old_loc.id).update_all(school_id: new_loc.id) + PatientLocation.where( + academic_year:, + location_id: old_loc.id + ).update_all(location_id: new_loc.id) ConsentForm.where(location_id: old_loc.id).update_all( location_id: new_loc.id ) diff --git a/app/lib/patient_merger.rb b/app/lib/patient_merger.rb index ae96097a5a..f8b21acf26 100644 --- a/app/lib/patient_merger.rb +++ b/app/lib/patient_merger.rb @@ -102,7 +102,8 @@ def call patient_to_destroy.patient_locations.each do |patient_location| if patient_to_keep.patient_locations.exists?( - session_id: patient_location.session_id + academic_year: patient_location.academic_year, + location_id: patient_location.location_id ) next end diff --git a/app/lib/stats/organisations.rb b/app/lib/stats/organisations.rb index df907af5d9..da08fbb8a6 100644 --- a/app/lib/stats/organisations.rb +++ b/app/lib/stats/organisations.rb @@ -20,7 +20,11 @@ def self.call(...) = new(...).call attr_reader :organisation, :teams, :programmes, :academic_year, :patients def build_patients_scope - Patient.joins(:teams).where(teams: { id: teams.pluck(:id) }).distinct + Patient.distinct.joins_sessions.where( + sessions: { + team_id: teams.map(&:id) + } + ) end def calculate_organisation_stats diff --git a/app/lib/status_updater.rb b/app/lib/status_updater.rb index b85c0625dd..6e33e4cc78 100644 --- a/app/lib/status_updater.rb +++ b/app/lib/status_updater.rb @@ -2,10 +2,15 @@ class StatusUpdater def initialize(patient: nil, session: nil) - scope = PatientLocation + scope = PatientLocation.joins_sessions scope = scope.where(patient:) if patient - scope = scope.where(session:) if session + + if session.is_a?(Session) + scope = scope.where(sessions: { id: session.id }) + elsif session + scope = scope.where(sessions: { id: session.pluck(:id) }) + end @patient_locations = scope end @@ -57,8 +62,8 @@ def update_registration_statuses! Patient::RegistrationStatus .where( - patient: patient_locations.select(:patient_id), - session: patient_locations.select(:session_id) + "(patient_id, session_id) IN (?)", + patient_locations.select("patient_id", "sessions.id") ) .includes( :patient, @@ -166,13 +171,13 @@ def vaccination_statuses_to_import def patient_location_statuses_to_import patient_locations - .joins(:patient, :session) + .joins(:patient) .pluck( - :"patients.id", - :"sessions.id", - :"sessions.location_id", - :"sessions.academic_year", - :"patients.birth_academic_year" + "patients.id", + "sessions.id", + "sessions.location_id", + "sessions.academic_year", + "patients.birth_academic_year" ) .filter_map do |patient_id, session_id, location_id, academic_year, birth_academic_year| year_group = birth_academic_year.to_year_group(academic_year:) diff --git a/app/lib/team_sessions_factory.rb b/app/lib/team_sessions_factory.rb index 9b5d0e33b6..d65a4dc034 100644 --- a/app/lib/team_sessions_factory.rb +++ b/app/lib/team_sessions_factory.rb @@ -36,9 +36,7 @@ def destroy_orphaned_sessions! .unscheduled .where(academic_year:) .where.not(location: team.locations) - .where - .missing(:patient_locations) - .destroy_all + .find_each { |session| session.destroy! if session.patients.empty? } end end end diff --git a/app/models/class_import.rb b/app/models/class_import.rb index 834fb3adfb..8fe3a99613 100644 --- a/app/models/class_import.rb +++ b/app/models/class_import.rb @@ -57,9 +57,9 @@ def postprocess_rows! existing_patients = Patient.where(birth_academic_year: birth_academic_years).where( PatientLocation - .joins(session: :location) + .joins(:location) .where("patient_id = patients.id") - .where(session: { academic_year:, location: }) + .where(academic_year:, location:) .arel .exists ) diff --git a/app/models/immunisation_import_row.rb b/app/models/immunisation_import_row.rb index 6a3e902cd7..b1ad69ee23 100644 --- a/app/models/immunisation_import_row.rb +++ b/app/models/immunisation_import_row.rb @@ -157,7 +157,9 @@ def to_vaccination_record end def to_patient_location - PatientLocation.new(patient:, session:) if patient && session + if patient && session + PatientLocation.new(patient:, location: session.location, academic_year:) + end end def batch_expiry = @data[:batch_expiry_date] diff --git a/app/models/location.rb b/app/models/location.rb index 7b5f08207e..8abe5bdd2e 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -56,6 +56,7 @@ class Location < ApplicationRecord has_many :attendance_records has_many :consent_forms has_many :location_programme_year_groups + has_many :patient_locations has_many :patients, foreign_key: :school_id has_many :sessions diff --git a/app/models/patient.rb b/app/models/patient.rb index 35db2e22c4..1e18884e7e 100644 --- a/app/models/patient.rb +++ b/app/models/patient.rb @@ -84,15 +84,8 @@ class Patient < ApplicationRecord has_many :vaccination_statuses has_many :patient_specific_directions + has_many :locations, through: :patient_locations has_many :parents, through: :parent_relationships - has_many :patient_specific_directions - has_many :sessions, through: :patient_locations - has_many :teams, -> { distinct }, through: :sessions - - has_many :pending_sessions, - -> { where(academic_year: AcademicYear.pending) }, - through: :patient_locations, - source: :session has_and_belongs_to_many :class_imports has_and_belongs_to_many :cohort_imports @@ -104,12 +97,18 @@ class Patient < ApplicationRecord scope :joins_archive_reasons, ->(team:) do joins( - "LEFT JOIN archive_reasons " \ + "LEFT OUTER JOIN archive_reasons " \ "ON archive_reasons.patient_id = patients.id " \ "AND archive_reasons.team_id = #{team.id}" ) end + scope :joins_sessions, -> { joins(:patient_locations).joins(<<-SQL) } + INNER JOIN sessions + ON sessions.location_id = patient_locations.location_id + AND sessions.academic_year = patient_locations.academic_year + SQL + scope :archived, ->(team:) do joins_archive_reasons(team:).where("archive_reasons.id IS NOT NULL") @@ -143,8 +142,7 @@ class Patient < ApplicationRecord ->(programmes, academic_year:) do where( PatientLocation - .joins(:session) - .where(sessions: { academic_year: }) + .where(academic_year:) .where("patient_id = patients.id") .appear_in_programmes(programmes) .arel @@ -156,8 +154,7 @@ class Patient < ApplicationRecord ->(programmes, academic_year:) do where.not( PatientLocation - .joins(:session) - .where(sessions: { academic_year: }) + .where(academic_year:) .where("patient_id = patients.id") .appear_in_programmes(programmes) .arel @@ -277,6 +274,10 @@ class Patient < ApplicationRecord delegate :fhir_record, to: :fhir_mapper + def sessions + Session.joins_patient_locations.where(patient_locations: { patient_id: id }) + end + def self.match_existing( nhs_number:, given_name:, @@ -346,6 +347,15 @@ def self.match_existing( results end + def teams + Team.left_outer_joins(:sessions).joins(<<-SQL) + INNER JOIN patient_locations + ON patient_locations.patient_id = #{id} + AND patient_locations.location_id = sessions.location_id + AND patient_locations.academic_year = sessions.academic_year + SQL + end + def archived?(team:) archive_reasons.exists?(team:) end @@ -504,8 +514,8 @@ def invalidate! def not_in_team?(team:, academic_year:) patient_locations - .joins(:session) - .where(session: { academic_year:, team: }) + .joins(location: :subteam) + .where(academic_year:, subteams: { team_id: team.id }) .empty? end @@ -513,8 +523,10 @@ def dup_for_pending_changes dup.tap do |new_patient| new_patient.nhs_number = nil - pending_sessions.each do |session| - new_patient.patient_locations.build(session:) + patient_locations.pending.find_each do |patient_location| + new_patient.patient_locations.build( + **patient_location.slice(:academic_year, :location_id) + ) end school_moves.each do |school_move| @@ -530,11 +542,13 @@ def dup_for_pending_changes end def clear_pending_sessions!(team: nil) - sessions = pending_sessions + scope = patient_locations.pending - sessions = sessions.where(team_id: team.id) unless team.nil? + unless team.nil? + scope = scope.joins_sessions.where("sessions.team_id = ?", team.id) + end - patient_locations.where(session: sessions).destroy_all_if_safe + scope.destroy_all_if_safe end def self.from_consent_form(consent_form) diff --git a/app/models/patient_location.rb b/app/models/patient_location.rb index 8480a142dc..ab6b36b8c1 100644 --- a/app/models/patient_location.rb +++ b/app/models/patient_location.rb @@ -4,80 +4,81 @@ # # Table name: patient_locations # -# id :bigint not null, primary key -# created_at :datetime not null -# updated_at :datetime not null -# patient_id :bigint not null -# session_id :bigint not null +# id :bigint not null, primary key +# academic_year :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# location_id :bigint not null +# patient_id :bigint not null # # Indexes # -# index_patient_locations_on_patient_id_and_session_id (patient_id,session_id) UNIQUE -# index_patient_locations_on_session_id (session_id) +# idx_on_patient_id_location_id_academic_year_08a1dc4afe (patient_id,location_id,academic_year) UNIQUE +# index_patient_locations_on_location_id (location_id) +# index_patient_locations_on_location_id_and_academic_year (location_id,academic_year) # # Foreign Keys # +# fk_rails_... (location_id => locations.id) # fk_rails_... (patient_id => patients.id) -# fk_rails_... (session_id => sessions.id) # -# The patient session model represents a patient who goes to the school or -# clinic location represented by the session. This doesn't necessarily mean -# that the patient is eligible for any of the programmes administered in any -# particular session they belong to. -# -# This is designed to support programmes being dynamically added or removed to -# sessions without needing to also create or destroy patient session -# instances. While also adding support for patients becoming eligible if year -# groups are added or removed to locations, again without needing to also -# create or destroy patient session instances. -# -# It also supports the scenario where a patient belongs to a session but is -# only eligible for one of many programmes administered in the session. In -# that case, the list of programmes they appear in won't be the same as the -# complete list of programmes administered in the session. - class PatientLocation < ApplicationRecord audited associated_with: :patient has_associated_audits belongs_to :patient - belongs_to :session + belongs_to :location - has_one :location, through: :session - has_one :subteam, through: :session - has_one :team, through: :session - has_one :organisation, through: :team + has_many :sessions, + -> { where(academic_year: it.academic_year) }, + through: :location, + class_name: "Session" - has_many :gillick_assessments, + has_one :organisation, through: :location + has_one :subteam, through: :location + has_one :team, through: :location + + has_many :attendance_records, -> { where(patient_id: it.patient_id) }, - through: :session + through: :location - has_many :pre_screenings, + has_many :gillick_assessments, -> { where(patient_id: it.patient_id) }, - through: :session + through: :sessions - has_many :attendance_records, + has_many :pre_screenings, -> { where(patient_id: it.patient_id) }, - through: :location + through: :sessions has_many :vaccination_records, - -> { where(session_id: it.session_id) }, - through: :patient + -> { where(patient_id: it.patient_id) }, + through: :sessions has_and_belongs_to_many :immunisation_imports + scope :current, -> { where(academic_year: AcademicYear.current) } + scope :pending, -> { where(academic_year: AcademicYear.pending) } + scope :archived, ->(team:) { merge(Patient.archived(team:)) } scope :not_archived, ->(team:) { merge(Patient.not_archived(team:)) } + scope :joins_sessions, -> { joins(<<-SQL) } + INNER JOIN sessions + ON sessions.location_id = patient_locations.location_id + AND sessions.academic_year = patient_locations.academic_year + SQL + scope :appear_in_programmes, ->(programmes) do # Are any of the programmes administered in the session? programme_in_session = SessionProgramme + .joins(:session) .where(programme: programmes) - .where("session_programmes.session_id = sessions.id") + .where("sessions.location_id = patient_locations.location_id") + .where("sessions.academic_year = patient_locations.academic_year") .arel .exists @@ -85,9 +86,11 @@ class PatientLocation < ApplicationRecord patient_in_administered_year_groups = LocationProgrammeYearGroup .where(programme: programmes) - .where("location_id = sessions.location_id") .where( - "year_group = sessions.academic_year " \ + "location_programme_year_groups.location_id = patient_locations.location_id" + ) + .where( + "year_group = patient_locations.academic_year " \ "- patients.birth_academic_year " \ "- #{Integer::AGE_CHILDREN_START_SCHOOL}" ) @@ -131,20 +134,12 @@ class PatientLocation < ApplicationRecord ) end - scope :includes_programmes, - -> do - preload( - :patient, - session: %i[programmes location_programme_year_groups] - ) - end - scope :has_consent_status, ->(status, programme:, vaccine_method: nil) do consent_status_scope = Patient::ConsentStatus .where("patient_id = patient_locations.patient_id") - .where("academic_year = sessions.academic_year") + .where("academic_year = patient_locations.academic_year") .where(status:, programme:) if vaccine_method @@ -152,16 +147,15 @@ class PatientLocation < ApplicationRecord consent_status_scope.has_vaccine_method(vaccine_method) end - joins(:session).where(consent_status_scope.arel.exists) + where(consent_status_scope.arel.exists) end scope :has_registration_status, - ->(status) do + ->(status, session:) do where( Patient::RegistrationStatus .where("patient_id = patient_locations.patient_id") - .where("session_id = patient_locations.session_id") - .where(status:) + .where(session:, status:) .arel .exists ) @@ -169,10 +163,10 @@ class PatientLocation < ApplicationRecord scope :has_triage_status, ->(status, programme:) do - joins(:session).where( + where( Patient::TriageStatus .where("patient_id = patient_locations.patient_id") - .where("academic_year = sessions.academic_year") + .where("academic_year = patient_locations.academic_year") .where(status:, programme:) .arel .exists @@ -181,10 +175,10 @@ class PatientLocation < ApplicationRecord scope :has_vaccination_status, ->(status, programme:) do - joins(:session).where( + where( Patient::VaccinationStatus .where("patient_id = patient_locations.patient_id") - .where("academic_year = sessions.academic_year") + .where("academic_year = patient_locations.academic_year") .where(status:, programme:) .arel .exists @@ -193,25 +187,25 @@ class PatientLocation < ApplicationRecord scope :has_vaccine_method, ->(vaccine_method, programme:) do - joins(:session).where( + where( Patient::TriageStatus .where("patient_id = patient_locations.patient_id") - .where("academic_year = sessions.academic_year") + .where("academic_year = patient_locations.academic_year") .where(vaccine_method:, programme:) .arel .exists ).or( - joins(:session).where( + where( Patient::TriageStatus .where("patient_id = patient_locations.patient_id") - .where("academic_year = sessions.academic_year") + .where("academic_year = patient_locations.academic_year") .where(status: "not_required", programme:) .arel .exists ).where( Patient::ConsentStatus .where("patient_id = patient_locations.patient_id") - .where("academic_year = sessions.academic_year") + .where("academic_year = patient_locations.academic_year") .where(programme:) .has_vaccine_method(vaccine_method) .arel @@ -221,15 +215,14 @@ class PatientLocation < ApplicationRecord end scope :consent_given_and_ready_to_vaccinate, - ->(programmes:, vaccine_method:) do + ->(programmes:, academic_year:, vaccine_method:) do select do |patient_location| patient = patient_location.patient - session = patient_location.session programmes.any? do |programme| patient.consent_given_and_safe_to_vaccinate?( programme:, - academic_year: session.academic_year, + academic_year:, vaccine_method: ) end @@ -238,10 +231,10 @@ class PatientLocation < ApplicationRecord scope :without_patient_specific_direction, ->(programme:, team:) do - joins(:session).where.not( + where.not( PatientSpecificDirection .where("patient_id = patient_locations.patient_id") - .where("academic_year = sessions.academic_year") + .where("academic_year = patient_locations.academic_year") .where(programme:, team:) .not_invalidated .arel @@ -251,10 +244,10 @@ class PatientLocation < ApplicationRecord scope :has_patient_specific_direction, ->(programme:, team:) do - joins(:session).where( + where( PatientSpecificDirection .where("patient_id = patient_locations.patient_id") - .where("academic_year = sessions.academic_year") + .where("academic_year = patient_locations.academic_year") .where(programme:, team:) .not_invalidated .arel @@ -265,17 +258,16 @@ class PatientLocation < ApplicationRecord scope :destroy_all_if_safe, -> do includes( - :gillick_assessments, :attendance_records, + :gillick_assessments, + :pre_screenings, :vaccination_records ).find_each(&:destroy_if_safe!) end - delegate :academic_year, to: :session - def safe_to_destroy? - vaccination_records.empty? && gillick_assessments.empty? && - attendance_records.none?(&:attending?) + attendance_records.none?(&:attending?) && gillick_assessments.empty? && + pre_screenings.empty? && vaccination_records.empty? end def destroy_if_safe! diff --git a/app/models/school_move.rb b/app/models/school_move.rb index 179dfbee88..1461f6e424 100644 --- a/app/models/school_move.rb +++ b/app/models/school_move.rb @@ -99,32 +99,14 @@ def update_archive_reasons!(user:) def update_sessions! patient .patient_locations - .joins(:session) .where("academic_year >= ?", academic_year) .destroy_all_if_safe - sessions_to_add.find_each do |session| - PatientLocation.find_or_create_by!(patient:, session:) - end + location = school || team.generic_clinic - StatusUpdater.call(patient:) - end + PatientLocation.find_or_create_by!(patient:, location:, academic_year:) - def sessions_to_add - @sessions_to_add ||= - begin - scope = - Session.includes(:location, :session_dates).where( - "academic_year >= ?", - academic_year - ) - - if school - scope.where(team: school.team, location: school) - else - scope.where(team:, locations: { type: "generic_clinic" }) - end - end + StatusUpdater.call(patient:) end def create_log_entry!(user:) diff --git a/app/models/session.rb b/app/models/session.rb index a9a25c319b..28253dce5d 100644 --- a/app/models/session.rb +++ b/app/models/session.rb @@ -20,9 +20,10 @@ # # Indexes # -# index_sessions_on_location_id (location_id) -# index_sessions_on_team_id_and_academic_year (team_id,academic_year) -# index_sessions_on_team_id_and_location_id (team_id,location_id) +# index_sessions_on_location_id (location_id) +# index_sessions_on_location_id_and_academic_year_and_team_id (location_id,academic_year,team_id) +# index_sessions_on_team_id_and_academic_year (team_id,academic_year) +# index_sessions_on_team_id_and_location_id (team_id,location_id) # # Foreign Keys # @@ -39,7 +40,6 @@ class Session < ApplicationRecord has_many :consent_notifications has_many :notes - has_many :patient_locations has_many :session_dates, -> { order(:value) } has_many :session_notifications has_many :session_programmes, @@ -49,6 +49,10 @@ class Session < ApplicationRecord has_and_belongs_to_many :immunisation_imports + has_many :patient_locations, + -> { where(academic_year: it.academic_year) }, + through: :location + has_one :organisation, through: :team has_one :subteam, through: :location has_many :pre_screenings, through: :session_dates @@ -63,6 +67,12 @@ class Session < ApplicationRecord accepts_nested_attributes_for :session_dates, allow_destroy: true + scope :joins_patient_locations, -> { joins(<<-SQL) } + INNER JOIN patient_locations + ON patient_locations.location_id = sessions.location_id + AND patient_locations.academic_year = sessions.academic_year + SQL + scope :has_date, ->(value) { where(SessionDate.for_session.where(value:).arel.exists) } @@ -174,17 +184,11 @@ class Session < ApplicationRecord delegate :clinic?, :generic_clinic?, :school?, to: :location - def to_param - slug - end + def to_param = slug - def today? - dates.any?(&:today?) - end + def today? = dates.any?(&:today?) - def unscheduled? - dates.empty? - end + def unscheduled? = dates.empty? def completed? return false if dates.empty? diff --git a/app/models/team.rb b/app/models/team.rb index c58316cf98..3e67de8a6d 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -52,8 +52,6 @@ class Team < ApplicationRecord has_many :community_clinics, through: :subteams has_many :locations, through: :subteams - has_many :patient_locations, through: :sessions - has_many :patients, -> { distinct }, through: :patient_locations has_many :programmes, through: :team_programmes has_many :schools, through: :subteams has_many :vaccination_records, through: :sessions @@ -76,13 +74,17 @@ class Team < ApplicationRecord validates :privacy_policy_url, presence: true validates :workgroup, presence: true, uniqueness: true + def patients + Patient.joins_sessions.where(sessions: { team_id: id }) + end + def year_groups @year_groups ||= location_programme_year_groups.pluck_year_groups end - def generic_clinic_session(academic_year:) - location = locations.generic_clinic.first + def generic_clinic = locations.generic_clinic.first + def generic_clinic_session(academic_year:) sessions .includes( :location, @@ -91,7 +93,7 @@ def generic_clinic_session(academic_year:) :session_dates ) .create_with(programmes:) - .find_or_create_by!(academic_year:, location:) + .find_or_create_by!(academic_year:, location: generic_clinic) end def weeks_before_consent_reminders diff --git a/app/policies/patient_policy.rb b/app/policies/patient_policy.rb index 0c84d437f9..89263f38c4 100644 --- a/app/policies/patient_policy.rb +++ b/app/policies/patient_policy.rb @@ -11,9 +11,9 @@ def resolve existence_criteria = [ PatientLocation .select("1") - .joins(:session) + .joins_sessions .where("patient_locations.patient_id = patients.id") - .where(sessions: { team_id: team.id }) + .where("sessions.team_id = ?", team.id) .arel, ArchiveReason .select("1") diff --git a/app/policies/school_move_policy.rb b/app/policies/school_move_policy.rb index 078f26f3c9..7bded900f2 100644 --- a/app/policies/school_move_policy.rb +++ b/app/policies/school_move_policy.rb @@ -6,23 +6,16 @@ def resolve team = user.selected_team return scope.none if team.nil? - patient_subquery = - Patient - .joins(patient_locations: :session) - .select(:id) - .distinct - .where(sessions: { team_id: team.id }) + patient_in_team = + team + .patients + .select("1") + .where("patients.id = school_moves.patient_id") .arel - .as("patients") + .exists + scope - .joins( - SchoolMove - .arel_table - .join(patient_subquery, Arel::Nodes::OuterJoin) - .on(SchoolMove.arel_table[:patient_id].eq(patient_subquery[:id])) - .join_sources - ) - .where(patient_subquery[:id].not_eq(nil)) + .where(patient_in_team) .or(scope.where(school: team.schools)) .or(scope.where(team:)) end diff --git a/app/policies/vaccination_record_policy.rb b/app/policies/vaccination_record_policy.rb index f8556cfe2f..a4d19d4764 100644 --- a/app/policies/vaccination_record_policy.rb +++ b/app/policies/vaccination_record_policy.rb @@ -67,17 +67,16 @@ def resolve team = user.selected_team return scope.none if team.nil? - relevant_patients = - Patient + patient_in_team = + team + .patients .select("1") - .joins(patient_locations: :session) .where("patients.id = vaccination_records.patient_id") - .where(sessions: { team_id: team.id }) .arel - + .exists scope .kept - .where(relevant_patients.exists) + .where(patient_in_team) .or(scope.kept.where(session: team.sessions)) .or( scope.kept.where( diff --git a/app/views/sessions/consent/show.html.erb b/app/views/sessions/consent/show.html.erb index b3ca8aa8e1..f7a8a9ff02 100644 --- a/app/views/sessions/consent/show.html.erb +++ b/app/views/sessions/consent/show.html.erb @@ -25,7 +25,7 @@ <% @patient_locations.each do |patient_location| %> <%= render AppPatientSessionSearchResultCardComponent.new( patient: patient_location.patient, - session: patient_location.session, + session: @session, context: :consent, programmes: @form.programmes, ) %> diff --git a/app/views/sessions/patient_specific_directions/show.html.erb b/app/views/sessions/patient_specific_directions/show.html.erb index 4aa9d0b076..a297d0e3fc 100644 --- a/app/views/sessions/patient_specific_directions/show.html.erb +++ b/app/views/sessions/patient_specific_directions/show.html.erb @@ -41,7 +41,7 @@ <% @patient_locations.each do |patient_location| %> <%= render AppPatientSessionSearchResultCardComponent.new( patient: patient_location.patient, - session: patient_location.session, + session: @session, context: :patient_specific_direction, ) %> <% end %> diff --git a/app/views/sessions/patients/show.html.erb b/app/views/sessions/patients/show.html.erb index d4ea5a8005..3259ca93f0 100644 --- a/app/views/sessions/patients/show.html.erb +++ b/app/views/sessions/patients/show.html.erb @@ -25,7 +25,7 @@ <% @patient_locations.each do |patient_location| %> <%= render AppPatientSessionSearchResultCardComponent.new( patient: patient_location.patient, - session: patient_location.session, + session: @session, context: :patients, programmes: @form.programmes, ) %> diff --git a/app/views/sessions/record/show.html.erb b/app/views/sessions/record/show.html.erb index ff95b77896..3899d686c7 100644 --- a/app/views/sessions/record/show.html.erb +++ b/app/views/sessions/record/show.html.erb @@ -27,7 +27,7 @@ <% @patient_locations.each do |patient_location| %> <%= render AppPatientSessionSearchResultCardComponent.new( patient: patient_location.patient, - session: patient_location.session, + session: @session, context: :record, programmes: @form.programmes, ) %> diff --git a/app/views/sessions/register/show.html.erb b/app/views/sessions/register/show.html.erb index ad98be5335..9e8a087409 100644 --- a/app/views/sessions/register/show.html.erb +++ b/app/views/sessions/register/show.html.erb @@ -27,7 +27,7 @@ <% @patient_locations.each do |patient_location| %> <%= render AppPatientSessionSearchResultCardComponent.new( patient: patient_location.patient, - session: patient_location.session, + session: @session, context: :register, programmes: @form.programmes, ) %> diff --git a/app/views/sessions/triage/show.html.erb b/app/views/sessions/triage/show.html.erb index a88df021fa..3e56b5514a 100644 --- a/app/views/sessions/triage/show.html.erb +++ b/app/views/sessions/triage/show.html.erb @@ -25,7 +25,7 @@ <% @patient_locations.each do |patient_location| %> <%= render AppPatientSessionSearchResultCardComponent.new( patient: patient_location.patient, - session: patient_location.session, + session: @session, context: :triage, programmes: @form.programmes, ) %> diff --git a/db/migrate/20250909145402_replace_patient_session_session_with_location.rb b/db/migrate/20250909145402_replace_patient_session_session_with_location.rb new file mode 100644 index 0000000000..456e42fae2 --- /dev/null +++ b/db/migrate/20250909145402_replace_patient_session_session_with_location.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +class ReplacePatientSessionSessionWithLocation < ActiveRecord::Migration[8.0] + def up + change_table :patient_locations, bulk: true do |t| + t.integer :academic_year + t.references :location, foreign_key: true + end + + execute <<-SQL + UPDATE patient_locations + SET academic_year = sessions.academic_year, location_id = sessions.location_id + FROM sessions + WHERE patient_locations.session_id = sessions.id + SQL + + change_table :patient_locations, bulk: true do |t| + t.change_null :academic_year, false + t.change_null :location_id, false + end + + remove_column :patient_locations, :session_id + + execute <<-SQL + DELETE FROM patient_locations a + USING patient_locations b + WHERE a.id > b.id + AND a.patient_id = b.patient_id + AND a.location_id = b.location_id + AND a.academic_year = b.academic_year + SQL + + add_index :patient_locations, %i[location_id academic_year] + + add_index :patient_locations, + %i[patient_id location_id academic_year], + unique: true + + add_index :sessions, %i[location_id academic_year team_id] + end +end diff --git a/db/schema.rb b/db/schema.rb index 9a601887d7..bbc4396eff 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -537,12 +537,14 @@ end create_table "patient_locations", force: :cascade do |t| - t.bigint "session_id", null: false t.bigint "patient_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["patient_id", "session_id"], name: "index_patient_locations_on_patient_id_and_session_id", unique: true - t.index ["session_id"], name: "index_patient_locations_on_session_id" + t.integer "academic_year", null: false + t.bigint "location_id", null: false + t.index ["location_id", "academic_year"], name: "index_patient_locations_on_location_id_and_academic_year" + t.index ["location_id"], name: "index_patient_locations_on_location_id" + t.index ["patient_id", "location_id", "academic_year"], name: "idx_on_patient_id_location_id_academic_year_08a1dc4afe", unique: true end create_table "patient_registration_statuses", force: :cascade do |t| @@ -796,6 +798,7 @@ t.boolean "requires_registration", default: true, null: false t.boolean "psd_enabled", default: false, null: false t.boolean "national_protocol_enabled", default: false, null: false + t.index ["location_id", "academic_year", "team_id"], name: "index_sessions_on_location_id_and_academic_year_and_team_id" t.index ["location_id"], name: "index_sessions_on_location_id" t.index ["team_id", "academic_year"], name: "index_sessions_on_team_id_and_academic_year" t.index ["team_id", "location_id"], name: "index_sessions_on_team_id_and_location_id" @@ -1037,8 +1040,8 @@ add_foreign_key "patient_changesets", "patients" add_foreign_key "patient_consent_statuses", "patients", on_delete: :cascade add_foreign_key "patient_consent_statuses", "programmes" + add_foreign_key "patient_locations", "locations" add_foreign_key "patient_locations", "patients" - add_foreign_key "patient_locations", "sessions" add_foreign_key "patient_registration_statuses", "patients", on_delete: :cascade add_foreign_key "patient_registration_statuses", "sessions", on_delete: :cascade add_foreign_key "patient_specific_directions", "patients" diff --git a/db/seeds.rb b/db/seeds.rb index d5c481705b..d1837659dc 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -167,13 +167,20 @@ def setup_clinic(team) clinic_session.update!(send_invitations_at: Date.current - 3.weeks) - # All patients belong to the community clinic. This is normally - # handled by school moves, but here we need to do it manually. + # All unknown school or home-schooled patients belong to the community clinic. + # This is normally handled by school moves, but here we need to do it manually. PatientLocation.import( - team.patients.map do - PatientLocation.new(patient: it, session: clinic_session) - end, + team + .patients + .where(school: nil) + .map do + PatientLocation.new( + patient: it, + location: clinic_session.location, + academic_year: clinic_session.academic_year + ) + end, on_duplicate_key_ignore: :all ) end diff --git a/spec/factories/patient_locations.rb b/spec/factories/patient_locations.rb index b93048c674..60a091798e 100644 --- a/spec/factories/patient_locations.rb +++ b/spec/factories/patient_locations.rb @@ -4,27 +4,30 @@ # # Table name: patient_locations # -# id :bigint not null, primary key -# created_at :datetime not null -# updated_at :datetime not null -# patient_id :bigint not null -# session_id :bigint not null +# id :bigint not null, primary key +# academic_year :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# location_id :bigint not null +# patient_id :bigint not null # # Indexes # -# index_patient_locations_on_patient_id_and_session_id (patient_id,session_id) UNIQUE -# index_patient_locations_on_session_id (session_id) +# idx_on_patient_id_location_id_academic_year_08a1dc4afe (patient_id,location_id,academic_year) UNIQUE +# index_patient_locations_on_location_id (location_id) +# index_patient_locations_on_location_id_and_academic_year (location_id,academic_year) # # Foreign Keys # +# fk_rails_... (location_id => locations.id) # fk_rails_... (patient_id => patients.id) -# fk_rails_... (session_id => sessions.id) # FactoryBot.define do factory :patient_location do - transient { programmes { [association(:programme)] } } + transient { session { association(:session) } } patient - session { association :session, programmes: } + location { session.location } + academic_year { session.academic_year } end end diff --git a/spec/factories/patients.rb b/spec/factories/patients.rb index 97c75f6844..dc6ae867b0 100644 --- a/spec/factories/patients.rb +++ b/spec/factories/patients.rb @@ -124,8 +124,14 @@ end after(:create) do |patient, evaluator| - if evaluator.session - PatientLocation.find_or_create_by!(patient:, session: evaluator.session) + if (session = evaluator.session) + location_id = session.location_id + academic_year = session.academic_year + PatientLocation.find_or_create_by!( + patient:, + location_id:, + academic_year: + ) end end diff --git a/spec/factories/sessions.rb b/spec/factories/sessions.rb index e1c0e7783f..b4756a4e03 100644 --- a/spec/factories/sessions.rb +++ b/spec/factories/sessions.rb @@ -20,9 +20,10 @@ # # Indexes # -# index_sessions_on_location_id (location_id) -# index_sessions_on_team_id_and_academic_year (team_id,academic_year) -# index_sessions_on_team_id_and_location_id (team_id,location_id) +# index_sessions_on_location_id (location_id) +# index_sessions_on_location_id_and_academic_year_and_team_id (location_id,academic_year,team_id) +# index_sessions_on_team_id_and_academic_year (team_id,academic_year) +# index_sessions_on_team_id_and_location_id (team_id,location_id) # # Foreign Keys # diff --git a/spec/features/cli_generate_consents_spec.rb b/spec/features/cli_generate_consents_spec.rb index 42f330495c..f4bf3df6e0 100644 --- a/spec/features/cli_generate_consents_spec.rb +++ b/spec/features/cli_generate_consents_spec.rb @@ -56,9 +56,17 @@ def then_consents_are_created_with_the_given_statuses expect( @team - .patient_locations - .has_consent_status(:given, programme: @programme) - .has_triage_status(:not_required, programme: @programme) + .patients + .has_consent_status( + :given, + programme: @programme, + academic_year: AcademicYear.current + ) + .has_triage_status( + :not_required, + programme: @programme, + academic_year: AcademicYear.current + ) .count ).to eq 1 end diff --git a/spec/features/import_child_pds_lookup_extravaganza_spec.rb b/spec/features/import_child_pds_lookup_extravaganza_spec.rb index 8eb9d89679..6e21140023 100644 --- a/spec/features/import_child_pds_lookup_extravaganza_spec.rb +++ b/spec/features/import_child_pds_lookup_extravaganza_spec.rb @@ -101,13 +101,8 @@ def given_i_am_signed_in @programme = create(:programme, :hpv) - @team = - create( - :team, - :with_generic_clinic, - :with_one_nurse, - programmes: [@programme] - ) + @team = create(:team, :with_one_nurse, programmes: [@programme]) + sign_in @team.users.first end diff --git a/spec/features/manage_children_spec.rb b/spec/features/manage_children_spec.rb index d53e0db518..20c8aa32af 100644 --- a/spec/features/manage_children_spec.rb +++ b/spec/features/manage_children_spec.rb @@ -163,6 +163,8 @@ def given_my_team_exists :with_one_nurse, programmes: [@programme] ) + + TeamSessionsFactory.call(@team, academic_year: AcademicYear.current) end def given_patients_exist diff --git a/spec/jobs/enqueue_vaccinations_search_in_nhs_job_spec.rb b/spec/jobs/enqueue_vaccinations_search_in_nhs_job_spec.rb index 2c461c00af..ac2116ae8f 100644 --- a/spec/jobs/enqueue_vaccinations_search_in_nhs_job_spec.rb +++ b/spec/jobs/enqueue_vaccinations_search_in_nhs_job_spec.rb @@ -7,7 +7,7 @@ let(:flu) { create(:programme, :flu) } let(:location) { create(:school, team:, programmes: [flu]) } let(:school) { location } - let!(:patient) { create(:patient, team:, school:) } + let!(:patient) { create(:patient, team:, school:, session:) } describe "#perform", :within_academic_year do subject { SearchVaccinationRecordsInNHSJob } @@ -23,8 +23,7 @@ dates:, send_invitations_at:, team:, - location:, - patients: [patient] + location: ) end diff --git a/spec/jobs/patient_nhs_number_lookup_job_spec.rb b/spec/jobs/patient_nhs_number_lookup_job_spec.rb index 3e795cffbc..8a5d315447 100644 --- a/spec/jobs/patient_nhs_number_lookup_job_spec.rb +++ b/spec/jobs/patient_nhs_number_lookup_job_spec.rb @@ -74,10 +74,8 @@ let!(:existing_patient) { create(:patient, nhs_number: "9449306168") } - let(:patient_location) do - create(:patient_location, patient:, programmes: [programme]) - end - let(:session) { patient_location.session } + let(:session) { create(:session, programmes: [programme]) } + let(:patient_location) { create(:patient_location, patient:, session:) } let(:gillick_assessment) do create(:gillick_assessment, :competent, patient:, session:) end @@ -116,7 +114,8 @@ create( :patient_location, patient: existing_patient, - session: patient_location.session + location: patient_location.location, + academic_year: patient_location.academic_year ) end diff --git a/spec/jobs/send_manual_school_consent_reminders_job_spec.rb b/spec/jobs/send_manual_school_consent_reminders_job_spec.rb index 4be5a21d7a..6bea4d41d0 100644 --- a/spec/jobs/send_manual_school_consent_reminders_job_spec.rb +++ b/spec/jobs/send_manual_school_consent_reminders_job_spec.rb @@ -42,7 +42,7 @@ before do create(:parent_relationship, patient:, parent:) - create(:patient_location, patient:, session:, programmes:) + create(:patient_location, patient:, session:) patient.reload end diff --git a/spec/lib/generate/vaccination_records_spec.rb b/spec/lib/generate/vaccination_records_spec.rb index 381916b6de..f19c8fe1b2 100644 --- a/spec/lib/generate/vaccination_records_spec.rb +++ b/spec/lib/generate/vaccination_records_spec.rb @@ -37,6 +37,8 @@ context "no patients without vaccinations" do it "raises an error" do + session + expect { described_class.call(team:, administered: 1) }.to raise_error( RuntimeError ) diff --git a/spec/lib/status_generator/session_spec.rb b/spec/lib/status_generator/session_spec.rb index e68fb4d5b5..f7dc565e74 100644 --- a/spec/lib/status_generator/session_spec.rb +++ b/spec/lib/status_generator/session_spec.rb @@ -3,8 +3,8 @@ describe StatusGenerator::Session do subject(:generator) do described_class.new( - session_id: patient_location.session_id, - academic_year: patient_location.academic_year, + session_id: session.id, + academic_year: session.academic_year, attendance_record: patient.attendance_records.last, programme:, patient:, @@ -14,15 +14,13 @@ ) end - let(:patient_location) { create(:patient_location, programmes: [programme]) } let(:programme) { create(:programme) } + let(:session) { create(:session, programmes: [programme]) } + let(:patient) { create(:patient, session:) } describe "#status" do subject(:status) { generator.status } - let(:patient) { patient_location.patient } - let(:session) { patient_location.session } - context "with no vaccination record" do it { should be(:none_yet) } end @@ -90,8 +88,6 @@ around { |example| travel_to(Time.zone.now) { example.run } } - let(:patient) { patient_location.patient } - let(:session) { patient_location.session } let(:performed_at) { 1.day.ago.beginning_of_minute } let(:created_at) { 2.days.ago.midday } @@ -99,10 +95,10 @@ before do create( :vaccination_record, - patient: patient, - session: session, - programme: programme, - performed_at: performed_at + patient:, + session:, + programme:, + performed_at: ) end diff --git a/spec/lib/status_updater_spec.rb b/spec/lib/status_updater_spec.rb index 16ff077716..811b525dde 100644 --- a/spec/lib/status_updater_spec.rb +++ b/spec/lib/status_updater_spec.rb @@ -5,7 +5,9 @@ around { |example| travel_to(Date.new(2025, 7, 31)) { example.run } } - before { create(:patient_location, patient:, programmes:) } + let(:session) { create(:session, programmes:) } + + before { create(:patient_location, patient:, session:) } context "with an HPV session and ineligible patient" do let(:programmes) { [create(:programme, :hpv)] } diff --git a/spec/models/cohort_import_spec.rb b/spec/models/cohort_import_spec.rb index 749de3a29b..341d61b41a 100644 --- a/spec/models/cohort_import_spec.rb +++ b/spec/models/cohort_import_spec.rb @@ -39,11 +39,12 @@ let(:file) { "valid.csv" } let(:csv) { fixture_file_upload("spec/fixtures/cohort_import/#{file}") } + let(:academic_year) { AcademicYear.current } # Ensure location URN matches the URN in our fixture files let!(:location) { create(:school, urn: "123456", team:) } - before { TeamSessionsFactory.call(team, academic_year: AcademicYear.current) } + before { TeamSessionsFactory.call(team, academic_year:) } it_behaves_like "a CSVImportable model" @@ -402,8 +403,13 @@ end before do - team.sessions.each do |session| - create(:patient_location, session:, patient: existing_patient) + team.locations.each do |location| + create( + :patient_location, + patient: existing_patient, + location:, + academic_year: + ) end end diff --git a/spec/models/patient_location_spec.rb b/spec/models/patient_location_spec.rb index a255be577e..6b18071ad8 100644 --- a/spec/models/patient_location_spec.rb +++ b/spec/models/patient_location_spec.rb @@ -4,21 +4,23 @@ # # Table name: patient_locations # -# id :bigint not null, primary key -# created_at :datetime not null -# updated_at :datetime not null -# patient_id :bigint not null -# session_id :bigint not null +# id :bigint not null, primary key +# academic_year :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# location_id :bigint not null +# patient_id :bigint not null # # Indexes # -# index_patient_locations_on_patient_id_and_session_id (patient_id,session_id) UNIQUE -# index_patient_locations_on_session_id (session_id) +# idx_on_patient_id_location_id_academic_year_08a1dc4afe (patient_id,location_id,academic_year) UNIQUE +# index_patient_locations_on_location_id (location_id) +# index_patient_locations_on_location_id_and_academic_year (location_id,academic_year) # # Foreign Keys # +# fk_rails_... (location_id => locations.id) # fk_rails_... (patient_id => patients.id) -# fk_rails_... (session_id => sessions.id) # describe PatientLocation do @@ -28,16 +30,16 @@ let(:session) { create(:session, programmes: [programme]) } describe "associations" do + it { should have_many(:attendance_records) } it { should have_many(:gillick_assessments) } it { should have_many(:pre_screenings) } + it { should have_many(:vaccination_records) } end describe "scopes" do describe "#appear_in_programmes" do subject(:scope) do - described_class.joins(:patient, :session).appear_in_programmes( - programmes - ) + described_class.joins(:patient).appear_in_programmes(programmes) end let(:programmes) { create_list(:programme, 1, :td_ipv) } @@ -83,6 +85,7 @@ subject(:scope) do described_class.consent_given_and_ready_to_vaccinate( programmes:, + academic_year:, vaccine_method: ) end diff --git a/spec/models/patient_spec.rb b/spec/models/patient_spec.rb index f97c7519fc..8e06e7f1ec 100644 --- a/spec/models/patient_spec.rb +++ b/spec/models/patient_spec.rb @@ -158,6 +158,7 @@ let(:location) do create(:school, programmes: [flu_programme, hpv_programme]) end + let(:academic_year) { AcademicYear.current } # Year 4 is eligible for flu only. let(:patient) { create(:patient, year_group: 4) } @@ -165,26 +166,16 @@ # Year 9 is eligible for flu and HPV only. let(:another_patient) { create(:patient, year_group: 9) } - let(:flu_session) do + before do create(:session, location:, programmes: [flu_programme]) - end - let(:hpv_session) do create(:session, location:, programmes: [hpv_programme]) - end - - before do - create(:patient_location, patient:, session: flu_session) - create(:patient_location, patient:, session: hpv_session) + create(:patient_location, patient:, location:, academic_year:) create( :patient_location, patient: another_patient, - session: flu_session - ) - create( - :patient_location, - patient: another_patient, - session: hpv_session + location:, + academic_year: ) end @@ -260,6 +251,7 @@ let(:location) do create(:school, programmes: [flu_programme, hpv_programme]) end + let(:academic_year) { AcademicYear.current } # Year 4 is eligible for flu only. let!(:patient) { create(:patient, year_group: 4) } @@ -267,26 +259,16 @@ # Year 9 is eligible for flu and HPV only. let(:another_patient) { create(:patient, year_group: 9) } - let(:flu_session) do + before do create(:session, location:, programmes: [flu_programme]) - end - let(:hpv_session) do create(:session, location:, programmes: [hpv_programme]) - end - - before do - create(:patient_location, patient:, session: flu_session) - create(:patient_location, patient:, session: hpv_session) + create(:patient_location, patient:, location:, academic_year:) create( :patient_location, patient: another_patient, - session: flu_session - ) - create( - :patient_location, - patient: another_patient, - session: hpv_session + location:, + academic_year: ) end @@ -1126,19 +1108,20 @@ end context "when the old patient has upcoming sessions" do - let(:session) do + let(:location) { create(:school) } + + before do create( - :session, - academic_year: AcademicYear.pending, - date: AcademicYear.pending.to_academic_year_date_range.begin + :patient_location, + patient: old_patient, + location:, + academic_year: AcademicYear.pending ) end - before { create(:patient_location, patient: old_patient, session:) } - it "adds the new patient to any upcoming sessions" do expect(new_patient.patient_locations.size).to eq(1) - expect(new_patient.patient_locations.first.session).to eq(session) + expect(new_patient.patient_locations.first.location).to eq(location) end end diff --git a/spec/models/session_spec.rb b/spec/models/session_spec.rb index 42064d0d26..9f8436decf 100644 --- a/spec/models/session_spec.rb +++ b/spec/models/session_spec.rb @@ -20,9 +20,10 @@ # # Indexes # -# index_sessions_on_location_id (location_id) -# index_sessions_on_team_id_and_academic_year (team_id,academic_year) -# index_sessions_on_team_id_and_location_id (team_id,location_id) +# index_sessions_on_location_id (location_id) +# index_sessions_on_location_id_and_academic_year_and_team_id (location_id,academic_year,team_id) +# index_sessions_on_team_id_and_academic_year (team_id,academic_year) +# index_sessions_on_team_id_and_location_id (team_id,location_id) # # Foreign Keys # diff --git a/spec/policies/school_move_policy_spec.rb b/spec/policies/school_move_policy_spec.rb new file mode 100644 index 0000000000..7757a405d7 --- /dev/null +++ b/spec/policies/school_move_policy_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +describe SchoolMovePolicy do + describe SchoolMovePolicy::Scope do + describe "#resolve" do + subject { described_class.new(user, SchoolMove).resolve } + + let(:programme) { create(:programme) } + let(:organisation) { create(:organisation) } + + let(:team) { create(:team, organisation:, programmes: [programme]) } + let(:other_team) { create(:team, organisation:, programmes: [programme]) } + let(:user) { create(:user, team:) } + + let(:session) { create(:session, team:, programmes: [programme]) } + let(:other_session) { create(:session, team:, programmes: [programme]) } + + context "patient belonging to two sessions" do + let(:patient) { create(:patient) } + let(:patient_session) { create(:patient_session, patient:, session:) } + let(:other_patient_session) do + create(:patient_session, patient:, session: other_session) + end + + let(:school) { create(:school, team:) } + let(:school_move) do + create(:school_move, :to_school, patient:, school:) + end + + it { should contain_exactly(school_move) } + end + end + end +end From a00739269ffe9f109b7e15c05a3f8e7f42db40a5 Mon Sep 17 00:00:00 2001 From: Thomas Leese Date: Fri, 12 Sep 2025 09:50:37 +0100 Subject: [PATCH 3/3] Simplify patient and session lookups This simplifies the lookups for patients and sessions by removing the duplicate scopes between patient locations and patients, instead using the scopes on the patient only, and making sure to filter out patients who shouldn't belong in the session before we need to query for this. Jira-Issue: MAV-1822 --- .../app_programme_session_table_component.rb | 37 +- .../app_session_actions_component.rb | 36 +- .../app_session_details_summary_component.rb | 14 +- ..._session_needs_review_warning_component.rb | 10 +- app/controllers/programmes/base_controller.rb | 19 +- .../sessions/consent_controller.rb | 11 +- .../patient_specific_directions_controller.rb | 35 +- .../sessions/patients_controller.rb | 8 +- app/controllers/sessions/record_controller.rb | 40 +-- .../sessions/register_controller.rb | 18 +- app/controllers/sessions/triage_controller.rb | 16 +- app/forms/patient_search_form.rb | 102 +++--- app/lib/programme_birth_academic_years.rb | 18 + app/lib/stats/organisations.rb | 2 +- app/lib/status_updater.rb | 1 - app/models/location_programme_year_group.rb | 5 + app/models/patient.rb | 142 ++++++-- app/models/patient_location.rb | 212 ++---------- app/models/session.rb | 35 +- app/views/programmes/sessions/index.html.erb | 2 +- app/views/sessions/consent/show.html.erb | 4 +- .../patient_specific_directions/show.html.erb | 4 +- app/views/sessions/patients/show.html.erb | 4 +- app/views/sessions/record/show.html.erb | 4 +- app/views/sessions/register/show.html.erb | 4 +- app/views/sessions/triage/show.html.erb | 4 +- ..._programme_session_table_component_spec.rb | 3 +- spec/features/parental_consent_flu_spec.rb | 2 +- spec/forms/patient_search_form_spec.rb | 317 ++++++++---------- ...eue_vaccinations_search_in_nhs_job_spec.rb | 7 - .../patients_aged_out_of_school_job_spec.rb | 7 +- spec/lib/patient_archiver_spec.rb | 3 +- spec/models/patient_location_spec.rb | 64 +--- spec/models/patient_spec.rb | 66 +++- spec/models/school_move_spec.rb | 4 +- 35 files changed, 604 insertions(+), 656 deletions(-) create mode 100644 app/lib/programme_birth_academic_years.rb diff --git a/app/components/app_programme_session_table_component.rb b/app/components/app_programme_session_table_component.rb index 8ecb7aa4cc..e7e181fc4c 100644 --- a/app/components/app_programme_session_table_component.rb +++ b/app/components/app_programme_session_table_component.rb @@ -1,23 +1,28 @@ # frozen_string_literal: true class AppProgrammeSessionTableComponent < ViewComponent::Base - def initialize(sessions, programme:) + def initialize(sessions, programme:, academic_year:) @sessions = sessions @programme = programme + @academic_year = academic_year end private - attr_reader :sessions, :programme + attr_reader :sessions, :programme, :academic_year delegate :govuk_table, to: :helpers def cohort_count(session:) - format_number(patient_locations(session:).count) + format_number(patients(session:).count) end def no_response_scope(session:) - patient_locations(session:).has_consent_status(:no_response, programme:) + patients(session:).has_consent_status( + :no_response, + programme:, + academic_year: + ) end def no_response_count(session:) @@ -27,13 +32,17 @@ def no_response_count(session:) def no_response_percentage(session:) format_percentage( no_response_scope(session:).count, - patient_locations(session:).count + patients(session:).count ) end def triage_needed_count(session:) format_number( - patient_locations(session:).has_triage_status(:required, programme:).count + patients(session:).has_triage_status( + :required, + programme:, + academic_year: + ).count ) end @@ -48,12 +57,22 @@ def vaccinated_count(session:) def vaccinated_percentage(session:) format_percentage( vaccinated_scope(session:).count, - patient_locations(session:).count + patients(session:).count + ) + end + + def patients(session:) + @patients ||= {} + @patients[session] = session.patients.where( + birth_academic_year: birth_academic_years(session:) ) end - def patient_locations(session:) - session.patient_locations.joins(:patient).appear_in_programmes([programme]) + def birth_academic_years(session:) + @birth_academic_years ||= {} + @birth_academic_years[session] = session.programme_birth_academic_years[ + programme + ] end def format_number(count) = count.to_s diff --git a/app/components/app_session_actions_component.rb b/app/components/app_session_actions_component.rb index c79a3d52cf..59f8cb729f 100644 --- a/app/components/app_session_actions_component.rb +++ b/app/components/app_session_actions_component.rb @@ -19,9 +19,7 @@ def render? = rows.any? delegate :govuk_summary_list, to: :helpers delegate :academic_year, :programmes, to: :session - def patient_locations - session.patient_locations.joins(:patient).appear_in_programmes(programmes) - end + def patients = session.patients def rows @rows ||= [ @@ -35,7 +33,7 @@ def rows end def no_nhs_number_row - count = patient_locations.merge(Patient.without_nhs_number).count + count = patients.without_nhs_number.count href = session_patients_path(session, missing_nhs_number: true) generate_row(:children_without_nhs_number, count:, href:) @@ -44,7 +42,11 @@ def no_nhs_number_row def no_consent_response_row status = "no_response" count = - patient_locations.has_consent_status(status, programme: programmes).count + patients.has_consent_status( + status, + programme: programmes, + academic_year: + ).count href = session_consent_path(session, consent_statuses: [status]) actions = [ { @@ -58,7 +60,11 @@ def no_consent_response_row def conflicting_consent_row status = "conflicts" count = - patient_locations.has_consent_status(status, programme: programmes).count + patients.has_consent_status( + status, + programme: programmes, + academic_year: + ).count href = session_consent_path(session, consent_statuses: [status]) generate_row(:children_with_conflicting_consent_response, count:, href:) @@ -67,7 +73,11 @@ def conflicting_consent_row def triage_required_row status = "required" count = - patient_locations.has_triage_status(status, programme: programmes).count + patients.has_triage_status( + status, + programme: programmes, + academic_year: + ).count href = session_triage_path(session, triage_status: status) generate_row(:children_requiring_triage, count:, href:) @@ -77,7 +87,7 @@ def register_attendance_row return nil unless session.requires_registration? && session.today? status = "unknown" - count = patient_locations.has_registration_status(status, session:).count + count = patients.has_registration_status(status, session:).count href = session_register_path(session, register_status: status) generate_row(:children_to_register, count:, href:) @@ -88,13 +98,11 @@ def ready_for_vaccinator_row counts_by_programme = session.programmes.index_with do |programme| - patient_locations + patients .has_registration_status(%w[attending completed], session:) - .includes( - patient: %i[consent_statuses triage_statuses vaccination_statuses] - ) - .count do |patient_location| - patient_location.patient.consent_given_and_safe_to_vaccinate?( + .includes(:consent_statuses, :triage_statuses, :vaccination_statuses) + .count do |patient| + patient.consent_given_and_safe_to_vaccinate?( programme:, academic_year: ) diff --git a/app/components/app_session_details_summary_component.rb b/app/components/app_session_details_summary_component.rb index f1f176667b..e9d3aadef3 100644 --- a/app/components/app_session_details_summary_component.rb +++ b/app/components/app_session_details_summary_component.rb @@ -14,14 +14,12 @@ def call attr_reader :session delegate :govuk_summary_list, to: :helpers - delegate :programmes, to: :session + delegate :programmes, :academic_year, to: :session - def patient_locations - session.patient_locations.joins(:patient).appear_in_programmes(programmes) - end + def patients = session.patients def cohort_row - count = patient_locations.count + count = patients.count { key: { text: "Cohort" }, value: { text: I18n.t("children", count:) } } end @@ -30,7 +28,11 @@ def consent_refused_row status = "refused" count = - patient_locations.has_consent_status(status, programme: programmes).count + patients.has_consent_status( + status, + programme: programmes, + academic_year: + ).count href = session_consent_path(session, consent_statuses: [status]) diff --git a/app/components/app_session_needs_review_warning_component.rb b/app/components/app_session_needs_review_warning_component.rb index 7aae02f179..3f902ea1da 100644 --- a/app/components/app_session_needs_review_warning_component.rb +++ b/app/components/app_session_needs_review_warning_component.rb @@ -32,8 +32,7 @@ def warning_href def warning_counts @warning_counts ||= { - children_without_nhs_number: - patient_locations.merge(Patient.without_nhs_number).count + children_without_nhs_number: patients.without_nhs_number.count } end @@ -42,10 +41,5 @@ def make_row_from_warning(warning) link_to(t(warning, count: warning_counts[warning]), warning_href[warning]) end - def patient_locations - @session - .patient_locations - .joins(:patient) - .appear_in_programmes(@session.programmes) - end + def patients = @session.patients end diff --git a/app/controllers/programmes/base_controller.rb b/app/controllers/programmes/base_controller.rb index 0e73d2fcf8..6f499e7fc8 100644 --- a/app/controllers/programmes/base_controller.rb +++ b/app/controllers/programmes/base_controller.rb @@ -22,22 +22,11 @@ def set_academic_year def patient_ids @patient_ids ||= - PatientLocation - .distinct - .joins(:patient) - .joins_sessions - .where("sessions.id IN (?)", session_ids) - .appear_in_programmes([@programme]) - .not_archived(team: current_team) - .pluck(:patient_id) - end - - def session_ids - @session_ids ||= current_team - .sessions - .where(academic_year: @academic_year) - .has_programmes([@programme]) + .patients + .appear_in_programmes([@programme], academic_year: @academic_year) + .not_archived(team: current_team) .pluck(:id) + .uniq end end diff --git a/app/controllers/sessions/consent_controller.rb b/app/controllers/sessions/consent_controller.rb index 83fe3e1aad..8e2f419622 100644 --- a/app/controllers/sessions/consent_controller.rb +++ b/app/controllers/sessions/consent_controller.rb @@ -27,15 +27,16 @@ def show scope = @session - .patient_locations - .includes(patient: [:consent_statuses, { notes: :created_by }]) + .patients + .includes(:consent_statuses, { notes: :created_by }) .has_consent_status( statuses_except_not_required, - programme: @form.programmes + programme: @form.programmes, + academic_year: @session.academic_year ) - patient_locations = @form.apply(scope) - @pagy, @patient_locations = pagy(patient_locations) + patients = @form.apply(scope) + @pagy, @patients = pagy(patients) end private diff --git a/app/controllers/sessions/patient_specific_directions_controller.rb b/app/controllers/sessions/patient_specific_directions_controller.rb index 1347a71773..04d959a4c7 100644 --- a/app/controllers/sessions/patient_specific_directions_controller.rb +++ b/app/controllers/sessions/patient_specific_directions_controller.rb @@ -9,21 +9,18 @@ class Sessions::PatientSpecificDirectionsController < ApplicationController before_action :set_patient_search_form def show - scope = - @session.patient_locations.includes( - patient: { - patient_specific_directions: :programme - } - ) - @eligible_for_bulk_psd_count = patient_locations_allowed_psd.count - patient_locations = @form.apply(scope) - @pagy, @patient_locations = pagy(patient_locations) + scope = @session.patients.includes(patient_specific_directions: :programme) + + @eligible_for_bulk_psd_count = patients_allowed_psd.count + + patients = @form.apply(scope) + @pagy, @patients = pagy(patients) render layout: "full" end def new - @eligible_for_bulk_psd_count = patient_locations_allowed_psd.count + @eligible_for_bulk_psd_count = patients_allowed_psd.count end def create @@ -56,12 +53,12 @@ def set_vaccine end def psds_to_create - patient_locations_allowed_psd.map do |patient_location| + patients_allowed_psd.map do |patient| PatientSpecificDirection.new( academic_year: @session.academic_year, created_by: current_user, delivery_site: "nose", - patient_id: patient_location.patient_id, + patient:, programme: @programme, team: current_team, vaccine: @vaccine, @@ -70,18 +67,24 @@ def psds_to_create end end - def patient_locations_allowed_psd - @patient_locations_allowed_psd ||= + def patients_allowed_psd + @patients_allowed_psd ||= @session - .patient_locations + .patients .has_consent_status( "given", programme: @programme, + academic_year: @session.academic_year, vaccine_method: "nasal" ) - .has_triage_status("not_required", programme: @programme) + .has_triage_status( + "not_required", + programme: @programme, + academic_year: @session.academic_year + ) .without_patient_specific_direction( programme: @programme, + academic_year: @session.academic_year, team: current_team ) end diff --git a/app/controllers/sessions/patients_controller.rb b/app/controllers/sessions/patients_controller.rb index 3b41de97ef..6c52fb2d99 100644 --- a/app/controllers/sessions/patients_controller.rb +++ b/app/controllers/sessions/patients_controller.rb @@ -12,12 +12,10 @@ def show @statuses = Patient::VaccinationStatus.statuses.keys scope = - @session.patient_locations.includes( - patient: [:vaccination_statuses, { notes: :created_by }] - ) + @session.patients.includes(:vaccination_statuses, notes: :created_by) - patient_locations = @form.apply(scope) - @pagy, @patient_locations = pagy(patient_locations) + patients = @form.apply(scope) + @pagy, @patients = pagy(patients) end private diff --git a/app/controllers/sessions/record_controller.rb b/app/controllers/sessions/record_controller.rb index 263ca42d61..e54c09add3 100644 --- a/app/controllers/sessions/record_controller.rb +++ b/app/controllers/sessions/record_controller.rb @@ -15,13 +15,11 @@ class Sessions::RecordController < ApplicationController def show scope = - @session.patient_locations.includes( - patient: [ - :consent_statuses, - :triage_statuses, - :vaccination_statuses, - { notes: :created_by } - ] + @session.patients.includes( + :consent_statuses, + :triage_statuses, + :vaccination_statuses, + notes: :created_by ) if @session.requires_registration? @@ -32,7 +30,7 @@ def show ) end - patient_locations = + patients = filter_on_vaccine_method_or_patient_specific_direction( @form.apply(scope) ).consent_given_and_ready_to_vaccinate( @@ -41,7 +39,7 @@ def show vaccine_method: @form.vaccine_method.presence ) - @pagy, @patient_locations = pagy_array(patient_locations) + @pagy, @patients = pagy_array(patients) render layout: "full" end @@ -108,28 +106,32 @@ def filter_on_vaccine_method_or_patient_specific_direction(scope) original_scope = scope + programme = @session.programmes + academic_year = @session.academic_year + team = current_team + if @session.psd_enabled? && @session.national_protocol_enabled? - original_scope.has_patient_specific_direction( - programme: @session.programmes, - team: current_team + original_scope.with_patient_specific_direction( + programme:, + academic_year:, + team: ).or( original_scope.has_vaccine_method( "injection", - programme: @session.programmes + programme:, + academic_year: ) ) elsif @session.pgd_supply_enabled? && @session.national_protocol_enabled? original_scope.has_vaccine_method( %w[nasal injection], - programme: @session.programmes + programme:, + academic_year: ) elsif @session.pgd_supply_enabled? - original_scope.has_vaccine_method("nasal", programme: @session.programmes) + original_scope.has_vaccine_method("nasal", programme:, academic_year:) elsif @session.national_protocol_enabled? - original_scope.has_vaccine_method( - "injection", - programme: @session.programmes - ) + original_scope.has_vaccine_method("injection", programme:, academic_year:) else original_scope.none end diff --git a/app/controllers/sessions/register_controller.rb b/app/controllers/sessions/register_controller.rb index 3c86885616..8a717d855b 100644 --- a/app/controllers/sessions/register_controller.rb +++ b/app/controllers/sessions/register_controller.rb @@ -14,18 +14,16 @@ def show @statuses = Patient::RegistrationStatus.statuses.keys scope = - @session.patient_locations.includes( - patient: [ - :consent_statuses, - :registration_statuses, - :triage_statuses, - :vaccination_statuses, - { notes: :created_by } - ] + @session.patients.includes( + :consent_statuses, + :registration_statuses, + :triage_statuses, + :vaccination_statuses, + notes: :created_by ) - patient_locations = @form.apply(scope) - @pagy, @patient_locations = pagy(patient_locations) + patients = @form.apply(scope) + @pagy, @patients = pagy(patients) end def create diff --git a/app/controllers/sessions/triage_controller.rb b/app/controllers/sessions/triage_controller.rb index 959bc497a1..e159a965d9 100644 --- a/app/controllers/sessions/triage_controller.rb +++ b/app/controllers/sessions/triage_controller.rb @@ -13,12 +13,16 @@ def show scope = @session - .patient_locations - .includes(patient: [:triage_statuses, { notes: :created_by }]) - .has_triage_status(@statuses, programme: @form.programmes) - - patient_locations = @form.apply(scope) - @pagy, @patient_locations = pagy(patient_locations) + .patients + .includes(:triage_statuses, notes: :created_by) + .has_triage_status( + @statuses, + programme: @form.programmes, + academic_year: @session.academic_year + ) + + patients = @form.apply(scope) + @pagy, @patients = pagy(patients) end private diff --git a/app/forms/patient_search_form.rb b/app/forms/patient_search_form.rb index b5ab4586dd..4d4222f213 100644 --- a/app/forms/patient_search_form.rb +++ b/app/forms/patient_search_form.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class PatientSearchForm < SearchForm - attr_accessor :current_user attr_writer :academic_year attribute :aged_out_of_programmes, :boolean @@ -43,7 +42,7 @@ def programmes if programme_types.present? Programme.where(type: programme_types) else - @session&.programmes + session&.programmes end end @@ -67,10 +66,12 @@ def apply(scope) private + attr_reader :current_user, :session + def academic_year = - @session&.academic_year || @academic_year || AcademicYear.pending + session&.academic_year || @academic_year || AcademicYear.pending - def team = @session&.team || @current_user.selected_team + def team = session&.team || current_user.selected_team def filter_name(scope) q.present? ? scope.search_by_name(q) : scope @@ -87,11 +88,11 @@ def filter_year_groups(scope) def filter_aged_out_of_programmes(scope) if aged_out_of_programmes scope.not_appear_in_programmes(team.programmes, academic_year:) - elsif @session || archived - scope - else + elsif session || archived # Archived patients won't appear in programmes, so we need to # skip this check if we're trying to view archived patients. + scope + else scope.appear_in_programmes(team.programmes, academic_year:) end end @@ -99,7 +100,7 @@ def filter_aged_out_of_programmes(scope) def filter_archived(scope) if archived scope.archived(team:) - elsif @session + elsif session scope else scope.not_archived(team:) @@ -127,9 +128,14 @@ def filter_nhs_number(scope) end def filter_programmes(scope) - if programmes.present? - if @session - scope.joins(:patient).appear_in_programmes(programmes) + if programme_types.present? + if session + birth_academic_years = + programmes.flat_map do |programme| + session.programme_birth_academic_years[programme] + end + + scope.where(birth_academic_year: birth_academic_years) else scope.appear_in_programmes(programmes, academic_year:) end @@ -148,33 +154,21 @@ def filter_consent_statuses(scope) vaccine_methods = given_with_vaccine_method_statuses.map { it.sub("given_", "") } - if @session - scope.has_consent_status( - "given", - programme: programmes, - vaccine_method: vaccine_methods - ) - else - scope.has_consent_status( - "given", - programme: programmes, - academic_year:, - vaccine_method: vaccine_methods - ) - end + scope.has_consent_status( + "given", + programme: programmes, + academic_year:, + vaccine_method: vaccine_methods + ) end other_status_scope = if other_statuses.any? - if @session - scope.has_consent_status(other_statuses, programme: programmes) - else - scope.has_consent_status( - other_statuses, - programme: programmes, - academic_year: - ) - end + scope.has_consent_status( + other_statuses, + programme: programmes, + academic_year: + ) end if given_with_vaccine_method_scope && other_status_scope @@ -189,15 +183,11 @@ def filter_consent_statuses(scope) def filter_vaccination_statuses(scope) if (status = vaccination_status&.to_sym).present? - if @session - scope.has_vaccination_status(status, programme: programmes) - else - scope.has_vaccination_status( - status, - programme: programmes, - academic_year: - ) - end + scope.has_vaccination_status( + status, + programme: programmes, + academic_year: + ) else scope end @@ -208,9 +198,17 @@ def filter_patient_specific_direction_status(scope) case status when :added - scope.has_patient_specific_direction(programme: programmes, team:) + scope.with_patient_specific_direction( + programme: programmes, + academic_year:, + team: + ) when :not_added - scope.without_patient_specific_direction(programme: programmes, team:) + scope.without_patient_specific_direction( + programme: programmes, + academic_year:, + team: + ) else scope end @@ -218,7 +216,7 @@ def filter_patient_specific_direction_status(scope) def filter_register_status(scope) if (status = register_status&.to_sym).present? - scope.has_registration_status(status, session: @session) + scope.has_registration_status(status, session:) else scope end @@ -226,11 +224,7 @@ def filter_register_status(scope) def filter_triage_status(scope) if (status = triage_status&.to_sym).present? - if @session - scope.has_triage_status(status, programme: programmes) - else - scope.has_triage_status(status, programme: programmes, academic_year:) - end + scope.has_triage_status(status, programme: programmes, academic_year:) else scope end @@ -238,7 +232,11 @@ def filter_triage_status(scope) def filter_vaccine_method(scope) if vaccine_method.present? - scope.has_vaccine_method(vaccine_method, programme: programmes) + scope.has_vaccine_method( + vaccine_method, + programme: programmes, + academic_year: + ) else scope end diff --git a/app/lib/programme_birth_academic_years.rb b/app/lib/programme_birth_academic_years.rb new file mode 100644 index 0000000000..3c71c09b8d --- /dev/null +++ b/app/lib/programme_birth_academic_years.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class ProgrammeBirthAcademicYears + def initialize(programme_year_groups, academic_year:) + @programme_year_groups = programme_year_groups + @academic_year = academic_year + end + + def [](programme) + programme_year_groups[programme].map do + it.to_birth_academic_year(academic_year:) + end + end + + private + + attr_reader :programme_year_groups, :academic_year +end diff --git a/app/lib/stats/organisations.rb b/app/lib/stats/organisations.rb index da08fbb8a6..4d80f98714 100644 --- a/app/lib/stats/organisations.rb +++ b/app/lib/stats/organisations.rb @@ -186,6 +186,6 @@ def calculate_vaccination_stats(programme) end def get_eligible_patients(programme) - patients.appear_in_programmes([programme], academic_year: academic_year) + patients.appear_in_programmes([programme], academic_year:) end end diff --git a/app/lib/status_updater.rb b/app/lib/status_updater.rb index 6e33e4cc78..8ae1281d56 100644 --- a/app/lib/status_updater.rb +++ b/app/lib/status_updater.rb @@ -207,7 +207,6 @@ def programme_ids_per_year_group def programme_ids_per_location_id_and_year_group @programme_ids_per_location_id_and_year_group ||= LocationProgrammeYearGroup - .distinct .pluck(:location_id, :programme_id, :year_group) .each_with_object({}) do |(location_id, programme_id, year_group), hash| hash[location_id] ||= {} diff --git a/app/models/location_programme_year_group.rb b/app/models/location_programme_year_group.rb index 845f2e282a..6129e9d1d4 100644 --- a/app/models/location_programme_year_group.rb +++ b/app/models/location_programme_year_group.rb @@ -29,6 +29,11 @@ class LocationProgrammeYearGroup < ApplicationRecord scope :pluck_year_groups, -> { distinct.order(:year_group).pluck(:year_group) } + scope :pluck_birth_academic_years, + ->(academic_year:) do + pluck_year_groups.map { it.to_birth_academic_year(academic_year:) } + end + def birth_academic_year(academic_year: nil) year_group.to_birth_academic_year(academic_year:) end diff --git a/app/models/patient.rb b/app/models/patient.rb index 1e18884e7e..c4fa6ba76c 100644 --- a/app/models/patient.rb +++ b/app/models/patient.rb @@ -104,10 +104,22 @@ class Patient < ApplicationRecord end scope :joins_sessions, -> { joins(:patient_locations).joins(<<-SQL) } - INNER JOIN sessions - ON sessions.location_id = patient_locations.location_id - AND sessions.academic_year = patient_locations.academic_year - SQL + INNER JOIN sessions + ON sessions.location_id = patient_locations.location_id + AND sessions.academic_year = patient_locations.academic_year + SQL + + scope :joins_session_programmes, -> { joins(<<-SQL) } + INNER JOIN session_programmes + ON session_programmes.session_id = sessions.id + SQL + + scope :joins_location_programme_year_groups, -> { joins(<<-SQL) } + INNER JOIN location_programme_year_groups + ON location_programme_year_groups.location_id = sessions.location_id + AND location_programme_year_groups.programme_id = session_programmes.programme_id + AND location_programme_year_groups.year_group = sessions.academic_year - patients.birth_academic_year - #{Integer::AGE_CHILDREN_START_SCHOOL} + SQL scope :archived, ->(team:) do @@ -141,24 +153,34 @@ class Patient < ApplicationRecord scope :appear_in_programmes, ->(programmes, academic_year:) do where( - PatientLocation - .where(academic_year:) - .where("patient_id = patients.id") - .appear_in_programmes(programmes) - .arel - .exists + id: + joins_sessions + .joins_session_programmes + .joins_location_programme_year_groups + .where(sessions: { academic_year: }) + .where( + session_programmes: { + programme_id: programmes.map(&:id) + } + ) + .select("patients.id") ) end scope :not_appear_in_programmes, ->(programmes, academic_year:) do where.not( - PatientLocation - .where(academic_year:) - .where("patient_id = patients.id") - .appear_in_programmes(programmes) - .arel - .exists + id: + joins_sessions + .joins_session_programmes + .joins_location_programme_year_groups + .where(sessions: { academic_year: }) + .where( + session_programmes: { + programme_id: programmes.map(&:id) + } + ) + .select("patients.id") ) end @@ -243,6 +265,80 @@ class Patient < ApplicationRecord ) end + scope :has_vaccine_method, + ->(vaccine_method, programme:, academic_year:) do + where( + Patient::TriageStatus + .where("patient_id = patients.id") + .where(vaccine_method:, programme:, academic_year:) + .arel + .exists + ).or( + where( + Patient::TriageStatus + .where("patient_id = patients.id") + .where(status: "not_required", programme:, academic_year:) + .arel + .exists + ).where( + Patient::ConsentStatus + .where("patient_id = patients.id") + .where(programme:, academic_year:) + .has_vaccine_method(vaccine_method) + .arel + .exists + ) + ) + end + + scope :has_registration_status, + ->(status, session:) do + where( + Patient::RegistrationStatus + .where("patient_id = patients.id") + .where(session:, status:) + .arel + .exists + ) + end + + scope :with_patient_specific_direction, + ->(programme:, academic_year:, team:) do + where( + PatientSpecificDirection + .where("patient_id = patients.id") + .where(programme:, academic_year:, team:) + .not_invalidated + .arel + .exists + ) + end + + scope :without_patient_specific_direction, + ->(programme:, academic_year:, team:) do + where.not( + PatientSpecificDirection + .where("patient_id = patients.id") + .where(programme:, academic_year:, team:) + .not_invalidated + .arel + .exists + ) + end + + scope :consent_given_and_ready_to_vaccinate, + ->(programmes:, academic_year:, vaccine_method:) do + select do |patient| + programmes.any? do |programme| + patient.consent_given_and_safe_to_vaccinate?( + programme:, + academic_year:, + vaccine_method: + ) + end + end + end + validates :given_name, :family_name, :date_of_birth, presence: true validates :birth_academic_year, comparison: { greater_than_or_equal_to: 1990 } @@ -274,10 +370,6 @@ class Patient < ApplicationRecord delegate :fhir_record, to: :fhir_mapper - def sessions - Session.joins_patient_locations.where(patient_locations: { patient_id: id }) - end - def self.match_existing( nhs_number:, given_name:, @@ -347,6 +439,16 @@ def self.match_existing( results end + def sessions + Session + .joins_patient_locations + .joins_patients + .joins(:session_programmes) + .joins_location_programme_year_groups + .where(patients: { id: }) + .distinct + end + def teams Team.left_outer_joins(:sessions).joins(<<-SQL) INNER JOIN patient_locations diff --git a/app/models/patient_location.rb b/app/models/patient_location.rb index ab6b36b8c1..e23f0acaec 100644 --- a/app/models/patient_location.rb +++ b/app/models/patient_location.rb @@ -60,198 +60,36 @@ class PatientLocation < ApplicationRecord scope :current, -> { where(academic_year: AcademicYear.current) } scope :pending, -> { where(academic_year: AcademicYear.pending) } - scope :archived, ->(team:) { merge(Patient.archived(team:)) } - - scope :not_archived, ->(team:) { merge(Patient.not_archived(team:)) } - scope :joins_sessions, -> { joins(<<-SQL) } - INNER JOIN sessions - ON sessions.location_id = patient_locations.location_id - AND sessions.academic_year = patient_locations.academic_year - SQL + INNER JOIN sessions + ON sessions.location_id = patient_locations.location_id + AND sessions.academic_year = patient_locations.academic_year + SQL + + scope :joins_session_programmes, -> { joins(<<-SQL) } + INNER JOIN session_programmes + ON session_programmes.session_id = sessions.id + SQL + + scope :joins_location_programme_year_groups, -> { joins(<<-SQL) } + INNER JOIN location_programme_year_groups + ON location_programme_year_groups.location_id = patient_locations.location_id + AND location_programme_year_groups.programme_id = session_programmes.programme_id + AND location_programme_year_groups.year_group = patient_locations.academic_year - patients.birth_academic_year - #{Integer::AGE_CHILDREN_START_SCHOOL} + SQL scope :appear_in_programmes, ->(programmes) do - # Are any of the programmes administered in the session? - programme_in_session = - SessionProgramme - .joins(:session) - .where(programme: programmes) - .where("sessions.location_id = patient_locations.location_id") - .where("sessions.academic_year = patient_locations.academic_year") - .arel - .exists - - # Is the patient eligible for any of those programmes by year group? - patient_in_administered_year_groups = - LocationProgrammeYearGroup - .where(programme: programmes) - .where( - "location_programme_year_groups.location_id = patient_locations.location_id" - ) - .where( - "year_group = patient_locations.academic_year " \ - "- patients.birth_academic_year " \ - "- #{Integer::AGE_CHILDREN_START_SCHOOL}" - ) - .arel - .exists - - where(programme_in_session).where(patient_in_administered_year_groups) - end - - scope :search_by_name, - ->(name) { joins(:patient).merge(Patient.search_by_name(name)) } - - scope :search_by_year_groups, - ->(year_groups, academic_year:) do - joins(:patient).merge( - Patient.search_by_year_groups(year_groups, academic_year:) - ) - end - - scope :search_by_date_of_birth_year, - ->(year) do - where("extract(year from patients.date_of_birth) = ?", year) - end - - scope :search_by_date_of_birth_month, - ->(month) do - where("extract(month from patients.date_of_birth) = ?", month) - end - - scope :search_by_date_of_birth_day, - ->(day) { where("extract(day from patients.date_of_birth) = ?", day) } - - scope :search_by_nhs_number, - ->(nhs_number) { merge(Patient.search_by_nhs_number(nhs_number)) } - - scope :order_by_name, - -> do - joins(:patient).order( - "LOWER(patients.family_name)", - "LOWER(patients.given_name)" - ) - end - - scope :has_consent_status, - ->(status, programme:, vaccine_method: nil) do - consent_status_scope = - Patient::ConsentStatus - .where("patient_id = patient_locations.patient_id") - .where("academic_year = patient_locations.academic_year") - .where(status:, programme:) - - if vaccine_method - consent_status_scope = - consent_status_scope.has_vaccine_method(vaccine_method) - end - - where(consent_status_scope.arel.exists) - end - - scope :has_registration_status, - ->(status, session:) do - where( - Patient::RegistrationStatus - .where("patient_id = patient_locations.patient_id") - .where(session:, status:) - .arel - .exists - ) - end - - scope :has_triage_status, - ->(status, programme:) do - where( - Patient::TriageStatus - .where("patient_id = patient_locations.patient_id") - .where("academic_year = patient_locations.academic_year") - .where(status:, programme:) - .arel - .exists - ) - end - - scope :has_vaccination_status, - ->(status, programme:) do - where( - Patient::VaccinationStatus - .where("patient_id = patient_locations.patient_id") - .where("academic_year = patient_locations.academic_year") - .where(status:, programme:) - .arel - .exists - ) - end - - scope :has_vaccine_method, - ->(vaccine_method, programme:) do - where( - Patient::TriageStatus - .where("patient_id = patient_locations.patient_id") - .where("academic_year = patient_locations.academic_year") - .where(vaccine_method:, programme:) - .arel - .exists - ).or( - where( - Patient::TriageStatus - .where("patient_id = patient_locations.patient_id") - .where("academic_year = patient_locations.academic_year") - .where(status: "not_required", programme:) - .arel - .exists - ).where( - Patient::ConsentStatus - .where("patient_id = patient_locations.patient_id") - .where("academic_year = patient_locations.academic_year") - .where(programme:) - .has_vaccine_method(vaccine_method) - .arel - .exists - ) - ) - end - - scope :consent_given_and_ready_to_vaccinate, - ->(programmes:, academic_year:, vaccine_method:) do - select do |patient_location| - patient = patient_location.patient - - programmes.any? do |programme| - patient.consent_given_and_safe_to_vaccinate?( - programme:, - academic_year:, - vaccine_method: - ) - end - end - end - - scope :without_patient_specific_direction, - ->(programme:, team:) do - where.not( - PatientSpecificDirection - .where("patient_id = patient_locations.patient_id") - .where("academic_year = patient_locations.academic_year") - .where(programme:, team:) - .not_invalidated - .arel - .exists - ) - end - - scope :has_patient_specific_direction, - ->(programme:, team:) do where( - PatientSpecificDirection - .where("patient_id = patient_locations.patient_id") - .where("academic_year = patient_locations.academic_year") - .where(programme:, team:) - .not_invalidated - .arel - .exists + id: + joins_session_programmes + .joins_location_programme_year_groups + .where( + session_programmes: { + programme_id: programmes.map(&:id) + } + ) + .select("patient_locations.id") ) end diff --git a/app/models/session.rb b/app/models/session.rb index 28253dce5d..d07e92e260 100644 --- a/app/models/session.rb +++ b/app/models/session.rb @@ -68,10 +68,22 @@ class Session < ApplicationRecord accepts_nested_attributes_for :session_dates, allow_destroy: true scope :joins_patient_locations, -> { joins(<<-SQL) } - INNER JOIN patient_locations - ON patient_locations.location_id = sessions.location_id - AND patient_locations.academic_year = sessions.academic_year - SQL + INNER JOIN patient_locations + ON patient_locations.location_id = sessions.location_id + AND patient_locations.academic_year = sessions.academic_year + SQL + + scope :joins_patients, -> { joins(<<-SQL) } + INNER JOIN patients + ON patients.id = patient_locations.patient_id + SQL + + scope :joins_location_programme_year_groups, -> { joins(<<-SQL) } + INNER JOIN location_programme_year_groups + ON location_programme_year_groups.location_id = sessions.location_id + AND location_programme_year_groups.programme_id = session_programmes.programme_id + AND location_programme_year_groups.year_group = sessions.academic_year - patients.birth_academic_year - #{Integer::AGE_CHILDREN_START_SCHOOL} + SQL scope :has_date, ->(value) { where(SessionDate.for_session.where(value:).arel.exists) } @@ -184,6 +196,21 @@ class Session < ApplicationRecord delegate :clinic?, :generic_clinic?, :school?, to: :location + def programme_birth_academic_years + @programme_birth_academic_years ||= + ProgrammeBirthAcademicYears.new(programme_year_groups, academic_year:) + end + + def patients + birth_academic_years = + location_programme_year_groups.pluck_birth_academic_years(academic_year:) + + Patient + .joins_sessions + .where(sessions: { id: }) + .where(birth_academic_year: birth_academic_years) + end + def to_param = slug def today? = dates.any?(&:today?) diff --git a/app/views/programmes/sessions/index.html.erb b/app/views/programmes/sessions/index.html.erb index 7f85f6ff85..6aeffa0992 100644 --- a/app/views/programmes/sessions/index.html.erb +++ b/app/views/programmes/sessions/index.html.erb @@ -12,4 +12,4 @@ <%= render AppProgrammeNavigationComponent.new(@programme, @academic_year, active: :sessions) %> -<%= render AppProgrammeSessionTableComponent.new(@sessions, programme: @programme) %> +<%= render AppProgrammeSessionTableComponent.new(@sessions, programme: @programme, academic_year: @academic_year) %> diff --git a/app/views/sessions/consent/show.html.erb b/app/views/sessions/consent/show.html.erb index f7a8a9ff02..8094ee63ee 100644 --- a/app/views/sessions/consent/show.html.erb +++ b/app/views/sessions/consent/show.html.erb @@ -22,9 +22,9 @@
<%= render AppSessionNeedsReviewWarningComponent.new(session: @session) %> <%= render AppSearchResultsComponent.new(@pagy, label: "children") do %> - <% @patient_locations.each do |patient_location| %> + <% @patients.each do |patient| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_location.patient, + patient:, session: @session, context: :consent, programmes: @form.programmes, diff --git a/app/views/sessions/patient_specific_directions/show.html.erb b/app/views/sessions/patient_specific_directions/show.html.erb index a297d0e3fc..c73e654abe 100644 --- a/app/views/sessions/patient_specific_directions/show.html.erb +++ b/app/views/sessions/patient_specific_directions/show.html.erb @@ -38,9 +38,9 @@ <%= render AppSessionNeedsReviewWarningComponent.new(session: @session) %> <%= render AppSearchResultsComponent.new(@pagy, label: "children", heading: "Review PSDs") do %> - <% @patient_locations.each do |patient_location| %> + <% @patients.each do |patient| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_location.patient, + patient:, session: @session, context: :patient_specific_direction, ) %> diff --git a/app/views/sessions/patients/show.html.erb b/app/views/sessions/patients/show.html.erb index 3259ca93f0..76bc9d1e1c 100644 --- a/app/views/sessions/patients/show.html.erb +++ b/app/views/sessions/patients/show.html.erb @@ -22,9 +22,9 @@
<%= render AppSessionNeedsReviewWarningComponent.new(session: @session) %> <%= render AppSearchResultsComponent.new(@pagy, label: "children") do %> - <% @patient_locations.each do |patient_location| %> + <% @patients.each do |patient| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_location.patient, + patient:, session: @session, context: :patients, programmes: @form.programmes, diff --git a/app/views/sessions/record/show.html.erb b/app/views/sessions/record/show.html.erb index 3899d686c7..d82a377be7 100644 --- a/app/views/sessions/record/show.html.erb +++ b/app/views/sessions/record/show.html.erb @@ -24,9 +24,9 @@ <%= render AppSessionNeedsReviewWarningComponent.new(session: @session) %> <%= render AppVaccinationsSummaryTableComponent.new(current_user:, session: @session, request_session: session) %> <%= render AppSearchResultsComponent.new(@pagy, label: "children") do %> - <% @patient_locations.each do |patient_location| %> + <% @patients.each do |patient| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_location.patient, + patient:, session: @session, context: :record, programmes: @form.programmes, diff --git a/app/views/sessions/register/show.html.erb b/app/views/sessions/register/show.html.erb index 9e8a087409..4df9a18f34 100644 --- a/app/views/sessions/register/show.html.erb +++ b/app/views/sessions/register/show.html.erb @@ -24,9 +24,9 @@
<%= render AppSessionNeedsReviewWarningComponent.new(session: @session) %> <%= render AppSearchResultsComponent.new(@pagy, label: "children") do %> - <% @patient_locations.each do |patient_location| %> + <% @patients.each do |patient| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_location.patient, + patient:, session: @session, context: :register, programmes: @form.programmes, diff --git a/app/views/sessions/triage/show.html.erb b/app/views/sessions/triage/show.html.erb index 3e56b5514a..d4e49a14cf 100644 --- a/app/views/sessions/triage/show.html.erb +++ b/app/views/sessions/triage/show.html.erb @@ -22,9 +22,9 @@
<%= render AppSessionNeedsReviewWarningComponent.new(session: @session) %> <%= render AppSearchResultsComponent.new(@pagy, label: "children") do %> - <% @patient_locations.each do |patient_location| %> + <% @patients.each do |patient| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_location.patient, + patient:, session: @session, context: :triage, programmes: @form.programmes, diff --git a/spec/components/app_programme_session_table_component_spec.rb b/spec/components/app_programme_session_table_component_spec.rb index fc96c1a13a..e96ea17172 100644 --- a/spec/components/app_programme_session_table_component_spec.rb +++ b/spec/components/app_programme_session_table_component_spec.rb @@ -3,9 +3,10 @@ describe AppProgrammeSessionTableComponent do subject(:rendered) { render_inline(component) } - let(:component) { described_class.new(sessions, programme:) } + let(:component) { described_class.new(sessions, programme:, academic_year:) } let(:programme) { create(:programme) } + let(:academic_year) { AcademicYear.current } let(:location) do create(:school, name: "Waterloo Road", programmes: [programme]) end diff --git a/spec/features/parental_consent_flu_spec.rb b/spec/features/parental_consent_flu_spec.rb index 332cd0c0c2..ae2bd375aa 100644 --- a/spec/features/parental_consent_flu_spec.rb +++ b/spec/features/parental_consent_flu_spec.rb @@ -39,7 +39,7 @@ def given_a_flu_programme_is_underway @programme = create(:programme, :flu) @team = create(:team, :with_one_nurse, programmes: [@programme]) - location = create(:school, name: "Pilot School") + location = create(:school, name: "Pilot School", programmes: [@programme]) @session = create(:session, :scheduled, programmes: [@programme], location:) @child = create(:patient, session: @session) end diff --git a/spec/forms/patient_search_form_spec.rb b/spec/forms/patient_search_form_spec.rb index c8485df99e..ff4be12440 100644 --- a/spec/forms/patient_search_form_spec.rb +++ b/spec/forms/patient_search_form_spec.rb @@ -59,7 +59,7 @@ let(:empty_params) { {} } - context "for patients" do + describe "#apply" do let(:scope) { Patient.all } let(:session_for_patients) { create(:session, team:, programmes:) } @@ -217,8 +217,8 @@ end end - context "filtering on programmes" do - let(:consent_status) { nil } + context "filtering on consent status" do + let(:consent_statuses) { %w[given refused] } let(:date_of_birth_day) { nil } let(:date_of_birth_month) { nil } let(:date_of_birth_year) { nil } @@ -230,133 +230,41 @@ let(:triage_status) { nil } let(:year_groups) { nil } - let(:programme) { create(:programme, :menacwy) } - let(:programmes) { [programme] } - - context "with a patient eligible for the programme" do - let(:patient) { create(:patient, session: session_for_patients) } + let(:session) { session_for_patients } - it "is included" do - expect(form.apply(scope)).to include(patient) - end - end + it "filters on consent status" do + patient_given = + create(:patient, :consent_given_triage_not_needed, session:) - context "with a patient not eligible for the programme" do - let(:patient) { create(:patient, year_group: 8) } + patient_refused = create(:patient, :consent_refused, session:) - it "is not included" do - expect(form.apply(scope)).not_to include(patient) - end + expect(form.apply(scope)).to contain_exactly( + patient_given, + patient_refused + ) end - end - context "filtering on programme status" do - let(:consent_statuses) { nil } - let(:date_of_birth_day) { nil } - let(:date_of_birth_month) { nil } - let(:date_of_birth_year) { nil } - let(:missing_nhs_number) { nil } - let(:vaccination_status) { "vaccinated" } - let(:programme_types) { programmes.map(&:type) } - let(:q) { nil } - let(:register_status) { nil } - let(:triage_status) { nil } - let(:year_groups) { nil } + context "with combined consent status and vaccine method" do + let(:consent_statuses) { %w[given_nasal] } + + it "filters on consent status" do + patient_given_nasal = + create( + :patient, + :consent_given_nasal_only_triage_not_needed, + session: + ) - it "filters on programme status" do - patient = create( :patient, - :vaccinated, - programmes:, - session: session_for_patients - ) - - expect(form.apply(scope)).to include(patient) - end - end - - context "searching on name" do - let(:consent_statuses) { nil } - let(:date_of_birth_day) { nil } - let(:date_of_birth_month) { nil } - let(:date_of_birth_year) { nil } - let(:missing_nhs_number) { nil } - let(:vaccination_status) { nil } - let(:q) { nil } - let(:register_status) { nil } - let(:triage_status) { nil } - let(:year_groups) { nil } - - let(:patient_a) do - create( - :patient, - given_name: "Harry", - family_name: "Potter", - session: session_for_patients - ) - end - let(:patient_b) do - create( - :patient, - given_name: "Hari", - family_name: "Potter", - session: session_for_patients - ) - end - let(:patient_c) do - create( - :patient, - given_name: "Arry", - family_name: "Pott", - session: session_for_patients - ) - end - let(:patient_d) do - create( - :patient, - given_name: "Ron", - family_name: "Weasley", - session: session_for_patients - ) - end - let(:patient_e) do - create( - :patient, - given_name: "Ginny", - family_name: "Weasley", - session: session_for_patients - ) - end - - context "with no search query" do - let(:q) { nil } - - it "sorts alphabetically by name" do - expect(form.apply(scope)).to eq( - [patient_c, patient_b, patient_a, patient_e, patient_d] + :consent_given_injection_only_triage_not_needed, + session: ) - end - end - context "with some search query" do - let(:q) { "Harry Potter" } - - it "sorts by similarity" do - expect(form.apply(scope)).to eq([patient_a, patient_b, patient_c]) + expect(form.apply(scope)).to contain_exactly(patient_given_nasal) end end end - end - - context "for patient sessions" do - let(:scope) { PatientLocation.all } - - let(:session) { create(:session, programmes:, team:) } - - it "doesn't raise an error" do - expect { form.apply(scope) }.not_to raise_error - end context "filtering on programmes" do let(:consent_status) { nil } @@ -371,75 +279,49 @@ let(:triage_status) { nil } let(:year_groups) { nil } - let(:programmes) { [create(:programme, :menacwy)] } - - context "with a patient session eligible for the programme" do - let(:patient) { create(:patient, year_group: 9) } + let(:programme) { create(:programme, :menacwy) } + let(:programmes) { [programme] } - let(:patient_location) { create(:patient_location, patient:, session:) } + context "with a patient eligible for the programme" do + let(:patient) { create(:patient, session: session_for_patients) } it "is included" do - expect(form.apply(scope)).to include(patient_location) + expect(form.apply(scope)).to include(patient) end end - context "with a patient session not eligible for the programme" do + context "with a patient not eligible for the programme" do let(:patient) { create(:patient, year_group: 8) } - let(:patient_location) { create(:patient_location, patient:, session:) } - it "is not included" do - expect(form.apply(scope)).not_to include(patient_location) + expect(form.apply(scope)).not_to include(patient) end end end - context "filtering on consent status" do - let(:consent_statuses) { %w[given refused] } + context "filtering on programme status" do + let(:consent_statuses) { nil } let(:date_of_birth_day) { nil } let(:date_of_birth_month) { nil } let(:date_of_birth_year) { nil } let(:missing_nhs_number) { nil } - let(:vaccination_status) { nil } + let(:vaccination_status) { "vaccinated" } let(:programme_types) { programmes.map(&:type) } let(:q) { nil } let(:register_status) { nil } let(:triage_status) { nil } let(:year_groups) { nil } - it "filters on consent status" do - patient_given = - create(:patient, :consent_given_triage_not_needed, session:) - - patient_refused = create(:patient, :consent_refused, session:) - - expect(form.apply(scope)).to contain_exactly( - patient_given.patient_locations.first, - patient_refused.patient_locations.first - ) - end - - context "with combined consent status and vaccine method" do - let(:consent_statuses) { %w[given_nasal] } - - it "filters on consent status" do - patient_location_given_nasal = - create( - :patient, - :consent_given_nasal_only_triage_not_needed, - session: - ).patient_locations.first - + it "filters on programme status" do + patient = create( :patient, - :consent_given_injection_only_triage_not_needed, - session: + :vaccinated, + programmes:, + session: session_for_patients ) - expect(form.apply(scope)).to contain_exactly( - patient_location_given_nasal - ) - end + expect(form.apply(scope)).to include(patient) end end @@ -455,10 +337,11 @@ let(:triage_status) { nil } let(:year_groups) { nil } + let(:session) { session_for_patients } + it "filters on register status" do patient = create(:patient, :in_attendance, session:) - patient_location = patient.patient_locations.first - expect(form.apply(scope)).to include(patient_location) + expect(form.apply(scope)).to include(patient) end end @@ -475,11 +358,12 @@ let(:triage_status) { "required" } let(:year_groups) { nil } + let(:session) { session_for_patients } + it "filters on triage status" do patient = create(:patient, :consent_given_triage_needed, session:) - patient_location = patient.patient_locations.first - expect(form.apply(scope)).to include(patient_location) + expect(form.apply(scope)).to include(patient) end end @@ -496,28 +380,25 @@ let(:triage_status) { nil } let(:year_groups) { nil } - let!(:patient_location_with_psd) do - create(:patient_location, session:).tap do |patient_location| - create( - :patient_specific_direction, - patient: patient_location.patient, - programme: programmes.first, - team: - ) - end - end + let(:session) { session_for_patients } + + let!(:patient_with_psd) { create(:patient, session:) } + let!(:patient_without_psd) { create(:patient, session:) } - let!(:patient_location_without_psd) do - create(:patient_location, session:) + before do + create( + :patient_specific_direction, + patient: patient_with_psd, + programme: programmes.first, + team: + ) end context "when status is 'added'" do let(:patient_specific_direction_status) { "added" } it "finds the patient with the PSD" do - expect(form.apply(scope)).to contain_exactly( - patient_location_with_psd - ) + expect(form.apply(scope)).to contain_exactly(patient_with_psd) end end @@ -525,9 +406,7 @@ let(:patient_specific_direction_status) { "not_added" } it "finds the patient that has no PSD" do - expect(form.apply(scope)).to contain_exactly( - patient_location_without_psd - ) + expect(form.apply(scope)).to contain_exactly(patient_without_psd) end end end @@ -546,7 +425,7 @@ let(:vaccine_method) { "nasal" } let(:year_groups) { nil } - let(:programme) { create(:programme, :flu) } + let(:session) { session_for_patients } it "filters on vaccine method" do nasal_patient = @@ -573,10 +452,80 @@ vaccine_methods: %w[injection nasal] ) - expect(form.apply(scope)).to contain_exactly( - nasal_patient.patient_locations.first + expect(form.apply(scope)).to contain_exactly(nasal_patient) + end + end + + context "searching on name" do + let(:consent_statuses) { nil } + let(:date_of_birth_day) { nil } + let(:date_of_birth_month) { nil } + let(:date_of_birth_year) { nil } + let(:missing_nhs_number) { nil } + let(:vaccination_status) { nil } + let(:q) { nil } + let(:register_status) { nil } + let(:triage_status) { nil } + let(:year_groups) { nil } + + let(:patient_a) do + create( + :patient, + given_name: "Harry", + family_name: "Potter", + session: session_for_patients ) end + let(:patient_b) do + create( + :patient, + given_name: "Hari", + family_name: "Potter", + session: session_for_patients + ) + end + let(:patient_c) do + create( + :patient, + given_name: "Arry", + family_name: "Pott", + session: session_for_patients + ) + end + let(:patient_d) do + create( + :patient, + given_name: "Ron", + family_name: "Weasley", + session: session_for_patients + ) + end + let(:patient_e) do + create( + :patient, + given_name: "Ginny", + family_name: "Weasley", + session: session_for_patients + ) + end + + context "with no search query" do + let(:q) { nil } + + it "sorts alphabetically by name" do + expect(form.apply(scope)).to eq( + [patient_c, patient_b, patient_a, patient_e, patient_d] + ) + end + end + + context "with some search query" do + let(:q) { "Harry Potter" } + + it "sorts by similarity" do + expect(form.apply(scope)).to eq([patient_a, patient_b, patient_c]) + end + end end end diff --git a/spec/jobs/enqueue_vaccinations_search_in_nhs_job_spec.rb b/spec/jobs/enqueue_vaccinations_search_in_nhs_job_spec.rb index ac2116ae8f..a8b832bd26 100644 --- a/spec/jobs/enqueue_vaccinations_search_in_nhs_job_spec.rb +++ b/spec/jobs/enqueue_vaccinations_search_in_nhs_job_spec.rb @@ -43,13 +43,6 @@ it { should have_received(:perform_bulk).once.with([[patient.id]]) } - context "community clinic session" do - let(:location) { create(:community_clinic, team:, programmes: [flu]) } - let(:school) { create(:school, team:, programmes: [flu]) } - - it { should have_received(:perform_bulk).exactly(:once) } - end - context "generic clinic session" do let(:location) { create(:generic_clinic, team:, programmes: [flu]) } let(:school) { create(:school, team:, programmes: [flu]) } diff --git a/spec/jobs/patients_aged_out_of_school_job_spec.rb b/spec/jobs/patients_aged_out_of_school_job_spec.rb index 465daf8ad3..6b7f40c3e9 100644 --- a/spec/jobs/patients_aged_out_of_school_job_spec.rb +++ b/spec/jobs/patients_aged_out_of_school_job_spec.rb @@ -13,9 +13,6 @@ let(:date_of_birth) { Date.new(2008, 1, 1) } let!(:patient) { create(:patient, school:, date_of_birth:) } - let!(:clinic_session) do - team.generic_clinic_session(academic_year: AcademicYear.pending) - end context "on the last day of July" do let(:today) { Date.new(2024, 7, 31) } @@ -58,8 +55,8 @@ end it "adds the patient to the clinic session" do - expect { perform }.to change { patient.sessions.count }.by(1) - expect(patient.sessions).to include(clinic_session) + expect { perform }.to change { patient.locations.count }.by(1) + expect(patient.locations).to include(team.generic_clinic) end end end diff --git a/spec/lib/patient_archiver_spec.rb b/spec/lib/patient_archiver_spec.rb index 965f0466a0..a227967519 100644 --- a/spec/lib/patient_archiver_spec.rb +++ b/spec/lib/patient_archiver_spec.rb @@ -21,8 +21,7 @@ context "when in upcoming sessions" do let(:session) { create(:session, :tomorrow, team:) } - - before { create(:patient_location, patient:, session:) } + let(:patient) { create(:patient, session:) } it "removes the patient from the sessions" do expect(patient.sessions).to include(session) diff --git a/spec/models/patient_location_spec.rb b/spec/models/patient_location_spec.rb index 6b18071ad8..22f3cc5fc8 100644 --- a/spec/models/patient_location_spec.rb +++ b/spec/models/patient_location_spec.rb @@ -39,7 +39,10 @@ describe "scopes" do describe "#appear_in_programmes" do subject(:scope) do - described_class.joins(:patient).appear_in_programmes(programmes) + described_class + .joins_sessions + .joins(:patient) + .appear_in_programmes(programmes) end let(:programmes) { create_list(:programme, 1, :td_ipv) } @@ -80,65 +83,6 @@ it { should_not include(patient_location) } end end - - describe "#consent_given_and_ready_to_vaccinate" do - subject(:scope) do - described_class.consent_given_and_ready_to_vaccinate( - programmes:, - academic_year:, - vaccine_method: - ) - end - - let(:programmes) { [create(:programme, :flu), create(:programme, :hpv)] } - let(:session) { create(:session, programmes:) } - let(:academic_year) { Date.current.academic_year } - let(:vaccine_method) { nil } - let(:patient_location) { patient.patient_locations.first } - - it { should be_empty } - - context "with a patient eligible for vaccination" do - let(:patient) do - create(:patient, :consent_given_triage_not_needed, session:) - end - - it { should include(patient_location) } - end - - context "when filtering on nasal spray" do - let(:vaccine_method) { "nasal" } - - context "with a patient eligible for vaccination" do - let(:patient) do - create(:patient, :consent_given_triage_not_needed, session:) - end - - before do - patient.consent_status( - programme: programmes.first, - academic_year: - ).update!(vaccine_methods: %w[nasal injection]) - end - - it { should include(patient_location) } - - context "when the patient has been vaccinated for flu" do - before do - create( - :vaccination_record, - programme: programmes.first, - session:, - patient: - ) - StatusUpdater.call(session:, patient:) - end - - it { should_not include(patient_location) } - end - end - end - end end describe "#safe_to_destroy?" do diff --git a/spec/models/patient_spec.rb b/spec/models/patient_spec.rb index 8e06e7f1ec..590b82ea0b 100644 --- a/spec/models/patient_spec.rb +++ b/spec/models/patient_spec.rb @@ -360,6 +360,64 @@ it { should eq([patient_a, patient_b, patient_c]) } end + + describe "#consent_given_and_ready_to_vaccinate" do + subject(:scope) do + described_class.consent_given_and_ready_to_vaccinate( + programmes:, + academic_year:, + vaccine_method: + ) + end + + let(:programmes) { [create(:programme, :flu), create(:programme, :hpv)] } + let(:session) { create(:session, programmes:) } + let(:academic_year) { Date.current.academic_year } + let(:vaccine_method) { nil } + + it { should be_empty } + + context "with a patient eligible for vaccination" do + let(:patient) do + create(:patient, :consent_given_triage_not_needed, session:) + end + + it { should include(patient) } + end + + context "when filtering on nasal spray" do + let(:vaccine_method) { "nasal" } + + context "with a patient eligible for vaccination" do + let(:patient) do + create(:patient, :consent_given_triage_not_needed, session:) + end + + before do + patient.consent_status( + programme: programmes.first, + academic_year: + ).update!(vaccine_methods: %w[nasal injection]) + end + + it { should include(patient) } + + context "when the patient has been vaccinated for flu" do + before do + create( + :vaccination_record, + programme: programmes.first, + session:, + patient: + ) + StatusUpdater.call(session:, patient:) + end + + it { should_not include(patient) } + end + end + end + end end describe "validations" do @@ -878,12 +936,14 @@ ) end - before { create(:patient_location, patient:, session:) } + let!(:patient_location) do + create(:patient_location, patient:, session:) + end it "removes the patient from the session" do - expect(session.patients).to include(patient) + expect(patient.patient_locations).to include(patient_location) update_from_pds! - expect(session.patients).not_to include(patient) + expect(patient.patient_locations).not_to include(patient_location) end it "archives the patient" do diff --git a/spec/models/school_move_spec.rb b/spec/models/school_move_spec.rb index 6c9a929228..ab93680a7f 100644 --- a/spec/models/school_move_spec.rb +++ b/spec/models/school_move_spec.rb @@ -167,7 +167,7 @@ let(:academic_year) { AcademicYear.pending } context "with a patient in no sessions" do - let(:patient) { create(:patient, team: nil) } + let(:patient) { create(:patient, team: nil, programmes:) } context "to a school with a scheduled session" do let(:school_move) do @@ -228,7 +228,7 @@ end context "with an archived patient" do - let(:patient) { create(:patient, team: nil) } + let(:patient) { create(:patient, team: nil, programmes:) } before { create(:archive_reason, :imported_in_error, patient:, team:) }