Skip to content

Commit ef191eb

Browse files
committed
Add "nhs_number_first_added_at" field to Patient
* Will be used by Automated Careplus reports Jira-Issue: MAV-7091
1 parent 1da9396 commit ef191eb

9 files changed

Lines changed: 121 additions & 6 deletions

app/models/patient.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
# invalidated_at :datetime
2323
# local_authority_mhclg_code :string
2424
# nhs_number :string
25+
# nhs_number_first_added_at :datetime
2526
# pending_changes :jsonb not null
2627
# preferred_family_name :string
2728
# preferred_given_name :string
@@ -47,6 +48,7 @@
4748
# index_patients_on_names_family_first (family_name,given_name)
4849
# index_patients_on_names_given_first (given_name,family_name)
4950
# index_patients_on_nhs_number (nhs_number) UNIQUE
51+
# index_patients_on_nhs_number_first_added_at (nhs_number_first_added_at)
5052
# index_patients_on_pending_changes_not_empty (id) WHERE (pending_changes <> '{}'::jsonb)
5153
# index_patients_on_school_id (school_id)
5254
#
@@ -495,6 +497,8 @@ class Patient < ApplicationRecord
495497
it.blank? ? nil : it.normalise_whitespace.gsub(/\s/, "")
496498
end
497499

500+
before_validation :set_nhs_number_first_added_at,
501+
if: :will_save_change_to_nhs_number?
498502
after_update :sync_vaccinations_to_nhs_immunisations_api
499503
after_commit :generate_important_notice_if_needed, on: :update
500504
after_commit :search_vaccinations_from_nhs_immunisations_api, on: :update
@@ -835,6 +839,14 @@ def destroy_childless_parents
835839
end
836840
end
837841

842+
def set_nhs_number_first_added_at
843+
old_nhs_number, new_nhs_number = nhs_number_change_to_be_saved
844+
845+
return unless old_nhs_number.blank? && new_nhs_number.present?
846+
847+
self.nhs_number_first_added_at ||= Time.current
848+
end
849+
838850
def archive_due_to_deceased!
839851
archive_reasons =
840852
teams.map do |team|
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# frozen_string_literal: true
2+
3+
class BackfillNHSNumberFirstAddedAtForPatients < ActiveRecord::Migration[8.1]
4+
disable_ddl_transaction!
5+
6+
BATCH_SIZE = 1000
7+
8+
def up
9+
migration = self.class.name
10+
started_at = Time.zone.now
11+
scope = Patient.where(nhs_number_first_added_at: nil).where.not(nhs_number: nil)
12+
total_records = scope.count
13+
total_batches = (total_records.to_f / BATCH_SIZE).ceil
14+
records_updated = 0
15+
16+
Rails.logger.info(
17+
event: "data_migration_start",
18+
migration:,
19+
total_records:,
20+
batch_size: BATCH_SIZE,
21+
total_batches:
22+
)
23+
24+
scope.in_batches(of: BATCH_SIZE).each_with_index do |batch, index|
25+
updated_count = batch.update_all("nhs_number_first_added_at = created_at")
26+
records_updated += updated_count
27+
28+
Rails.logger.info(
29+
event: "data_migration_batch",
30+
migration:,
31+
batch_number: index + 1,
32+
total_batches:,
33+
updated_count:,
34+
records_updated:
35+
)
36+
end
37+
38+
duration_minutes = ((Time.zone.now - started_at) / 60.0).round
39+
40+
Rails.logger.info(
41+
event: "data_migration_finish",
42+
migration:,
43+
duration_minutes:,
44+
records_updated:
45+
)
46+
end
47+
48+
def down
49+
raise ActiveRecord::IrreversibleMigration
50+
end
51+
end

db/data_schema.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
DataMigrate::Data.define(version: 2026_04_22_155320)
1+
DataMigrate::Data.define(version: 2026_04_22_120100)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# frozen_string_literal: true
2+
3+
class AddNHSNumberFirstAddedAtForPatient < ActiveRecord::Migration[8.1]
4+
def change
5+
add_column :patients, :nhs_number_first_added_at, :datetime
6+
end
7+
end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# frozen_string_literal: true
2+
3+
class AddIndexOnPatientsNHSNumberFirstAddedAt < ActiveRecord::Migration[8.1]
4+
disable_ddl_transaction!
5+
6+
def change
7+
add_index :patients, :nhs_number_first_added_at, algorithm: :concurrently
8+
end
9+
end

db/schema.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema[8.1].define(version: 2026_04_28_080729) do
13+
ActiveRecord::Schema[8.1].define(version: 2026_04_22_121000) do
1414
# These are extensions that must be enabled in order to support this database
1515
enable_extension "pg_catalog.plpgsql"
1616
enable_extension "pg_trgm"
@@ -162,7 +162,6 @@
162162
t.bigint "class_import_id", null: false
163163
t.bigint "parent_relationship_id", null: false
164164
t.index ["class_import_id", "parent_relationship_id"], name: "idx_on_class_import_id_parent_relationship_id_8225058195", unique: true
165-
t.index ["parent_relationship_id", "class_import_id"], name: "idx_on_parent_relationship_id_class_import_id_d7c05d6c2c", unique: true
166165
end
167166

168167
create_table "class_imports_parents", id: false, force: :cascade do |t|
@@ -220,7 +219,6 @@
220219
t.bigint "cohort_import_id", null: false
221220
t.bigint "parent_relationship_id", null: false
222221
t.index ["cohort_import_id", "parent_relationship_id"], name: "idx_on_cohort_import_id_parent_relationship_id_c65e20d1f8", unique: true
223-
t.index ["parent_relationship_id", "cohort_import_id"], name: "idx_on_parent_relationship_id_cohort_import_id_40fb9846d6", unique: true
224222
end
225223

