From a8b5426eb6155c3440d333bd064a2db3df8b68f1 Mon Sep 17 00:00:00 2001 From: Thomas Leese Date: Mon, 8 Sep 2025 12:37:55 +0100 Subject: [PATCH 1/3] Rename `SessionAttendance` This renames the model to `AttendanceRecord` to remove the naming of session as the association is going to be removed and replaced instead with a separate location and date column. Jira-Issue: MAV-1938 --- app/components/app_activity_log_component.rb | 16 ++++++------ ...nt_session_search_result_card_component.rb | 6 ++--- .../api/testing/teams_controller.rb | 2 +- ...ontroller.rb => attendances_controller.rb} | 26 +++++++++---------- app/controllers/patients_controller.rb | 2 +- .../sessions/register_controller.rb | 4 +-- app/lib/generate/vaccination_records.rb | 10 +++---- app/lib/patient_merger.rb | 8 +++--- app/lib/status_generator/registration.rb | 10 +++---- app/lib/status_generator/session.rb | 10 +++---- app/lib/status_updater.rb | 4 +-- ...ion_attendance.rb => attendance_record.rb} | 10 +++---- app/models/patient.rb | 2 +- app/models/patient/registration_status.rb | 8 +++--- app/models/patient/vaccination_status.rb | 6 ++--- app/models/patient_session.rb | 8 +++--- app/models/session.rb | 2 +- app/models/session_date.rb | 4 +-- ..._policy.rb => attendance_record_policy.rb} | 2 +- app/views/patient_sessions/_header.html.erb | 6 ++--- .../edit.html.erb | 6 ++--- config/routes.rb | 2 +- ...0250908112554_rename_session_attendance.rb | 7 +++++ db/schema.rb | 26 +++++++++---------- ...n_attendances.rb => attendance_records.rb} | 10 +++---- spec/factories/patient_sessions.rb | 2 +- spec/factories/patients.rb | 2 +- spec/features/manage_attendance_spec.rb | 2 +- spec/lib/patient_merger_spec.rb | 18 ++++++------- .../lib/status_generator/registration_spec.rb | 10 +++---- spec/lib/status_generator/session_spec.rb | 10 +++---- ...ance_spec.rb => attendance_record_spec.rb} | 12 ++++----- .../patient/registration_status_spec.rb | 20 +++++++------- spec/models/patient_session_spec.rb | 6 ++--- spec/models/session_date_spec.rb | 2 +- ...ec.rb => attendance_record_policy_spec.rb} | 20 +++++--------- 36 files changed, 150 insertions(+), 151 deletions(-) rename app/controllers/patient_sessions/{session_attendances_controller.rb => attendances_controller.rb} (61%) rename app/models/{session_attendance.rb => attendance_record.rb} (69%) rename app/policies/{session_attendance_policy.rb => attendance_record_policy.rb} (93%) rename app/views/patient_sessions/{session_attendances => attendances}/edit.html.erb (87%) create mode 100644 db/migrate/20250908112554_rename_session_attendance.rb rename spec/factories/{session_attendances.rb => attendance_records.rb} (69%) rename spec/models/{session_attendance_spec.rb => attendance_record_spec.rb} (63%) rename spec/policies/{session_attendance_policy_spec.rb => attendance_record_policy_spec.rb} (81%) diff --git a/app/components/app_activity_log_component.rb b/app/components/app_activity_log_component.rb index 5ff76eb414..4e6fceca31 100644 --- a/app/components/app_activity_log_component.rb +++ b/app/components/app_activity_log_component.rb @@ -25,6 +25,9 @@ def initialize(team:, patient: nil, patient_session: nil) @patient_sessions = patient_session ? [patient_session] : patient.patient_sessions + @attendance_records = + (patient || patient_session).attendance_records.includes(:location) + @consents = @patient.consents.includes( :consent_form, @@ -52,9 +55,6 @@ def initialize(team:, patient: nil, patient_session: nil) @pre_screenings = (patient || patient_session).pre_screenings.includes(:performed_by) - @session_attendances = - (patient || patient_session).session_attendances.includes(:location) - @triages = @patient.triages.includes(:performed_by) @vaccination_records = @@ -78,7 +78,7 @@ def initialize(team:, patient: nil, patient_session: nil) :patient_sessions, :patient_specific_directions, :pre_screenings, - :session_attendances, + :attendance_records, :triages, :vaccination_records @@ -382,19 +382,19 @@ def vaccination_events end def attendance_events - session_attendances.map do |session_attendance| + attendance_records.map do |attendance_record| title = ( - if session_attendance.attending? + if attendance_record.attending? "Attended session" else "Absent from session" end ) - title += " at #{session_attendance.location.name}" + title += " at #{attendance_record.location.name}" - { title:, at: session_attendance.created_at } + { title:, at: attendance_record.created_at } end end diff --git a/app/components/app_patient_session_search_result_card_component.rb b/app/components/app_patient_session_search_result_card_component.rb index bda6a3e972..06e2abb91e 100644 --- a/app/components/app_patient_session_search_result_card_component.rb +++ b/app/components/app_patient_session_search_result_card_component.rb @@ -95,13 +95,13 @@ def initialize(patient_session, context:, programmes: []) delegate :academic_year, to: :session def can_register_attendance? - session_attendance = - SessionAttendance.new( + attendance_record = + AttendanceRecord.new( patient:, session_date: SessionDate.new(session:, value: Date.current) ) - policy(session_attendance).new? + policy(attendance_record).new? end def patient_path diff --git a/app/controllers/api/testing/teams_controller.rb b/app/controllers/api/testing/teams_controller.rb index c231b44b8d..d3907caf46 100644 --- a/app/controllers/api/testing/teams_controller.rb +++ b/app/controllers/api/testing/teams_controller.rb @@ -31,6 +31,7 @@ def destroy log_destroy(AccessLogEntry.where(patient_id: patient_ids)) log_destroy(ArchiveReason.where(patient_id: patient_ids)) + log_destroy(AttendanceRecord.where(patient_id: patient_ids)) log_destroy(ConsentNotification.where(patient_id: patient_ids)) log_destroy(GillickAssessment.where(patient_id: patient_ids)) log_destroy(Note.where(patient_id: patient_ids)) @@ -45,7 +46,6 @@ def destroy log_destroy(SchoolMove.where(patient_id: patient_ids)) log_destroy(SchoolMove.where(team:)) log_destroy(SchoolMoveLogEntry.where(patient_id: patient_ids)) - log_destroy(SessionAttendance.where(patient_id: patient_ids)) log_destroy(VaccinationRecord.where(patient_id: patient_ids)) log_destroy(SessionDate.where(session: sessions)) diff --git a/app/controllers/patient_sessions/session_attendances_controller.rb b/app/controllers/patient_sessions/attendances_controller.rb similarity index 61% rename from app/controllers/patient_sessions/session_attendances_controller.rb rename to app/controllers/patient_sessions/attendances_controller.rb index 77fee1c494..320b0dd62e 100644 --- a/app/controllers/patient_sessions/session_attendances_controller.rb +++ b/app/controllers/patient_sessions/attendances_controller.rb @@ -1,19 +1,19 @@ # frozen_string_literal: true -class PatientSessions::SessionAttendancesController < PatientSessions::BaseController +class PatientSessions::AttendancesController < PatientSessions::BaseController before_action :set_session_date - before_action :set_session_attendance + before_action :set_attendance_record def edit end def update - @session_attendance.assign_attributes(session_attendance_params) + @attendance_record.assign_attributes(attendance_record_params) - if @session_attendance.attending.nil? - @session_attendance.destroy! + if @attendance_record.attending.nil? + @attendance_record.destroy! else - @session_attendance.save! + @attendance_record.save! end => success StatusUpdater.call(patient: @patient) @@ -21,9 +21,9 @@ def update if success name = @patient.full_name - flash[:info] = if @session_attendance.attending? + flash[:info] = if @attendance_record.attending? t("attendance_flash.present", name:) - elsif @session_attendance.attending.nil? + elsif @attendance_record.attending.nil? t("attendance_flash.not_registered", name:) else t("attendance_flash.absent", name:) @@ -41,19 +41,19 @@ def update private - def set_session_attendance - @session_attendance = + def set_attendance_record + @attendance_record = authorize( @patient - .session_attendances + .attendance_records .includes(:patient, session_date: { session: :programmes }) .find_or_initialize_by(session_date: @session_date) ) end - def session_attendance_params + def attendance_record_params params - .expect(session_attendance: :attending) + .expect(attendance_record: :attending) .tap { |p| p[:attending] = nil if p[:attending] == "not_registered" } end end diff --git a/app/controllers/patients_controller.rb b/app/controllers/patients_controller.rb index 65c6856456..c660919e30 100644 --- a/app/controllers/patients_controller.rb +++ b/app/controllers/patients_controller.rb @@ -47,7 +47,7 @@ def set_patient :school, consents: %i[parent patient], parent_relationships: :parent, - patient_sessions: %i[location session_attendances], + patient_sessions: %i[location attendance_records], vaccination_records: :programme ).find(params[:id]) end diff --git a/app/controllers/sessions/register_controller.rb b/app/controllers/sessions/register_controller.rb index 6990e65137..7e43adde98 100644 --- a/app/controllers/sessions/register_controller.rb +++ b/app/controllers/sessions/register_controller.rb @@ -25,7 +25,7 @@ def show end def create - session_attendance = + attendance_record = ActiveRecord::Base.transaction do record = authorize @patient_session.todays_attendance record.update!(attending: params[:status] == "present") @@ -35,7 +35,7 @@ def create name = @patient_session.patient.full_name - flash[:info] = if session_attendance.attending? + flash[:info] = if attendance_record.attending? t("attendance_flash.present", name:) else t("attendance_flash.absent", name:) diff --git a/app/lib/generate/vaccination_records.rb b/app/lib/generate/vaccination_records.rb index 269fc05c12..7fb1a14310 100644 --- a/app/lib/generate/vaccination_records.rb +++ b/app/lib/generate/vaccination_records.rb @@ -19,21 +19,21 @@ def self.call(...) = new(...).call attr_reader :config, :team, :programme, :session, :administered def create_vaccinations - session_attendances = [] + attendance_records = [] vaccination_records = [] random_patient_sessions.each do |patient_session| patient = patient_session.patient session = patient_session.session - unless SessionAttendance.joins(:session_date).exists?( + unless AttendanceRecord.joins(:session_date).exists?( patient:, session_date: { session: } ) - session_attendances << FactoryBot.build( - :session_attendance, + attendance_records << FactoryBot.build( + :attendance_record, :present, patient:, session: @@ -57,7 +57,7 @@ def create_vaccinations ) end - SessionAttendance.import!(session_attendances) + AttendanceRecord.import!(attendance_records) VaccinationRecord.import!(vaccination_records) StatusUpdater.call(patient: vaccination_records.map(&:patient)) diff --git a/app/lib/patient_merger.rb b/app/lib/patient_merger.rb index 4d826d51d6..dd7ea8a675 100644 --- a/app/lib/patient_merger.rb +++ b/app/lib/patient_merger.rb @@ -24,6 +24,10 @@ def call patient_to_destroy.archive_reasons.destroy_all + patient_to_destroy.attendance_records.update_all( + patient_id: patient_to_keep.id + ) + patient_to_destroy.consent_notifications.update_all( patient_id: patient_to_keep.id ) @@ -63,10 +67,6 @@ def call patient_id: patient_to_keep.id ) - patient_to_destroy.session_attendances.update_all( - patient_id: patient_to_keep.id - ) - patient_to_destroy.session_notifications.update_all( patient_id: patient_to_keep.id ) diff --git a/app/lib/status_generator/registration.rb b/app/lib/status_generator/registration.rb index 84c54ae4a6..ff3824044d 100644 --- a/app/lib/status_generator/registration.rb +++ b/app/lib/status_generator/registration.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true class StatusGenerator::Registration - def initialize(patient:, session:, session_attendance:, vaccination_records:) + def initialize(patient:, session:, attendance_record:, vaccination_records:) @patient = patient @session = session - @session_attendance = session_attendance + @attendance_record = attendance_record @vaccination_records = vaccination_records end @@ -22,7 +22,7 @@ def status private - attr_reader :patient, :session, :session_attendance, :vaccination_records + attr_reader :patient, :session, :attendance_record, :vaccination_records delegate :academic_year, to: :session @@ -37,10 +37,10 @@ def status_should_be_completed? end def status_should_be_attending? - session_attendance&.attending + attendance_record&.attending end def status_should_be_not_attending? - session_attendance&.attending == false + attendance_record&.attending == false end end diff --git a/app/lib/status_generator/session.rb b/app/lib/status_generator/session.rb index 7f9c04b263..aecbb2357d 100644 --- a/app/lib/status_generator/session.rb +++ b/app/lib/status_generator/session.rb @@ -4,7 +4,7 @@ class StatusGenerator::Session def initialize( session_id:, academic_year:, - session_attendance:, + attendance_record:, programme:, patient:, consents:, @@ -13,7 +13,7 @@ def initialize( ) @session_id = session_id @academic_year = academic_year - @session_attendance = session_attendance + @attendance_record = attendance_record @programme = programme @patient = patient @consents = consents @@ -63,7 +63,7 @@ def status_changed_at attr_reader :session_id, :academic_year, - :session_attendance, + :attendance_record, :programme, :patient, :consents, @@ -113,7 +113,7 @@ def refusal_date def status_should_be_absent_from_session? vaccination_record&.absent_from_session? || - session_attendance&.attending == false + attendance_record&.attending == false end def absence_date @@ -121,7 +121,7 @@ def absence_date if vaccination_record&.absent_from_session? vaccination_record.performed_at end, - (session_attendance.created_at if session_attendance&.attending == false) + (attendance_record.created_at if attendance_record&.attending == false) ].compact.min end diff --git a/app/lib/status_updater.rb b/app/lib/status_updater.rb index 761ec45f56..a755f33a26 100644 --- a/app/lib/status_updater.rb +++ b/app/lib/status_updater.rb @@ -62,7 +62,7 @@ def update_registration_statuses! ) .includes( :patient, - :session_attendances, + :attendance_records, :session_date, :vaccination_records, session: :programmes @@ -118,7 +118,7 @@ def update_vaccination_statuses! :consents, :triages, :vaccination_records, - :session_attendance + :attendance_record ) .find_in_batches(batch_size: 10_000) do |batch| batch.each(&:assign_status) diff --git a/app/models/session_attendance.rb b/app/models/attendance_record.rb similarity index 69% rename from app/models/session_attendance.rb rename to app/models/attendance_record.rb index c430bb9780..fe5e316767 100644 --- a/app/models/session_attendance.rb +++ b/app/models/attendance_record.rb @@ -2,7 +2,7 @@ # == Schema Information # -# Table name: session_attendances +# Table name: attendance_records # # id :bigint not null, primary key # attending :boolean not null @@ -13,16 +13,16 @@ # # Indexes # -# index_session_attendances_on_patient_id (patient_id) -# index_session_attendances_on_patient_id_and_session_date_id (patient_id,session_date_id) UNIQUE -# index_session_attendances_on_session_date_id (session_date_id) +# index_attendance_records_on_patient_id (patient_id) +# index_attendance_records_on_patient_id_and_session_date_id (patient_id,session_date_id) UNIQUE +# index_attendance_records_on_session_date_id (session_date_id) # # Foreign Keys # # fk_rails_... (patient_id => patients.id) # fk_rails_... (session_date_id => session_dates.id) # -class SessionAttendance < ApplicationRecord +class AttendanceRecord < ApplicationRecord audited associated_with: :patient belongs_to :patient diff --git a/app/models/patient.rb b/app/models/patient.rb index d5cb77365c..655a4e5976 100644 --- a/app/models/patient.rb +++ b/app/models/patient.rb @@ -61,6 +61,7 @@ class Patient < ApplicationRecord has_many :access_log_entries has_many :archive_reasons + has_many :attendance_records has_many :changesets, class_name: "PatientChangeset" has_many :consent_notifications has_many :consent_statuses @@ -75,7 +76,6 @@ class Patient < ApplicationRecord has_many :registration_statuses has_many :school_move_log_entries has_many :school_moves - has_many :session_attendances has_many :session_notifications has_many :triage_statuses has_many :triages diff --git a/app/models/patient/registration_status.rb b/app/models/patient/registration_status.rb index 1e578ed21e..7d9651f8da 100644 --- a/app/models/patient/registration_status.rb +++ b/app/models/patient/registration_status.rb @@ -31,15 +31,15 @@ class Patient::RegistrationStatus < ApplicationRecord has_one :session_date, -> { today }, through: :session, source: :session_dates - has_many :session_attendances, through: :patient + has_many :attendance_records, through: :patient enum :status, { unknown: 0, attending: 1, not_attending: 2, completed: 3 }, default: :unknown, validate: true - def session_attendance - session_attendances.find { it.session_date_id == session_date&.id } + def attendance_record + attendance_records.find { it.session_date_id == session_date&.id } end def assign_status @@ -53,7 +53,7 @@ def generator StatusGenerator::Registration.new( patient:, session:, - session_attendance:, + attendance_record:, vaccination_records: ) end diff --git a/app/models/patient/vaccination_status.rb b/app/models/patient/vaccination_status.rb index ce639c3f7f..3454fc8cbe 100644 --- a/app/models/patient/vaccination_status.rb +++ b/app/models/patient/vaccination_status.rb @@ -40,10 +40,10 @@ class Patient::VaccinationStatus < ApplicationRecord has_one :patient_session - has_one :session_attendance, + has_one :attendance_record, -> { today }, through: :patient, - source: :session_attendances + source: :attendance_records enum :status, { none_yet: 0, vaccinated: 1, could_not_vaccinate: 2 }, @@ -92,7 +92,7 @@ def session_generator StatusGenerator::Session.new( session_id:, academic_year:, - session_attendance:, + attendance_record:, programme:, patient:, consents:, diff --git a/app/models/patient_session.rb b/app/models/patient_session.rb index 3903dd789a..9f9bdb73f2 100644 --- a/app/models/patient_session.rb +++ b/app/models/patient_session.rb @@ -70,7 +70,7 @@ class PatientSession < ApplicationRecord source: :registration_statuses, class_name: "Patient::RegistrationStatus" - has_many :session_attendances, + has_many :attendance_records, -> { where(patient_id: it.patient_id) }, through: :session @@ -299,7 +299,7 @@ class PatientSession < ApplicationRecord -> do includes( :gillick_assessments, - :session_attendances, + :attendance_records, :vaccination_records ).find_each(&:destroy_if_safe!) end @@ -312,7 +312,7 @@ def has_patient_specific_direction?(**query) def safe_to_destroy? vaccination_records.empty? && gillick_assessments.empty? && - session_attendances.none?(&:attending?) + attendance_records.none?(&:attending?) end def destroy_if_safe! @@ -324,7 +324,7 @@ def programmes = session.programmes_for(patient:, academic_year:) def todays_attendance if (session_date = session.session_dates.today.first) patient - .session_attendances + .attendance_records .includes(:patient, session_date: { session: :programmes }) .find_or_initialize_by(session_date:) end diff --git a/app/models/session.rb b/app/models/session.rb index b30aef39a2..ee20a0169f 100644 --- a/app/models/session.rb +++ b/app/models/session.rb @@ -55,7 +55,7 @@ class Session < ApplicationRecord has_many :programmes, through: :session_programmes has_many :gillick_assessments, through: :session_dates has_many :patients, through: :patient_sessions - has_many :session_attendances, through: :session_dates + has_many :attendance_records, through: :session_dates has_many :vaccines, through: :programmes has_many :location_programme_year_groups, diff --git a/app/models/session_date.rb b/app/models/session_date.rb index 1e366eaaae..2f3b0d0961 100644 --- a/app/models/session_date.rb +++ b/app/models/session_date.rb @@ -23,7 +23,7 @@ class SessionDate < ApplicationRecord has_many :gillick_assessments, dependent: :restrict_with_error has_many :pre_screenings, dependent: :restrict_with_error - has_many :session_attendances, dependent: :restrict_with_error + has_many :attendance_records, dependent: :restrict_with_error scope :for_session, -> { where("session_id = sessions.id") } @@ -45,7 +45,7 @@ def today_or_past? = today? || past? def today_or_future? = today? || future? def has_been_attended? - gillick_assessments.any? || pre_screenings.any? || session_attendances.any? + gillick_assessments.any? || pre_screenings.any? || attendance_records.any? end private diff --git a/app/policies/session_attendance_policy.rb b/app/policies/attendance_record_policy.rb similarity index 93% rename from app/policies/session_attendance_policy.rb rename to app/policies/attendance_record_policy.rb index c5ce680a88..57bee96745 100644 --- a/app/policies/session_attendance_policy.rb +++ b/app/policies/attendance_record_policy.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class SessionAttendancePolicy < ApplicationPolicy +class AttendanceRecordPolicy < ApplicationPolicy def create? !already_vaccinated? && !was_seen_by_nurse? end diff --git a/app/views/patient_sessions/_header.html.erb b/app/views/patient_sessions/_header.html.erb index ea53d249b7..dcbab5f859 100644 --- a/app/views/patient_sessions/_header.html.erb +++ b/app/views/patient_sessions/_header.html.erb @@ -38,16 +38,16 @@ <% end %> - <% if @session.requires_registration? && (session_attendance = @patient_session.todays_attendance) %> + <% if @session.requires_registration? && (attendance_record = @patient_session.todays_attendance) %>
  • <%= render AppStatusTagComponent.new(@patient_session.registration_status&.status || "unknown", context: :register) %>
  • - <% if policy(session_attendance).edit? %> + <% if policy(attendance_record).edit? %>
  • <%= link_to( "Update attendance", - edit_session_patient_session_attendance_path(@session, @patient) + edit_session_patient_attendance_path(@session, @patient) ) %>
  • <% end %> diff --git a/app/views/patient_sessions/session_attendances/edit.html.erb b/app/views/patient_sessions/attendances/edit.html.erb similarity index 87% rename from app/views/patient_sessions/session_attendances/edit.html.erb rename to app/views/patient_sessions/attendances/edit.html.erb index 807648855b..6ab254d845 100644 --- a/app/views/patient_sessions/session_attendances/edit.html.erb +++ b/app/views/patient_sessions/attendances/edit.html.erb @@ -5,8 +5,8 @@ <% title = "Is #{@patient.full_name} attending today’s session?" %> <% content_for :page_title, title %> -<%= form_with model: @session_attendance, - url: session_patient_session_attendance_path(@session, @patient), +<%= form_with model: @attendance_record, + url: session_patient_attendance_path(@session, @patient), method: :put do |f| %> <%= f.govuk_error_summary %> <%= f.govuk_radio_buttons_fieldset(:attending, @@ -28,7 +28,7 @@ <%= f.govuk_radio_button( :attending, "not_registered", label: { text: "They have not been registered yet" }, - checked: @session_attendance.attending.nil?, + checked: @attendance_record.attending.nil?, ) %> <% end %> diff --git a/config/routes.rb b/config/routes.rb index fa915e4a33..9ec0bdcdf7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -285,7 +285,7 @@ only: [], module: :patient_sessions do resource :activity, only: %i[show create] - resource :session_attendance, path: "attendance", only: %i[edit update] + resource :attendance, only: %i[edit update] resources :programmes, path: "", param: :type, only: :show do get "record-already-vaccinated" diff --git a/db/migrate/20250908112554_rename_session_attendance.rb b/db/migrate/20250908112554_rename_session_attendance.rb new file mode 100644 index 0000000000..9564d9c589 --- /dev/null +++ b/db/migrate/20250908112554_rename_session_attendance.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class RenameSessionAttendance < ActiveRecord::Migration[8.0] + def change + rename_table :session_attendances, :attendance_records + end +end diff --git a/db/schema.rb b/db/schema.rb index 86ccc8931d..5a095af02e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -49,6 +49,17 @@ t.index ["team_id"], name: "index_archive_reasons_on_team_id" end + create_table "attendance_records", force: :cascade do |t| + t.bigint "session_date_id", null: false + t.boolean "attending", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "patient_id", null: false + t.index ["patient_id", "session_date_id"], name: "index_attendance_records_on_patient_id_and_session_date_id", unique: true + t.index ["patient_id"], name: "index_attendance_records_on_patient_id" + t.index ["session_date_id"], name: "index_attendance_records_on_session_date_id" + end + create_table "audits", force: :cascade do |t| t.integer "auditable_id" t.string "auditable_type" @@ -839,17 +850,6 @@ t.index ["team_id"], name: "index_school_moves_on_team_id" end - create_table "session_attendances", force: :cascade do |t| - t.bigint "session_date_id", null: false - t.boolean "attending", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.bigint "patient_id", null: false - t.index ["patient_id", "session_date_id"], name: "index_session_attendances_on_patient_id_and_session_date_id", unique: true - t.index ["patient_id"], name: "index_session_attendances_on_patient_id" - t.index ["session_date_id"], name: "index_session_attendances_on_session_date_id" - end - create_table "session_dates", force: :cascade do |t| t.bigint "session_id", null: false t.date "value", null: false @@ -1055,6 +1055,8 @@ add_foreign_key "archive_reasons", "patients" add_foreign_key "archive_reasons", "teams" add_foreign_key "archive_reasons", "users", column: "created_by_user_id" + add_foreign_key "attendance_records", "patients" + add_foreign_key "attendance_records", "session_dates" add_foreign_key "batches", "teams" add_foreign_key "batches", "vaccines" add_foreign_key "batches_immunisation_imports", "batches" @@ -1153,8 +1155,6 @@ add_foreign_key "school_moves", "locations", column: "school_id" add_foreign_key "school_moves", "patients" add_foreign_key "school_moves", "teams" - add_foreign_key "session_attendances", "patients" - add_foreign_key "session_attendances", "session_dates" add_foreign_key "session_dates", "sessions" add_foreign_key "session_notifications", "patients" add_foreign_key "session_notifications", "sessions" diff --git a/spec/factories/session_attendances.rb b/spec/factories/attendance_records.rb similarity index 69% rename from spec/factories/session_attendances.rb rename to spec/factories/attendance_records.rb index ba17f19af0..c54e73adf3 100644 --- a/spec/factories/session_attendances.rb +++ b/spec/factories/attendance_records.rb @@ -2,7 +2,7 @@ # == Schema Information # -# Table name: session_attendances +# Table name: attendance_records # # id :bigint not null, primary key # attending :boolean not null @@ -13,9 +13,9 @@ # # Indexes # -# index_session_attendances_on_patient_id (patient_id) -# index_session_attendances_on_patient_id_and_session_date_id (patient_id,session_date_id) UNIQUE -# index_session_attendances_on_session_date_id (session_date_id) +# index_attendance_records_on_patient_id (patient_id) +# index_attendance_records_on_patient_id_and_session_date_id (patient_id,session_date_id) UNIQUE +# index_attendance_records_on_session_date_id (session_date_id) # # Foreign Keys # @@ -23,7 +23,7 @@ # fk_rails_... (session_date_id => session_dates.id) # FactoryBot.define do - factory :session_attendance do + factory :attendance_record do transient { session { association(:session) } } patient diff --git a/spec/factories/patient_sessions.rb b/spec/factories/patient_sessions.rb index 56f3283050..597abf6630 100644 --- a/spec/factories/patient_sessions.rb +++ b/spec/factories/patient_sessions.rb @@ -55,7 +55,7 @@ trait :in_attendance do after(:create) do |patient_session| create( - :session_attendance, + :attendance_record, :present, patient: patient_session.patient, session: patient_session.session diff --git a/spec/factories/patients.rb b/spec/factories/patients.rb index a626f468b0..5b8ec65924 100644 --- a/spec/factories/patients.rb +++ b/spec/factories/patients.rb @@ -129,7 +129,7 @@ if evaluator.in_attendance create( - :session_attendance, + :attendance_record, :present, patient:, session: evaluator.session diff --git a/spec/features/manage_attendance_spec.rb b/spec/features/manage_attendance_spec.rb index 40818d746d..7f94525e47 100644 --- a/spec/features/manage_attendance_spec.rb +++ b/spec/features/manage_attendance_spec.rb @@ -201,7 +201,7 @@ def when_i_go_to_the_session_patients end def and_i_go_to_a_patient - click_link Patient.where.missing(:session_attendances).first.full_name + click_link Patient.where.missing(:attendance_records).first.full_name end def then_the_patient_is_not_registered_yet diff --git a/spec/lib/patient_merger_spec.rb b/spec/lib/patient_merger_spec.rb index 6a4b712998..93d2e60620 100644 --- a/spec/lib/patient_merger_spec.rb +++ b/spec/lib/patient_merger_spec.rb @@ -33,6 +33,9 @@ let(:access_log_entry) do create(:access_log_entry, patient: patient_to_destroy) end + let(:attendance_record) do + create(:attendance_record, :present, patient: patient_to_destroy) + end let(:consent) { create(:consent, patient: patient_to_destroy, programme:) } let(:consent_notification) do create( @@ -68,9 +71,6 @@ let(:duplicate_school_move) do create(:school_move, patient: patient_to_keep, school: school_move.school) end - let(:session_attendance) do - create(:session_attendance, :present, patient: patient_to_destroy) - end let(:session_notification) do create( :session_notification, @@ -110,6 +110,12 @@ ) end + it "moves attendance records" do + expect { call }.to change { attendance_record.reload.patient }.to( + patient_to_keep + ) + end + it "moves consents" do expect { call }.to change { consent.reload.patient }.to(patient_to_keep) end @@ -177,12 +183,6 @@ expect { school_move.reload }.to raise_error(ActiveRecord::RecordNotFound) end - it "moves session attendances" do - expect { call }.to change { session_attendance.reload.patient }.to( - patient_to_keep - ) - end - it "moves session notifications" do expect { call }.to change { session_notification.reload.patient }.to( patient_to_keep diff --git a/spec/lib/status_generator/registration_spec.rb b/spec/lib/status_generator/registration_spec.rb index ffc8274d56..d713988f5e 100644 --- a/spec/lib/status_generator/registration_spec.rb +++ b/spec/lib/status_generator/registration_spec.rb @@ -5,8 +5,8 @@ described_class.new( patient:, session:, - session_attendance: - patient_session.session_attendances.find_by( + attendance_record: + patient_session.attendance_records.find_by( session_date: session.session_dates.last ), vaccination_records: patient.vaccination_records @@ -34,7 +34,7 @@ context "with a session attendance for a different day to today" do before do create( - :session_attendance, + :attendance_record, :present, patient:, session_date: session.session_dates.first @@ -47,7 +47,7 @@ context "with a present session attendance for today" do before do create( - :session_attendance, + :attendance_record, :present, patient:, session_date: session.session_dates.second @@ -60,7 +60,7 @@ context "with an absent session attendance for today" do before do create( - :session_attendance, + :attendance_record, :absent, patient:, session_date: session.session_dates.second diff --git a/spec/lib/status_generator/session_spec.rb b/spec/lib/status_generator/session_spec.rb index 05c25b4e27..123dd50e9d 100644 --- a/spec/lib/status_generator/session_spec.rb +++ b/spec/lib/status_generator/session_spec.rb @@ -5,7 +5,7 @@ described_class.new( session_id: patient_session.session_id, academic_year: patient_session.academic_year, - session_attendance: patient_session.session_attendances.last, + attendance_record: patient_session.attendance_records.last, programme:, patient:, consents: patient.consents, @@ -79,7 +79,7 @@ end context "when not attending the session" do - before { create(:session_attendance, :absent, patient:, session:) } + before { create(:attendance_record, :absent, patient:, session:) } it { should be(:absent_from_session) } end @@ -275,7 +275,7 @@ context "with absent from session attendance" do before do - create(:session_attendance, :absent, patient:, session:, created_at:) + create(:attendance_record, :absent, patient:, session:, created_at:) end it { should eq(created_at) } @@ -297,7 +297,7 @@ ) create( - :session_attendance, + :attendance_record, :absent, patient:, session:, @@ -320,7 +320,7 @@ ) create( - :session_attendance, + :attendance_record, :absent, patient:, session:, diff --git a/spec/models/session_attendance_spec.rb b/spec/models/attendance_record_spec.rb similarity index 63% rename from spec/models/session_attendance_spec.rb rename to spec/models/attendance_record_spec.rb index fc037f2ffe..ebe1b9a377 100644 --- a/spec/models/session_attendance_spec.rb +++ b/spec/models/attendance_record_spec.rb @@ -2,7 +2,7 @@ # == Schema Information # -# Table name: session_attendances +# Table name: attendance_records # # id :bigint not null, primary key # attending :boolean not null @@ -13,17 +13,17 @@ # # Indexes # -# index_session_attendances_on_patient_id (patient_id) -# index_session_attendances_on_patient_id_and_session_date_id (patient_id,session_date_id) UNIQUE -# index_session_attendances_on_session_date_id (session_date_id) +# index_attendance_records_on_patient_id (patient_id) +# index_attendance_records_on_patient_id_and_session_date_id (patient_id,session_date_id) UNIQUE +# index_attendance_records_on_session_date_id (session_date_id) # # Foreign Keys # # fk_rails_... (patient_id => patients.id) # fk_rails_... (session_date_id => session_dates.id) # -describe SessionAttendance do - subject(:session_attendance) { build(:session_attendance) } +describe AttendanceRecord do + subject(:attendance_record) { build(:attendance_record) } describe "associations" do it { should belong_to(:patient) } diff --git a/spec/models/patient/registration_status_spec.rb b/spec/models/patient/registration_status_spec.rb index dde9e6865e..a20888abb2 100644 --- a/spec/models/patient/registration_status_spec.rb +++ b/spec/models/patient/registration_status_spec.rb @@ -47,12 +47,12 @@ it { should belong_to(:session) } end - describe "#session_attendance" do + describe "#attendance_record" do subject do described_class - .includes(:session_attendances) + .includes(:attendance_records) .find(patient_registration_status.id) - .session_attendance + .attendance_record end let(:patient_registration_status) do @@ -70,9 +70,9 @@ end context "with an attendance today and yesterday" do - let(:today_session_attendance) do + let(:today_attendance_record) do create( - :session_attendance, + :attendance_record, :present, patient:, session_date: session.session_dates.find_by(value: Date.current) @@ -81,14 +81,14 @@ before do create( - :session_attendance, + :attendance_record, :absent, patient:, session_date: session.session_dates.find_by(value: Date.yesterday) ) end - it { should eq(today_session_attendance) } + it { should eq(today_attendance_record) } end end @@ -102,7 +102,7 @@ context "with a session attendance for a different day to today" do before do create( - :session_attendance, + :attendance_record, :present, patient:, session_date: session.session_dates.first @@ -115,7 +115,7 @@ context "with a present session attendance for today" do before do create( - :session_attendance, + :attendance_record, :present, patient:, session_date: session.session_dates.second @@ -128,7 +128,7 @@ context "with an absent session attendance for today" do before do create( - :session_attendance, + :attendance_record, :absent, patient:, session_date: session.session_dates.second diff --git a/spec/models/patient_session_spec.rb b/spec/models/patient_session_spec.rb index 46e5c1d36f..1602acb0e7 100644 --- a/spec/models/patient_session_spec.rb +++ b/spec/models/patient_session_spec.rb @@ -168,7 +168,7 @@ it { should be true } it "is safe with only absent attendances" do - create(:session_attendance, :absent, patient:, session:) + create(:attendance_record, :absent, patient:, session:) expect(safe_to_destroy?).to be true end end @@ -185,12 +185,12 @@ end it "is unsafe with present attendances" do - create(:session_attendance, :present, patient:, session:) + create(:attendance_record, :present, patient:, session:) expect(safe_to_destroy?).to be false end it "is unsafe with mixed conditions" do - create(:session_attendance, :absent, patient:, session:) + create(:attendance_record, :absent, patient:, session:) create(:vaccination_record, programme:, patient:, session:) expect(safe_to_destroy?).to be false end diff --git a/spec/models/session_date_spec.rb b/spec/models/session_date_spec.rb index 48ef5f4e0f..96abb08118 100644 --- a/spec/models/session_date_spec.rb +++ b/spec/models/session_date_spec.rb @@ -73,7 +73,7 @@ end context "with a session attendance" do - before { create(:session_attendance, :present, session:) } + before { create(:attendance_record, :present, session:) } it { should be(true) } end diff --git a/spec/policies/session_attendance_policy_spec.rb b/spec/policies/attendance_record_policy_spec.rb similarity index 81% rename from spec/policies/session_attendance_policy_spec.rb rename to spec/policies/attendance_record_policy_spec.rb index c2940806db..bf9eb24d48 100644 --- a/spec/policies/session_attendance_policy_spec.rb +++ b/spec/policies/attendance_record_policy_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -describe SessionAttendancePolicy do - subject(:policy) { described_class.new(user, session_attendance) } +describe AttendanceRecordPolicy do + subject(:policy) { described_class.new(user, attendance_record) } let(:user) { create(:nurse) } @@ -12,17 +12,13 @@ shared_examples "allow if not yet vaccinated or seen by nurse" do context "with a new session attendance" do - let(:session_attendance) do - build(:session_attendance, patient:, session:) - end + let(:attendance_record) { build(:attendance_record, patient:, session:) } it { should be(true) } end context "with session attendance and one vaccination record from a different session" do - let(:session_attendance) do - build(:session_attendance, patient:, session:) - end + let(:attendance_record) { build(:attendance_record, patient:, session:) } before do create( @@ -39,9 +35,7 @@ end context "with session attendance and both vaccination records" do - let(:session_attendance) do - build(:session_attendance, patient:, session:) - end + let(:attendance_record) { build(:attendance_record, patient:, session:) } before do programmes.each do |programme| @@ -61,9 +55,7 @@ end context "with session attendance and both vaccination records from a different date" do - let(:session_attendance) do - build(:session_attendance, patient:, session:) - end + let(:attendance_record) { build(:attendance_record, patient:, session:) } around { |example| travel_to(Date.new(2025, 8, 31)) { example.run } } From b186b98a3f80acbaec1ed5a5629dfb0bcec5fab2 Mon Sep 17 00:00:00 2001 From: Thomas Leese Date: Thu, 4 Sep 2025 18:08:45 +0100 Subject: [PATCH 2/3] Remove `PatientSession#todays_attendance` It's specific to the session so we need to move it out of this model so it can be used when we no longer have an explicit link to the session. Jira-Issue: MAV-1938 --- .../sessions/register_controller.rb | 21 +++++++++++-------- app/models/patient_session.rb | 9 -------- app/views/patient_sessions/_header.html.erb | 4 +++- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/app/controllers/sessions/register_controller.rb b/app/controllers/sessions/register_controller.rb index 7e43adde98..1596a19206 100644 --- a/app/controllers/sessions/register_controller.rb +++ b/app/controllers/sessions/register_controller.rb @@ -5,8 +5,8 @@ class Sessions::RegisterController < ApplicationController before_action :set_session before_action :set_patient_search_form, only: :show + before_action :set_session_date, only: :create before_action :set_patient, only: :create - before_action :set_patient_session, only: :create layout "full" @@ -27,13 +27,17 @@ def show def create attendance_record = ActiveRecord::Base.transaction do - record = authorize @patient_session.todays_attendance + record = + authorize @patient + .attendance_records + .includes(:session_date) + .find_or_initialize_by(session_date: @session_date) record.update!(attending: params[:status] == "present") - StatusUpdater.call(patient: @patient_session.patient) + StatusUpdater.call(patient: @patient) record end - name = @patient_session.patient.full_name + name = @patient.full_name flash[:info] = if attendance_record.attending? t("attendance_flash.present", name:) @@ -50,12 +54,11 @@ def set_session @session = policy_scope(Session).find_by!(slug: params[:session_slug]) end - def set_patient - @patient = policy_scope(Patient).find(params[:patient_id]) + def set_session_date + @session_date = @session.session_dates.find_by!(value: Date.current) end - def set_patient_session - @patient_session = - PatientSession.find_by!(patient: @patient, session: @session) + def set_patient + @patient = policy_scope(Patient).find(params[:patient_id]) end end diff --git a/app/models/patient_session.rb b/app/models/patient_session.rb index 9f9bdb73f2..bdd0338f69 100644 --- a/app/models/patient_session.rb +++ b/app/models/patient_session.rb @@ -321,15 +321,6 @@ def destroy_if_safe! def programmes = session.programmes_for(patient:, academic_year:) - def todays_attendance - if (session_date = session.session_dates.today.first) - patient - .attendance_records - .includes(:patient, session_date: { session: :programmes }) - .find_or_initialize_by(session_date:) - end - end - def next_activity(programme:) if patient.vaccination_status(programme:, academic_year:).vaccinated? return nil diff --git a/app/views/patient_sessions/_header.html.erb b/app/views/patient_sessions/_header.html.erb index dcbab5f859..48388199e4 100644 --- a/app/views/patient_sessions/_header.html.erb +++ b/app/views/patient_sessions/_header.html.erb @@ -38,7 +38,9 @@ <% end %> - <% if @session.requires_registration? && (attendance_record = @patient_session.todays_attendance) %> + <% if @session.requires_registration? && (session_date = @session.session_dates.today.first) %> + <% attendance_record = @patient.attendance_records.includes(:session_date).find_or_initialize_by(session_date:) %> +
  • <%= render AppStatusTagComponent.new(@patient_session.registration_status&.status || "unknown", context: :register) %>
  • From d17b3ffffc8f330224d05b0dcdd69898206693e7 Mon Sep 17 00:00:00 2001 From: Thomas Leese Date: Mon, 8 Sep 2025 17:21:23 +0100 Subject: [PATCH 3/3] Remove session association from `AttendanceRecord` This replaces the session association with one to the location instead, as we want registration status to apply across all sessions for a particular location, in preparation for the removal of the `PatientSession` model. Jira-Issue: MAV-1938 --- ...nt_session_search_result_card_component.rb | 5 ++- .../attendances_controller.rb | 16 +++++---- .../sessions/register_controller.rb | 23 ++++++------ app/lib/generate/vaccination_records.rb | 7 +--- app/lib/status_updater.rb | 1 - app/models/attendance_record.rb | 31 ++++++++-------- app/models/location.rb | 1 + app/models/patient/registration_status.rb | 6 ++-- app/models/patient_session.rb | 2 +- app/models/session.rb | 1 - app/models/session_date.rb | 5 ++- app/policies/attendance_record_policy.rb | 8 ++--- app/views/patient_sessions/_header.html.erb | 5 +-- ...3_remove_session_from_attendance_record.rb | 36 +++++++++++++++++++ db/schema.rb | 9 ++--- spec/factories/attendance_records.rb | 36 ++++++++++++------- .../lib/status_generator/registration_spec.rb | 13 +++---- spec/models/attendance_record_spec.rb | 25 +++++++------ .../patient/registration_status_spec.rb | 23 +++++------- 19 files changed, 149 insertions(+), 104 deletions(-) create mode 100644 db/migrate/20250908125713_remove_session_from_attendance_record.rb diff --git a/app/components/app_patient_session_search_result_card_component.rb b/app/components/app_patient_session_search_result_card_component.rb index 06e2abb91e..008be010cb 100644 --- a/app/components/app_patient_session_search_result_card_component.rb +++ b/app/components/app_patient_session_search_result_card_component.rb @@ -98,9 +98,12 @@ def can_register_attendance? attendance_record = AttendanceRecord.new( patient:, - session_date: SessionDate.new(session:, value: Date.current) + location: session.location, + date: Date.current ) + attendance_record.session = session + policy(attendance_record).new? end diff --git a/app/controllers/patient_sessions/attendances_controller.rb b/app/controllers/patient_sessions/attendances_controller.rb index 320b0dd62e..6d2b142e4f 100644 --- a/app/controllers/patient_sessions/attendances_controller.rb +++ b/app/controllers/patient_sessions/attendances_controller.rb @@ -42,18 +42,20 @@ def update private def set_attendance_record - @attendance_record = - authorize( - @patient - .attendance_records - .includes(:patient, session_date: { session: :programmes }) - .find_or_initialize_by(session_date: @session_date) + attendance_record = + @patient.attendance_records.find_or_initialize_by( + location: @session.location, + date: @session_date.value ) + + attendance_record.session = @session + + @attendance_record = authorize attendance_record end def attendance_record_params params .expect(attendance_record: :attending) - .tap { |p| p[:attending] = nil if p[:attending] == "not_registered" } + .tap { it[:attending] = nil if it[:attending] == "not_registered" } end end diff --git a/app/controllers/sessions/register_controller.rb b/app/controllers/sessions/register_controller.rb index 1596a19206..aee1ed5f2e 100644 --- a/app/controllers/sessions/register_controller.rb +++ b/app/controllers/sessions/register_controller.rb @@ -26,16 +26,19 @@ def show def create attendance_record = - ActiveRecord::Base.transaction do - record = - authorize @patient - .attendance_records - .includes(:session_date) - .find_or_initialize_by(session_date: @session_date) - record.update!(attending: params[:status] == "present") - StatusUpdater.call(patient: @patient) - record - end + @patient.attendance_records.find_or_initialize_by( + location: @session.location, + date: @session_date.value + ) + + attendance_record.session = @session + + authorize attendance_record + + ActiveRecord::Base.transaction do + attendance_record.update!(attending: params[:status] == "present") + StatusUpdater.call(patient: @patient) + end name = @patient.full_name diff --git a/app/lib/generate/vaccination_records.rb b/app/lib/generate/vaccination_records.rb index 7fb1a14310..65f5f6550b 100644 --- a/app/lib/generate/vaccination_records.rb +++ b/app/lib/generate/vaccination_records.rb @@ -26,12 +26,7 @@ def create_vaccinations patient = patient_session.patient session = patient_session.session - unless AttendanceRecord.joins(:session_date).exists?( - patient:, - session_date: { - session: - } - ) + unless AttendanceRecord.exists?(patient:, location: session.location) attendance_records << FactoryBot.build( :attendance_record, :present, diff --git a/app/lib/status_updater.rb b/app/lib/status_updater.rb index a755f33a26..c1af25ff92 100644 --- a/app/lib/status_updater.rb +++ b/app/lib/status_updater.rb @@ -63,7 +63,6 @@ def update_registration_statuses! .includes( :patient, :attendance_records, - :session_date, :vaccination_records, session: :programmes ) diff --git a/app/models/attendance_record.rb b/app/models/attendance_record.rb index fe5e316767..200d837fe7 100644 --- a/app/models/attendance_record.rb +++ b/app/models/attendance_record.rb @@ -4,34 +4,35 @@ # # Table name: attendance_records # -# id :bigint not null, primary key -# attending :boolean not null -# created_at :datetime not null -# updated_at :datetime not null -# patient_id :bigint not null -# session_date_id :bigint not null +# id :bigint not null, primary key +# attending :boolean not null +# date :date not null +# created_at :datetime not null +# updated_at :datetime not null +# location_id :bigint not null +# patient_id :bigint not null # # Indexes # -# index_attendance_records_on_patient_id (patient_id) -# index_attendance_records_on_patient_id_and_session_date_id (patient_id,session_date_id) UNIQUE -# index_attendance_records_on_session_date_id (session_date_id) +# idx_on_patient_id_location_id_date_e5912f40c4 (patient_id,location_id,date) UNIQUE +# index_attendance_records_on_location_id (location_id) +# index_attendance_records_on_patient_id (patient_id) # # Foreign Keys # +# fk_rails_... (location_id => locations.id) # fk_rails_... (patient_id => patients.id) -# fk_rails_... (session_date_id => session_dates.id) # class AttendanceRecord < ApplicationRecord audited associated_with: :patient belongs_to :patient - belongs_to :session_date + belongs_to :location - has_one :session, through: :session_date - has_one :location, through: :session + scope :today, -> { where(date: Date.current) } - scope :today, -> { joins(:session_date).merge(SessionDate.today) } + delegate :today?, to: :date - delegate :today?, to: :session_date + # This is needed to be able to pass a session to the policy. + attr_accessor :session end diff --git a/app/models/location.rb b/app/models/location.rb index 7e851e5e66..7b5f08207e 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -53,6 +53,7 @@ class Location < ApplicationRecord primary_key: :gias_code, optional: true + has_many :attendance_records has_many :consent_forms has_many :location_programme_year_groups has_many :patients, foreign_key: :school_id diff --git a/app/models/patient/registration_status.rb b/app/models/patient/registration_status.rb index 7d9651f8da..5c91ad9f6b 100644 --- a/app/models/patient/registration_status.rb +++ b/app/models/patient/registration_status.rb @@ -29,9 +29,7 @@ class Patient::RegistrationStatus < ApplicationRecord -> { kept.order(performed_at: :desc) }, through: :patient - has_one :session_date, -> { today }, through: :session, source: :session_dates - - has_many :attendance_records, through: :patient + has_many :attendance_records, -> { today }, through: :patient enum :status, { unknown: 0, attending: 1, not_attending: 2, completed: 3 }, @@ -39,7 +37,7 @@ class Patient::RegistrationStatus < ApplicationRecord validate: true def attendance_record - attendance_records.find { it.session_date_id == session_date&.id } + attendance_records.find { it.location_id == session.location_id } end def assign_status diff --git a/app/models/patient_session.rb b/app/models/patient_session.rb index bdd0338f69..70b8685b35 100644 --- a/app/models/patient_session.rb +++ b/app/models/patient_session.rb @@ -72,7 +72,7 @@ class PatientSession < ApplicationRecord has_many :attendance_records, -> { where(patient_id: it.patient_id) }, - through: :session + through: :location has_many :session_notifications, -> { where(session_id: it.session_id) }, diff --git a/app/models/session.rb b/app/models/session.rb index ee20a0169f..b483a90508 100644 --- a/app/models/session.rb +++ b/app/models/session.rb @@ -55,7 +55,6 @@ class Session < ApplicationRecord has_many :programmes, through: :session_programmes has_many :gillick_assessments, through: :session_dates has_many :patients, through: :patient_sessions - has_many :attendance_records, through: :session_dates has_many :vaccines, through: :programmes has_many :location_programme_year_groups, diff --git a/app/models/session_date.rb b/app/models/session_date.rb index 2f3b0d0961..b9797da44a 100644 --- a/app/models/session_date.rb +++ b/app/models/session_date.rb @@ -21,9 +21,12 @@ class SessionDate < ApplicationRecord belongs_to :session + has_one :location, through: :session + has_many :gillick_assessments, dependent: :restrict_with_error has_many :pre_screenings, dependent: :restrict_with_error - has_many :attendance_records, dependent: :restrict_with_error + + has_many :attendance_records, -> { where(date: it.value) }, through: :location scope :for_session, -> { where("session_id = sessions.id") } diff --git a/app/policies/attendance_record_policy.rb b/app/policies/attendance_record_policy.rb index 57bee96745..f670bb7f03 100644 --- a/app/policies/attendance_record_policy.rb +++ b/app/policies/attendance_record_policy.rb @@ -11,8 +11,8 @@ def update? private - delegate :patient, :session_date, to: :record - delegate :session, to: :session_date + delegate :patient, :location_id, :date, :session, to: :record + delegate :academic_year, to: :session def already_vaccinated? @@ -26,8 +26,8 @@ def already_vaccinated? def was_seen_by_nurse? VaccinationRecord.kept.exists?( patient:, - session:, - performed_at: session_date.value.all_day + location_id:, + performed_at: date.all_day ) end end diff --git a/app/views/patient_sessions/_header.html.erb b/app/views/patient_sessions/_header.html.erb index 48388199e4..96da41cbaf 100644 --- a/app/views/patient_sessions/_header.html.erb +++ b/app/views/patient_sessions/_header.html.erb @@ -38,13 +38,14 @@ <% end %> - <% if @session.requires_registration? && (session_date = @session.session_dates.today.first) %> - <% attendance_record = @patient.attendance_records.includes(:session_date).find_or_initialize_by(session_date:) %> + <% if @session.requires_registration? && (date = @session.dates.find(&:today?)) %> + <% attendance_record = @patient.attendance_records.find_or_initialize_by(location: @session.location, date:) %>
  • <%= render AppStatusTagComponent.new(@patient_session.registration_status&.status || "unknown", context: :register) %>
  • + <% attendance_record.session = @session %> <% if policy(attendance_record).edit? %>
  • <%= link_to( diff --git a/db/migrate/20250908125713_remove_session_from_attendance_record.rb b/db/migrate/20250908125713_remove_session_from_attendance_record.rb new file mode 100644 index 0000000000..fed23a070e --- /dev/null +++ b/db/migrate/20250908125713_remove_session_from_attendance_record.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +class RemoveSessionFromAttendanceRecord < ActiveRecord::Migration[8.0] + def up + change_table :attendance_records, bulk: true do |t| + t.date :date + t.references :location, foreign_key: true + end + + execute <<-SQL + UPDATE attendance_records + SET location_id = sessions.location_id, date = session_dates.value + FROM session_dates + JOIN sessions ON sessions.id = session_dates.session_id + WHERE session_dates.id = attendance_records.session_date_id + SQL + + change_table :attendance_records, bulk: true do |t| + t.change_null :date, false + t.change_null :location_id, false + end + + remove_column :attendance_records, :session_date_id + + execute <<-SQL + DELETE FROM attendance_records a + USING attendance_records b + WHERE a.id < b.id + AND a.patient_id = b.patient_id + AND a.location_id = b.location_id + AND a.date = b.date + SQL + + add_index :attendance_records, %i[patient_id location_id date], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 5a095af02e..5104685a3f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -50,14 +50,15 @@ end create_table "attendance_records", force: :cascade do |t| - t.bigint "session_date_id", null: false t.boolean "attending", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false t.bigint "patient_id", null: false - t.index ["patient_id", "session_date_id"], name: "index_attendance_records_on_patient_id_and_session_date_id", unique: true + t.date "date", null: false + t.bigint "location_id", null: false + t.index ["location_id"], name: "index_attendance_records_on_location_id" + t.index ["patient_id", "location_id", "date"], name: "idx_on_patient_id_location_id_date_e5912f40c4", unique: true t.index ["patient_id"], name: "index_attendance_records_on_patient_id" - t.index ["session_date_id"], name: "index_attendance_records_on_session_date_id" end create_table "audits", force: :cascade do |t| @@ -1055,8 +1056,8 @@ add_foreign_key "archive_reasons", "patients" add_foreign_key "archive_reasons", "teams" add_foreign_key "archive_reasons", "users", column: "created_by_user_id" + add_foreign_key "attendance_records", "locations" add_foreign_key "attendance_records", "patients" - add_foreign_key "attendance_records", "session_dates" add_foreign_key "batches", "teams" add_foreign_key "batches", "vaccines" add_foreign_key "batches_immunisation_imports", "batches" diff --git a/spec/factories/attendance_records.rb b/spec/factories/attendance_records.rb index c54e73adf3..d9f3237e85 100644 --- a/spec/factories/attendance_records.rb +++ b/spec/factories/attendance_records.rb @@ -4,30 +4,40 @@ # # Table name: attendance_records # -# id :bigint not null, primary key -# attending :boolean not null -# created_at :datetime not null -# updated_at :datetime not null -# patient_id :bigint not null -# session_date_id :bigint not null +# id :bigint not null, primary key +# attending :boolean not null +# date :date not null +# created_at :datetime not null +# updated_at :datetime not null +# location_id :bigint not null +# patient_id :bigint not null # # Indexes # -# index_attendance_records_on_patient_id (patient_id) -# index_attendance_records_on_patient_id_and_session_date_id (patient_id,session_date_id) UNIQUE -# index_attendance_records_on_session_date_id (session_date_id) +# idx_on_patient_id_location_id_date_e5912f40c4 (patient_id,location_id,date) UNIQUE +# index_attendance_records_on_location_id (location_id) +# index_attendance_records_on_patient_id (patient_id) # # Foreign Keys # +# fk_rails_... (location_id => locations.id) # fk_rails_... (patient_id => patients.id) -# fk_rails_... (session_date_id => session_dates.id) # FactoryBot.define do factory :attendance_record do - transient { session { association(:session) } } - patient - session_date { session.session_dates.first } + session + + location { session.location } + date { session.dates.first } + + trait :today do + date { Date.current } + end + + trait :yesterday do + date { Date.yesterday } + end trait :present do attending { true } diff --git a/spec/lib/status_generator/registration_spec.rb b/spec/lib/status_generator/registration_spec.rb index d713988f5e..98a6f348f4 100644 --- a/spec/lib/status_generator/registration_spec.rb +++ b/spec/lib/status_generator/registration_spec.rb @@ -6,9 +6,7 @@ patient:, session:, attendance_record: - patient_session.attendance_records.find_by( - session_date: session.session_dates.last - ), + patient_session.attendance_records.find_by(date: session.dates.last), vaccination_records: patient.vaccination_records ) end @@ -37,7 +35,8 @@ :attendance_record, :present, patient:, - session_date: session.session_dates.first + session:, + date: session.dates.first ) end @@ -50,7 +49,8 @@ :attendance_record, :present, patient:, - session_date: session.session_dates.second + session:, + date: session.dates.second ) end @@ -63,7 +63,8 @@ :attendance_record, :absent, patient:, - session_date: session.session_dates.second + session:, + date: session.dates.second ) end diff --git a/spec/models/attendance_record_spec.rb b/spec/models/attendance_record_spec.rb index ebe1b9a377..a8ba46f7a5 100644 --- a/spec/models/attendance_record_spec.rb +++ b/spec/models/attendance_record_spec.rb @@ -4,31 +4,30 @@ # # Table name: attendance_records # -# id :bigint not null, primary key -# attending :boolean not null -# created_at :datetime not null -# updated_at :datetime not null -# patient_id :bigint not null -# session_date_id :bigint not null +# id :bigint not null, primary key +# attending :boolean not null +# date :date not null +# created_at :datetime not null +# updated_at :datetime not null +# location_id :bigint not null +# patient_id :bigint not null # # Indexes # -# index_attendance_records_on_patient_id (patient_id) -# index_attendance_records_on_patient_id_and_session_date_id (patient_id,session_date_id) UNIQUE -# index_attendance_records_on_session_date_id (session_date_id) +# idx_on_patient_id_location_id_date_e5912f40c4 (patient_id,location_id,date) UNIQUE +# index_attendance_records_on_location_id (location_id) +# index_attendance_records_on_patient_id (patient_id) # # Foreign Keys # +# fk_rails_... (location_id => locations.id) # fk_rails_... (patient_id => patients.id) -# fk_rails_... (session_date_id => session_dates.id) # describe AttendanceRecord do subject(:attendance_record) { build(:attendance_record) } describe "associations" do it { should belong_to(:patient) } - it { should belong_to(:session_date) } - - it { should have_one(:session).through(:session_date) } + it { should belong_to(:location) } end end diff --git a/spec/models/patient/registration_status_spec.rb b/spec/models/patient/registration_status_spec.rb index a20888abb2..69271d2751 100644 --- a/spec/models/patient/registration_status_spec.rb +++ b/spec/models/patient/registration_status_spec.rb @@ -71,21 +71,11 @@ context "with an attendance today and yesterday" do let(:today_attendance_record) do - create( - :attendance_record, - :present, - patient:, - session_date: session.session_dates.find_by(value: Date.current) - ) + create(:attendance_record, :present, :today, patient:, session:) end before do - create( - :attendance_record, - :absent, - patient:, - session_date: session.session_dates.find_by(value: Date.yesterday) - ) + create(:attendance_record, :absent, :yesterday, patient:, session:) end it { should eq(today_attendance_record) } @@ -105,7 +95,8 @@ :attendance_record, :present, patient:, - session_date: session.session_dates.first + session:, + date: session.dates.first ) end @@ -118,7 +109,8 @@ :attendance_record, :present, patient:, - session_date: session.session_dates.second + session:, + date: session.dates.second ) end @@ -131,7 +123,8 @@ :attendance_record, :absent, patient:, - session_date: session.session_dates.second + session:, + date: session.dates.second ) end