From a4e1ce9dbafd7689c2b49634fa7852f943e85416 Mon Sep 17 00:00:00 2001 From: Thomas Leese Date: Thu, 21 Aug 2025 14:55:32 +0100 Subject: [PATCH] Replace pre-screening patient session foreign key This replaces the foreign key association between pre-screenings and patient sessions to instead link directly to the patient and the session (via the date). This is needed as we eventually want to replace the PatientSession model and to do that we need to make sure all foreign keys to it have been replaced. The functionality should be the same before and after, actually slightly improved as pre-screening is only supposed to exist on the day it happened, so now we link directly to the session date rather than the overall session. Jira-Issue: MAV-1819 --- .../app_patient_session_record_component.rb | 10 +++- .../app_vaccinate_form_component.rb | 3 +- .../api/testing/teams_controller.rb | 9 ++-- .../patient_sessions/base_controller.rb | 4 ++ .../session_attendances_controller.rb | 4 -- .../vaccinations_controller.rb | 4 +- app/forms/vaccinate_form.rb | 19 +++++--- app/lib/patient_merger.rb | 18 ++++--- app/models/patient.rb | 2 +- app/models/patient_session.rb | 5 +- app/models/pre_screening.rb | 18 +++---- app/models/session.rb | 1 + app/models/session_date.rb | 3 +- ...ove_patient_session_from_pre_screenings.rb | 47 +++++++++++++++++++ db/schema.rb | 9 ++-- .../app_activity_log_component_spec.rb | 3 +- .../app_vaccinate_form_component_spec.rb | 5 +- .../api/testing/teams_controller_spec.rb | 2 +- spec/factories/pre_screenings.rb | 17 +++++-- spec/lib/patient_merger_spec.rb | 2 +- spec/models/pre_screening_spec.rb | 20 ++++++-- spec/models/session_date_spec.rb | 6 +++ 22 files changed, 153 insertions(+), 58 deletions(-) create mode 100644 db/migrate/20250821124416_remove_patient_session_from_pre_screenings.rb diff --git a/app/components/app_patient_session_record_component.rb b/app/components/app_patient_session_record_component.rb index 33c1f94760..6322d761d1 100644 --- a/app/components/app_patient_session_record_component.rb +++ b/app/components/app_patient_session_record_component.rb @@ -22,7 +22,7 @@ def render? ( patient_session.registration_status&.attending? || patient_session.registration_status&.completed? || - !patient_session.session.requires_registration? + !session.requires_registration? ) end @@ -35,8 +35,14 @@ def render? def default_vaccinate_form pre_screening_confirmed = patient.pre_screenings.today.exists?(programme:) + session_date = session.session_dates.today.first - VaccinateForm.new(patient_session:, programme:, pre_screening_confirmed:) + VaccinateForm.new( + patient:, + session_date:, + programme:, + pre_screening_confirmed: + ) end def heading diff --git a/app/components/app_vaccinate_form_component.rb b/app/components/app_vaccinate_form_component.rb index 8426b39792..06dc19a26c 100644 --- a/app/components/app_vaccinate_form_component.rb +++ b/app/components/app_vaccinate_form_component.rb @@ -11,8 +11,7 @@ def initialize(vaccinate_form) attr_reader :vaccinate_form - delegate :patient_session, :programme, to: :vaccinate_form - delegate :patient, :session, to: :patient_session + delegate :patient, :session, :programme, to: :vaccinate_form delegate :academic_year, to: :session def url diff --git a/app/controllers/api/testing/teams_controller.rb b/app/controllers/api/testing/teams_controller.rb index a330d973f6..a1625f7812 100644 --- a/app/controllers/api/testing/teams_controller.rb +++ b/app/controllers/api/testing/teams_controller.rb @@ -27,11 +27,7 @@ def destroy patient_ids = team.patients.pluck(:id) - patient_sessions = PatientSession.where(session: sessions) - log_destroy(PreScreening.where(patient_session: patient_sessions)) - patient_sessions.in_batches { log_destroy(it) } - - log_destroy(SessionDate.where(session: sessions)) + log_destroy(PatientSession.where(session: sessions)) log_destroy(AccessLogEntry.where(patient_id: patient_ids)) log_destroy(ArchiveReason.where(patient_id: patient_ids)) @@ -41,11 +37,14 @@ def destroy # In local dev we can end up with NotifyLogEntries without a patient log_destroy(NotifyLogEntry.where(patient_id: nil)) log_destroy(NotifyLogEntry.where(patient_id: patient_ids)) + log_destroy(PreScreening.where(patient_id: patient_ids)) log_destroy(SchoolMove.where(patient_id: patient_ids)) log_destroy(SchoolMove.where(team:)) log_destroy(SchoolMoveLogEntry.where(patient_id: patient_ids)) log_destroy(VaccinationRecord.where(patient_id: patient_ids)) + log_destroy(SessionDate.where(session: sessions)) + log_destroy(ConsentForm.where(team:)) log_destroy(Consent.where(team:)) log_destroy(Triage.where(team:)) diff --git a/app/controllers/patient_sessions/base_controller.rb b/app/controllers/patient_sessions/base_controller.rb index 8a3614bc56..f67917208b 100644 --- a/app/controllers/patient_sessions/base_controller.rb +++ b/app/controllers/patient_sessions/base_controller.rb @@ -19,6 +19,10 @@ def set_session ) end + def set_session_date + @session_date = @session.session_dates.find_by!(value: Date.current) + end + def set_academic_year @academic_year = @session.academic_year end diff --git a/app/controllers/patient_sessions/session_attendances_controller.rb b/app/controllers/patient_sessions/session_attendances_controller.rb index 18965d87cd..9f2c762d33 100644 --- a/app/controllers/patient_sessions/session_attendances_controller.rb +++ b/app/controllers/patient_sessions/session_attendances_controller.rb @@ -41,10 +41,6 @@ def update private - def set_session_date - @session_date = @session.session_dates.find_by!(value: Date.current) - end - def set_session_attendance @session_attendance = authorize @patient_session diff --git a/app/controllers/patient_sessions/vaccinations_controller.rb b/app/controllers/patient_sessions/vaccinations_controller.rb index 03c173c2ed..72a9b2268c 100644 --- a/app/controllers/patient_sessions/vaccinations_controller.rb +++ b/app/controllers/patient_sessions/vaccinations_controller.rb @@ -5,6 +5,7 @@ class PatientSessions::VaccinationsController < PatientSessions::BaseController include VaccinationMailerConcern before_action :set_todays_batch + before_action :set_session_date after_action :verify_authorized @@ -17,7 +18,8 @@ def create @vaccinate_form = VaccinateForm.new( current_user:, - patient_session: @patient_session, + patient: @patient, + session_date: @session_date, programme: @programme, todays_batch: @todays_batch, **vaccinate_form_params diff --git a/app/forms/vaccinate_form.rb b/app/forms/vaccinate_form.rb index 3c08ef1267..c354de203b 100644 --- a/app/forms/vaccinate_form.rb +++ b/app/forms/vaccinate_form.rb @@ -4,7 +4,13 @@ class VaccinateForm include ActiveModel::Model include ActiveModel::Attributes - attr_accessor :patient_session, :programme, :current_user, :todays_batch + attr_accessor :patient, + :session_date, + :programme, + :current_user, + :todays_batch + + delegate :session, to: :session_date attribute :identity_check_confirmed_by_other_name, :string attribute :identity_check_confirmed_by_other_relationship, :string @@ -90,19 +96,19 @@ def save(draft_vaccination_record:) identity_check_confirmed_by_patient draft_vaccination_record.location_id = session.location_id unless session.generic_clinic? - draft_vaccination_record.patient_id = patient_session.patient_id + draft_vaccination_record.patient_id = patient.id draft_vaccination_record.performed_at = Time.current draft_vaccination_record.performed_by_user = current_user draft_vaccination_record.performed_ods_code = organisation.ods_code draft_vaccination_record.programme = programme - draft_vaccination_record.session_id = patient_session.session_id + draft_vaccination_record.session_id = session.id draft_vaccination_record.save # rubocop:disable Rails/SaveBang end private - delegate :organisation, :session, to: :patient_session + delegate :organisation, to: :session def administered? = vaccine_method != "none" @@ -131,10 +137,11 @@ def delivery_method def pre_screening @pre_screening ||= - patient_session.pre_screenings.build( + patient.pre_screenings.build( notes: pre_screening_notes, performed_by: current_user, - programme: + programme:, + session_date: ) end end diff --git a/app/lib/patient_merger.rb b/app/lib/patient_merger.rb index 1c7ae387c9..2d054a5bb6 100644 --- a/app/lib/patient_merger.rb +++ b/app/lib/patient_merger.rb @@ -40,6 +40,10 @@ def call patient_id: patient_to_keep.id ) + patient_to_destroy.pre_screenings.update_all( + patient_id: patient_to_keep.id + ) + patient_to_destroy.school_moves.find_each do |school_move| if patient_to_keep.school_moves.exists?( home_educated: school_move.home_educated, @@ -78,18 +82,12 @@ def call end patient_to_destroy.patient_sessions.each do |patient_session| - if ( - existing_patient_session = - patient_to_keep.patient_sessions.find_by( - session_id: patient_session.session_id - ) + if patient_to_keep.patient_sessions.exists?( + session_id: patient_session.session_id ) - patient_session.pre_screenings.update_all( - patient_session_id: existing_patient_session.id - ) - else - patient_session.update!(patient: patient_to_keep) + next end + patient_session.update!(patient: patient_to_keep) end PatientSession.where(patient: patient_to_destroy).destroy_all diff --git a/app/models/patient.rb b/app/models/patient.rb index 835427976b..eac95b4e5b 100644 --- a/app/models/patient.rb +++ b/app/models/patient.rb @@ -70,6 +70,7 @@ class Patient < ApplicationRecord has_many :parent_relationships, -> { order(:created_at) } has_many :patient_sessions has_many :pds_search_results + has_many :pre_screenings has_many :school_move_log_entries has_many :school_moves has_many :session_notifications @@ -81,7 +82,6 @@ class Patient < ApplicationRecord has_many :gillick_assessments has_many :parents, through: :parent_relationships has_many :patient_specific_directions - has_many :pre_screenings, through: :patient_sessions has_many :session_attendances, through: :patient_sessions has_many :sessions, through: :patient_sessions has_many :teams, -> { distinct }, through: :sessions diff --git a/app/models/patient_session.rb b/app/models/patient_session.rb index 712b37fa50..15f6339b2f 100644 --- a/app/models/patient_session.rb +++ b/app/models/patient_session.rb @@ -44,7 +44,6 @@ class PatientSession < ApplicationRecord belongs_to :patient belongs_to :session - has_many :pre_screenings has_many :session_attendances, dependent: :destroy has_one :registration_status @@ -64,6 +63,10 @@ class PatientSession < ApplicationRecord through: :patient, source: :notes + has_many :pre_screenings, + -> { where(patient_id: it.patient_id) }, + through: :session + has_many :session_notifications, -> { where(session_id: it.session_id) }, through: :patient diff --git a/app/models/pre_screening.rb b/app/models/pre_screening.rb index e6efc5a290..7c735542c2 100644 --- a/app/models/pre_screening.rb +++ b/app/models/pre_screening.rb @@ -8,34 +8,36 @@ # notes :text default(""), not null # created_at :datetime not null # updated_at :datetime not null -# patient_session_id :bigint not null +# patient_id :bigint not null # performed_by_user_id :bigint not null # programme_id :bigint not null +# session_date_id :bigint not null # # Indexes # -# index_pre_screenings_on_patient_session_id (patient_session_id) +# index_pre_screenings_on_patient_id (patient_id) # index_pre_screenings_on_performed_by_user_id (performed_by_user_id) # index_pre_screenings_on_programme_id (programme_id) +# index_pre_screenings_on_session_date_id (session_date_id) # # Foreign Keys # -# fk_rails_... (patient_session_id => patient_sessions.id) +# fk_rails_... (patient_id => patients.id) # fk_rails_... (performed_by_user_id => users.id) # fk_rails_... (programme_id => programmes.id) +# fk_rails_... (session_date_id => session_dates.id) # class PreScreening < ApplicationRecord - audited associated_with: :patient_session + audited associated_with: :patient - belongs_to :patient_session + belongs_to :patient + belongs_to :session_date belongs_to :programme belongs_to :performed_by, class_name: "User", foreign_key: :performed_by_user_id - has_one :patient, through: :patient_session - - scope :today, -> { where(created_at: Date.current.all_day) } + scope :today, -> { joins(:session_date).merge(SessionDate.today) } encrypts :notes diff --git a/app/models/session.rb b/app/models/session.rb index 1d7031bf8e..765452bed1 100644 --- a/app/models/session.rb +++ b/app/models/session.rb @@ -51,6 +51,7 @@ class Session < ApplicationRecord 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 diff --git a/app/models/session_date.rb b/app/models/session_date.rb index ef617f17d7..1e366eaaae 100644 --- a/app/models/session_date.rb +++ b/app/models/session_date.rb @@ -22,6 +22,7 @@ class SessionDate < ApplicationRecord belongs_to :session has_many :gillick_assessments, dependent: :restrict_with_error + has_many :pre_screenings, dependent: :restrict_with_error has_many :session_attendances, dependent: :restrict_with_error scope :for_session, -> { where("session_id = sessions.id") } @@ -44,7 +45,7 @@ def today_or_past? = today? || past? def today_or_future? = today? || future? def has_been_attended? - gillick_assessments.any? || session_attendances.any? + gillick_assessments.any? || pre_screenings.any? || session_attendances.any? end private diff --git a/db/migrate/20250821124416_remove_patient_session_from_pre_screenings.rb b/db/migrate/20250821124416_remove_patient_session_from_pre_screenings.rb new file mode 100644 index 0000000000..5a1813b755 --- /dev/null +++ b/db/migrate/20250821124416_remove_patient_session_from_pre_screenings.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class RemovePatientSessionFromPreScreenings < ActiveRecord::Migration[8.0] + def up + change_table :pre_screenings, bulk: true do |t| + t.references :patient, foreign_key: true + t.references :session_date, foreign_key: true + end + + PreScreening.find_each do |pre_screening| + patient_session = PatientSession.find(pre_screening.patient_session_id) + patient_id = patient_session.patient_id + session_id = patient_session.session_id + session_date_id = + SessionDate.find_by!( + session_id:, + value: pre_screening.created_at.to_date + ) + pre_screening.update_columns(patient_id:, session_date_id:) + end + + change_table :pre_screenings, bulk: true do |t| + t.change_null :patient_id, false + t.change_null :session_date_id, false + t.remove_references :patient_session + end + end + + def down + add_reference :pre_screenings, :patient_session + + PreScreening.find_each do |pre_screening| + session_id = SessionDate.find(pre_screening.session_date_id).session_id + patient_session = + PatientSession.find_by!( + patient_id: pre_screening.patient_id, + session_id: + ) + pre_screening.update_column(:patient_session_id, patient_session.id) + end + + change_table :pre_screenings, bulk: true do |t| + t.change_null :patient_session_id, false + t.remove_references :patient, :session_date + end + end +end diff --git a/db/schema.rb b/db/schema.rb index d4cd8a8ec5..7673f8b546 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -722,15 +722,17 @@ end create_table "pre_screenings", force: :cascade do |t| - t.bigint "patient_session_id", null: false t.bigint "performed_by_user_id", null: false t.text "notes", default: "", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false t.bigint "programme_id", null: false - t.index ["patient_session_id"], name: "index_pre_screenings_on_patient_session_id" + t.bigint "patient_id", null: false + t.bigint "session_date_id", null: false + t.index ["patient_id"], name: "index_pre_screenings_on_patient_id" t.index ["performed_by_user_id"], name: "index_pre_screenings_on_performed_by_user_id" t.index ["programme_id"], name: "index_pre_screenings_on_programme_id" + t.index ["session_date_id"], name: "index_pre_screenings_on_session_date_id" end create_table "programmes", force: :cascade do |t| @@ -1072,8 +1074,9 @@ add_foreign_key "patients", "locations", column: "gp_practice_id" add_foreign_key "patients", "locations", column: "school_id" add_foreign_key "pds_search_results", "patients" - add_foreign_key "pre_screenings", "patient_sessions" + add_foreign_key "pre_screenings", "patients" add_foreign_key "pre_screenings", "programmes" + add_foreign_key "pre_screenings", "session_dates" add_foreign_key "pre_screenings", "users", column: "performed_by_user_id" add_foreign_key "reporting_api_one_time_tokens", "users" add_foreign_key "school_move_log_entries", "locations", column: "school_id" diff --git a/spec/components/app_activity_log_component_spec.rb b/spec/components/app_activity_log_component_spec.rb index 24d0d5bb59..85da16d3df 100644 --- a/spec/components/app_activity_log_component_spec.rb +++ b/spec/components/app_activity_log_component_spec.rb @@ -483,7 +483,8 @@ create( :pre_screening, performed_by: user, - patient_session:, + patient:, + session: patient_session.session, notes: "Some notes", created_at: Time.zone.local(2025, 6, 1, 12) ) diff --git a/spec/components/app_vaccinate_form_component_spec.rb b/spec/components/app_vaccinate_form_component_spec.rb index 946549d72b..40ef233d3a 100644 --- a/spec/components/app_vaccinate_form_component_spec.rb +++ b/spec/components/app_vaccinate_form_component_spec.rb @@ -6,6 +6,7 @@ let(:programme) { create(:programme) } let(:programmes) { [programme] } let(:session) { create(:session, :today, programmes:) } + let(:session_date) { session.session_dates.first } let(:patient) do create( :patient, @@ -18,7 +19,9 @@ create(:patient_session, :in_attendance, programmes:, patient:, session:) end - let(:vaccinate_form) { VaccinateForm.new(patient_session:, programme:) } + let(:vaccinate_form) do + VaccinateForm.new(patient:, session_date:, programme:) + end let(:component) { described_class.new(vaccinate_form) } diff --git a/spec/controllers/api/testing/teams_controller_spec.rb b/spec/controllers/api/testing/teams_controller_spec.rb index 5935538b82..67c1bdc75e 100644 --- a/spec/controllers/api/testing/teams_controller_spec.rb +++ b/spec/controllers/api/testing/teams_controller_spec.rb @@ -71,7 +71,7 @@ create(:school_move, :to_school, patient: Patient.first) create(:session_date, session: Session.first) - create(:pre_screening, patient_session: PatientSession.first) + create(:pre_screening, patient: Patient.first, session: Session.first) end it "deletes associated data" do diff --git a/spec/factories/pre_screenings.rb b/spec/factories/pre_screenings.rb index 4b7b4d3d7d..5ea885a108 100644 --- a/spec/factories/pre_screenings.rb +++ b/spec/factories/pre_screenings.rb @@ -8,27 +8,34 @@ # notes :text default(""), not null # created_at :datetime not null # updated_at :datetime not null -# patient_session_id :bigint not null +# patient_id :bigint not null # performed_by_user_id :bigint not null # programme_id :bigint not null +# session_date_id :bigint not null # # Indexes # -# index_pre_screenings_on_patient_session_id (patient_session_id) +# index_pre_screenings_on_patient_id (patient_id) # index_pre_screenings_on_performed_by_user_id (performed_by_user_id) # index_pre_screenings_on_programme_id (programme_id) +# index_pre_screenings_on_session_date_id (session_date_id) # # Foreign Keys # -# fk_rails_... (patient_session_id => patient_sessions.id) +# fk_rails_... (patient_id => patients.id) # fk_rails_... (performed_by_user_id => users.id) # fk_rails_... (programme_id => programmes.id) +# fk_rails_... (session_date_id => session_dates.id) # FactoryBot.define do factory :pre_screening do - patient_session - programme { patient_session.programmes.first } + transient { session { association(:session) } } + + patient + session_date { session.session_dates.first } + programme { session_date.session.programmes.first } performed_by + notes { "Fine to vaccinate" } end end diff --git a/spec/lib/patient_merger_spec.rb b/spec/lib/patient_merger_spec.rb index b8791ea848..22e3d41a98 100644 --- a/spec/lib/patient_merger_spec.rb +++ b/spec/lib/patient_merger_spec.rb @@ -57,7 +57,7 @@ let(:patient_session) do create(:patient_session, session:, patient: patient_to_destroy) end - let(:pre_screening) { create(:pre_screening, patient_session:) } + let(:pre_screening) { create(:pre_screening, patient: patient_to_destroy) } let(:school_move) do create(:school_move, :to_school, patient: patient_to_destroy) end diff --git a/spec/models/pre_screening_spec.rb b/spec/models/pre_screening_spec.rb index d75e2f0ffd..82c6c812e2 100644 --- a/spec/models/pre_screening_spec.rb +++ b/spec/models/pre_screening_spec.rb @@ -8,25 +8,34 @@ # notes :text default(""), not null # created_at :datetime not null # updated_at :datetime not null -# patient_session_id :bigint not null +# patient_id :bigint not null # performed_by_user_id :bigint not null # programme_id :bigint not null +# session_date_id :bigint not null # # Indexes # -# index_pre_screenings_on_patient_session_id (patient_session_id) +# index_pre_screenings_on_patient_id (patient_id) # index_pre_screenings_on_performed_by_user_id (performed_by_user_id) # index_pre_screenings_on_programme_id (programme_id) +# index_pre_screenings_on_session_date_id (session_date_id) # # Foreign Keys # -# fk_rails_... (patient_session_id => patient_sessions.id) +# fk_rails_... (patient_id => patients.id) # fk_rails_... (performed_by_user_id => users.id) # fk_rails_... (programme_id => programmes.id) +# fk_rails_... (session_date_id => session_dates.id) # describe PreScreening do subject(:pre_screening) { build(:pre_screening) } + describe "associations" do + it { should belong_to(:patient) } + it { should belong_to(:session_date) } + it { should belong_to(:programme) } + end + describe "scopes" do describe "#today" do subject { described_class.today } @@ -37,8 +46,9 @@ it { should include(pre_screening) } end - context "with an instance created yesterday" do - let(:pre_screening) { create(:pre_screening, created_at: 1.day.ago) } + context "with an instance for yesterday" do + let(:session_date) { create(:session_date, value: Date.yesterday) } + let(:pre_screening) { create(:pre_screening, session_date:) } it { should_not include(pre_screening) } end diff --git a/spec/models/session_date_spec.rb b/spec/models/session_date_spec.rb index 0149ee04a1..70bc81bab2 100644 --- a/spec/models/session_date_spec.rb +++ b/spec/models/session_date_spec.rb @@ -66,6 +66,12 @@ it { should be(true) } end + context "with a pre-screening" do + before { create(:pre_screening, session:) } + + it { should be(true) } + end + context "with a session attendance" do before do create(