226224
create_table "cohort_imports_parents", id: false, force: :cascade do |t|
@@ -457,7 +455,6 @@
457455
t.bigint "immunisation_import_id", null: false
458456
t.bigint "vaccination_record_id", null: false
459457
t.index ["immunisation_import_id", "vaccination_record_id"], name: "idx_on_immunisation_import_id_vaccination_record_id_588e859772", unique: true
460-
t.index ["vaccination_record_id", "immunisation_import_id"], name: "idx_on_vaccination_record_id_immunisation_import_id_813c516ad7", unique: true
461458
end
462459

463460
create_table "important_notices", force: :cascade do |t|
@@ -760,6 +757,7 @@
760757
t.datetime "invalidated_at"
761758
t.string "local_authority_mhclg_code"
762759
t.string "nhs_number"
760+
t.datetime "nhs_number_first_added_at"
763761
t.jsonb "pending_changes", default: {}, null: false
764762
t.string "preferred_family_name"
765763
t.string "preferred_given_name"
@@ -781,6 +779,7 @@
781779
t.index ["id"], name: "index_patients_on_pending_changes_not_empty", where: "(pending_changes <> '{}'::jsonb)"
782780
t.index ["local_authority_mhclg_code"], name: "index_patients_on_local_authority_mhclg_code"
783781
t.index ["nhs_number"], name: "index_patients_on_nhs_number", unique: true
782+
t.index ["nhs_number_first_added_at"], name: "index_patients_on_nhs_number_first_added_at"
784783
t.index ["school_id"], name: "index_patients_on_school_id"
785784
end
786785

spec/factories/patients.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
# invalidated_at :datetime
2323
# local_authority_mhclg_code :string
2424
# nhs_number :string
25+
# nhs_number_first_added_at :datetime
2526
# pending_changes :jsonb not null
2627
# preferred_family_name :string
2728
# preferred_given_name :string
@@ -47,6 +48,7 @@
4748
# index_patients_on_names_family_first (family_name,given_name)
4849
# index_patients_on_names_given_first (given_name,family_name)
4950
# index_patients_on_nhs_number (nhs_number) UNIQUE
51+
# index_patients_on_nhs_number_first_added_at (nhs_number_first_added_at)
5052
# index_patients_on_pending_changes_not_empty (id) WHERE (pending_changes <> '{}'::jsonb)
5153
# index_patients_on_school_id (school_id)
5254
#
@@ -91,6 +93,7 @@
9193
"#{base}#{check_digit}"
9294
end
9395
end
96+
nhs_number_first_added_at { nhs_number.present? ? Time.current : nil }
9497

9598
given_name { Faker::Name.first_name }
9699
family_name { Faker::Name.last_name }

spec/features/cli_reports_send_to_careplus_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@
115115
)
116116

117117
then_the_error_output_includes(
118-
"does not have CarePlus credentials configured"
118+
"needs the CarePlus username, password, and namespace configured to send reports."
119119
)
120120
and_no_request_was_made
121121
end

spec/models/patient_spec.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
# invalidated_at :datetime
2323
# local_authority_mhclg_code :string
2424
# nhs_number :string
25+
# nhs_number_first_added_at :datetime
2526
# pending_changes :jsonb not null
2627
# preferred_family_name :string
2728
# preferred_given_name :string
@@ -47,6 +48,7 @@
4748
# index_patients_on_names_family_first (family_name,given_name)
4849
# index_patients_on_names_given_first (given_name,family_name)
4950
# index_patients_on_nhs_number (nhs_number) UNIQUE
51+
# index_patients_on_nhs_number_first_added_at (nhs_number_first_added_at)
5052
# index_patients_on_pending_changes_not_empty (id) WHERE (pending_changes <> '{}'::jsonb)
5153
# index_patients_on_school_id (school_id)
5254
#
@@ -1307,6 +1309,38 @@
13071309
end
13081310
end
13091311

1312+
describe "NHS number first added timestamp" do
1313+
it "sets nhs_number_first_added_at when an NHS number is first added" do
1314+
patient =
1315+
create(:patient, nhs_number: nil, nhs_number_first_added_at: nil)
1316+
1317+
freeze_time do
1318+
expect { patient.update!(nhs_number: "9449310475") }.to change {
1319+
patient.reload.nhs_number_first_added_at
1320+
}.from(nil).to(Time.current)
1321+
end
1322+
end
1323+
1324+
it "does not clear nhs_number_first_added_at when an NHS number is removed" do
1325+
patient = create(:patient)
1326+
1327+
expect { patient.update!(nhs_number: nil) }.not_to(
1328+
change { patient.reload.nhs_number_first_added_at }
1329+
)
1330+
end
1331+
1332+
it "does not overwrite nhs_number_first_added_at when an NHS number is re-added" do
1333+
patient = create(:patient)
1334+
first_added_at = patient.nhs_number_first_added_at
1335+
1336+
patient.update!(nhs_number: nil)
1337+
1338+
expect { patient.update!(nhs_number: "9449310475") }.not_to change {
1339+
patient.reload.nhs_number_first_added_at
1340+
}.from(first_added_at)
1341+
end
1342+
end
1343+
13101344
describe "#should_search_vaccinations_from_nhs_immunisations_api?" do
13111345
subject(:should_search_vaccinations_from_nhs_immunisations_api?) do
13121346
patient.send(:should_search_vaccinations_from_nhs_immunisations_api?)

0 commit comments

Comments
 (0)