diff --git a/app/components/app_activity_log_component.rb b/app/components/app_activity_log_component.rb index 247e6cf816..e0a914c2c0 100644 --- a/app/components/app_activity_log_component.rb +++ b/app/components/app_activity_log_component.rb @@ -63,12 +63,13 @@ def initialize(team:, patient:, session: nil) session ? scope.where(programme_ids: session.programmes.ids) : scope end - @patient_sessions = + @patient_locations = @patient - .patient_sessions - .includes_programmes - .includes(session: :location) - .then { |scope| session ? scope.where(session:) : scope } + .patient_locations + .includes(:location) + .then do |scope| + session ? scope.where(location: session.location) : scope + end @patient_specific_directions = @patient @@ -105,7 +106,7 @@ def initialize(team:, patient:, session: nil) :notes, :notify_log_entries, :patient, - :patient_sessions, + :patient_locations, :patient_specific_directions, :pre_screenings, :attendance_records, @@ -346,15 +347,11 @@ 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| [ { - title: "Added to the session at #{session.location.name}", - at: patient_session.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 7464847b37..e2e8ccf29d 100644 --- a/app/components/app_patient_programmes_table_component.rb +++ b/app/components/app_patient_programmes_table_component.rb @@ -258,7 +258,7 @@ 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.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..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_sessions(session:).count) + format_number(patients(session:).count) end def no_response_scope(session:) - patient_sessions(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_sessions(session:).count + patients(session:).count ) end def triage_needed_count(session:) format_number( - patient_sessions(session:).has_triage_status(:required, programme:).count + patients(session:).has_triage_status( + :required, + programme:, + academic_year: + ).count ) end @@ -48,15 +57,22 @@ def vaccinated_count(session:) def vaccinated_percentage(session:) format_percentage( vaccinated_scope(session:).count, - patient_sessions(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_sessions(session:) - session - .patient_sessions - .joins(:patient, :session) - .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 98f1d10fc7..59f8cb729f 100644 --- a/app/components/app_session_actions_component.rb +++ b/app/components/app_session_actions_component.rb @@ -19,12 +19,7 @@ def render? = rows.any? delegate :govuk_summary_list, to: :helpers delegate :academic_year, :programmes, to: :session - def patient_sessions - session - .patient_sessions - .joins(:patient, :session) - .appear_in_programmes(programmes) - end + def patients = session.patients def rows @rows ||= [ @@ -38,7 +33,7 @@ def rows end def no_nhs_number_row - count = patient_sessions.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:) @@ -47,7 +42,11 @@ def no_nhs_number_row def no_consent_response_row status = "no_response" count = - patient_sessions.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 = [ { @@ -61,7 +60,11 @@ def no_consent_response_row def conflicting_consent_row status = "conflicts" count = - patient_sessions.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:) @@ -70,7 +73,11 @@ def conflicting_consent_row def triage_required_row status = "required" count = - patient_sessions.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:) @@ -80,7 +87,7 @@ def register_attendance_row return nil unless session.requires_registration? && session.today? status = "unknown" - count = patient_sessions.has_registration_status(status).count + count = patients.has_registration_status(status, session:).count href = session_register_path(session, register_status: status) generate_row(:children_to_register, count:, href:) @@ -91,13 +98,11 @@ def ready_for_vaccinator_row counts_by_programme = session.programmes.index_with do |programme| - patient_sessions - .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?( + patients + .has_registration_status(%w[attending completed], session:) + .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 5abb39c2ad..e9d3aadef3 100644 --- a/app/components/app_session_details_summary_component.rb +++ b/app/components/app_session_details_summary_component.rb @@ -14,17 +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_sessions - session - .patient_sessions - .joins(:patient, :session) - .appear_in_programmes(programmes) - end + def patients = session.patients def cohort_row - count = patient_sessions.count + count = patients.count { key: { text: "Cohort" }, value: { text: I18n.t("children", count:) } } end @@ -33,7 +28,11 @@ def consent_refused_row status = "refused" count = - patient_sessions.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 14c4ee1ac1..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_sessions.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_sessions - @session - .patient_sessions - .joins(:patient, :session) - .appear_in_programmes(@session.programmes) - end + def patients = @session.patients end diff --git a/app/controllers/api/testing/teams_controller.rb b/app/controllers/api/testing/teams_controller.rb index 98ab0b3eab..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(PatientSession.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)) @@ -41,7 +43,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/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 dde01ee584..35d4e04adb 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,13 @@ 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, + 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 b30d4465e0..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) - - PatientSession.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 2b88881161..6f499e7fc8 100644 --- a/app/controllers/programmes/base_controller.rb +++ b/app/controllers/programmes/base_controller.rb @@ -22,21 +22,11 @@ def set_academic_year def patient_ids @patient_ids ||= - PatientSession - .distinct - .joins(:patient, :session) - .where(session_id: 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 10bf452bb1..8e2f419622 100644 --- a/app/controllers/sessions/consent_controller.rb +++ b/app/controllers/sessions/consent_controller.rb @@ -27,16 +27,16 @@ def show scope = @session - .patient_sessions - .includes_programmes - .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_sessions = @form.apply(scope) - @pagy, @patient_sessions = pagy(patient_sessions) + patients = @form.apply(scope) + @pagy, @patients = pagy(patients) 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..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_sessions.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) + 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_sessions_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_sessions_allowed_psd.map do |patient_session| + patients_allowed_psd.map do |patient| PatientSpecificDirection.new( academic_year: @session.academic_year, created_by: current_user, delivery_site: "nose", - patient_id: patient_session.patient_id, + patient:, programme: @programme, team: current_team, vaccine: @vaccine, @@ -70,18 +67,24 @@ def psds_to_create end end - def patient_sessions_allowed_psd - @patient_sessions_allowed_psd ||= + def patients_allowed_psd + @patients_allowed_psd ||= @session - .patient_sessions + .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 87f0390318..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_sessions.includes_programmes.includes( - patient: [:vaccination_statuses, { notes: :created_by }] - ) + @session.patients.includes(:vaccination_statuses, notes: :created_by) - patient_sessions = @form.apply(scope) - @pagy, @patient_sessions = pagy(patient_sessions) + 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 0655f461d4..e54c09add3 100644 --- a/app/controllers/sessions/record_controller.rb +++ b/app/controllers/sessions/record_controller.rb @@ -15,28 +15,31 @@ class Sessions::RecordController < ApplicationController def show scope = - @session.patient_sessions.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? - scope = scope.has_registration_status(%w[attending completed]) + scope = + scope.has_registration_status( + %w[attending completed], + session: @session + ) end - patient_sessions = + patients = filter_on_vaccine_method_or_patient_specific_direction( @form.apply(scope) ).consent_given_and_ready_to_vaccinate( programmes: @form.programmes, + academic_year: @session.academic_year, vaccine_method: @form.vaccine_method.presence ) - @pagy, @patient_sessions = pagy_array(patient_sessions) + @pagy, @patients = pagy_array(patients) render layout: "full" end @@ -103,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 dc5d30c1c0..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_sessions.includes_programmes.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_sessions = @form.apply(scope) - @pagy, @patient_sessions = pagy(patient_sessions) + 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 206e8528c0..e159a965d9 100644 --- a/app/controllers/sessions/triage_controller.rb +++ b/app/controllers/sessions/triage_controller.rb @@ -13,13 +13,16 @@ def show scope = @session - .patient_sessions - .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) + .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/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 0c2c07519e..81f0f532e0 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -17,11 +17,12 @@ def index sessions = @form.apply(scope) @patient_count_by_session_id = - PatientSession - .where(session_id: sessions.map(&:id)) - .joins(:patient, :session) + PatientLocation + .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/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/forms/patient_search_form.rb b/app/forms/patient_search_form.rb index 25a22b7301..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, :session).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) + 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/jobs/concerns/send_school_consent_notification_concern.rb b/app/jobs/concerns/send_school_consent_notification_concern.rb index c183e1d240..c297e1985d 100644 --- a/app/jobs/concerns/send_school_consent_notification_concern.rb +++ b/app/jobs/concerns/send_school_consent_notification_concern.rb @@ -9,11 +9,10 @@ def patient_programmes_eligible_for_notification(session:) return unless session.school? && session.open_for_consent? session - .patient_sessions - .includes_programmes + .patient_locations .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..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,14 +17,11 @@ def perform(sessions = nil) .references(:session_dates) end - patient_ids = - Patient - .includes(:sessions) - .where(patient_sessions: { session: sessions.pluck(:id) }) - .ids + 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/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 74% rename from app/lib/clinic_patient_sessions_factory.rb rename to app/lib/clinic_patient_locations_factory.rb index e3f38fd91e..ef81d452e9 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,9 +21,10 @@ def patient_sessions_to_create programmes:, session_date: ) - PatientSession.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 65f5f6550b..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_sessions.each do |patient_session| - patient = patient_session.patient - session = patient_session.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_session.location.name if patient_session.session.clinic? - - vaccination_records << FactoryBot.build( - :vaccination_record, - :administered, - patient: patient_session.patient, - programme:, - team:, - performed_by:, - session: patient_session.session, - vaccine:, - batch:, - location_name: - ) end AttendanceRecord.import!(attendance_records) @@ -58,39 +56,39 @@ def create_vaccinations StatusUpdater.call(patient: vaccination_records.map(&:patient)) end - def random_patient_sessions + def random_patients_for(session:) if administered&.positive? - patient_sessions + patients_for(session:) .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 + patients_for(session:) end end - def patient_sessions - (session.presence || team) - .patient_sessions - .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 e8be90a105..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:) - PatientSession.import!( - %i[patient_id session_id], - patient_ids.map { [it, session.id] }, + def add_patients! + PatientLocation.import!( + %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 870c38d4b9..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) @@ -19,15 +21,14 @@ def call(source_urn:, target_urn:) return end - unless PatientSession - .joins(:patient, :session) + unless PatientLocation + .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 31e4fdc72c..f8b21acf26 100644 --- a/app/lib/patient_merger.rb +++ b/app/lib/patient_merger.rb @@ -100,16 +100,17 @@ 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?( + academic_year: patient_location.academic_year, + location_id: patient_location.location_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/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/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/stats/organisations.rb b/app/lib/stats/organisations.rb index df907af5d9..4d80f98714 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 @@ -182,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 c1af25ff92..8ae1281d56 100644 --- a/app/lib/status_updater.rb +++ b/app/lib/status_updater.rb @@ -2,12 +2,17 @@ class StatusUpdater def initialize(patient: nil, session: nil) - scope = PatientSession + scope = PatientLocation.joins_sessions scope = scope.where(patient:) if patient - scope = scope.where(session:) if session - @patient_sessions = scope + 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 def call @@ -23,7 +28,7 @@ def self.call(...) = new(...).call private - attr_reader :patient_sessions + attr_reader :patient_locations def update_consent_statuses! Patient::ConsentStatus.import!( @@ -33,7 +38,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 +56,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_id, session_id) IN (?)", + patient_locations.select("patient_id", "sessions.id") ) .includes( :patient, @@ -87,7 +92,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 +115,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 +143,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,15 +169,15 @@ def vaccination_statuses_to_import end end - def patient_session_statuses_to_import - patient_sessions - .joins(:patient, :session) + def patient_location_statuses_to_import + patient_locations + .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:) @@ -202,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/lib/team_sessions_factory.rb b/app/lib/team_sessions_factory.rb index 465a0ff198..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_sessions) - .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 9a2cadc313..8fe3a99613 100644 --- a/app/models/class_import.rb +++ b/app/models/class_import.rb @@ -56,10 +56,10 @@ def postprocess_rows! existing_patients = Patient.where(birth_academic_year: birth_academic_years).where( - PatientSession - .joins(session: :location) + PatientLocation + .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.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..b1ad69ee23 100644 --- a/app/models/immunisation_import_row.rb +++ b/app/models/immunisation_import_row.rb @@ -156,8 +156,10 @@ def to_vaccination_record vaccination_record end - def to_patient_session - PatientSession.new(patient:, session:) if patient && session + def to_patient_location + 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/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 0e8389dbfd..c4fa6ba76c 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 @@ -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_sessions - has_many :teams, -> { distinct }, through: :sessions - - has_many :pending_sessions, - -> { where(academic_year: AcademicYear.pending) }, - through: :patient_sessions, - source: :session has_and_belongs_to_many :class_imports has_and_belongs_to_many :cohort_imports @@ -104,12 +97,30 @@ 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 :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 joins_archive_reasons(team:).where("archive_reasons.id IS NOT NULL") @@ -142,26 +153,34 @@ class Patient < ApplicationRecord scope :appear_in_programmes, ->(programmes, academic_year:) do where( - PatientSession - .joins(:session) - .where(sessions: { 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( - PatientSession - .joins(:session) - .where(sessions: { 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 @@ -246,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 } @@ -346,6 +439,25 @@ 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 + 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 @@ -503,9 +615,9 @@ def invalidate! end def not_in_team?(team:, academic_year:) - patient_sessions - .joins(:session) - .where(session: { academic_year:, team: }) + patient_locations + .joins(location: :subteam) + .where(academic_year:, subteams: { team_id: team.id }) .empty? end @@ -513,8 +625,10 @@ def dup_for_pending_changes dup.tap do |new_patient| new_patient.nhs_number = nil - pending_sessions.each do |session| - new_patient.patient_sessions.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 +644,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_sessions.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/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_location.rb b/app/models/patient_location.rb new file mode 100644 index 0000000000..e23f0acaec --- /dev/null +++ b/app/models/patient_location.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: patient_locations +# +# 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 +# +# 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) +# + +class PatientLocation < ApplicationRecord + audited associated_with: :patient + has_associated_audits + + belongs_to :patient + belongs_to :location + + has_many :sessions, + -> { where(academic_year: it.academic_year) }, + through: :location, + class_name: "Session" + + 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: :location + + has_many :gillick_assessments, + -> { where(patient_id: it.patient_id) }, + through: :sessions + + has_many :pre_screenings, + -> { where(patient_id: it.patient_id) }, + through: :sessions + + has_many :vaccination_records, + -> { 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 :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 :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 + where( + id: + joins_session_programmes + .joins_location_programme_year_groups + .where( + session_programmes: { + programme_id: programmes.map(&:id) + } + ) + .select("patient_locations.id") + ) + end + + scope :destroy_all_if_safe, + -> do + includes( + :attendance_records, + :gillick_assessments, + :pre_screenings, + :vaccination_records + ).find_each(&:destroy_if_safe!) + end + + def safe_to_destroy? + attendance_records.none?(&:attending?) && gillick_assessments.empty? && + pre_screenings.empty? && vaccination_records.empty? + end + + def destroy_if_safe! + destroy! if safe_to_destroy? + end +end diff --git a/app/models/patient_session.rb b/app/models/patient_session.rb deleted file mode 100644 index 89b1e8fe94..0000000000 --- a/app/models/patient_session.rb +++ /dev/null @@ -1,284 +0,0 @@ -# frozen_string_literal: true - -# == Schema Information -# -# Table name: patient_sessions -# -# 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 -# -# Indexes -# -# index_patient_sessions_on_patient_id_and_session_id (patient_id,session_id) UNIQUE -# index_patient_sessions_on_session_id (session_id) -# -# Foreign Keys -# -# 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 PatientSession < ApplicationRecord - audited associated_with: :patient - has_associated_audits - - belongs_to :patient - belongs_to :session - - has_one :location, through: :session - has_one :subteam, through: :session - has_one :team, through: :session - has_one :organisation, through: :team - - has_many :gillick_assessments, - -> { where(patient_id: it.patient_id) }, - through: :session - - has_many :pre_screenings, - -> { where(patient_id: it.patient_id) }, - through: :session - - has_many :attendance_records, - -> { where(patient_id: it.patient_id) }, - through: :location - - has_many :vaccination_records, - -> { where(session_id: it.session_id) }, - through: :patient - - has_and_belongs_to_many :immunisation_imports - - scope :archived, ->(team:) { merge(Patient.archived(team:)) } - - scope :not_archived, ->(team:) { merge(Patient.not_archived(team:)) } - - scope :appear_in_programmes, - ->(programmes) do - # Are any of the programmes administered in the session? - programme_in_session = - SessionProgramme - .where(programme: programmes) - .where("session_programmes.session_id = sessions.id") - .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_id = sessions.location_id") - .where( - "year_group = sessions.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 :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_sessions.patient_id") - .where("academic_year = sessions.academic_year") - .where(status:, programme:) - - if vaccine_method - consent_status_scope = - consent_status_scope.has_vaccine_method(vaccine_method) - end - - joins(:session).where(consent_status_scope.arel.exists) - end - - scope :has_registration_status, - ->(status) do - where( - Patient::RegistrationStatus - .where("patient_id = patient_sessions.patient_id") - .where("session_id = patient_sessions.session_id") - .where(status:) - .arel - .exists - ) - end - - scope :has_triage_status, - ->(status, programme:) do - joins(:session).where( - Patient::TriageStatus - .where("patient_id = patient_sessions.patient_id") - .where("academic_year = sessions.academic_year") - .where(status:, programme:) - .arel - .exists - ) - end - - scope :has_vaccination_status, - ->(status, programme:) do - joins(:session).where( - Patient::VaccinationStatus - .where("patient_id = patient_sessions.patient_id") - .where("academic_year = sessions.academic_year") - .where(status:, programme:) - .arel - .exists - ) - end - - scope :has_vaccine_method, - ->(vaccine_method, programme:) do - joins(:session).where( - Patient::TriageStatus - .where("patient_id = patient_sessions.patient_id") - .where("academic_year = sessions.academic_year") - .where(vaccine_method:, programme:) - .arel - .exists - ).or( - joins(:session).where( - Patient::TriageStatus - .where("patient_id = patient_sessions.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("academic_year = sessions.academic_year") - .where(programme:) - .has_vaccine_method(vaccine_method) - .arel - .exists - ) - ) - end - - scope :consent_given_and_ready_to_vaccinate, - ->(programmes:, vaccine_method:) do - select do |patient_session| - patient = patient_session.patient - session = patient_session.session - - programmes.any? do |programme| - patient.consent_given_and_safe_to_vaccinate?( - programme:, - academic_year: session.academic_year, - vaccine_method: - ) - end - end - end - - scope :without_patient_specific_direction, - ->(programme:, team:) do - joins(:session).where.not( - PatientSpecificDirection - .where("patient_id = patient_sessions.patient_id") - .where("academic_year = sessions.academic_year") - .where(programme:, team:) - .not_invalidated - .arel - .exists - ) - end - - scope :has_patient_specific_direction, - ->(programme:, team:) do - joins(:session).where( - PatientSpecificDirection - .where("patient_id = patient_sessions.patient_id") - .where("academic_year = sessions.academic_year") - .where(programme:, team:) - .not_invalidated - .arel - .exists - ) - end - - scope :destroy_all_if_safe, - -> do - includes( - :gillick_assessments, - :attendance_records, - :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?) - end - - def destroy_if_safe! - destroy! if safe_to_destroy? - end -end diff --git a/app/models/school_move.rb b/app/models/school_move.rb index e16187e993..1461f6e424 100644 --- a/app/models/school_move.rb +++ b/app/models/school_move.rb @@ -98,33 +98,15 @@ def update_archive_reasons!(user:) def update_sessions! patient - .patient_sessions - .joins(:session) + .patient_locations .where("academic_year >= ?", academic_year) .destroy_all_if_safe - sessions_to_add.find_each do |session| - PatientSession.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 b23129714a..d07e92e260 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_sessions has_many :session_dates, -> { order(:value) } has_many :session_notifications has_many :session_programmes, @@ -49,12 +49,16 @@ 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 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, @@ -63,6 +67,24 @@ 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 :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) } @@ -174,18 +196,27 @@ class Session < ApplicationRecord delegate :clinic?, :generic_clinic?, :school?, to: :location - def to_param - slug + def programme_birth_academic_years + @programme_birth_academic_years ||= + ProgrammeBirthAcademicYears.new(programme_year_groups, academic_year:) end - def today? - dates.any?(&:today?) - end + def patients + birth_academic_years = + location_programme_year_groups.pluck_birth_academic_years(academic_year:) - def unscheduled? - dates.empty? + Patient + .joins_sessions + .where(sessions: { id: }) + .where(birth_academic_year: birth_academic_years) end + def to_param = slug + + def today? = dates.any?(&:today?) + + def unscheduled? = dates.empty? + def completed? return false if dates.empty? Date.current > dates.max @@ -301,7 +332,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..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_sessions, through: :sessions - has_many :patients, -> { distinct }, through: :patient_sessions 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 e7f2ca1d1d..89263f38c4 100644 --- a/app/policies/patient_policy.rb +++ b/app/policies/patient_policy.rb @@ -9,11 +9,11 @@ def resolve return scope.none if team.nil? existence_criteria = [ - PatientSession + PatientLocation .select("1") - .joins(:session) - .where("patient_sessions.patient_id = patients.id") - .where(sessions: { team_id: team.id }) + .joins_sessions + .where("patient_locations.patient_id = patients.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 a1c15ef832..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_sessions: :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 b6b8ed8a19..a4d19d4764 100644 --- a/app/policies/vaccination_record_policy.rb +++ b/app/policies/vaccination_record_policy.rb @@ -67,16 +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_sessions: :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/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 72cfa112ea..8094ee63ee 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| %> + <% @patients.each do |patient| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_session.patient, - session: patient_session.session, + 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 1f88295d09..c73e654abe 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| %> + <% @patients.each do |patient| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_session.patient, - session: patient_session.session, + patient:, + 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 b4c570e1f2..76bc9d1e1c 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| %> + <% @patients.each do |patient| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_session.patient, - session: patient_session.session, + 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 a84035a2c0..d82a377be7 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| %> + <% @patients.each do |patient| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_session.patient, - session: patient_session.session, + 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 2e61205792..4df9a18f34 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| %> + <% @patients.each do |patient| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_session.patient, - session: patient_session.session, + 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 9676d9a85f..d4e49a14cf 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| %> + <% @patients.each do |patient| %> <%= render AppPatientSessionSearchResultCardComponent.new( - patient: patient_session.patient, - session: patient_session.session, + patient:, + session: @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/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 5af036ab1e..bbc4396eff 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,17 @@ t.index ["status"], name: "index_patient_consent_statuses_on_status" end + create_table "patient_locations", force: :cascade do |t| + t.bigint "patient_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + 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| t.integer "status", default: 0, null: false t.bigint "patient_id", null: false @@ -546,15 +557,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 @@ -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" @@ -1013,8 +1016,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 +1040,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", "locations" + add_foreign_key "patient_locations", "patients" 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..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. - - PatientSession.import( - team.patients.map do - PatientSession.new(patient: it, session: clinic_session) - end, + # 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 + .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/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_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/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_locations.rb b/spec/factories/patient_locations.rb new file mode 100644 index 0000000000..60a091798e --- /dev/null +++ b/spec/factories/patient_locations.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: patient_locations +# +# 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 +# +# 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) +# +FactoryBot.define do + factory :patient_location do + transient { session { association(:session) } } + + patient + location { session.location } + academic_year { session.academic_year } + end +end diff --git a/spec/factories/patient_sessions.rb b/spec/factories/patient_sessions.rb deleted file mode 100644 index 7f791bc095..0000000000 --- a/spec/factories/patient_sessions.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -# == Schema Information -# -# Table name: patient_sessions -# -# 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 -# -# Indexes -# -# index_patient_sessions_on_patient_id_and_session_id (patient_id,session_id) UNIQUE -# index_patient_sessions_on_session_id (session_id) -# -# Foreign Keys -# -# fk_rails_... (patient_id => patients.id) -# fk_rails_... (session_id => sessions.id) -# -FactoryBot.define do - factory :patient_session do - transient { programmes { [association(:programme)] } } - - patient - session { association :session, programmes: } - end -end diff --git a/spec/factories/patients.rb b/spec/factories/patients.rb index f38f95fbba..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 - PatientSession.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/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..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_sessions - .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/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_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/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/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/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/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..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) { PatientSession.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_session) { create(:patient_session, 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_session) + 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_session) { create(:patient_session, patient:, session:) } - it "is not included" do - expect(form.apply(scope)).not_to include(patient_session) + 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_sessions.first, - patient_refused.patient_sessions.first - ) - end - - context "with combined consent status and vaccine method" do - let(:consent_statuses) { %w[given_nasal] } - - it "filters on consent status" do - patient_session_given_nasal = - create( - :patient, - :consent_given_nasal_only_triage_not_needed, - session: - ).patient_sessions.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_session_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_session = patient.patient_sessions.first - expect(form.apply(scope)).to include(patient_session) + 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_session = patient.patient_sessions.first - expect(form.apply(scope)).to include(patient_session) + expect(form.apply(scope)).to include(patient) end end @@ -496,24 +380,25 @@ let(:triage_status) { nil } let(:year_groups) { nil } - let!(:patient_session_with_psd) do - create(:patient_session, session:).tap do |patient_session| - create( - :patient_specific_direction, - patient: patient_session.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_session_without_psd) { create(:patient_session, 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_session_with_psd) + expect(form.apply(scope)).to contain_exactly(patient_with_psd) end end @@ -521,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_session_without_psd - ) + expect(form.apply(scope)).to contain_exactly(patient_without_psd) end end end @@ -542,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 = @@ -569,10 +452,80 @@ vaccine_methods: %w[injection nasal] ) - expect(form.apply(scope)).to contain_exactly( - nasal_patient.patient_sessions.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_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/enqueue_vaccinations_search_in_nhs_job_spec.rb b/spec/jobs/enqueue_vaccinations_search_in_nhs_job_spec.rb index 2c461c00af..a8b832bd26 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 @@ -44,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/patient_nhs_number_lookup_job_spec.rb b/spec/jobs/patient_nhs_number_lookup_job_spec.rb index 92607ba75b..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_session) do - create(:patient_session, patient:, programmes: [programme]) - end - let(:session) { patient_session.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 @@ -114,9 +112,10 @@ 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 + location: patient_location.location, + academic_year: patient_location.academic_year ) end 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/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..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_session, patient:, session:, programmes:) + create(:patient_location, patient:, session:) 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/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/patient_archiver_spec.rb b/spec/lib/patient_archiver_spec.rb index 046ad667d0..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_session, 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/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..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_session.session_id, - academic_year: patient_session.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_session) { create(:patient_session, 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_session.patient } - let(:session) { patient_session.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_session.patient } - let(:session) { patient_session.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 cb3ed7c6a4..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_session, 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/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..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_session, 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/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_location_spec.rb b/spec/models/patient_location_spec.rb new file mode 100644 index 0000000000..22f3cc5fc8 --- /dev/null +++ b/spec/models/patient_location_spec.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: patient_locations +# +# 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 +# +# 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) +# + +describe PatientLocation do + subject(:patient_location) { create(:patient_location, session:) } + + let(:programme) { create(:programme) } + 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_sessions + .joins(:patient) + .appear_in_programmes(programmes) + end + + let(:programmes) { create_list(:programme, 1, :td_ipv) } + let(:session) { create(:session, programmes:) } + + 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_location) } + end + + context "in a session but the wrong year group" do + let(:patient) { create(:patient, year_group: 8) } + + it { should_not include(patient_location) } + end + + context "in a session with the right year group for the programme but not the location" do + let(:location) { create(:school, :secondary) } + let(:session) { create(:session, location:, programmes:) } + let(:patient) { create(:patient, year_group: 9) } + + before do + programmes.each do |programme| + create( + :location_programme_year_group, + programme:, + location:, + year_group: 10 + ) + end + end + + it { should_not include(patient_location) } + end + end + end + + describe "#safe_to_destroy?" do + subject(:safe_to_destroy?) { patient_location.safe_to_destroy? } + + let(:patient_location) { create(:patient_location, session:) } + let(:patient) { patient_location.patient } + + context "when safe to destroy" do + it { should be true } + + it "is safe with only absent attendances" do + create(:attendance_record, :absent, patient:, session:) + expect(safe_to_destroy?).to be true + end + end + + context "when unsafe to destroy" do + it "is unsafe with vaccination records" do + create(:vaccination_record, programme:, patient:, session:) + expect(safe_to_destroy?).to be false + end + + it "is unsafe with gillick assessment" do + create(:gillick_assessment, :competent, patient:, session:) + expect(safe_to_destroy?).to be false + end + + it "is unsafe with present attendances" do + create(:attendance_record, :present, patient:, session:) + expect(safe_to_destroy?).to be false + end + + it "is unsafe with mixed conditions" do + create(:attendance_record, :absent, patient:, session:) + create(:vaccination_record, programme:, patient:, session:) + expect(safe_to_destroy?).to be false + end + end + end +end diff --git a/spec/models/patient_session_spec.rb b/spec/models/patient_session_spec.rb deleted file mode 100644 index 10c12f8005..0000000000 --- a/spec/models/patient_session_spec.rb +++ /dev/null @@ -1,179 +0,0 @@ -# frozen_string_literal: true - -# == Schema Information -# -# Table name: patient_sessions -# -# 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 -# -# Indexes -# -# index_patient_sessions_on_patient_id_and_session_id (patient_id,session_id) UNIQUE -# index_patient_sessions_on_session_id (session_id) -# -# Foreign Keys -# -# fk_rails_... (patient_id => patients.id) -# fk_rails_... (session_id => sessions.id) -# - -describe PatientSession do - subject(:patient_session) { create(:patient_session, session:) } - - let(:programme) { create(:programme) } - let(:session) { create(:session, programmes: [programme]) } - - describe "associations" do - it { should have_many(:gillick_assessments) } - it { should have_many(:pre_screenings) } - end - - describe "scopes" do - describe "#appear_in_programmes" do - subject(:scope) do - described_class.joins(:patient, :session).appear_in_programmes( - programmes - ) - end - - let(:programmes) { create_list(:programme, 1, :td_ipv) } - let(:session) { create(:session, programmes:) } - - let(:patient_session) { create(:patient_session, 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) } - end - - context "in a session but the wrong year group" do - let(:patient) { create(:patient, year_group: 8) } - - it { should_not include(patient_session) } - end - - context "in a session with the right year group for the programme but not the location" do - let(:location) { create(:school, :secondary) } - let(:session) { create(:session, location:, programmes:) } - let(:patient) { create(:patient, year_group: 9) } - - before do - programmes.each do |programme| - create( - :location_programme_year_group, - programme:, - location:, - year_group: 10 - ) - end - end - - it { should_not include(patient_session) } - end - end - - describe "#consent_given_and_ready_to_vaccinate" do - subject(:scope) do - described_class.consent_given_and_ready_to_vaccinate( - programmes:, - 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_session) { patient.patient_sessions.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_session) } - 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_session) } - - 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_session) } - end - end - end - end - end - - describe "#safe_to_destroy?" do - subject(:safe_to_destroy?) { patient_session.safe_to_destroy? } - - let(:patient_session) { create(:patient_session, session:) } - let(:patient) { patient_session.patient } - - context "when safe to destroy" do - it { should be true } - - it "is safe with only absent attendances" do - create(:attendance_record, :absent, patient:, session:) - expect(safe_to_destroy?).to be true - end - end - - context "when unsafe to destroy" do - it "is unsafe with vaccination records" do - create(:vaccination_record, programme:, patient:, session:) - expect(safe_to_destroy?).to be false - end - - it "is unsafe with gillick assessment" do - create(:gillick_assessment, :competent, patient:, session:) - expect(safe_to_destroy?).to be false - end - - it "is unsafe with present attendances" do - create(:attendance_record, :present, patient:, session:) - expect(safe_to_destroy?).to be false - end - - it "is unsafe with mixed conditions" do - create(:attendance_record, :absent, patient:, session:) - create(:vaccination_record, programme:, patient:, session:) - expect(safe_to_destroy?).to be false - end - end - end -end diff --git a/spec/models/patient_spec.rb b/spec/models/patient_spec.rb index 2f71102d67..590b82ea0b 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_session, patient:, session: flu_session) - create(:patient_session, patient:, session: hpv_session) + create(:patient_location, patient:, location:, academic_year:) create( - :patient_session, + :patient_location, patient: another_patient, - session: flu_session - ) - create( - :patient_session, - 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_session, patient:, session: flu_session) - create(:patient_session, patient:, session: hpv_session) - - create( - :patient_session, - patient: another_patient, - session: flu_session - ) + create(:patient_location, patient:, location:, academic_year:) create( - :patient_session, + :patient_location, patient: another_patient, - session: hpv_session + location:, + academic_year: ) end @@ -378,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 @@ -896,12 +936,14 @@ ) end - before { create(:patient_session, 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 @@ -1126,19 +1168,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_session, 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.location).to eq(location) end end 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:) } 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/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:) ) 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