diff --git a/Gemfile b/Gemfile index 76979e43e1..f04d0133dd 100644 --- a/Gemfile +++ b/Gemfile @@ -24,6 +24,7 @@ gem "stackprof" gem "activerecord-import" gem "activerecord-session_store" gem "amazing_print" +gem "array_enum" gem "audited", git: "https://github.com/tvararu/audited", branch: "encryption" gem "caxlsx" gem "charlock_holmes" diff --git a/Gemfile.lock b/Gemfile.lock index 48119db26a..37ac208732 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -102,6 +102,8 @@ GEM aes_key_wrap (1.1.0) amazing_print (1.7.2) annotaterb (4.14.0) + array_enum (1.6.0) + activemodel asciidoctor (2.0.23) asciidoctor-diagram (3.0.0) asciidoctor (>= 1.5.7, < 3.x) @@ -713,6 +715,7 @@ DEPENDENCIES activerecord-session_store amazing_print annotaterb + array_enum asciidoctor asciidoctor-diagram audited! diff --git a/app/controllers/patient_sessions/consents_controller.rb b/app/controllers/patient_sessions/consents_controller.rb index 1b45f30449..eeae4b2cb8 100644 --- a/app/controllers/patient_sessions/consents_controller.rb +++ b/app/controllers/patient_sessions/consents_controller.rb @@ -123,7 +123,8 @@ def create_params { patient_session: @patient_session, programme: @programme, - recorded_by: current_user + recorded_by: current_user, + vaccine_methods: %w[injection] } end diff --git a/app/models/concerns/has_vaccine_methods.rb b/app/models/concerns/has_vaccine_methods.rb new file mode 100644 index 0000000000..d06bd7cc86 --- /dev/null +++ b/app/models/concerns/has_vaccine_methods.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module HasVaccineMethods + extend ActiveSupport::Concern + + included do + extend ArrayEnum + + array_enum vaccine_methods: { injection: 0, nasal: 1 } + + validates :vaccine_methods, subset: %w[injection nasal] + end +end diff --git a/app/models/consent.rb b/app/models/consent.rb index e063be6084..39b85e0260 100644 --- a/app/models/consent.rb +++ b/app/models/consent.rb @@ -13,6 +13,7 @@ # response :integer not null # route :integer not null # submitted_at :datetime not null +# vaccine_methods :integer default([]), not null, is an Array # withdrawn_at :datetime # created_at :datetime not null # updated_at :datetime not null @@ -42,6 +43,7 @@ class Consent < ApplicationRecord include Invalidatable include HasHealthAnswers + include HasVaccineMethods audited associated_with: :patient @@ -99,6 +101,8 @@ class Consent < ApplicationRecord presence: true, unless: -> { via_self_consent? || via_website? } + validates :vaccine_methods, presence: true, if: :response_given? + def self.verbal_routes = routes.except("website", "self_consent") def name @@ -158,42 +162,43 @@ def self.from_consent_form!(consent_form, patient:, current_user:) parent = consent_form.find_or_create_parent_with_relationship_to!(patient:) - consent_given = - consent_form.given_programmes.map do |programme| - patient.consents.create!( - consent_form:, - organisation: consent_form.organisation, - programme:, - parent:, - notes: "", - response: "given", - route: "website", - health_answers: consent_form.health_answers, - recorded_by: current_user, - submitted_at: consent_form.recorded_at - ) - end - - consent_refused = - consent_form.refused_programmes.map do |programme| - patient.consents.create!( - consent_form:, - organisation: consent_form.organisation, - programme:, - parent:, - reason_for_refusal: consent_form.reason, - notes: consent_form.reason_notes.presence || "", - response: "refused", - route: "website", - health_answers: consent_form.health_answers, - recorded_by: current_user, - submitted_at: consent_form.recorded_at - ) - end + consents = + consent_form + .consent_form_programmes + .includes(:programme) + .map do |consent_form_programme| + notes = + if consent_form_programme.response_given? + "" + else + consent_form.reason_notes.presence || "" + end + reason_for_refusal = + if consent_form_programme.response_given? + nil + else + consent_form.reason + end + + patient.consents.create!( + consent_form:, + health_answers: consent_form.health_answers, + notes:, + organisation: consent_form.organisation, + parent:, + programme: consent_form_programme.programme, + reason_for_refusal:, + recorded_by: current_user, + response: consent_form_programme.response, + route: "website", + submitted_at: consent_form.recorded_at, + vaccine_methods: consent_form_programme.vaccine_methods + ) + end StatusUpdater.call(patient:) - consent_given + consent_refused + consents end end diff --git a/app/models/consent_form.rb b/app/models/consent_form.rb index 13232c52a7..07eb63a4a6 100644 --- a/app/models/consent_form.rb +++ b/app/models/consent_form.rb @@ -454,7 +454,10 @@ def home_educated_changed? def update_programme_responses case response when "given" - consent_form_programmes.each { it.response = "given" } + consent_form_programmes.each do + it.response = "given" + it.vaccine_methods = %w[injection] + end when "given_one" consent_form_programmes.each do |consent_form_programme| consent_form_programme.response = @@ -463,6 +466,7 @@ def update_programme_responses else "refused" end + consent_form_programme.vaccine_methods = %w[injection] end when "refused" consent_form_programmes.each { it.response = "refused" } diff --git a/app/models/consent_form_programme.rb b/app/models/consent_form_programme.rb index c6f1c657eb..533c4c4fb5 100644 --- a/app/models/consent_form_programme.rb +++ b/app/models/consent_form_programme.rb @@ -6,6 +6,7 @@ # # id :bigint not null, primary key # response :integer +# vaccine_methods :integer default([]), not null, is an Array # consent_form_id :bigint not null # programme_id :bigint not null # @@ -20,6 +21,8 @@ # fk_rails_... (programme_id => programmes.id) # class ConsentFormProgramme < ApplicationRecord + include HasVaccineMethods + belongs_to :consent_form belongs_to :programme diff --git a/app/models/draft_consent.rb b/app/models/draft_consent.rb index 9194af674f..056002e4f6 100644 --- a/app/models/draft_consent.rb +++ b/app/models/draft_consent.rb @@ -33,6 +33,7 @@ def self.request_session_key attribute :route, :string attribute :triage_notes, :string attribute :triage_status, :string + attribute :vaccine_methods, array: true, default: [] def wizard_steps [ @@ -333,6 +334,7 @@ def writable_attribute_names response route organisation_id + vaccine_methods ] end diff --git a/db/migrate/20250606083902_add_vaccine_methods_to_consent_and_consent_forms.rb b/db/migrate/20250606083902_add_vaccine_methods_to_consent_and_consent_forms.rb new file mode 100644 index 0000000000..3d493c5b31 --- /dev/null +++ b/db/migrate/20250606083902_add_vaccine_methods_to_consent_and_consent_forms.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class AddVaccineMethodsToConsentAndConsentForms < ActiveRecord::Migration[8.0] + def change + add_column :consent_form_programmes, + :vaccine_methods, + :integer, + null: false, + array: true, + default: [] + + add_column :consents, + :vaccine_methods, + :integer, + null: false, + array: true, + default: [] + + # We don't support getting consent for nasal spray yet. + reversible do |dir| + dir.up do + ConsentFormProgramme.response_given.update_all( + vaccine_methods: %w[injection] + ) + Consent.response_given.update_all(vaccine_methods: %w[injection]) + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 239d07c029..9c4df31952 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -156,6 +156,7 @@ t.bigint "programme_id", null: false t.bigint "consent_form_id", null: false t.integer "response" + t.integer "vaccine_methods", default: [], null: false, array: true t.index ["consent_form_id"], name: "index_consent_form_programmes_on_consent_form_id" t.index ["programme_id", "consent_form_id"], name: "idx_on_programme_id_consent_form_id_2113cb7f37", unique: true end @@ -236,6 +237,7 @@ t.datetime "invalidated_at" t.boolean "notify_parents" t.datetime "submitted_at", null: false + t.integer "vaccine_methods", default: [], null: false, array: true t.index ["organisation_id"], name: "index_consents_on_organisation_id" t.index ["parent_id"], name: "index_consents_on_parent_id" t.index ["patient_id"], name: "index_consents_on_patient_id" diff --git a/spec/factories/consent_form_programmes.rb b/spec/factories/consent_form_programmes.rb index 15cf808fb8..d424408d2a 100644 --- a/spec/factories/consent_form_programmes.rb +++ b/spec/factories/consent_form_programmes.rb @@ -6,6 +6,7 @@ # # id :bigint not null, primary key # response :integer +# vaccine_methods :integer default([]), not null, is an Array # consent_form_id :bigint not null # programme_id :bigint not null # @@ -24,6 +25,14 @@ consent_form programme - traits_for_enum :response + trait :given do + response { "given" } + vaccine_methods { %w[injection] } + end + + trait :refused do + response { "refused" } + vaccine_methods { [] } + end end end diff --git a/spec/factories/consent_forms.rb b/spec/factories/consent_forms.rb index 9154b484f9..c64b07e32f 100644 --- a/spec/factories/consent_forms.rb +++ b/spec/factories/consent_forms.rb @@ -123,8 +123,11 @@ end after(:create) do |consent_form, evaluator| + vaccine_methods = evaluator.response == "given" ? %w[injection] : [] + consent_form.consent_form_programmes.update_all( - response: evaluator.response + response: evaluator.response, + vaccine_methods: ) end diff --git a/spec/factories/consents.rb b/spec/factories/consents.rb index 176d33e2c3..6fe173e645 100644 --- a/spec/factories/consents.rb +++ b/spec/factories/consents.rb @@ -13,6 +13,7 @@ # response :integer not null # route :integer not null # submitted_at :datetime not null +# vaccine_methods :integer default([]), not null, is an Array # withdrawn_at :datetime # created_at :datetime not null # updated_at :datetime not null @@ -64,6 +65,7 @@ end response { "given" } + vaccine_methods { %w[injection] } route { "website" } health_answers do @@ -74,8 +76,6 @@ submitted_at { consent_form&.recorded_at || Time.current } - traits_for_enum :response - trait :given_verbally do given route { "phone" } @@ -93,10 +93,16 @@ end trait :refused do - response { :refused } - reason_for_refusal { :personal_choice } + response { "refused" } + reason_for_refusal { "personal_choice" } health_answers { [] } notes { "Refused." } + vaccine_methods { [] } + end + + trait :not_provided do + response { "not_provided" } + vaccine_methods { [] } end trait :from_mum do diff --git a/spec/models/consent_spec.rb b/spec/models/consent_spec.rb index 4f8b316515..602065b6e5 100644 --- a/spec/models/consent_spec.rb +++ b/spec/models/consent_spec.rb @@ -13,6 +13,7 @@ # response :integer not null # route :integer not null # submitted_at :datetime not null +# vaccine_methods :integer default([]), not null, is an Array # withdrawn_at :datetime # created_at :datetime not null # updated_at :datetime not null @@ -42,6 +43,18 @@ describe Consent do describe "validations" do it { should validate_length_of(:notes).is_at_most(1000) } + + context "when response is given" do + subject { build(:consent, :given) } + + it { should validate_presence_of(:vaccine_methods) } + end + + context "when response is refused" do + subject { build(:consent, :refused) } + + it { should_not validate_presence_of(:vaccine_methods) } + end end describe "#verbal_routes" do @@ -94,7 +107,7 @@ end describe "#from_consent_form!" do - describe "the created consent object" do + context "with on programme" do subject(:consent) do described_class.from_consent_form!( consent_form, @@ -111,7 +124,7 @@ expect(consent.recorded_by).to eq(current_user) end - it "copies over attributes from consent_form" do + it "copies over attributes from consent form" do expect(consent).to( have_attributes( programme: consent_form.programmes.first, @@ -160,9 +173,21 @@ ) end end + + context "when consenting to nasal spray" do + before do + consent_form.consent_form_programmes.first.update!( + vaccine_methods: %w[nasal] + ) + end + + it "stores this preference on the consent" do + expect(consent.vaccine_methods).to contain_exactly("nasal") + end + end end - context "when only consenting to one programme" do + context "with multiple programmes" do subject(:consents) do described_class.from_consent_form!( consent_form, @@ -189,8 +214,14 @@ end before do - consent_form.consent_form_programmes.first.update!(response: "given") - consent_form.consent_form_programmes.second.update!(response: "refused") + consent_form.consent_form_programmes.first.update!( + response: "given", + vaccine_methods: %w[injection] + ) + consent_form.consent_form_programmes.second.update!( + response: "refused", + vaccine_methods: [] + ) end it "creates a consent per programme" do