From 1ca4829e418c890c2fd79e6b5467ba94697eb572 Mon Sep 17 00:00:00 2001 From: Steve Hook Date: Tue, 14 Apr 2026 20:04:25 +0100 Subject: [PATCH 1/4] Failing specs for consent with new association Duplicates `verbal_consent_given_spec.rb` feature spec into a variant with and without the `one_patient_per_parent` feature flag. The `verbal_consent_given_without_parent_association_feature_flag_spec` variant can be deleted when we drop the feature flag. Jira-Issue: MAV-6057 --- spec/factories/patients.rb | 12 +- spec/features/verbal_consent_given_spec.rb | 50 +-- ...ut_parent_association_feature_flag_spec.rb | 352 ++++++++++++++++++ 3 files changed, 391 insertions(+), 23 deletions(-) create mode 100644 spec/features/verbal_consent_given_without_parent_association_feature_flag_spec.rb diff --git a/spec/factories/patients.rb b/spec/factories/patients.rb index e4afa3cc49..e4f44c4371 100644 --- a/spec/factories/patients.rb +++ b/spec/factories/patients.rb @@ -138,7 +138,17 @@ parent_relationships do parents.map do |parent| - association(:parent_relationship, patient: instance, parent:) + association( + :parent_relationship, + patient: instance, + parent:, + email: parent.email, + full_name: parent.full_name, + phone: parent.phone, + phone_receive_updates: parent.phone_receive_updates, + contact_method_type: parent.contact_method_type, + contact_method_other_details: parent.contact_method_other_details + ) end end diff --git a/spec/features/verbal_consent_given_spec.rb b/spec/features/verbal_consent_given_spec.rb index 428146edd2..6b4ee53ebd 100644 --- a/spec/features/verbal_consent_given_spec.rb +++ b/spec/features/verbal_consent_given_spec.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true describe "Verbal consent" do + before { Flipper.enable(:one_patient_per_parent) } + scenario "Given HPV" do given_an_hpv_programme_is_underway and_i_am_signed_in @@ -123,6 +125,7 @@ def create_programme(programme) parents: [@parent], date_of_birth: Programme::MIN_MMRV_ELIGIBILITY_DATE - 1.year ) + @parent_relationship = @patient.parent_relationships.first PatientStatusUpdater.call end @@ -201,12 +204,10 @@ def record_that_verbal_consent_was_given( "Choose who you are trying to get consent from" ) - parent_label = - @patient - .parent_relationships - .includes(:parent) - .find_by(patient: @patient) - .label_with_parent + parent_relationship = + @patient.parent_relationships.find_by(patient: @patient) + parent_relationship.strict_loading!(false) + parent_label = parent_relationship.label_with_parent choose parent_label click_button "Continue" @@ -273,26 +274,27 @@ def when_i_confirm_the_consent_response def then_the_parent_details_are_saved_to_the_consent consent = @patient.consents.last - parent_relationship = @patient.parent_relationships.first expect(consent).to have_attributes( - parent_full_name: @parent.full_name, - parent_email: @parent.email, - parent_phone: @parent.phone, - parent_phone_receive_updates: @parent.phone_receive_updates, - parent_relationship_type: parent_relationship.type + parent_full_name: @parent_relationship.full_name, + parent_email: @parent_relationship.email, + parent_phone: @parent_relationship.phone, + parent_phone_receive_updates: @parent_relationship.phone_receive_updates, + parent_relationship_type: @parent_relationship.type ) expect(consent.parent_relationship_other_name.to_s).to eq( - parent_relationship.other_name.to_s + @parent_relationship.other_name.to_s ) end def and_i_can_see_the_consent_response_details(number_of_health_questions:) click_link @patient.full_name, match: :first - click_link @parent.full_name + click_link @parent_relationship.full_name - expect(page).to have_content("Consent response from #{@parent.full_name}") + expect(page).to have_content( + "Consent response from #{@parent_relationship.full_name}" + ) expect(page).to have_content(["Date", Date.current.to_fs(:long)].join) expect(page).to have_content(["Response", "Consent given"].join) expect(page).to have_content(["Method", "By phone"].join) @@ -303,12 +305,16 @@ def and_i_can_see_the_consent_response_details(number_of_health_questions:) ) expect(page).to have_content(["School", @patient.school.name].join) - expect(page).to have_content(["Name", @parent.full_name].join) + expect(page).to have_content(["Name", @parent_relationship.full_name].join) expect(page).to have_content( ["Relationship", @patient.parent_relationships.first.label].join ) - expect(page).to have_content(["Email address", @parent.email].join("\n")) - expect(page).to have_content(["Phone number", @parent.phone].join("\n")) + expect(page).to have_content( + ["Email address", @parent_relationship.email].join("\n") + ) + expect(page).to have_content( + ["Phone number", @parent_relationship.phone].join("\n") + ) expect(page).to have_content("Answers to health questions") expect(page).to have_content( @@ -320,7 +326,7 @@ def and_i_can_see_the_consent_response_details(number_of_health_questions:) def then_an_email_is_sent_to_the_parent_confirming_their_consent expect(email_deliveries).to include( matching_notify_email( - to: @parent.email, + to: @parent_relationship.email, template: :consent_confirmation_given ).with_content_including("You’ve given consent", "withdraw your consent") ) @@ -329,7 +335,7 @@ def then_an_email_is_sent_to_the_parent_confirming_their_consent def and_a_text_is_sent_to_the_parent_confirming_their_consent expect(sms_deliveries).to include( matching_notify_sms( - phone_number: @parent.phone, + phone_number: @parent_relationship.phone, template: :consent_confirmation_given ).with_content_including( "You've given consent for Alex", @@ -346,7 +352,7 @@ def and_i_can_see_the_log_entries_for_the_email_and_sms click_on "Back" click_on "Session activity and notes" expect(page).to have_content("Consent confirmation given", count: 2) - expect(page).to have_content(@parent.email) - expect(page).to have_content(@parent.phone) + expect(page).to have_content(@parent_relationship.email) + expect(page).to have_content(@parent_relationship.phone) end end diff --git a/spec/features/verbal_consent_given_without_parent_association_feature_flag_spec.rb b/spec/features/verbal_consent_given_without_parent_association_feature_flag_spec.rb new file mode 100644 index 0000000000..428146edd2 --- /dev/null +++ b/spec/features/verbal_consent_given_without_parent_association_feature_flag_spec.rb @@ -0,0 +1,352 @@ +# frozen_string_literal: true + +describe "Verbal consent" do + scenario "Given HPV" do + given_an_hpv_programme_is_underway + and_i_am_signed_in + + when_i_record_that_verbal_consent_was_given + then_i_see_the_check_and_confirm_page + + when_i_confirm_the_consent_response + then_the_parent_details_are_saved_to_the_consent + then_an_email_is_sent_to_the_parent_confirming_their_consent + and_a_text_is_sent_to_the_parent_confirming_their_consent + and_i_can_see_the_consent_response_details(number_of_health_questions: 4) + and_i_can_see_the_log_entries_for_the_email_and_sms + end + + scenario "Given flu injection" do + given_a_flu_programme_is_underway + and_i_am_signed_in + + when_i_record_that_verbal_injection_consent_was_given + then_i_see_the_check_and_confirm_page + and_i_see_the_flu_injection_consent_given + + when_i_confirm_the_consent_response + then_an_email_is_sent_to_the_parent_confirming_their_consent + and_a_text_is_sent_to_the_parent_confirming_their_consent + end + + scenario "Given flu injection with a PSD already" do + given_a_flu_programme_is_underway + and_i_am_signed_in + and_the_patient_has_a_psd + + when_i_record_that_verbal_injection_consent_was_given + then_i_see_the_check_and_confirm_page + and_i_see_the_flu_injection_consent_given + + when_i_confirm_the_consent_response + then_an_email_is_sent_to_the_parent_confirming_their_consent + and_a_text_is_sent_to_the_parent_confirming_their_consent + and_the_psd_is_invalidated + end + + scenario "Given flu nasal spray" do + given_a_flu_programme_is_underway + and_i_am_signed_in + + when_i_record_that_verbal_nasal_consent_was_given + then_i_see_the_check_and_confirm_page + and_i_see_the_flu_nasal_consent_given + + when_i_confirm_the_consent_response + then_an_email_is_sent_to_the_parent_confirming_their_consent + and_a_text_is_sent_to_the_parent_confirming_their_consent + end + + scenario "Given flu nasal spray and injection" do + given_a_flu_programme_is_underway + and_i_am_signed_in + + when_i_record_that_verbal_nasal_and_injection_consent_was_given + then_i_see_the_check_and_confirm_page + and_i_see_the_flu_nasal_and_injection_consent_given + + when_i_confirm_the_consent_response + then_an_email_is_sent_to_the_parent_confirming_their_consent + and_a_text_is_sent_to_the_parent_confirming_their_consent + end + + scenario "Given MMR" do + given_an_mmr_programme_is_underway + and_i_am_signed_in + + when_i_record_that_verbal_consent_was_given_with_gelatine + then_i_see_the_check_and_confirm_page + + when_i_confirm_the_consent_response + then_an_email_is_sent_to_the_parent_confirming_their_consent + and_a_text_is_sent_to_the_parent_confirming_their_consent + and_i_can_see_the_consent_response_details(number_of_health_questions: 3) + end + + scenario "Given MMR without gelatine" do + given_an_mmr_programme_is_underway + and_i_am_signed_in + + when_i_record_that_verbal_consent_was_given_without_gelatine + then_i_see_the_check_and_confirm_page + + when_i_confirm_the_consent_response + then_an_email_is_sent_to_the_parent_confirming_their_consent + and_a_text_is_sent_to_the_parent_confirming_their_consent + and_i_can_see_the_consent_response_details(number_of_health_questions: 3) + end + + def given_an_hpv_programme_is_underway + create_programme(Programme.hpv) + end + + def given_a_flu_programme_is_underway + create_programme(Programme.flu) + end + + def given_an_mmr_programme_is_underway + create_programme(Programme.mmr) + end + + def create_programme(programme) + @programme = programme + programmes = [@programme] + @team = create(:team, :with_one_nurse, programmes:) + @session = create(:session, team: @team, programmes:) + + @parent = create(:parent) + @patient = + create( + :patient, + given_name: "Alex", + session: @session, + parents: [@parent], + date_of_birth: Programme::MIN_MMRV_ELIGIBILITY_DATE - 1.year + ) + + PatientStatusUpdater.call + end + + def and_i_am_signed_in + sign_in @team.users.first + end + + def and_the_patient_has_a_psd + @patient_specific_direction = + create( + :patient_specific_direction, + patient: @patient, + programme: @programme + ) + end + + def when_i_record_that_verbal_consent_was_given + record_that_verbal_consent_was_given( + consent_option: "Yes, they agree", + number_of_health_questions: 4 + ) + end + + def when_i_record_that_verbal_injection_consent_was_given + record_that_verbal_consent_was_given( + consent_option: "Yes, for the injected vaccine only", + number_of_health_questions: 4 + ) + end + + def when_i_record_that_verbal_nasal_consent_was_given + record_that_verbal_consent_was_given( + consent_option: "Yes, for the nasal spray", + number_of_health_questions: 9 + ) + end + + def when_i_record_that_verbal_nasal_and_injection_consent_was_given + record_that_verbal_consent_was_given( + consent_option: "Yes, for the nasal spray", + number_of_health_questions: 10, + injective_alternative: true + ) + end + + def when_i_record_that_verbal_consent_was_given_with_gelatine + record_that_verbal_consent_was_given( + consent_option: "Yes, they agree", + number_of_health_questions: 3, + without_gelatine: false + ) + end + + def when_i_record_that_verbal_consent_was_given_without_gelatine + record_that_verbal_consent_was_given( + consent_option: "Yes, they agree", + number_of_health_questions: 3, + without_gelatine: true + ) + end + + def record_that_verbal_consent_was_given( + consent_option:, + number_of_health_questions:, + injective_alternative: nil, + without_gelatine: nil + ) + visit session_patients_path(@session) + click_link @patient.full_name + click_button "Record a new consent response" + + # Who are you trying to get consent from? + click_button "Continue" + expect(page).to have_content( + "Choose who you are trying to get consent from" + ) + + parent_label = + @patient + .parent_relationships + .includes(:parent) + .find_by(patient: @patient) + .label_with_parent + + choose parent_label + click_button "Continue" + + # Details for parent or guardian + expect(page).to have_content("Details for #{parent_label}") + # don't change any details + click_button "Continue" + + # How was the response given? + choose "By phone" + click_button "Continue" + + # Do they agree? + choose consent_option + if consent_option.include?("nasal") + choose injective_alternative ? "Yes" : "No" + end + unless without_gelatine.nil? + if without_gelatine + choose "Yes, they want their child to have a vaccine that does not contain gelatine" + else + choose "Their child can have either type of vaccine" + end + end + click_button "Continue" + + number_of_health_questions.times do |index| + find_all(".nhsuk-fieldset")[index].choose "No" + end + + click_button "Continue" + end + + def then_i_see_the_check_and_confirm_page + expect(page).to have_content("Check and confirm answers") + expect(page).to have_content(["Method", "By phone"].join) + expect(page).not_to have_content( + "Confirmation of vaccination sent to parent" + ) + end + + def and_i_see_the_flu_injection_consent_given + expect(page).to have_content("ResponseConsent given") + expect(page).to have_content("Chosen vaccineInjected vaccine only") + end + + def and_i_see_the_flu_nasal_consent_given + expect(page).to have_content("ResponseConsent given") + expect(page).to have_content("Chosen vaccineNasal spray only") + end + + def and_i_see_the_flu_nasal_and_injection_consent_given + expect(page).to have_content("ResponseConsent given") + expect(page).to have_content( + "Chosen vaccineNasal spray or injected vaccine" + ) + end + + def when_i_confirm_the_consent_response + click_button "Confirm" + expect(page).to have_content("Consent recorded for #{@patient.full_name}") + end + + def then_the_parent_details_are_saved_to_the_consent + consent = @patient.consents.last + parent_relationship = @patient.parent_relationships.first + + expect(consent).to have_attributes( + parent_full_name: @parent.full_name, + parent_email: @parent.email, + parent_phone: @parent.phone, + parent_phone_receive_updates: @parent.phone_receive_updates, + parent_relationship_type: parent_relationship.type + ) + + expect(consent.parent_relationship_other_name.to_s).to eq( + parent_relationship.other_name.to_s + ) + end + + def and_i_can_see_the_consent_response_details(number_of_health_questions:) + click_link @patient.full_name, match: :first + click_link @parent.full_name + + expect(page).to have_content("Consent response from #{@parent.full_name}") + expect(page).to have_content(["Date", Date.current.to_fs(:long)].join) + expect(page).to have_content(["Response", "Consent given"].join) + expect(page).to have_content(["Method", "By phone"].join) + + expect(page).to have_content(["Full name", @patient.full_name].join) + expect(page).to have_content( + ["Date of birth", @patient.date_of_birth.to_fs(:long)].join + ) + expect(page).to have_content(["School", @patient.school.name].join) + + expect(page).to have_content(["Name", @parent.full_name].join) + expect(page).to have_content( + ["Relationship", @patient.parent_relationships.first.label].join + ) + expect(page).to have_content(["Email address", @parent.email].join("\n")) + expect(page).to have_content(["Phone number", @parent.phone].join("\n")) + + expect(page).to have_content("Answers to health questions") + expect(page).to have_content( + "#{@patient.parent_relationships.first.label} responded: No", + count: number_of_health_questions + ) + end + + def then_an_email_is_sent_to_the_parent_confirming_their_consent + expect(email_deliveries).to include( + matching_notify_email( + to: @parent.email, + template: :consent_confirmation_given + ).with_content_including("You’ve given consent", "withdraw your consent") + ) + end + + def and_a_text_is_sent_to_the_parent_confirming_their_consent + expect(sms_deliveries).to include( + matching_notify_sms( + phone_number: @parent.phone, + template: :consent_confirmation_given + ).with_content_including( + "You've given consent for Alex", + "Please let them know what to expect" + ) + ) + end + + def and_the_psd_is_invalidated + expect(@patient_specific_direction.reload).to be_invalidated + end + + def and_i_can_see_the_log_entries_for_the_email_and_sms + click_on "Back" + click_on "Session activity and notes" + expect(page).to have_content("Consent confirmation given", count: 2) + expect(page).to have_content(@parent.email) + expect(page).to have_content(@parent.phone) + end +end From 05770c4be5c02deaaaed38a39e1395ee68eb82ef Mon Sep 17 00:00:00 2001 From: Steve Hook Date: Wed, 15 Apr 2026 11:38:51 +0100 Subject: [PATCH 2/4] Fix `verbal_consent_given_spec` for new parent-patient model Jira-Issue: MAV-6057 --- app/controllers/draft_consents_controller.rb | 67 ++++++++++++------- app/models/draft_consent.rb | 5 +- .../draft_consents/parent_details.html.erb | 2 +- config/locales/en.yml | 3 + spec/factories/parents.rb | 2 + 5 files changed, 51 insertions(+), 28 deletions(-) diff --git a/app/controllers/draft_consents_controller.rb b/app/controllers/draft_consents_controller.rb index 5b0448b672..f954e0f40d 100644 --- a/app/controllers/draft_consents_controller.rb +++ b/app/controllers/draft_consents_controller.rb @@ -213,17 +213,6 @@ def set_steps self.steps = @draft_consent.wizard_steps end - def set_back_link_path - @back_link_path = - if @draft_consent.editing? - wizard_path("confirm") - elsif current_step == @draft_consent.wizard_steps.first - session_patient_programme_path(@session, @patient, @programme) - else - previous_wizard_path - end - end - def is_who_step? = current_step == :who NewOrExistingContactOption = Struct.new(:value, :label, :hint) @@ -241,22 +230,39 @@ def set_new_or_existing_contact_options ) end - parent_relationships = - ( - @patient.parent_relationships.includes(:parent) + + @new_or_existing_contact_options += + if Flipper.enabled?(:one_patient_per_parent) + parents = (@patient.parents + @patient .consents - .where(programme_type: @programme.type) - .filter_map(&:parent_relationship) - ).compact.uniq.sort_by(&:label) - - @new_or_existing_contact_options += - parent_relationships.map do |parent_relationship| - NewOrExistingContactOption.new( - value: parent_relationship.parent.id, - label: parent_relationship.label_with_parent, - hint: parent_relationship.parent.contact_label - ) + .select { it.programme_type == @programme.type } + .filter_map(&:parent) + ).compact.uniq.sort_by(&:label) + + parents.map do |parent| + NewOrExistingContactOption.new( + value: parent.id, + label: parent.label_with_parent, + hint: parent.contact_label + ) + end + else + parent_relationships = + ( + @patient.parent_relationships.includes(:parent) + + @patient + .consents + .where(programme_type: @programme.type) + .filter_map(&:parent_relationship) + ).compact.uniq.sort_by(&:label) + + parent_relationships.map do |parent_relationship| + NewOrExistingContactOption.new( + value: parent_relationship.parent.id, + label: parent_relationship.label_with_parent, + hint: parent_relationship.parent.contact_label + ) + end end @new_or_existing_contact_options << NewOrExistingContactOption.new( @@ -287,6 +293,17 @@ def set_triage_form end end + def set_back_link_path + @back_link_path = + if @draft_consent.editing? + wizard_path("confirm") + elsif current_step == @draft_consent.wizard_steps.first + session_patient_programme_path(@session, @patient, @programme) + else + previous_wizard_path + end + end + # Returns: # { # question_0: %i[notes response], diff --git a/app/models/draft_consent.rb b/app/models/draft_consent.rb index 6b000f4d8f..b83dfe71a8 100644 --- a/app/models/draft_consent.rb +++ b/app/models/draft_consent.rb @@ -279,8 +279,9 @@ def parent=(value) self.parent_full_name = value&.full_name self.parent_phone = patient.restricted? ? "" : value&.phone self.parent_phone_receive_updates = value&.phone_receive_updates - self.parent_relationship_type = parent_relationship&.type - self.parent_relationship_other_name = parent_relationship&.other_name + self.parent_relationship_type = parent_relationship&.type || value&.type + self.parent_relationship_other_name = + parent_relationship&.other_name || value&.other_name self.parent_responsibility = value ? true : nil end diff --git a/app/views/draft_consents/parent_details.html.erb b/app/views/draft_consents/parent_details.html.erb index f1cdc955e7..05ce0d26bf 100644 --- a/app/views/draft_consents/parent_details.html.erb +++ b/app/views/draft_consents/parent_details.html.erb @@ -3,7 +3,7 @@ <% end %> <% page_title = if @parent.persisted? - "Details for #{@draft_consent.parent_relationship.label_with_parent}" + "Details for #{Flipper.enabled?(:one_patient_per_parent) ? @parent.label_with_parent : @draft_consent.parent_relationship.label_with_parent}" else "Details for parent or guardian" end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 94da2fd3b9..8b6d237517 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -400,6 +400,9 @@ en: other: Other text: Can only receive text messages voice: Can only receive voice calls + types: + father: dad + mother: mum parent_relationship: types: father: dad diff --git a/spec/factories/parents.rb b/spec/factories/parents.rb index f76665f406..22367071da 100644 --- a/spec/factories/parents.rb +++ b/spec/factories/parents.rb @@ -29,6 +29,8 @@ email { Faker::Internet.email } phone { "07700 900#{rand(0..999).to_s.rjust(3, "0")}" } phone_receive_updates { phone.present? } + type { %w[father guardian mother other].sample } + other_name { type == "other" ? "Other" : nil } trait :non_contactable do phone { nil } From 0c772e939e1b350aeb6e0f83b17bd59119a695e7 Mon Sep 17 00:00:00 2001 From: Steve Hook Date: Wed, 15 Apr 2026 14:22:32 +0100 Subject: [PATCH 3/4] Adapt consent with new parent spec to new association Jira-Issue: MAV-6057 --- app/controllers/draft_consents_controller.rb | 45 ++----- app/models/draft_consent.rb | 28 ++-- .../draft_consents/parent_details.html.erb | 2 +- .../patient_sessions/consents/show.html.erb | 2 +- spec/factories/parents.rb | 2 - ...sent_given_by_new_parental_contact_spec.rb | 5 +- ...ut_parent_association_feature_flag_spec.rb | 126 ++++++++++++++++++ ...ut_parent_association_feature_flag_spec.rb | 2 + 8 files changed, 167 insertions(+), 45 deletions(-) create mode 100644 spec/features/verbal_consent_given_by_new_parental_contact_without_parent_association_feature_flag_spec.rb diff --git a/app/controllers/draft_consents_controller.rb b/app/controllers/draft_consents_controller.rb index f954e0f40d..a0413ac226 100644 --- a/app/controllers/draft_consents_controller.rb +++ b/app/controllers/draft_consents_controller.rb @@ -230,39 +230,22 @@ def set_new_or_existing_contact_options ) end - @new_or_existing_contact_options += - if Flipper.enabled?(:one_patient_per_parent) - parents = (@patient.parents + + parent_relationships = + ( + @patient.parent_relationships.includes(:parent) + @patient .consents - .select { it.programme_type == @programme.type } - .filter_map(&:parent) - ).compact.uniq.sort_by(&:label) - - parents.map do |parent| - NewOrExistingContactOption.new( - value: parent.id, - label: parent.label_with_parent, - hint: parent.contact_label - ) - end - else - parent_relationships = - ( - @patient.parent_relationships.includes(:parent) + - @patient - .consents - .where(programme_type: @programme.type) - .filter_map(&:parent_relationship) - ).compact.uniq.sort_by(&:label) - - parent_relationships.map do |parent_relationship| - NewOrExistingContactOption.new( - value: parent_relationship.parent.id, - label: parent_relationship.label_with_parent, - hint: parent_relationship.parent.contact_label - ) - end + .where(programme_type: @programme.type) + .filter_map(&:parent_relationship) + ).compact.uniq.sort_by(&:label) + + @new_or_existing_contact_options += + parent_relationships.map do |parent_relationship| + NewOrExistingContactOption.new( + value: parent_relationship.parent.id, + label: parent_relationship.label_with_parent, + hint: parent_relationship.parent.contact_label + ) end @new_or_existing_contact_options << NewOrExistingContactOption.new( diff --git a/app/models/draft_consent.rb b/app/models/draft_consent.rb index b83dfe71a8..8ce9efd73c 100644 --- a/app/models/draft_consent.rb +++ b/app/models/draft_consent.rb @@ -247,7 +247,7 @@ def parent parent.email = parent_email parent.full_name = parent_full_name parent.phone = parent_phone - parent.phone_receive_updates = parent_phone_receive_updates + parent.phone_receive_updates = parent_phone_receive_updates.presence # We can't use find_or_initialize_by here because we need the object to # remain attached to the parent so we can save the parent with its @@ -260,7 +260,11 @@ def parent parent_relationship.assign_attributes( patient:, # acts as preload type: parent_relationship_type, - other_name: parent_relationship_other_name + other_name: parent_relationship_other_name, + phone: parent_phone, + full_name: parent_full_name, + email: parent_email, + phone_receive_updates: parent_phone_receive_updates ) if parent_relationship.new_record? @@ -275,13 +279,15 @@ def parent=(value) parent_relationship = value&.parent_relationships&.find_by(patient_id:) - self.parent_email = patient.restricted? ? "" : value&.email - self.parent_full_name = value&.full_name - self.parent_phone = patient.restricted? ? "" : value&.phone - self.parent_phone_receive_updates = value&.phone_receive_updates - self.parent_relationship_type = parent_relationship&.type || value&.type - self.parent_relationship_other_name = - parent_relationship&.other_name || value&.other_name + self.parent_email = + patient.restricted? ? "" : parent_relationship&.email || value&.email + self.parent_full_name = parent_relationship&.full_name || value&.full_name + self.parent_phone = + patient.restricted? ? "" : parent_relationship&.phone || value&.phone + self.parent_phone_receive_updates = + parent_relationship&.phone_receive_updates || value&.phone_receive_updates + self.parent_relationship_type = parent_relationship&.type + self.parent_relationship_other_name = parent_relationship&.other_name self.parent_responsibility = value ? true : nil end @@ -376,6 +382,10 @@ def parent_relationship it.patient = patient # acts as preload it.type = parent_relationship_type it.other_name = parent_relationship_other_name + it.full_name = parent_full_name + it.email = parent_email + it.phone = parent_phone + it.phone_receive_updates = parent_phone_receive_updates end end diff --git a/app/views/draft_consents/parent_details.html.erb b/app/views/draft_consents/parent_details.html.erb index 05ce0d26bf..f1cdc955e7 100644 --- a/app/views/draft_consents/parent_details.html.erb +++ b/app/views/draft_consents/parent_details.html.erb @@ -3,7 +3,7 @@ <% end %> <% page_title = if @parent.persisted? - "Details for #{Flipper.enabled?(:one_patient_per_parent) ? @parent.label_with_parent : @draft_consent.parent_relationship.label_with_parent}" + "Details for #{@draft_consent.parent_relationship.label_with_parent}" else "Details for parent or guardian" end %> diff --git a/app/views/patient_sessions/consents/show.html.erb b/app/views/patient_sessions/consents/show.html.erb index 2ee383f698..0a597a3b9c 100644 --- a/app/views/patient_sessions/consents/show.html.erb +++ b/app/views/patient_sessions/consents/show.html.erb @@ -26,7 +26,7 @@ <%= render AppConsentPatientSummaryComponent.new(@consent) %> <% end %> -<% if @consent.parent_relationship.present? %> +<% if @consent.parent_relationship.present? || @consent.parent.present? %> <%= render AppParentCardComponent.new(@consent) %> <% end %> diff --git a/spec/factories/parents.rb b/spec/factories/parents.rb index 22367071da..f76665f406 100644 --- a/spec/factories/parents.rb +++ b/spec/factories/parents.rb @@ -29,8 +29,6 @@ email { Faker::Internet.email } phone { "07700 900#{rand(0..999).to_s.rjust(3, "0")}" } phone_receive_updates { phone.present? } - type { %w[father guardian mother other].sample } - other_name { type == "other" ? "Other" : nil } trait :non_contactable do phone { nil } diff --git a/spec/features/verbal_consent_given_by_new_parental_contact_spec.rb b/spec/features/verbal_consent_given_by_new_parental_contact_spec.rb index 1ba70a5dc8..16d16123fa 100644 --- a/spec/features/verbal_consent_given_by_new_parental_contact_spec.rb +++ b/spec/features/verbal_consent_given_by_new_parental_contact_spec.rb @@ -3,7 +3,10 @@ describe "Verbal consent" do around { |example| travel_to(Date.new(2025, 7, 31)) { example.run } } - before { given_i_am_signed_in } + before do + Flipper.enable(:one_patient_per_parent) + given_i_am_signed_in + end scenario "Given by a new mother parent contact" do when_i_start_recording_consent_from_a_new_parental_contact diff --git a/spec/features/verbal_consent_given_by_new_parental_contact_without_parent_association_feature_flag_spec.rb b/spec/features/verbal_consent_given_by_new_parental_contact_without_parent_association_feature_flag_spec.rb new file mode 100644 index 0000000000..fbc5d6db5a --- /dev/null +++ b/spec/features/verbal_consent_given_by_new_parental_contact_without_parent_association_feature_flag_spec.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +describe "Verbal consent" do + around { |example| travel_to(Date.new(2025, 7, 31)) { example.run } } + + before do + Flipper.disable(:one_patient_per_parent) + given_i_am_signed_in + end + + scenario "Given by a new mother parent contact" do + when_i_start_recording_consent_from_a_new_parental_contact + and_i_enter_a_mum_relationship + and_i_record_that_verbal_consent_was_given + then_an_email_is_sent_to_the_parent_confirming_their_consent + and_i_can_see_the_parents_details_on_the_consent_response( + relationship: "Mum" + ) + end + + scenario "Given by a new other parent contact" do + when_i_start_recording_consent_from_a_new_parental_contact + and_i_enter_an_other_relationship + and_i_record_that_verbal_consent_was_given + then_an_email_is_sent_to_the_parent_confirming_their_consent + and_i_can_see_the_parents_details_on_the_consent_response( + relationship: "Other – Carer" + ) + end + + def given_i_am_signed_in + programmes = [Programme.hpv] + team = create(:team, :with_one_nurse, programmes:) + @session = create(:session, team:, programmes:) + @patient = create(:patient, session: @session) + + PatientStatusUpdater.call + + sign_in team.users.first + end + + def when_i_start_recording_consent_from_a_new_parental_contact + visit session_patients_path(@session) + click_link @patient.full_name + click_button "Record a new consent response" + + # Who are you trying to get consent from? + choose "Add a new parental contact" + click_button "Continue" + end + + def and_i_enter_a_mum_relationship + fill_in "Full name", with: "Jane Smith" + choose "Mum" + fill_in "Email address", with: "jsmith@example.com" + fill_in "Phone number", with: "07987654321" + check "Get updates by text" + click_button "Continue" + end + + def and_i_enter_an_other_relationship + fill_in "Full name", with: "Jane Smith" + choose "Other" + + click_button "Continue" + expect(page).to have_content("Enter a relationship") + expect(page).to have_content( + "Choose whether there is parental responsibility" + ) + + fill_in "Relationship to the child", with: "Carer" + choose "Yes" + + fill_in "Email address", with: "jsmith@example.com" + fill_in "Phone number", with: "07987654321" + check "Get updates by text" + click_button "Continue" + end + + def and_i_record_that_verbal_consent_was_given + # How was the response given? + choose "By phone" + click_button "Continue" + + # Do they agree? + choose "Yes, they agree" + click_button "Continue" + + # Health questions + find_all(".nhsuk-fieldset")[0].choose "No" + find_all(".nhsuk-fieldset")[1].choose "No" + find_all(".nhsuk-fieldset")[2].choose "No" + find_all(".nhsuk-fieldset")[3].choose "No" + click_button "Continue" + + # Confirm + expect(page).to have_content("Check and confirm answers") + expect(page).to have_content(["Method", "By phone"].join) + click_button "Confirm" + + # Back on the consent responses page + expect(page).to have_content("Consent recorded for #{@patient.full_name}") + end + + def and_i_can_see_the_parents_details_on_the_consent_response(relationship:) + click_link @patient.full_name, match: :first + click_link "Jane Smith" + + expect(page).to have_content("Consent response from Jane Smith") + + expect(page).to have_content(["Name", "Jane Smith"].join) + expect(page).to have_content(["Relationship", relationship].join) + expect(page).to have_content( + ["Email address", "jsmith@example.com"].join("\n") + ) + expect(page).to have_content(["Phone number", "07987 654321"].join("\n")) + end + + def then_an_email_is_sent_to_the_parent_confirming_their_consent + expect_email_to("jsmith@example.com", :consent_confirmation_given) + end + + def and_i_a_text_is_sent_to_the_parent_confirming_their_consent + expect_sms_to("07987 654321", :consent_confirmation_given) + end +end diff --git a/spec/features/verbal_consent_given_without_parent_association_feature_flag_spec.rb b/spec/features/verbal_consent_given_without_parent_association_feature_flag_spec.rb index 428146edd2..b84b9ed077 100644 --- a/spec/features/verbal_consent_given_without_parent_association_feature_flag_spec.rb +++ b/spec/features/verbal_consent_given_without_parent_association_feature_flag_spec.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true describe "Verbal consent" do + before { Flipper.disable(:one_patient_per_parent) } + scenario "Given HPV" do given_an_hpv_programme_is_underway and_i_am_signed_in From d8ff6d57b6dded15ff9421ade9672c0c96587caa Mon Sep 17 00:00:00 2001 From: Steve Hook Date: Thu, 16 Apr 2026 10:00:06 +0100 Subject: [PATCH 4/4] Fix spec regression `Parent#phone_receive_updates` can end up with a `nil` value which is invalid as it's a mandatory boolean attribute. --- app/models/draft_consent.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/draft_consent.rb b/app/models/draft_consent.rb index 8ce9efd73c..89755f2a05 100644 --- a/app/models/draft_consent.rb +++ b/app/models/draft_consent.rb @@ -247,7 +247,8 @@ def parent parent.email = parent_email parent.full_name = parent_full_name parent.phone = parent_phone - parent.phone_receive_updates = parent_phone_receive_updates.presence + parent.phone_receive_updates = + parent_phone_receive_updates.presence || false # We can't use find_or_initialize_by here because we need the object to # remain attached to the parent so we can save the parent with its