diff --git a/.github/workflows/call-end-to-end-tests.yml b/.github/workflows/call-end-to-end-tests.yml index 3b614a5b81..34e9b12ef6 100644 --- a/.github/workflows/call-end-to-end-tests.yml +++ b/.github/workflows/call-end-to-end-tests.yml @@ -16,9 +16,6 @@ on: HTTP_AUTH_TOKEN_FOR_TESTS: description: HTTP Basic Auth token for the environment under test required: false - MAVIS_TESTING_REPO_ACCESS_TOKEN: - description: Access token for the manage-vaccinations-in-schools-testing repository - required: false IMMS_API_KEY_FOR_TESTS: description: API key to use NHS Immunisations API required: false @@ -39,7 +36,6 @@ jobs: endpoint: ${{ inputs.endpoint }} secrets: HTTP_AUTH_TOKEN_FOR_TESTS: ${{ secrets.HTTP_AUTH_TOKEN_FOR_TESTS }} - MAVIS_TESTING_REPO_ACCESS_TOKEN: ${{ secrets.MAVIS_TESTING_REPO_ACCESS_TOKEN }} IMMS_API_KEY: ${{ secrets.IMMS_API_KEY_FOR_TESTS }} IMMS_API_KID: ${{ secrets.IMMS_API_KID_FOR_TESTS }} IMMS_API_PEM: ${{ secrets.IMMS_API_PEM_FOR_TESTS }} diff --git a/.github/workflows/end-to-end-tests-aws.yml b/.github/workflows/end-to-end-tests-aws.yml index 7dbd42fabf..2f3ce8aae4 100644 --- a/.github/workflows/end-to-end-tests-aws.yml +++ b/.github/workflows/end-to-end-tests-aws.yml @@ -12,13 +12,6 @@ on: type: string required: false default: next - secrets: - HTTP_AUTH_TOKEN_FOR_TESTS: - description: HTTP Basic Auth token for the environment under test - required: true - MAVIS_TESTING_REPO_ACCESS_TOKEN: - description: Access token for the manage-vaccinations-in-schools-testing repository - required: true permissions: {} diff --git a/.github/workflows/end-to-end-tests-on-pull-request.yml b/.github/workflows/end-to-end-tests-on-pull-request.yml index 4176087f45..d5ef817deb 100644 --- a/.github/workflows/end-to-end-tests-on-pull-request.yml +++ b/.github/workflows/end-to-end-tests-on-pull-request.yml @@ -19,7 +19,6 @@ jobs: with: git_reference_for_database_image: ${{ github.base_ref }} git_reference_for_application_image: ${{ github.head_ref }} - secrets: inherit local-e2e-flow: if: github.event.pull_request.head.repo.full_name != github.repository diff --git a/Gemfile.lock b/Gemfile.lock index 03dde407a8..e59652107d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -175,11 +175,11 @@ GEM protocol-websocket (~> 0.17) attr_required (1.0.2) aws-eventstream (1.4.0) - aws-partitions (1.1233.0) - aws-sdk-accessanalyzer (1.86.0) + aws-partitions (1.1238.0) + aws-sdk-accessanalyzer (1.88.0) aws-sdk-core (~> 3, >= 3.244.0) aws-sigv4 (~> 1.5) - aws-sdk-cloudwatch (1.132.0) + aws-sdk-cloudwatch (1.133.0) aws-sdk-core (~> 3, >= 3.244.0) aws-sigv4 (~> 1.5) aws-sdk-core (3.244.0) @@ -190,10 +190,10 @@ GEM bigdecimal jmespath (~> 1, >= 1.6.1) logger - aws-sdk-ec2 (1.610.0) + aws-sdk-ec2 (1.611.0) aws-sdk-core (~> 3, >= 3.244.0) aws-sigv4 (~> 1.5) - aws-sdk-ecr (1.124.0) + aws-sdk-ecr (1.125.0) aws-sdk-core (~> 3, >= 3.244.0) aws-sigv4 (~> 1.5) aws-sdk-iam (1.142.0) @@ -205,7 +205,7 @@ GEM aws-sdk-rds (1.310.0) aws-sdk-core (~> 3, >= 3.244.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.218.0) + aws-sdk-s3 (1.219.0) aws-sdk-core (~> 3, >= 3.244.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -220,7 +220,7 @@ GEM i18n bcrypt (3.1.22) benchmark (0.5.0) - bigdecimal (4.1.0) + bigdecimal (4.1.1) bindata (2.5.1) bindex (0.8.1) bootsnap (1.23.0) @@ -380,11 +380,11 @@ GEM raabro (~> 1.4) globalid (1.3.0) activesupport (>= 6.1) - govuk-components (6.0.0) + govuk-components (6.1.0) html-attributes-utils (~> 1.0.0, >= 1.0.0) pagy (>= 6, < 10) - view_component (>= 4.0, < 4.3) - govuk_design_system_formbuilder (6.0.0) + view_component (>= 4.0, < 4.7) + govuk_design_system_formbuilder (6.1.0) actionview (>= 6.1) activemodel (>= 6.1) activesupport (>= 6.1) @@ -493,7 +493,7 @@ GEM mime-types-data (~> 3.2025, >= 3.2025.0507) mime-types-data (3.2025.0924) mini_mime (1.1.5) - minitest (6.0.2) + minitest (6.0.3) drb (~> 2.0) prism (~> 1.5) msgpack (1.8.0) @@ -560,7 +560,7 @@ GEM racc pg (1.6.3-arm64-darwin) pg (1.6.3-x86_64-linux) - phonelib (0.10.17) + phonelib (0.10.18) pp (0.6.3) prettyprint prettier_print (1.2.1) @@ -879,7 +879,7 @@ GEM validate_url (1.0.15) activemodel (>= 3.0.0) public_suffix - view_component (4.2.0) + view_component (4.6.0) actionview (>= 7.1.0) activesupport (>= 7.1.0) concurrent-ruby (~> 1) diff --git a/app/components/app_action_link_component.rb b/app/components/app_action_link_component.rb new file mode 100644 index 0000000000..65db024501 --- /dev/null +++ b/app/components/app_action_link_component.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class AppActionLinkComponent < ViewComponent::Base + erb_template <<-ERB + <%= link_to @href, class: "nhsuk-action-link", **@options do %> + + <%= @text %> + <% end %> + ERB + + def initialize(text:, href:, **options) + @text = text + @href = href + @options = options + @options[:class] = ["nhsuk-action-link", options[:class]].compact.join(" ") + end +end diff --git a/app/components/app_import_format_details_component.html.erb b/app/components/app_import_format_details_component.html.erb index 3e13ed5a12..bd12469433 100644 --- a/app/components/app_import_format_details_component.html.erb +++ b/app/components/app_import_format_details_component.html.erb @@ -1,4 +1,4 @@ -<%= govuk_details(summary_text:) do %> +<%= govuk_details(summary_text: "What your CSV file must include") do %>

Make sure the CSV you upload uses the following columns:

<%= govuk_table(classes: "app-table--csv app-table--small") do |table| diff --git a/app/components/app_import_format_details_component.rb b/app/components/app_import_format_details_component.rb index 6d6d90a3b7..33fea34d33 100644 --- a/app/components/app_import_format_details_component.rb +++ b/app/components/app_import_format_details_component.rb @@ -10,23 +10,6 @@ def initialize(import:) delegate :team, to: :@import delegate :govuk_details, :govuk_table, to: :helpers - def summary_text - case @import - when ClassImport - "How to format your Mavis CSV file for class lists" - when CohortImport - "How to format your Mavis CSV file for child records" - when ImmunisationImport - if team.has_national_reporting_access? - "How to format your CSV file for vaccination records" - else - "How to format your Mavis CSV file for vaccination records" - end - else - raise ArgumentError, "Unsupported import type: #{@import.class}" - end - end - def columns case @import when ClassImport @@ -44,7 +27,7 @@ def class_import_columns { name: "CHILD_POSTCODE", notes: - "Optional, must be formatted as a valid postcode, for example #{tag.i("SW1A 1AA")}." + "Optional, must be a full postcode, for example #{tag.i("SW1A 1AA")}" } ] + parent_columns end @@ -55,7 +38,7 @@ def cohort_import_columns { name: "CHILD_POSTCODE", notes: - "#{tag.strong("Required")}, must be formatted as a valid postcode, for example #{tag.i("SW1A 1AA")}." + "#{tag.strong("Required")}, must be a full postcode, for example #{tag.i("SW1A 1AA")}" }, { name: "CHILD_SCHOOL_URN", @@ -77,33 +60,26 @@ def immunisation_import_columns def point_of_care_immunisation_import_columns organisation_code(optionality: "Optional") + - school_urn(optionality: "Optional") + school_name + - nhs_number(optionality: "Optional") + - patient_demographics(optionality: "Required") + - date_and_time_of_vaccination(date_optionality: "Required") + + school_urn(optionality: "Optional") + school_name + nhs_number + + patient_demographics + date_and_time_of_vaccination + vaccinated( - optionality: "Required", - extra_notes: - "Can be omitted if #{tag.code("VACCINE_GIVEN")} is provided." + notes: + "Enter #{tag.i("Y")} or #{tag.i("N")}. Mavis will assume #{tag.i("Y")} if " \ + "#{tag.code(" VACCINE_GIVEN ")} is provided." ) + vaccine_and_batch + programme + anatomical_site + reason_not_vaccinated_and_notes + dose_sequence + care_setting + performing_professional end def national_reporting_immunisation_import_columns - organisation_code(optionality: "Mandatory") + - school_urn(optionality: "Mandatory") + - nhs_number(optionality: "Required") + - patient_demographics( - optionality: "Mandatory", - gender_field_name: "PERSON_GENDER" - ) + + organisation_code(optionality: "Required") + + school_urn(optionality: "Required") + nhs_number + + patient_demographics(gender_field_name: "PERSON_GENDER") + vaccinated( - optionality: "Optional", - extra_notes: - "Rows with the value #{tag.i("N")} will not be validated and will not be imported." - ) + date_and_time_of_vaccination(date_optionality: "Mandatory") + - national_reporting_vaccine_and_batch + + notes: + "Optional, enter #{tag.i("Y")} or #{tag.i("N")}. If you enter nothing, Mavis will assume " \ + "#{tag.i("Y")}. If you enter #{tag.i("N")}, the row will not be uploaded." + ) + date_and_time_of_vaccination + national_reporting_vaccine_and_batch + national_reporting_anatomical_site + national_reporting_dose_sequence + national_reporting_performing_professional_names + local_patient_id end @@ -146,7 +122,7 @@ def child_columns { name: "CHILD_YEAR_GROUP", notes: - "Optional, numeric, the child’s year group, for example #{tag.i("8")}. If present, and " \ + "Optional, numeric, for example #{tag.i("8")}. If present, and " \ "when the child’s date of birth would place them in a different year, this value can " \ "be used to override the cohort the child will be placed in." }, @@ -157,7 +133,7 @@ def child_columns }, { name: "CHILD_NHS_NUMBER", - notes: "Optional, must be 10 digits and numeric." + notes: "You must enter a valid NHS number if available." }, { name: "CHILD_GENDER", @@ -181,12 +157,17 @@ def organisation_code(optionality:) ] end - def nhs_number(optionality:) + def nhs_number [ { name: "NHS_NUMBER", notes: - "#{tag.strong(optionality)}, must be a valid #{link_to("NHS number", "https://www.datadictionary.nhs.uk/attributes/nhs_number.html")}." + "You must enter a valid #{ + link_to( + "NHS number", + "https://www.datadictionary.nhs.uk/attributes/nhs_number.html" + ) + } if available." } ] end @@ -212,60 +193,56 @@ def school_name ] end - def patient_demographics( - optionality:, - gender_field_name: "PERSON_GENDER_CODE" - ) + def patient_demographics(gender_field_name: "PERSON_GENDER_CODE") [ - { name: "PERSON_FORENAME", notes: tag.strong(optionality) }, - { name: "PERSON_SURNAME", notes: tag.strong(optionality) }, + { + name: "PERSON_FORENAME", + notes: + "#{tag.strong("Required")}, must use alphabetical characters " \ + "(you can include accents like Chloë, hyphens like Anne-Marie or apostrophes like D'Arcy " \ + "but no other special characters)." + }, + { + name: "PERSON_SURNAME", + notes: + "#{tag.strong("Required")}, must use alphabetical characters " \ + "(you can include accents like Jiménez, hyphens like Burne-Jones or apostrophes like O'Hare " \ + "but no other special characters)." + }, { name: "PERSON_DOB", notes: - "#{tag.strong(optionality)}, must use either #{tag.i("YYYYMMDD")} or " \ + "#{tag.strong("Required")}, you must use either #{tag.i("YYYYMMDD")} or " \ "#{tag.i("DD/MM/YYYY")} format." }, { name: gender_field_name, notes: - "#{tag.strong(optionality)}, must be one of: #{tag.i("female")}, " \ + "#{tag.strong("Required")}, must be one of: #{tag.i("female")}, " \ "#{tag.i("male")}, #{tag.i("not known")} or #{tag.i("not specified")}." }, { name: "PERSON_POSTCODE", notes: - "#{tag.strong(optionality)}, must be formatted as a valid postcode, for example #{tag.i("SW1A 1AA")}." + "#{tag.strong("Required")}, must be a full postcode, for example #{tag.i("SW1A 1AA")}" } ] end - def vaccinated(optionality:, extra_notes: "") - optionality_html = - if %w[Mandatory Required].include?(optionality) - tag.strong(optionality) - else - optionality - end - - [ - { - name: "VACCINATED", - notes: - "#{optionality_html}, must be #{tag.i("Y")} or #{tag.i("N")}. #{extra_notes}" - } - ] + def vaccinated(notes:) + [{ name: "VACCINATED", notes: }] end - def date_and_time_of_vaccination(date_optionality:) + def date_and_time_of_vaccination [ { name: "DATE_OF_VACCINATION", notes: - "#{tag.strong(date_optionality)}, must use either #{tag.i("YYYYMMDD")} or #{tag.i("DD/MM/YYYY")} format." + "#{tag.strong("Required")}, you must use either #{tag.i("YYYYMMDD")} or #{tag.i("DD/MM/YYYY")} format." }, { name: "TIME_OF_VACCINATION", - notes: "Optional, must use #{tag.i("HH:MM:SS")} format." + notes: "Optional, use #{tag.i("HH:MM:SS")} format." } ] end @@ -300,12 +277,7 @@ def parent_columns def programme programmes = team.programmes.flat_map(&:import_names).map { tag.i(it) } - - programmes_sentence = - programmes.to_sentence( - last_word_connector: " or ", - two_words_connector: " or " - ) + programmes_sentence = to_sentence_with_or(programmes) [ { @@ -315,59 +287,50 @@ def programme ] end - def make_vaccines_sentence(vaccines:) - vaccines.to_sentence( - last_word_connector: " or ", - two_words_connector: " or " - ) - end - def vaccine_and_batch vaccines = team.vaccines.pluck(:upload_name).map { tag.i(it) } - vaccines_sentence = make_vaccines_sentence(vaccines:) [ { name: "VACCINE_GIVEN", - notes: "Optional, must be one of: #{vaccines_sentence}." - }, - { - name: "BATCH_NUMBER", - notes: "Required if #{tag.code("BATCH_EXPIRY_DATE")} is provided." + notes: "Optional, must be one of: #{to_sentence_with_or(vaccines)}." }, + { name: "BATCH_NUMBER", notes: "Optional" }, { name: "BATCH_EXPIRY_DATE", notes: - "Required if #{tag.code("BATCH_NUMBER")} is provided, must use " \ - "either #{tag.i("YYYYMMDD")} or #{tag.i("DD/MM/YYYY")} format." + "Optional, use either #{tag.i("YYYYMMDD")} or #{tag.i("DD/MM/YYYY")} format." } ] end def national_reporting_vaccine_and_batch - hpv_vaccines = - Programme.hpv.vaccines.pluck(:nivs_name).compact.map { tag.i(it) } - flu_vaccines = - Programme.flu.vaccines.pluck(:nivs_name).compact.map { tag.i(it) } - - hpv_vaccines_sentence = make_vaccines_sentence(vaccines: hpv_vaccines) - flu_vaccines_sentence = make_vaccines_sentence(vaccines: flu_vaccines) + vaccine_given_notes_per_programme = + [Programme.hpv, Programme.flu].map do |programme| + vaccines = + programme + .vaccines + .where.not(nivs_name: [nil, ""]) + .order(:nivs_name) + .pluck(:nivs_name) + .map { tag.i(it) } + + "#{tag.br}#{tag.br}" \ + "For #{programme.name_in_sentence} records, must be one of: " \ + "#{to_sentence_with_or(vaccines)}." + end [ { name: "VACCINE_GIVEN", notes: - "#{tag.strong("Mandatory")}" \ - "#{tag.br}#{tag.br}" \ - "For HPV records, must be one of: #{hpv_vaccines_sentence}." \ - "#{tag.br}#{tag.br}" \ - "For flu records, must be one of: #{flu_vaccines_sentence}." + ([tag.strong("Required")] + vaccine_given_notes_per_programme).join }, - { name: "BATCH_NUMBER", notes: tag.strong("Mandatory") }, + { name: "BATCH_NUMBER", notes: tag.strong("Required") }, { name: "BATCH_EXPIRY_DATE", notes: - "#{tag.strong("Mandatory")}, must use #{tag.i("YYYYMMDD")} format." + "#{tag.strong("Required")}, must use #{tag.i("YYYYMMDD")} format." } ] end @@ -394,7 +357,7 @@ def anatomical_site def reason_not_vaccinated_and_notes reasons = ImmunisationImportRow::REASONS_NOT_ADMINISTERED.keys.sort.map do - tag.i(_1) + tag.i(it) end reasons_sentence = reasons.to_sentence( @@ -466,11 +429,11 @@ def national_reporting_performing_professional_names [ { name: "PERFORMING_PROFESSIONAL_FORENAME", - notes: "Mandatory for flu records, optional for HPV records." + notes: "Required for flu records, optional for HPV records." }, { name: "PERFORMING_PROFESSIONAL_SURNAME", - notes: "Mandatory for flu records, optional for HPV records." + notes: "Required for flu records, optional for HPV records." } ] end @@ -485,7 +448,7 @@ def supplier end def national_reporting_anatomical_site - sites = ImmunisationImportRow::DELIVERY_SITES.keys.sort.map { tag.i(_1) } + sites = ImmunisationImportRow::DELIVERY_SITES.keys.sort.map { tag.i(it) } site_sentence = sites.to_sentence( @@ -496,7 +459,7 @@ def national_reporting_anatomical_site [ { name: "ANATOMICAL_SITE", - notes: "#{tag.strong("Mandatory")}, must be one of: #{site_sentence}." + notes: "#{tag.strong("Required")}, must be one of: #{site_sentence}." } ] end @@ -509,7 +472,7 @@ def national_reporting_dose_sequence { name: "DOSE_SEQUENCE", notes: - "Mandatory for HPV records, optional for flu records." \ + "Required for HPV records, optional for flu records." \ "#{tag.br} #{tag.br}" \ "Must be a number between 1 and #{hpv_max} for HPV records and between 1 and #{flu_max} for flu records." } @@ -518,8 +481,20 @@ def national_reporting_dose_sequence def local_patient_id [ - { name: "LOCAL_PATIENT_ID", notes: tag.strong("Mandatory") }, - { name: "LOCAL_PATIENT_ID_URI", notes: tag.strong("Mandatory") } + { + name: "LOCAL_PATIENT_ID", + notes: + "#{tag.strong("Required")}, supplied automatically by your vaccination recording system." + }, + { + name: "LOCAL_PATIENT_ID_URI", + notes: + "#{tag.strong("Required")}, supplied automatically by your vaccination recording system." + } ] end + + def to_sentence_with_or(items) + items.to_sentence(last_word_connector: " or ", two_words_connector: " or ") + end end diff --git a/app/components/app_parent_summary_component.rb b/app/components/app_parent_summary_component.rb deleted file mode 100644 index c5d2ea146c..0000000000 --- a/app/components/app_parent_summary_component.rb +++ /dev/null @@ -1,156 +0,0 @@ -# frozen_string_literal: true - -class AppParentSummaryComponent < ViewComponent::Base - def initialize(parent_relationship:, change_links: {}) - @parent_relationship = parent_relationship - @parent = parent_relationship.parent - @patient = parent_relationship.patient - - @change_links = change_links - end - - def call - govuk_summary_list do |summary_list| - summary_list.with_row do |row| - row.with_key { "Name" } - - if @parent.full_name.present? - row.with_value { @parent.full_name } - if (href = @change_links[:name]) - row.with_action(text: "Change", href:, visually_hidden_text: "name") - end - elsif (href = @change_links[:name]) - row.with_value { link_to("Add name", href) } - else - row.with_value { "Not provided" } - end - end - - summary_list.with_row do |row| - row.with_key { "Relationship" } - row.with_value { @parent_relationship.label } - if (href = @change_links[:relationship]) - row.with_action( - text: "Change", - href:, - visually_hidden_text: "relationship" - ) - end - end - - unless @patient&.restricted? - summary_list.with_row do |row| - row.with_key { "Email address" } - if @parent.email.present? - row.with_value { email_address } - if (href = @change_links[:email]) - row.with_action( - text: "Change", - href:, - visually_hidden_text: "email address" - ) - end - elsif (href = @change_links[:email]) - row.with_value { link_to("Add email address", href) } - else - row.with_value { "Not provided" } - end - end - - summary_list.with_row do |row| - row.with_key { "Phone number" } - if @parent.phone.present? - row.with_value { phone_number } - if (href = @change_links[:phone]) - row.with_action( - text: "Change", - href:, - visually_hidden_text: "phone number" - ) - end - elsif (href = @change_links[:phone]) - row.with_value { link_to("Add phone number", href) } - else - row.with_value { "Not provided" } - end - end - - if @parent.contact_method_type.present? - summary_list.with_row do |row| - row.with_key { "Communication needs" } - row.with_value { @parent.contact_method_description } - if (href = @change_links[:phone]) - row.with_action( - text: "Change", - href:, - visually_hidden_text: "communication needs" - ) - end - end - end - - summary_list.with_row do |row| - row.with_key { "Get updates by text message" } - row.with_value { @parent.phone_receive_updates ? "Yes" : "No" } - if (href = @change_links[:phone]) - row.with_action( - text: "Change", - href:, - visually_hidden_text: "get updates by text message" - ) - end - end - end - end - end - - private - - delegate :govuk_summary_list, to: :helpers - - EMAIL_FAILURE_TEXTS = { - "permanent_failure" => "Email address does not exist", - "temporary_failure" => "Inbox not accepting messages right now" - }.freeze - - def email_address - delivery_status = @parent.email_delivery_status - - elements = [ - tag.p(@parent.email, class: "nhsuk-u-margin-0"), - if (failure_text = EMAIL_FAILURE_TEXTS[delivery_status]) - render AppStatusComponent.new( - text: failure_text, - colour: "red", - small: true - ) - end - ].compact - - safe_join(elements) - end - - PHONE_FAILURE_TEXTS = { - "not_uk_mobile_number_failure" => - "Phone number is a landline not accepting text messages", - "permanent_failure" => "Phone number does not exist", - "temporary_failure" => "Inbox not accepting messages right now" - }.freeze - - def phone_number - delivery_status = @parent.sms_delivery_status - - elements = [ - tag.p(@parent.phone, class: "nhsuk-u-margin-0"), - if (failure_text = PHONE_FAILURE_TEXTS[delivery_status]) - render AppStatusComponent.new( - text: failure_text, - colour: "red", - small: true - ) - end - ].compact - - safe_join(elements) - end -end diff --git a/app/components/app_session_search_form_component.rb b/app/components/app_session_search_form_component.rb index b9ebc11e1d..25aa42cdbd 100644 --- a/app/components/app_session_search_form_component.rb +++ b/app/components/app_session_search_form_component.rb @@ -13,7 +13,7 @@ def initialize(form, url:, programmes:, academic_years:) STATUSES = %w[in_progress unscheduled scheduled completed].freeze TYPES = { - "school" => "School session", + "gias_school" => "School session", "generic_clinic" => "Community clinic" }.freeze diff --git a/app/components/app_session_summary_component.rb b/app/components/app_session_summary_component.rb index 27f5773a20..2c8a934442 100644 --- a/app/components/app_session_summary_component.rb +++ b/app/components/app_session_summary_component.rb @@ -63,7 +63,7 @@ def location_row end def school_urn_row - return unless show_location && location.school? + return unless show_location && location.gias_school? { key: { diff --git a/app/components/app_timeline_component.rb b/app/components/app_timeline_component.rb index 6068ece5b7..9aa020fa13 100644 --- a/app/components/app_timeline_component.rb +++ b/app/components/app_timeline_component.rb @@ -38,7 +38,7 @@ def render? = @items.present? "Triage" => "red", "VaccinationRecord" => "grey", "SchoolMove" => "orange", - "SchoolMoveLogEntry" => "pink" + "SchoolMoveLogEntry" => "orange" }.freeze def format_heading(item) @@ -63,6 +63,8 @@ def format_description(item) "#{id_info}#{formatted_details}" elsif item[:description].present? item[:description] + else + "" end end diff --git a/app/controllers/api/reporting/totals_controller.rb b/app/controllers/api/reporting/totals_controller.rb index db376554c8..a90c840a56 100644 --- a/app/controllers/api/reporting/totals_controller.rb +++ b/app/controllers/api/reporting/totals_controller.rb @@ -25,6 +25,13 @@ class API::Reporting::TotalsController < API::Reporting::BaseController patient_school_name: "School Name" }.freeze + SCHOOL_LOCATION_TYPES = %w[gias_school generic_school].freeze + COMMUNITY_LOCATION_TYPES = %w[ + generic_clinic + community_clinic + gp_practice + ].freeze + METRIC_HEADERS = { cohort: "Cohort", no_consent: "No Consent", @@ -179,7 +186,7 @@ def apply_year_group_filter def team_vaccination_records_scope VaccinationRecord .where(discarded_at: nil, outcome: :administered) - .joins(session: :team_location) + .joins(session: { team_location: :location }) .where( team_locations: { team_id: @team&.id || current_user.team_ids, @@ -205,24 +212,50 @@ def team_vaccination_records_scope end def team_vaccinations_given_count - team_vaccination_records_scope.count + pivot_location_counts( + team_vaccination_records_scope.group("locations.type").count + ) end def team_monthly_vaccinations_given - counts = - team_vaccination_records_scope - .group( - Arel.sql( - "EXTRACT(YEAR FROM vaccination_records.performed_at_date)::integer" - ), - Arel.sql( - "EXTRACT(MONTH FROM vaccination_records.performed_at_date)::integer" - ) - ) - .count - .map do |(year, month), count| - { year:, month: Date::MONTHNAMES[month], count: } - end - counts.sort_by! { [it[:year], Date::MONTHNAMES.index(it[:month])] } + counts_by_key = + team_vaccination_records_scope.group( + Arel.sql( + "EXTRACT(YEAR FROM vaccination_records.performed_at_date)::integer" + ), + Arel.sql( + "EXTRACT(MONTH FROM vaccination_records.performed_at_date)::integer" + ), + "locations.type" + ).count + + grouped = Hash.new { |h, k| h[k] = { school_count: 0, community_count: 0 } } + + counts_by_key.each do |(year, month, loc_type), count| + key = [year, month] + if SCHOOL_LOCATION_TYPES.include?(loc_type) + grouped[key][:school_count] += count + elsif COMMUNITY_LOCATION_TYPES.include?(loc_type) + grouped[key][:community_count] += count + end + end + + result = + grouped.map do |(year, month), counts| + { year:, month: Date::MONTHNAMES[month], **counts } + end + result.sort_by! { [it[:year], Date::MONTHNAMES.index(it[:month])] } + end + + def pivot_location_counts(counts_by_type) + school_count = + counts_by_type.sum do |type, count| + SCHOOL_LOCATION_TYPES.include?(type) ? count : 0 + end + community_count = + counts_by_type.sum do |type, count| + COMMUNITY_LOCATION_TYPES.include?(type) ? count : 0 + end + { school_count:, community_count: } end end diff --git a/app/controllers/class_imports_controller.rb b/app/controllers/class_imports_controller.rb index 6f319f966e..8b4a48bf94 100644 --- a/app/controllers/class_imports_controller.rb +++ b/app/controllers/class_imports_controller.rb @@ -5,7 +5,7 @@ class ClassImportsController < ApplicationController before_action :set_draft_import, only: %i[new create] before_action :set_class_import, - only: %i[show update approve cancel re_review imported_records] + only: %i[show approve cancel re_review imported_records] before_action :set_open_sections, only: %i[show] before_action :set_review_records, only: %i[re_review imported_records] @@ -84,12 +84,6 @@ def show } end - def update - @class_import.process! - - redirect_to class_import_path(@class_import) - end - def re_review render template: "imports/re_review", layout: "full", diff --git a/app/controllers/cohort_imports_controller.rb b/app/controllers/cohort_imports_controller.rb index 935e43a93a..7d1a08239c 100644 --- a/app/controllers/cohort_imports_controller.rb +++ b/app/controllers/cohort_imports_controller.rb @@ -5,7 +5,7 @@ class CohortImportsController < ApplicationController before_action :set_draft_import, only: %i[new create] before_action :set_cohort_import, - only: %i[show update approve cancel re_review imported_records] + only: %i[show approve cancel re_review imported_records] before_action :set_open_sections, only: %i[show] before_action :set_review_records, only: %i[re_review imported_records] @@ -89,12 +89,6 @@ def show } end - def update - @cohort_import.process! - - redirect_to cohort_import_path(@cohort_import) - end - def re_review render template: "imports/re_review", layout: "full", diff --git a/app/controllers/draft_imports_controller.rb b/app/controllers/draft_imports_controller.rb index 5d96dfb050..720ece00e8 100644 --- a/app/controllers/draft_imports_controller.rb +++ b/app/controllers/draft_imports_controller.rb @@ -38,7 +38,7 @@ def set_location end def set_location_options - @location_options = policy_scope(Location).school + @location_options = policy_scope(Location).gias_school end def set_year_group_options diff --git a/app/controllers/draft_schools_controller.rb b/app/controllers/draft_schools_controller.rb index 77bfe01c1e..f46e9bcf64 100644 --- a/app/controllers/draft_schools_controller.rb +++ b/app/controllers/draft_schools_controller.rb @@ -67,7 +67,7 @@ def set_school def set_school_options @school_options = policy_scope(Location) - .school + .gias_school .joins(:team_locations) .where(team_locations: { academic_year: AcademicYear.pending }) .distinct diff --git a/app/controllers/draft_sessions_controller.rb b/app/controllers/draft_sessions_controller.rb index 6b133b7ea0..9645a3522f 100644 --- a/app/controllers/draft_sessions_controller.rb +++ b/app/controllers/draft_sessions_controller.rb @@ -69,7 +69,7 @@ def set_steps def set_schools @schools = policy_scope(Location) - .school + .gias_school .joins(:team_locations) .where( team_locations: { diff --git a/app/controllers/draft_vaccination_records_controller.rb b/app/controllers/draft_vaccination_records_controller.rb index 6724f84ddb..43b11b58dd 100644 --- a/app/controllers/draft_vaccination_records_controller.rb +++ b/app/controllers/draft_vaccination_records_controller.rb @@ -307,7 +307,7 @@ def set_batches def set_locations if @draft_vaccination_record.national_reporting_user_and_record? @location_query = params[:q] - scope = Location.school.where(status: "open") + scope = Location.gias_school.where(status: "open") if @location_query.present? scope = scope.search_by_name(@location_query) diff --git a/app/controllers/immunisation_imports_controller.rb b/app/controllers/immunisation_imports_controller.rb index 1de2b78f94..a07b384266 100644 --- a/app/controllers/immunisation_imports_controller.rb +++ b/app/controllers/immunisation_imports_controller.rb @@ -3,7 +3,7 @@ class ImmunisationImportsController < ApplicationController include Pagy::Backend - before_action :set_immunisation_import, only: %i[show update] + before_action :set_immunisation_import, only: %i[show] skip_after_action :verify_policy_scoped, only: %i[new create] @@ -56,12 +56,6 @@ def show } end - def update - @immunisation_import.process! - - redirect_to immunisation_import_path(@immunisation_import) - end - private def type diff --git a/app/controllers/inspect/graphs_controller.rb b/app/controllers/inspect/graphs_controller.rb index 6f04691821..26e9b5803f 100644 --- a/app/controllers/inspect/graphs_controller.rb +++ b/app/controllers/inspect/graphs_controller.rb @@ -14,104 +14,147 @@ class GraphsController < ApplicationController def show authorize :inspect, :graph? - @primary_type = safe_get_primary_type - if @primary_type.nil? + if primary_type.nil? render plain: "You don't have permission to view object type: #{params[:object_type].to_s.downcase.singularize}", status: :bad_request and return end - @primary_id = params[:object_id] # Set default relationships when loading a page for the first time if params[:relationships].blank? && - GraphRecords::DEFAULT_TRAVERSALS.key?(@primary_type) - default_rels = GraphRecords::DEFAULT_TRAVERSALS[@primary_type] || {} + GraphRecords::DEFAULT_TRAVERSALS.key?(primary_type) + default_rels = GraphRecords::DEFAULT_TRAVERSALS[primary_type] || {} new_params = params.to_unsafe_h.merge("relationships" => default_rels) redirect_to inspect_path(new_params) and return end - @object = @primary_type.to_s.classify.constantize.find(@primary_id) - - @traversals_config = build_traversals_config - @graph_params = build_graph_params - set_pii_settings - @graph_record = GraphRecords.new( - traversals_config: @traversals_config, - primary_type: @primary_type, + traversals_config:, + primary_type:, clickable: true, - show_pii: @show_pii + show_pii: ) - @mermaid = @graph_record.graph(**@graph_params).join("\n") + @mermaid = @graph_record.graph(**graph_params).join("\n") end private - def set_pii_settings - @user_is_allowed_to_access_pii = policy(:inspect).show_pii? + def show_pii + return @show_pii if defined?(@show_pii) @show_pii = - @user_is_allowed_to_access_pii && - (params[:show_pii] || SHOW_PII_BY_DEFAULT) + user_is_allowed_to_access_pii && pii_access_requested_by_user && + !sensitive_patient_in_graph end - def build_traversals_config - traversals_config = {} - to_process = Set.new([@primary_type]) - processed = Set.new + def user_is_allowed_to_access_pii + if defined?(@user_is_allowed_to_access_pii) + return @user_is_allowed_to_access_pii + end + @user_is_allowed_to_access_pii = policy(:inspect).show_pii? + end - # Process types until we've explored all connected relationships - while (type = to_process.first) - to_process.delete(type) - processed.add(type) + def sensitive_patient_in_graph + if defined?(@sensitive_patient_in_graph) + return @sensitive_patient_in_graph + end + @sensitive_patient_in_graph = + begin + graph_with_pii = + GraphRecords.new( + traversals_config:, + primary_type:, + clickable: true, + show_pii: true + ) + graph_with_pii.graph(**graph_params) + graph_with_pii.patients_with_pii_in_graph.any?(&:restricted?) + end + end - selected_rels = - Array(params.dig(:relationships, type)).reject(&:blank?).map(&:to_sym) + def pii_access_requested_by_user + if defined?(@pii_access_requested_by_user) + return @pii_access_requested_by_user + end + @pii_access_requested_by_user = params[:show_pii] || SHOW_PII_BY_DEFAULT + end - traversals_config[type] = selected_rels + def traversals_config + @traversals_config ||= + begin + traversals = {} + to_process = Set.new([primary_type]) + processed = Set.new + + # Process types until we've explored all connected relationships + while (type = to_process.first) + to_process.delete(type) + processed.add(type) + + selected_rels = + Array(params.dig(:relationships, type)).reject(&:blank?).map( + &:to_sym + ) - # Add target types to process queue - klass = type.to_s.classify.constantize - selected_rels.each do |rel| - association = klass.reflect_on_association(rel) - next unless association + traversals[type] = selected_rels - target_type = association.klass.name.underscore.to_sym - to_process.add(target_type) unless processed.include?(target_type) - end - end + # Add target types to process queue + klass = type.to_s.classify.constantize + selected_rels.each do |rel| + association = klass.reflect_on_association(rel) + next unless association + + target_type = association.klass.name.underscore.to_sym + to_process.add(target_type) unless processed.include?(target_type) + end + end - traversals_config + traversals + end end - def build_graph_params - graph_params = { @primary_type => [@object.id] } - - if params[:additional_ids].present? - params[:additional_ids].each do |type, ids_string| - next if ids_string.blank? - additional_ids = ids_string.split(",").map { |s| s.strip.to_i } - next unless additional_ids.any? - type_sym = type.to_sym - graph_params[type_sym] ||= [] - graph_params[type_sym].concat(additional_ids) + def graph_params + @graph_params ||= + begin + graph_params = { primary_type => [primary_object.id] } + + if params[:additional_ids].present? + params[:additional_ids].each do |type, ids_string| + next if ids_string.blank? + additional_ids = ids_string.split(",").map { |s| s.strip.to_i } + next unless additional_ids.any? + type_sym = type.to_sym + graph_params[type_sym] ||= [] + graph_params[type_sym].concat(additional_ids) + end + end + + graph_params end - end + end - graph_params + def primary_type + @primary_type ||= + begin + singular_type = params[:object_type].downcase.singularize + return nil unless GraphRecords::ALLOWED_TYPES.include?(singular_type) + singular_type.to_sym + end end - def safe_get_primary_type - singular_type = params[:object_type].downcase.singularize - return nil unless GraphRecords::ALLOWED_TYPES.include?(singular_type) - singular_type.to_sym + def primary_object + @primary_object ||= + begin + @primary_id = params[:object_id] + primary_type.to_s.classify.constantize.find(@primary_id) + end end def pii_accessed? - return false unless @show_pii + return false unless show_pii - build_traversals_config.any? do |from_type, rels| + traversals_config.any? do |from_type, rels| GraphRecords::EXTRA_DETAIL_WHITELIST_WITH_PII.key?( from_type.name.underscore.to_sym ) || @@ -142,7 +185,7 @@ def record_access_log_entry controller: "graph", action: "show_pii", request_details: { - primary_type: @primary_type, + primary_type:, primary_id: @primary_id, additional_ids: additional_ids.presence, visible_fields: request_details diff --git a/app/controllers/inspect/timeline/patients_controller.rb b/app/controllers/inspect/timeline/patients_controller.rb index 1b98af6ae5..c7b2e86451 100644 --- a/app/controllers/inspect/timeline/patients_controller.rb +++ b/app/controllers/inspect/timeline/patients_controller.rb @@ -13,12 +13,9 @@ class Inspect::Timeline::PatientsController < ApplicationController def show authorize :inspect, :timeline? - set_pii_settings params[:audit_config] ||= {} - compare_option = params[:compare_option] || nil - # Set default values if none present if params[:detail_config].nil? && params[:event_names].nil? && params[:show_pii].nil? @@ -40,7 +37,6 @@ def show params[:audit_config][:include_filtered_audit_changes] } - @compare_patient = sample_patient(params[:compare_option]) if compare_option @event_names = params[:event_names] || [] record_access_log_entry @@ -48,9 +44,9 @@ def show @patient_timeline = TimelineRecords.new( @patient, - detail_config: build_details_config, - audit_config: audit_config, - show_pii: @show_pii + details_config:, + audit_config:, + show_pii: ).load_timeline_events(@event_names) @no_events_message = true if @patient_timeline.empty? @@ -61,9 +57,9 @@ def show @compare_patient_timeline = TimelineRecords.new( @compare_patient, - detail_config: build_details_config, - audit_config: audit_config, - show_pii: @show_pii + details_config:, + audit_config:, + show_pii: ).load_timeline_events(@event_names) @no_events_compare_message = true if @compare_patient_timeline.empty? @@ -72,11 +68,33 @@ def show private - def set_pii_settings - @user_is_allowed_to_access_pii = policy(:inspect).show_pii? + def show_pii + return @show_pii if defined?(@show_pii) @show_pii = - @user_is_allowed_to_access_pii && - (params[:show_pii] || SHOW_PII_BY_DEFAULT) + user_is_allowed_to_access_pii && pii_access_requested_by_user && + !patient_accessed_is_sensitive + end + + def user_is_allowed_to_access_pii + if defined?(@user_is_allowed_to_access_pii) + return @user_is_allowed_to_access_pii + end + @user_is_allowed_to_access_pii = policy(:inspect).show_pii? + end + + def patient_accessed_is_sensitive + if defined?(@patient_accessed_is_sensitive) + return @patient_accessed_is_sensitive + end + @patient_accessed_is_sensitive = + @patient.restricted? || @compare_patient&.restricted? + end + + def pii_access_requested_by_user + if defined?(@pii_access_requested_by_user) + return @pii_access_requested_by_user + end + @pii_access_requested_by_user = params[:show_pii] || SHOW_PII_BY_DEFAULT end def set_patient @@ -84,6 +102,9 @@ def set_patient timeline = TimelineRecords.new(@patient) @patient_events = timeline.patient_events(@patient) @additional_events = timeline.additional_events(@patient) + + compare_option = params[:compare_option] || nil + @compare_patient = sample_patient(params[:compare_option]) if compare_option end # TODO: Fix so that a new comparison patient isn't sampled every time @@ -118,22 +139,42 @@ def sample_patient(compare_option) end end - def build_details_config - details_params = params[:detail_config] || {} - details_params = details_params.to_unsafe_h unless details_params.is_a?( - Hash - ) + def details_config + @details_config ||= + begin + details_params = params[:detail_config] || {} + details_params = details_params.to_unsafe_h unless details_params.is_a?( + Hash + ) - details_params.each_with_object({}) do |(event_type, fields), hash| - selected_fields = Array(fields).reject(&:blank?).map(&:to_sym) - hash[event_type.to_sym] = selected_fields - end + event_list_details = + (@event_names - ["audits"]).map { [it.to_sym, []] }.to_h + user_submitted_details = + details_params.each_with_object( + event_list_details + ) do |(event_type, fields), hash| + selected_fields = Array(fields).reject(&:blank?).map(&:to_sym) + hash[event_type.to_sym] = selected_fields + end + + # Removes details from the config which the user does not have permission to view + details_mask = + ( + if show_pii + TimelineRecords::AVAILABLE_DETAILS_CONFIG_WITH_PII + else + TimelineRecords::AVAILABLE_DETAILS_CONFIG + end + ) + (details_mask.keys & user_submitted_details.keys).index_with do |key| + details_mask[key] & user_submitted_details[key] + end + end end def pii_accessed? - return false unless @show_pii - detail_config = build_details_config - detail_config.any? do |event_type, selected_fields| + return false unless show_pii + details_config.any? do |event_type, selected_fields| pii_fields = TimelineRecords::AVAILABLE_DETAILS_CONFIG_PII[event_type] || [] (selected_fields & pii_fields).any? @@ -141,14 +182,14 @@ def pii_accessed? end def audit_pii_accessed? - true if @show_pii && @event_names.include?("audits") + true if show_pii && @event_names.include?("audits") end def record_access_log_entry return unless pii_accessed? || audit_pii_accessed? details_accessed = - build_details_config.reverse_merge( + details_config.reverse_merge( @event_names.map { |key| [key.to_sym, []] }.to_h ) details_accessed[:audits] = :accessed if details_accessed.key?(:audits) diff --git a/app/controllers/patients/edit_controller.rb b/app/controllers/patients/edit_controller.rb index 5dc1167114..967d0335c9 100644 --- a/app/controllers/patients/edit_controller.rb +++ b/app/controllers/patients/edit_controller.rb @@ -133,7 +133,7 @@ def set_eligible_schools @eligible_schools = current_team .locations - .where(type: %w[school generic_school]) + .school .joins(:location_year_groups) .where(location_year_groups: { value: year_group }) .distinct diff --git a/app/controllers/schools/base_controller.rb b/app/controllers/schools/base_controller.rb index 5a6a819168..baa985c112 100644 --- a/app/controllers/schools/base_controller.rb +++ b/app/controllers/schools/base_controller.rb @@ -10,9 +10,9 @@ class Schools::BaseController < ApplicationController def set_location @location = - policy_scope(Location).where( - type: %w[school generic_school] - ).find_by_urn_and_site!(params[:school_urn_and_site]) + policy_scope(Location).school.find_by_urn_and_site!( + params[:school_urn_and_site] + ) authorize @location, policy_class: SchoolPolicy end diff --git a/app/controllers/schools_controller.rb b/app/controllers/schools_controller.rb index cc10326f5f..c708a352fc 100644 --- a/app/controllers/schools_controller.rb +++ b/app/controllers/schools_controller.rb @@ -10,10 +10,7 @@ class SchoolsController < ApplicationController def index authorize Location, policy_class: SchoolPolicy - locations = - @form.apply( - policy_scope(Location).school.or(policy_scope(Location).generic_school) - ) + locations = @form.apply(policy_scope(Location).school) @pagy, @locations = pagy(locations) diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb index e353c31922..3c37afc359 100644 --- a/app/controllers/teams_controller.rb +++ b/app/controllers/teams_controller.rb @@ -29,7 +29,7 @@ def set_team def set_schools @schools = @team - .schools + .gias_schools .joins(:team_locations) .where(team_locations: { academic_year: AcademicYear.pending }) .distinct diff --git a/app/helpers/consents_helper.rb b/app/helpers/consents_helper.rb index 70cff3ec4f..852f539661 100644 --- a/app/helpers/consents_helper.rb +++ b/app/helpers/consents_helper.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module ConsentsHelper - ConsentRefusalOption = Struct.new(:value, :label, :divider) + ConsentRefusalOption = Struct.new(:value, :label, :hint, :divider) def consent_refusal_reasons(consent) reasons = %w[ @@ -16,10 +16,19 @@ def consent_refusal_reasons(consent) reasons.insert(0, "contains_gelatine") end + if consent.respond_to?(:location) && consent.location&.school? + reasons.insert(-2, "do_not_want_vaccination_at_school") + end + reasons.map do |value| label = refusal_reason_label(consent, value) + hint = + I18n.t( + "activerecord.attributes.consent.reason_for_refusal_hints.#{value}", + default: nil + ) - ConsentRefusalOption.new(value:, label:, divider: value == "other") + ConsentRefusalOption.new(value:, label:, hint:, divider: value == "other") end end diff --git a/app/helpers/patients_helper.rb b/app/helpers/patients_helper.rb index 6e399e5d3a..1815e6d177 100644 --- a/app/helpers/patients_helper.rb +++ b/app/helpers/patients_helper.rb @@ -77,4 +77,25 @@ def patient_year_group(patient, academic_year:) def patient_parents(patient) format_parents_with_relationships(patient.parent_relationships) end + + def patient_needs_more_doses?(patient, programme, academic_year) + patient.programme_status(programme, academic_year:).needs_more_doses? + end + + def patient_next_dose_label(patient, programme, academic_year) + patient + .programme_status(programme, academic_year:) + .dose_sequence + &.ordinalize + end + + def patient_previous_dose_label(patient, programme, academic_year) + dose = patient.programme_status(programme, academic_year:).dose_sequence + (dose - 1).ordinalize if dose && dose > 1 + end + + def patient_short_name_possessive(patient) + name = patient.short_name + name.ends_with?("s") ? "#{name}’" : "#{name}’s" + end end diff --git a/app/helpers/programmes_helper.rb b/app/helpers/programmes_helper.rb new file mode 100644 index 0000000000..050414ff34 --- /dev/null +++ b/app/helpers/programmes_helper.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module ProgrammesHelper + def programme_name_for_parents(programme) + nhs_name = programme.name_on_nhs_uk + if nhs_name + "#{programme.name_in_sentence} (#{nhs_name})" + else + programme.name_in_sentence + end + end + + def programme_disease_names(programme) + programme + .disease_types + .map { I18n.t!(it, scope: :disease_types) } + .to_sentence + end +end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb index 162f7e8157..2abf45709d 100644 --- a/app/helpers/sessions_helper.rb +++ b/app/helpers/sessions_helper.rb @@ -73,4 +73,8 @@ def session_title(session) def session_consent_style(session) session.outbreak ? "Outbreak request" : "Standard request" end + + def session_future_dates(session) + session.future_dates.map { it.to_fs(:short_day_of_week) }.to_sentence + end end diff --git a/app/helpers/teams_helper.rb b/app/helpers/teams_helper.rb new file mode 100644 index 0000000000..c63d5d1226 --- /dev/null +++ b/app/helpers/teams_helper.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module TeamsHelper + include PhoneHelper + + def team_contact_name(session: nil, vaccination_record: nil) + contact_entity(session:, vaccination_record:).name + end + + def team_contact_email(session: nil, vaccination_record: nil) + contact_entity(session:, vaccination_record:).email + end + + def team_contact_phone(session: nil, vaccination_record: nil) + format_phone_with_instructions( + contact_entity(session:, vaccination_record:) + ) + end + + private + + def contact_entity(session: nil, vaccination_record: nil) + if session.nil? == vaccination_record.nil? + raise ArgumentError, + "provide either session: or vaccination_record:, not both or neither" + end + + team_location = + session&.team_location || vaccination_record&.session&.team_location || + vaccination_record + &.patient + &.school + &.team_locations + &.includes(:team, :subteam) + &.ordered + &.find_by(academic_year: AcademicYear.current) + + team_location&.subteam || team_location&.team + end +end diff --git a/app/helpers/vaccination_records_helper.rb b/app/helpers/vaccination_records_helper.rb index 227bab38f1..2e7fbc8504 100644 --- a/app/helpers/vaccination_records_helper.rb +++ b/app/helpers/vaccination_records_helper.rb @@ -1,6 +1,18 @@ # frozen_string_literal: true module VaccinationRecordsHelper + def vaccination_record_date(vaccination_record) + vaccination_record.performed_at.to_date.to_fs(:uk_short) + end + + def vaccination_record_today_or_date(vaccination_record) + if vaccination_record.performed_at.today? + "today" + else + "on #{vaccination_record.performed_at.to_date.to_fs(:long)}" + end + end + def vaccination_record_location(vaccination_record) vaccination_record.location_name.presence || vaccination_record.location&.name || "Unknown" diff --git a/app/helpers/vaccines_helper.rb b/app/helpers/vaccines_helper.rb index 1075479590..e08745c0c9 100644 --- a/app/helpers/vaccines_helper.rb +++ b/app/helpers/vaccines_helper.rb @@ -4,4 +4,22 @@ module VaccinesHelper def vaccine_heading(vaccine) "#{vaccine.brand} (#{vaccine.programme.name})" end + + def vaccine_method(vaccine) + return unless vaccine + + vaccine.human_enum_name(:method)&.downcase + end + + def vaccine_side_effects_list(vaccine) + return if vaccine.nil? + + vaccine + .side_effects + .map { Vaccine.human_enum_name(:side_effect, it) } + .sort + .uniq + .map { "- #{it}" } + .join("\n") + end end diff --git a/app/jobs/enqueue_patients_aged_out_of_schools_job.rb b/app/jobs/enqueue_patients_aged_out_of_schools_job.rb index 06e232d83b..329fa2aa4a 100644 --- a/app/jobs/enqueue_patients_aged_out_of_schools_job.rb +++ b/app/jobs/enqueue_patients_aged_out_of_schools_job.rb @@ -5,7 +5,7 @@ class EnqueuePatientsAgedOutOfSchoolsJob < ApplicationJob def perform academic_year = AcademicYear.pending - ids = Location.school.with_team(academic_year:).pluck(:id) + ids = Location.gias_school.with_team(academic_year:).pluck(:id) PatientsAgedOutOfSchoolJob.perform_bulk(ids.zip) end end diff --git a/app/jobs/enqueue_school_consent_reminders_job.rb b/app/jobs/enqueue_school_consent_reminders_job.rb index 4811835da7..e3ca424490 100644 --- a/app/jobs/enqueue_school_consent_reminders_job.rb +++ b/app/jobs/enqueue_school_consent_reminders_job.rb @@ -5,7 +5,10 @@ class EnqueueSchoolConsentRemindersJob < ApplicationJob def perform sessions = - Session.send_consent_reminders.joins(:location).merge(Location.school) + Session + .send_consent_reminders + .joins(:location) + .merge(Location.gias_school) sessions.find_each do |session| SendAutomaticSchoolConsentRemindersJob.perform_later(session) diff --git a/app/jobs/enqueue_school_consent_requests_job.rb b/app/jobs/enqueue_school_consent_requests_job.rb index b0190639c0..435f327366 100644 --- a/app/jobs/enqueue_school_consent_requests_job.rb +++ b/app/jobs/enqueue_school_consent_requests_job.rb @@ -5,7 +5,7 @@ class EnqueueSchoolConsentRequestsJob < ApplicationJob def perform sessions = - Session.send_consent_requests.joins(:location).merge(Location.school) + Session.send_consent_requests.joins(:location).merge(Location.gias_school) sessions.find_each do |session| SendSchoolConsentRequestsJob.perform_later(session) diff --git a/app/jobs/enqueue_school_session_reminders_job.rb b/app/jobs/enqueue_school_session_reminders_job.rb index 44f6424a46..7a50cda3e0 100644 --- a/app/jobs/enqueue_school_session_reminders_job.rb +++ b/app/jobs/enqueue_school_session_reminders_job.rb @@ -9,7 +9,7 @@ def perform .includes(:session_programme_year_groups) .has_date(Date.tomorrow) .joins(:location) - .merge(Location.school) + .merge(Location.gias_school) sessions.find_each do |session| SendSchoolSessionRemindersJob.perform_later(session) diff --git a/app/jobs/patients_aged_out_of_school_job.rb b/app/jobs/patients_aged_out_of_school_job.rb index 9e486d1192..6f820320d4 100644 --- a/app/jobs/patients_aged_out_of_school_job.rb +++ b/app/jobs/patients_aged_out_of_school_job.rb @@ -11,7 +11,10 @@ def perform(school_id) academic_year = AcademicYear.pending school = - Location.school.includes(:location_year_groups).find_by(id: school_id) + Location + .gias_school + .includes(:location_year_groups) + .find_by(id: school_id) return if school.nil? diff --git a/app/jobs/search_vaccination_records_in_nhs_job.rb b/app/jobs/search_vaccination_records_in_nhs_job.rb index 0ea7116ad2..dbc56a9a9f 100644 --- a/app/jobs/search_vaccination_records_in_nhs_job.rb +++ b/app/jobs/search_vaccination_records_in_nhs_job.rb @@ -20,7 +20,7 @@ def perform(patient_id) return unless feature_flags_enabled - existing_vaccination_records.find_each do |vaccination_record| + existing_vaccination_records.each do |vaccination_record| incoming_vaccination_record = incoming_vaccination_records.find do it.nhs_immunisations_api_id == @@ -28,11 +28,15 @@ def perform(patient_id) end if incoming_vaccination_record - vaccination_record.update!( + vaccination_record.assign_attributes( incoming_vaccination_record .attributes .except("id", "uuid", "created_at") - .merge(updated_at: Time.current) + .merge( + duplicate_of_vaccination_record: + incoming_vaccination_record.duplicate_of_vaccination_record, + updated_at: Time.current + ) ) incoming_vaccination_records.delete(incoming_vaccination_record) @@ -41,10 +45,12 @@ def perform(patient_id) end end - # Remaining incoming_vaccination_records are new. # Save non-discarded records first so they have IDs before discarded # duplicates reference them via duplicate_of_vaccination_record_id. - incoming_vaccination_records.sort_by { it.discarded? ? 1 : 0 }.each(&:save!) + ( + existing_vaccination_records.reject(&:destroyed?) + + incoming_vaccination_records + ).sort_by { it.discarded? ? 1 : 0 }.each(&:save!) update_vaccination_search_timestamps if patient.nhs_number.present? @@ -104,6 +110,7 @@ def existing_vaccination_records @existing_vaccination_records ||= patient .vaccination_records + .with_discarded .includes(:identity_check) .sourced_from_nhs_immunisations_api .for_programmes(programmes) @@ -126,6 +133,7 @@ def deduplicate_vaccination_records(incoming_vaccination_records) service_vaccination_records = patient .vaccination_records + .administered .with_correct_source_for_nhs_immunisations_api .includes(:team) @@ -150,8 +158,23 @@ def deduplicate_vaccination_records(incoming_vaccination_records) end elsif records.any?(&:nhs_immunisations_api_primary_source) # If some records have `primarySource: true`, set `discarded_at` for all `primarySource: false` records, - # pointing each at the first `primarySource: true` record - canonical = records.find(&:nhs_immunisations_api_primary_source) + # pointing each at the first `primarySource: true` record. + + canonical_incoming = + records.find(&:nhs_immunisations_api_primary_source) + canonical_existing = + patient + .vaccination_records + .sourced_from_nhs_immunisations_api + .with_discarded + .find_by( + nhs_immunisations_api_id: + canonical_incoming.nhs_immunisations_api_id + ) + + # Prefer the persisted DB record (if it already exists from a previous run) so that + # `duplicate_of_vaccination_record_id` is non-nil when the non-primary record is saved. + canonical = canonical_existing || canonical_incoming records .select(&:sourced_from_nhs_immunisations_api?) .reject(&:nhs_immunisations_api_primary_source) diff --git a/app/lib/feature_flag_factory.rb b/app/lib/feature_flag_factory.rb index 33a4a3966a..8ec9c52e57 100644 --- a/app/lib/feature_flag_factory.rb +++ b/app/lib/feature_flag_factory.rb @@ -16,7 +16,11 @@ def self.call end end - FEATURES_FOR_DEVELOPMENT = %i[dev_tools testing_api].freeze + FEATURES_FOR_DEVELOPMENT = %i[ + dev_tools + testing_api + vaccinating_16_plus_year_olds + ].freeze def self.enable_for_development!(check_rails_env: true) if check_rails_env && !(Rails.env.development? || Rails.env.end_to_end?) diff --git a/app/lib/fhir_mapper/location.rb b/app/lib/fhir_mapper/location.rb index ed6d0aa5af..07b515d3cb 100644 --- a/app/lib/fhir_mapper/location.rb +++ b/app/lib/fhir_mapper/location.rb @@ -2,7 +2,7 @@ module FHIRMapper class Location - delegate :school?, :clinic?, :type, :urn, :ods_code, to: :@location + delegate :gias_school?, :clinic?, :type, :urn, :ods_code, to: :@location def initialize(location) @location = location @@ -14,7 +14,7 @@ class UnknownValueError < StandardError end def fhir_reference - if school? + if gias_school? value = urn || UNKNOWN_IDENTIFIER system = "https://fhir.hl7.org.uk/Id/urn-school-number" elsif clinic? diff --git a/app/lib/fhir_mapper/vaccination_record.rb b/app/lib/fhir_mapper/vaccination_record.rb index df945af9b5..e09ac3373c 100644 --- a/app/lib/fhir_mapper/vaccination_record.rb +++ b/app/lib/fhir_mapper/vaccination_record.rb @@ -14,6 +14,9 @@ class VaccinationRecord BATCH_EXPIRY_MIN = Date.new(Date.current.year - 100, 1, 1) BATCH_EXPIRY_MAX = Date.new(Date.current.year + 100, 1, 1) + VACCINATION_PROCEDURE_EXTENSION_URL = + "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure" + def initialize(vaccination_record) @vaccination_record = vaccination_record end @@ -45,7 +48,8 @@ def fhir_record sourced_from_service? || sourced_from_national_reporting? immunisation.manufacturer = vaccine.fhir_manufacturer_reference - immunisation.location = (location || ::Location.school.new).fhir_reference + immunisation.location = + (location || ::Location.gias_school.new).fhir_reference immunisation.lotNumber = batch_number immunisation.expirationDate = batch_expiry.to_s immunisation.site = fhir_site @@ -82,6 +86,18 @@ def self.from_fhir_record(fhir_record, patient:) .sole .value attrs[:nhs_immunisations_api_primary_source] = fhir_record.primarySource + recorded = fhir_record.recorded + attrs[:nhs_immunisations_api_recorded_at] = Time.zone.parse( + recorded + ) if recorded + + procedure_coding = vaccination_procedure_coding_from_fhir(fhir_record) + attrs[ + :nhs_immunisations_api_snomed_procedure_code + ] = procedure_coding&.code + attrs[ + :nhs_immunisations_api_snomed_procedure_term + ] = procedure_coding&.display attrs[:programme] = Programme.from_fhir_record(fhir_record) @@ -109,11 +125,16 @@ def self.from_fhir_record(fhir_record, patient:) ) end - performer_ods_code = org_performer_ods_code_from_fhir(fhir_record) + org_actor = org_performer_actor_from_fhir(fhir_record) + performer_ods_code = org_actor&.identifier&.value unless performer_ods_code == FHIRMapper::Location::UNKNOWN_IDENTIFIER attrs[:performed_ods_code] = performer_ods_code end + if org_actor&.display.present? + notes << "Performing organisation display name: #{org_actor.display}" + end + user_performer_name = user_performer_name_from_fhir(fhir_record) attrs[:performed_by_given_name] = user_performer_name&.given&.first attrs[:performed_by_family_name] = user_performer_name&.family @@ -122,13 +143,27 @@ def self.from_fhir_record(fhir_record, patient:) attrs[:delivery_site] = site_from_fhir(fhir_record) dose_sequence = dose_sequence_from_fhir(fhir_record) - max_dose_sequence = attrs[:programme].maximum_dose_sequence - if dose_sequence.present? && dose_sequence > max_dose_sequence - notes << "Reported dose sequence: #{dose_sequence}" + if dose_sequence + if dose_sequence > attrs[:programme].maximum_dose_sequence || + dose_sequence < 1 + notes << "Reported dose number: #{dose_sequence}" + else + attrs[:dose_sequence] = dose_sequence + end else - attrs[:dose_sequence] = dose_sequence + notes << dose_number_string_note_from_fhir(fhir_record) end + reason_coding = reason_coding_from_fhir(fhir_record) + attrs[:nhs_immunisations_api_snomed_reason_code] = reason_coding&.code + attrs[:nhs_immunisations_api_snomed_reason_term] = reason_coding&.display + + product_coding = vaccine_product_coding_from_fhir(fhir_record) + attrs[:nhs_immunisations_api_snomed_product_code] = product_coding&.code + attrs[ + :nhs_immunisations_api_snomed_product_term + ] = product_coding&.display + attrs[:vaccine] = Vaccine.from_fhir_record(fhir_record) attrs[:batch_number] = fhir_record.lotNumber&.to_s @@ -145,7 +180,6 @@ def self.from_fhir_record(fhir_record, patient:) ) else attrs[:disease_types] = attrs[:programme].disease_types - notes << vaccine_batch_notes_from_fhir(fhir_record) attrs[:full_dose] = true end @@ -170,8 +204,7 @@ def fhir_identifier def fhir_vaccination_procedure_extension FHIR::Extension.new( - url: - "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure", + url: VACCINATION_PROCEDURE_EXTENSION_URL, valueCodeableConcept: vaccine.fhir_procedure_coding(dose_sequence:) ) end @@ -285,21 +318,6 @@ def fhir_dose_quantity end end - private_class_method def self.vaccine_batch_notes_from_fhir(fhir_record) - fhir_vaccine = - fhir_record.vaccineCode&.coding&.find do - it.system == "http://snomed.info/sct" - end - - vaccine_snomed_code = fhir_vaccine&.code - vaccine_description = fhir_vaccine&.display.presence - - [ - ("SNOMED product code: #{vaccine_snomed_code}" if vaccine_snomed_code), - ("SNOMED description: #{vaccine_description}" if vaccine_description) - ].compact.join("\n").presence - end - def fhir_user_performer(reference_id:) FHIR::Immunization::Performer.new( actor: FHIR::Reference.new(reference: "##{reference_id}") @@ -327,10 +345,31 @@ def fhir_org_performer ) end - private_class_method def self.org_performer_ods_code_from_fhir(fhir_record) - org_actor = - fhir_record.performer.find { it.actor&.type == "Organization" }&.actor - org_actor&.identifier&.value + private_class_method def self.org_performer_actor_from_fhir(fhir_record) + fhir_record.performer.find { it.actor&.type == "Organization" }&.actor + end + + private_class_method def self.vaccination_procedure_coding_from_fhir( + fhir_record + ) + fhir_record + .extension + &.find { it.url == VACCINATION_PROCEDURE_EXTENSION_URL } + &.valueCodeableConcept + &.coding + &.find { it.system == "http://snomed.info/sct" } + end + + private_class_method def self.reason_coding_from_fhir(fhir_record) + fhir_record.reasonCode&.first&.coding&.find do + it.system == "http://snomed.info/sct" + end + end + + private_class_method def self.vaccine_product_coding_from_fhir(fhir_record) + fhir_record.vaccineCode&.coding&.find do + it.system == "http://snomed.info/sct" + end end def fhir_reason_code @@ -350,10 +389,21 @@ def fhir_protocol_applied end private_class_method def self.dose_sequence_from_fhir(fhir_record) - # TODO: currently we only look at `doseNumberPositiveInt` but often `doseNumberString` is populated instead - # This doesn't matter much for flu, but this may need to be revisited when we start consuming programmes - # where dose number matters more (eg MMR) - fhir_record.protocolApplied.sole.doseNumberPositiveInt + protocol = fhir_record.protocolApplied&.sole + + if protocol&.doseNumberPositiveInt.present? + return protocol&.doseNumberPositiveInt + end + + Integer(protocol&.doseNumberString, exception: false) + end + + private_class_method def self.dose_number_string_note_from_fhir(fhir_record) + dose_string = fhir_record.protocolApplied&.sole&.doseNumberString + + return if dose_string.blank? + + "Reported dose number string: #{dose_string}" end end end diff --git a/app/lib/generate/cohort_imports.rb b/app/lib/generate/cohort_imports.rb index 3e120fb1cd..270ad3df5e 100644 --- a/app/lib/generate/cohort_imports.rb +++ b/app/lib/generate/cohort_imports.rb @@ -15,7 +15,7 @@ def initialize( ) @team = team @programmes = programmes.presence || team.programmes - @urns = urns || @team.schools.pluck(:urn) + @urns = urns || @team.gias_schools.pluck(:urn) @school_year_groups = school_year_groups @patient_count = patient_count @progress_bar = progress_bar diff --git a/app/lib/generate/consents.rb b/app/lib/generate/consents.rb index b25267e1e4..ad34c5b206 100644 --- a/app/lib/generate/consents.rb +++ b/app/lib/generate/consents.rb @@ -48,7 +48,7 @@ def patients :session_programme_year_groups, :team_location ) - .merge(Location.school) + .merge(Location.gias_school) .has_all_programmes_of([programme]) end @@ -79,7 +79,7 @@ def session_for(patient) .joins( "INNER JOIN locations ON locations.id = team_locations.location_id" ) - .merge(Location.school) + .merge(Location.gias_school) .has_all_programmes_of([programme]) .sample end @@ -97,7 +97,8 @@ def create_consents(response, count) consent_forms = available_patient_sessions.map do |patient, session| - school = session.location.school? ? session.location : patient.school + school = + session.location.gias_school? ? session.location : patient.school @updated_patients << patient @@ -124,7 +125,7 @@ def validate_programme_and_session(programme, session) end elsif Session .has_any_programmes_of([programme]) - .none? { it.location.school? } + .none? { it.location.gias_school? } raise "Programme #{programme.type} does not have a school session" end end diff --git a/app/lib/gias.rb b/app/lib/gias.rb index 3403db702f..ba348f105e 100644 --- a/app/lib/gias.rb +++ b/app/lib/gias.rb @@ -66,7 +66,7 @@ def import( next if gias_establishment_number.blank? schools << Location.new( - type: :school, + type: "gias_school", urn: row["URN"], gias_local_authority_code: row["LA (code)"], gias_establishment_number:, @@ -110,7 +110,7 @@ def check_import(input_file: DEFAULT_FILE_PATH, progress_bar: nil) existing: Set.new( Location - .school + .gias_school .joins(:sessions) .merge(Session.scheduled) .pluck(:urn) @@ -131,12 +131,12 @@ def check_import(input_file: DEFAULT_FILE_PATH, progress_bar: nil) } } - existing_schools = Set.new(Location.school.pluck(:urn)) + existing_schools = Set.new(Location.gias_school.pluck(:urn)) team_schools = Set.new( TeamLocation .joins(:location) - .merge(Location.school) + .merge(Location.gias_school) .pluck(:"locations.urn") ) @@ -394,7 +394,7 @@ def check_for_year_group_changes(row, school_set, existing_schools) return unless urn.in? existing_schools new_year_groups = process_year_groups(row) - current_year_groups = Location.school.find_by(urn:).gias_year_groups + current_year_groups = Location.gias_school.find_by(urn:).gias_year_groups if new_year_groups != current_year_groups school_set[:year_group_changes][urn] = { @@ -415,7 +415,7 @@ def calculate_percentage(schools_set, metric) def format_successors_with_teams(successor_urns) annotated_successor_urns = successor_urns.map do |successor_urn| - locations = Location.school.where(urn: successor_urn) + locations = Location.gias_school.where(urn: successor_urn) if locations.count == 1 teams = locations.sole.teams.uniq diff --git a/app/lib/govuk_notify_personalisation.rb b/app/lib/govuk_notify_personalisation.rb index a0b7e0f7bb..58d9f4e230 100644 --- a/app/lib/govuk_notify_personalisation.rb +++ b/app/lib/govuk_notify_personalisation.rb @@ -3,8 +3,13 @@ class GovukNotifyPersonalisation include Rails.application.routes.url_helpers + include PatientsHelper include PhoneHelper + include ProgrammesHelper + include SessionsHelper + include TeamsHelper include VaccinationRecordsHelper + include VaccinesHelper def initialize( academic_year: nil, @@ -61,57 +66,53 @@ def initialize( :team_location, :vaccination_record - def batch_name - vaccination_record&.batch_number - end - - def consent_deadline - session&.consent_deadline_date&.to_fs(:short_day_of_week) - end - - def consent_link - return nil if (session.nil? && team_location.nil?) || programmes.empty? - - programme_params = programmes.flat_map { it.variant_for(patient:).to_param } - - host + - start_parent_interface_consent_forms_path( - session || team_location, - programme_params.join("-") - ) - end - - def consented_vaccine_methods_message - return if consent.nil? && consent_form.nil? - - consent_form_programmes = - (consent ? [consent] : consent_form.consent_form_programmes) - - programmes = consent_form_programmes.map(&:programme) + delegate :has_multiple_dates?, + :next_or_today_session_date, + :next_or_today_session_dates, + :next_or_today_session_dates_or, + :next_session_date, + :next_session_dates, + :next_session_dates_or, + :subsequent_session_dates_offered_message, + to: :session_dates_presenter + + delegate :consent_deadline, + :consent_link, + :consented_vaccine_methods_message, + :follow_up_discussion, + :reason_for_refusal, + :survey_deadline_date, + :talk_to_your_child_message, + to: :consent_details_presenter + + delegate :is_catch_up?, + :outcome_administered?, + :outcome_not_administered?, + :reason_did_not_vaccinate, + :show_additional_instructions?, + :vaccination, + :vaccination_and_dates, + :vaccination_and_dates_sms, + :vaccination_and_method, + :vaccine, + :vaccine_and_dose, + :vaccine_and_method, + :vaccine_is?, + :vaccine_side_effects, + to: :vaccination_details_presenter + + delegate :invitation_to_clinic_custom_mmr_message, + :invitation_to_clinic_generic_message, + :mmr_or_mmrv_vaccine, + :mmr_programme, + :mmr_second_dose_required?, + :next_mmr_dose_date, + :patient_on_last_dose?, + to: :mmr_details_presenter + + delegate :delay_vaccination_review_context, to: :triage_details_presenter - consented_vaccine_methods = - if programmes.any?(&:has_multiple_vaccine_methods?) - if consent_form_programmes.any?(&:vaccine_method_injection_and_nasal?) - "nasal spray flu vaccine, or the injected flu vaccine if the nasal spray is not suitable" - elsif consent_form_programmes.any?(&:vaccine_method_nasal?) - "nasal spray flu vaccine" - else - "injected flu vaccine" - end - elsif programmes.any?(&:vaccine_may_contain_gelatine?) - if consent_form_programmes.any?(&:without_gelatine) - "vaccine without gelatine" - end - end - - return "" if consented_vaccine_methods.nil? - - "You’ve agreed for #{short_patient_name} to have the #{consented_vaccine_methods}." - end - - def day_month_year_of_vaccination - vaccination_record&.performed_at&.to_date&.to_fs(:uk_short) - end + delegate :privacy_notice_url, :privacy_policy_url, to: :team, prefix: true def full_and_preferred_patient_name (consent_form || patient).full_name_with_known_as(context: :parents) @@ -135,195 +136,10 @@ def location_name end end - def mmr_second_dose_message - return unless patient - return unless mmr_programme - - programme_status = patient.programme_status(mmr_programme, academic_year:) - - return "" if programme_status.vaccinated? - - [ - "## Your child still needs a second dose of the MMR vaccine", - "To be fully protected against measles, mumps and rubella, your " \ - "child needs a second dose of the vaccine. Our team will be in " \ - "touch about this soon." - ].join("\n\n") - end - - def mmr_second_dose_required? - mmr_programme.present? && patient_on_last_dose? - end - - def mmr_second_dose_required - mmr_second_dose_required? - end - - def invitation_to_clinic_generic_message - [ - ( - if mmr_second_dose_required - "If you would like your local GP surgery to give #{short_patient_name} " \ - "their 2nd dose, contact the surgery in the usual way." - end - ), - "#{mmr_second_dose_required ? "Alternatively, they" : "They"} can have this vaccination " \ - "at a community clinic. If you’d like to book a clinic appointment, please contact " \ - "us using the details below.", - (mmr_second_dose_waiting_period_message if mmr_second_dose_required) - ].compact.join("\n\n") - end - - def invitation_to_clinic_custom_mmr_message - return "" unless mmr_second_dose_required - - case team&.organisation&.ods_code - when "RT5" # Leicestershire Partnership Trust (LPT) - [ - mmr_second_dose_waiting_period_message, - "It’s also possible for #{short_patient_name} to be vaccinated at your local GP surgery. " \ - "To book an appointment, contact the surgery in the usual way." - ].join("\n\n") - when "RYG" # Coventry & Warwickshire Partnership NHS Trust (CWPT) - [ - mmr_second_dose_waiting_period_message, - "## You have 2 options for booking the vaccination", - "You can ask your local GP surgery to give #{short_patient_name} their 2nd dose. " \ - "To book an appointment, contact the surgery in the usual way." - ].join("\n\n") - end - end - - def mmr_second_dose_waiting_period_message - "It’s important to wait at least 28 days after the 1st dose of an MMR or " \ - "MMRV vaccination before getting the 2nd dose. #{short_patient_name} " \ - "should not get the 2nd dose until #{next_mmr_dose_date}. Please keep this in " \ - "mind when booking the appointment." - end - - def next_mmr_dose_date - return if patient.nil? - return if mmr_programme.nil? - - patient - .programme_status(mmr_programme, academic_year:) - .next_dose_eligible_date - &.to_fs(:long) - end - - def patient_on_last_dose? - return unless patient - return if mmr_programme.nil? - - patient.reload.programme_status(mmr_programme, academic_year:).on_last_dose? - end - - def mmr_or_mmrv_vaccine - if mmr_programme.present? - if mmr_programme.variant_type == "mmrv" - "MMR or MMRV vaccine" - else - "MMR vaccine" - end - end - end - - def mmr_programme - @mmr_programme ||= programmes.find(&:mmr?) - end - - def delay_vaccination_review_context - return if patient.nil? || session.nil? - - latest_delayed_triage = - patient.latest_delayed_triage(programme_types: session.programme_types) - - return if latest_delayed_triage.nil? - - session_date = session.next_date(include_today: true) - triage_date = latest_delayed_triage.created_at.to_date - - if session_date && triage_date == session_date - "assessed #{short_patient_name} in the vaccination session" - else - "reviewed the answers you gave to the health questions about #{short_patient_name}" - end - end - - def next_or_today_session_date - return "" unless session_dates_are_accurate? - - session&.next_date(include_today: true)&.to_fs(:short_day_of_week) - end - - def next_or_today_session_dates - return "" unless session_dates_are_accurate? - - session - &.today_or_future_dates - &.map { it.to_fs(:short_day_of_week) } - &.to_sentence - end - - def next_or_today_session_dates_or - return "" unless session_dates_are_accurate? - - session - &.today_or_future_dates - &.map { it.to_fs(:short_day_of_week) } - &.to_sentence(last_word_connector: ", or ", two_words_connector: " or ") - end - - def next_session_date - return "" unless session_dates_are_accurate? - - session&.next_date(include_today: false)&.to_fs(:short_day_of_week) - end - - def next_session_dates - return "" unless session_dates_are_accurate? - - session&.future_dates&.map { it.to_fs(:short_day_of_week) }&.to_sentence - end - - def next_session_dates_or - return "" unless session_dates_are_accurate? - - session - &.future_dates - &.map { it.to_fs(:short_day_of_week) } - &.to_sentence(last_word_connector: ", or ", two_words_connector: " or ") - end - - def outcome_not_administered? - vaccination_record.nil? || !outcome_administered? - end - def patient_date_of_birth patient&.date_of_birth&.to_fs(:long) end - def reason_did_not_vaccinate - return if vaccination_record.nil? || vaccination_record.administered? - - I18n.t( - vaccination_record.outcome, - scope: "mailers.vaccination_mailer.reasons_did_not_vaccinate", - short_patient_name: - ) - end - - def follow_up_discussion - consent_form&.follow_up_requested - end - - def reason_for_refusal - reason = consent_form&.reason_for_refusal || consent&.reason_for_refusal - return if reason.nil? - - I18n.t(reason, scope: "mailers.consent_form_mailer.reasons_for_refusal") - end - def short_patient_name (consent_form || patient)&.short_name end @@ -334,20 +150,6 @@ def short_patient_name_apos short_patient_name + apos end - def show_additional_instructions? = - vaccination_record.present? && !vaccination_record.already_had? - - def subsequent_session_dates_offered_message - return nil if session.nil? - - dates = session.future_dates.drop(1) - return "" if dates.empty? - - "If they’re not seen, they’ll be offered the vaccination on #{ - dates.map { it.to_fs(:short_day_of_week) }.to_sentence - }." - end - def subteam_email = (subteam || team).email def subteam_name = (subteam || team).name @@ -356,193 +158,29 @@ def subteam_phone format_phone_with_instructions(subteam || team) end - def survey_deadline_date - recorded_at = consent_form&.recorded_at || consent&.created_at - return if recorded_at.nil? - - (recorded_at + 7.days).to_date.to_fs(:long) - end - - def talk_to_your_child_message - return nil if patient.nil? - return "" if patient_year_group <= 6 - - [ - "## Talk to your child about what they want", - "We suggest you talk to your child about the vaccination before you respond to us. " \ - "Young people have the right to refuse vaccinations.", - "They also have [the right to consent to their own vaccinations]" \ - "(https://www.nhs.uk/conditions/consent-to-treatment/children/) " \ - "if they show they fully understand what’s involved. Our team might give young " \ - "people this opportunity if they assess them as suitably competent." - ].join("\n\n") - end - - delegate :privacy_notice_url, :privacy_policy_url, to: :team, prefix: true - - def today_or_date_of_vaccination - return if vaccination_record.nil? - - if vaccination_record.performed_at.today? - "today" - else - "on #{vaccination_record.performed_at.to_date.to_fs(:long)}" - end - end - - def vaccination - if vaccination_record.present? - # We're sending communication about a specific vaccination that took place. - "#{programme_names.to_sentence} vaccination".pluralize( - programme_names.length - ) - else - # We're sending about a vaccination that will take place. - names = programme_names - - if mmr_second_dose_required - names = names.map { it == "MMR" ? "2nd dose of the MMR" : it } - end - - "#{names.to_sentence} vaccination".pluralize(names.length) - end - end - - def vaccination_and_dates - if next_or_today_session_dates_or.present? - "#{vaccination} on #{next_or_today_session_dates_or}" - else - vaccination - end - end - - # TODO: Remove this method when schools start offering MMRV. - def vaccination_and_dates_sms - if next_or_today_session_dates_or.present? - "#{vaccination} on #{next_or_today_session_dates_or}" - else - vaccination - end - end - - def vaccination_and_method - "#{programme_names_and_methods.to_sentence} vaccination".pluralize( - programme_names_and_methods.length - ) - end - - def vaccine - "#{programme_names.to_sentence} vaccine".pluralize(programme_names.length) - end - - def vaccine_and_dose - if (dose_sequence = vaccination_record&.dose_sequence) - "#{programme_names.to_sentence} #{dose_sequence.ordinalize} dose" - else - programme_names.to_sentence - end - end - - def vaccine_and_method - "#{programme_names_and_methods.to_sentence} vaccine".pluralize( - programme_names_and_methods.length - ) - end - - def vaccine_brand - vaccination_record&.vaccine&.brand - end - - def vaccine_is?(method) - if vaccination_record - vaccination_record.vaccine&.method == method - elsif programmes.present? - if patient - programmes.any? do |programme| - patient.vaccine_criteria(programme:, academic_year:).primary_method == - method - end - else - Vaccine.for_programmes(programmes).exists?(method:) - end - end - end - - def vaccine_side_effects - side_effects = - if vaccination_record - vaccination_record.vaccine&.side_effects - elsif programmes.present? - if patient - programmes.flat_map do |programme| - patient.vaccine_criteria(programme:, academic_year:).side_effects - end - else - Vaccine.for_programmes(programmes).active.flat_map(&:side_effects) - end - end - - return if side_effects.nil? - - descriptions = - side_effects.map { Vaccine.human_enum_name(:side_effect, it) }.sort.uniq - - descriptions.map { "- #{it}" }.join("\n") - end - - def is_catch_up? - return false if patient.nil? || programmes.empty? - - @is_catch_up ||= - programmes.any? { it.is_catch_up?(year_group: patient_year_group) } - end - - def has_multiple_dates? - return false if session.nil? - - session.future_dates.length > 1 - end - - def outcome_administered? - vaccination_record.nil? || vaccination_record.administered? - end - private def session_dates_are_accurate? consent_form ? consent_form.session_dates_are_accurate? : true end - def patient_year_group - @patient_year_group ||= patient.year_group(academic_year:) + def session_dates_presenter + @session_dates_presenter ||= SessionDatesPresenter.new(self) end - def programme_names - @programme_names ||= programmes.map(&:name_in_sentence) + def consent_details_presenter + @consent_details_presenter ||= ConsentDetailsPresenter.new(self) end - def programme_names_and_methods - @programme_names_and_methods ||= - programmes.map do |programme| - if programme.has_multiple_vaccine_methods? - vaccine_method = - if vaccination_record - Vaccine.delivery_method_to_vaccine_method( - vaccination_record.delivery_method - ) - elsif patient - patient.vaccine_criteria( - programme:, - academic_year: - ).primary_method - end + def vaccination_details_presenter + @vaccination_details_presenter ||= VaccinationDetailsPresenter.new(self) + end - method_prefix = - Vaccine.human_enum_name(:method_prefix, vaccine_method) - "#{method_prefix} #{programme.name_in_sentence}".lstrip - else - programme.name_in_sentence - end - end + def mmr_details_presenter + @mmr_details_presenter ||= MmrDetailsPresenter.new(self) + end + + def triage_details_presenter + @triage_details_presenter ||= TriageDetailsPresenter.new(self) end end diff --git a/app/lib/govuk_notify_personalisation/consent_details_presenter.rb b/app/lib/govuk_notify_personalisation/consent_details_presenter.rb new file mode 100644 index 0000000000..b2335ca4f5 --- /dev/null +++ b/app/lib/govuk_notify_personalisation/consent_details_presenter.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +class GovukNotifyPersonalisation + class ConsentDetailsPresenter + include Rails.application.routes.url_helpers + + def initialize(personalisation) + @personalisation = personalisation + end + + attr_reader :personalisation + + delegate :consent, + :consent_form, + :host, + :patient, + :programmes, + :session, + :short_patient_name, + :team_location, + to: :personalisation + + def consent_deadline + session&.consent_deadline_date&.to_fs(:short_day_of_week) + end + + def consent_link + return nil if (session.nil? && team_location.nil?) || programmes.empty? + + programme_params = + programmes.flat_map { it.variant_for(patient:).to_param } + + host + + start_parent_interface_consent_forms_path( + session || team_location, + programme_params.join("-") + ) + end + + def consented_vaccine_methods_message + return if consent.nil? && consent_form.nil? + + consent_form_programmes = + (consent ? [consent] : consent_form.consent_form_programmes) + + consent_programmes = consent_form_programmes.map(&:programme) + + consented_vaccine_methods = + if consent_programmes.any?(&:has_multiple_vaccine_methods?) + if consent_form_programmes.any?(&:vaccine_method_injection_and_nasal?) + "nasal spray flu vaccine, or the injected flu vaccine if the nasal spray is not suitable" + elsif consent_form_programmes.any?(&:vaccine_method_nasal?) + "nasal spray flu vaccine" + else + "injected flu vaccine" + end + elsif consent_programmes.any?(&:vaccine_may_contain_gelatine?) + if consent_form_programmes.any?(&:without_gelatine) + "vaccine without gelatine" + end + end + + return "" if consented_vaccine_methods.nil? + + "You’ve agreed for #{short_patient_name} to have the #{consented_vaccine_methods}." + end + + def follow_up_discussion + consent_form&.follow_up_requested + end + + def reason_for_refusal + reason = consent_form&.reason_for_refusal || consent&.reason_for_refusal + return if reason.nil? + + I18n.t(reason, scope: "mailers.consent_form_mailer.reasons_for_refusal") + end + + def survey_deadline_date + recorded_at = consent_form&.recorded_at || consent&.created_at + return if recorded_at.nil? + + (recorded_at + 7.days).to_date.to_fs(:long) + end + + def talk_to_your_child_message + return nil if patient.nil? + return "" if patient.year_group(academic_year:) <= 6 + + [ + "## Talk to your child about what they want", + "We suggest you talk to your child about the vaccination before you respond to us. " \ + "Young people have the right to refuse vaccinations.", + "They also have [the right to consent to their own vaccinations]" \ + "(https://www.nhs.uk/conditions/consent-to-treatment/children/) " \ + "if they show they fully understand what’s involved. Our team might give young " \ + "people this opportunity if they assess them as suitably competent." + ].join("\n\n") + end + + private + + def academic_year + personalisation.academic_year + end + end +end diff --git a/app/lib/govuk_notify_personalisation/mmr_details_presenter.rb b/app/lib/govuk_notify_personalisation/mmr_details_presenter.rb new file mode 100644 index 0000000000..94abb9d8b9 --- /dev/null +++ b/app/lib/govuk_notify_personalisation/mmr_details_presenter.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +class GovukNotifyPersonalisation + class MmrDetailsPresenter + def initialize(personalisation) + @personalisation = personalisation + end + + attr_reader :personalisation + + delegate :academic_year, + :patient, + :programmes, + :short_patient_name, + :team, + to: :personalisation + + def invitation_to_clinic_custom_mmr_message + return "" unless mmr_second_dose_required? + + case team&.organisation&.ods_code + when "RT5" # Leicestershire Partnership Trust (LPT) + [ + mmr_second_dose_waiting_period_message, + "It’s also possible for #{short_patient_name} to be vaccinated at your local GP surgery. " \ + "To book an appointment, contact the surgery in the usual way." + ].join("\n\n") + when "RYG" # Coventry & Warwickshire Partnership NHS Trust (CWPT) + [ + mmr_second_dose_waiting_period_message, + "## You have 2 options for booking the vaccination", + "You can ask your local GP surgery to give #{short_patient_name} their 2nd dose. " \ + "To book an appointment, contact the surgery in the usual way." + ].join("\n\n") + end + end + + def invitation_to_clinic_generic_message + [ + ( + if mmr_second_dose_required? + "If you would like your local GP surgery to give #{short_patient_name} " \ + "their 2nd dose, contact the surgery in the usual way." + end + ), + "#{mmr_second_dose_required? ? "Alternatively, they" : "They"} can have this vaccination " \ + "at a community clinic. If you’d like to book a clinic appointment, please contact " \ + "us using the details below.", + (mmr_second_dose_waiting_period_message if mmr_second_dose_required?) + ].compact.join("\n\n") + end + + def mmr_or_mmrv_vaccine + return if mmr_programme.blank? + + if mmr_programme.variant_type == "mmrv" + "MMR or MMRV vaccine" + else + "MMR vaccine" + end + end + + def mmr_programme + @mmr_programme ||= programmes.find(&:mmr?) + end + + def mmr_second_dose_required? + mmr_programme.present? && patient_on_last_dose? + end + + def next_mmr_dose_date + return if patient.nil? + return if mmr_programme.nil? + + patient + .programme_status(mmr_programme, academic_year:) + .next_dose_eligible_date + &.to_fs(:long) + end + + def patient_on_last_dose? + return unless patient + return if mmr_programme.nil? + + patient + .reload + .programme_status(mmr_programme, academic_year:) + .on_last_dose? + end + + private + + def mmr_second_dose_waiting_period_message + "It’s important to wait at least 28 days after the 1st dose of an MMR or " \ + "MMRV vaccination before getting the 2nd dose. #{short_patient_name} " \ + "should not get the 2nd dose until #{next_mmr_dose_date}. Please keep this in " \ + "mind when booking the appointment." + end + end +end diff --git a/app/lib/govuk_notify_personalisation/session_dates_presenter.rb b/app/lib/govuk_notify_personalisation/session_dates_presenter.rb new file mode 100644 index 0000000000..122519400d --- /dev/null +++ b/app/lib/govuk_notify_personalisation/session_dates_presenter.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +class GovukNotifyPersonalisation + class SessionDatesPresenter + def initialize(personalisation) + @personalisation = personalisation + end + + attr_reader :personalisation + + delegate :consent_form, :session, to: :personalisation + + def has_multiple_dates? + return false if session.nil? + + session.future_dates.length > 1 + end + + def next_or_today_session_date + return "" unless session_dates_are_accurate? + + session&.next_date(include_today: true)&.to_fs(:short_day_of_week) + end + + def next_or_today_session_dates + return "" unless session_dates_are_accurate? + + session + &.today_or_future_dates + &.map { it.to_fs(:short_day_of_week) } + &.to_sentence + end + + def next_or_today_session_dates_or + return "" unless session_dates_are_accurate? + + session + &.today_or_future_dates + &.map { it.to_fs(:short_day_of_week) } + &.to_sentence(last_word_connector: ", or ", two_words_connector: " or ") + end + + def next_session_date + return "" unless session_dates_are_accurate? + + session&.next_date(include_today: false)&.to_fs(:short_day_of_week) + end + + def next_session_dates + return "" unless session_dates_are_accurate? + + session&.future_dates&.map { it.to_fs(:short_day_of_week) }&.to_sentence + end + + def next_session_dates_or + return "" unless session_dates_are_accurate? + + session + &.future_dates + &.map { it.to_fs(:short_day_of_week) } + &.to_sentence(last_word_connector: ", or ", two_words_connector: " or ") + end + + def subsequent_session_dates_offered_message + return nil if session.nil? + + dates = session.future_dates.drop(1) + return "" if dates.empty? + + "If they’re not seen, they’ll be offered the vaccination on #{ + dates.map { it.to_fs(:short_day_of_week) }.to_sentence + }." + end + + private + + def session_dates_are_accurate? + consent_form ? consent_form.session_dates_are_accurate? : true + end + end +end diff --git a/app/lib/govuk_notify_personalisation/triage_details_presenter.rb b/app/lib/govuk_notify_personalisation/triage_details_presenter.rb new file mode 100644 index 0000000000..10f3d6e73f --- /dev/null +++ b/app/lib/govuk_notify_personalisation/triage_details_presenter.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class GovukNotifyPersonalisation + class TriageDetailsPresenter + def initialize(personalisation) + @personalisation = personalisation + end + + attr_reader :personalisation + + delegate :patient, :session, :short_patient_name, to: :personalisation + + def delay_vaccination_review_context + return if patient.nil? || session.nil? + + latest_delayed_triage = + patient.latest_delayed_triage(programme_types: session.programme_types) + + return if latest_delayed_triage.nil? + + session_date = session.next_date(include_today: true) + triage_date = latest_delayed_triage.created_at.to_date + + if session_date && triage_date == session_date + "assessed #{short_patient_name} in the vaccination session" + else + "reviewed the answers you gave to the health questions about #{short_patient_name}" + end + end + end +end diff --git a/app/lib/govuk_notify_personalisation/vaccination_details_presenter.rb b/app/lib/govuk_notify_personalisation/vaccination_details_presenter.rb new file mode 100644 index 0000000000..0391297365 --- /dev/null +++ b/app/lib/govuk_notify_personalisation/vaccination_details_presenter.rb @@ -0,0 +1,185 @@ +# frozen_string_literal: true + +class GovukNotifyPersonalisation + class VaccinationDetailsPresenter + def initialize(personalisation) + @personalisation = personalisation + end + + attr_reader :personalisation + + delegate :academic_year, + :mmr_second_dose_required?, + :next_or_today_session_dates_or, + :patient, + :programmes, + :programme_name_for_parents, + :vaccination_record, + to: :personalisation + + def is_catch_up? + return false if patient.nil? || programmes.empty? + + @is_catch_up ||= + programmes.any? { it.is_catch_up?(year_group: patient_year_group) } + end + + def outcome_not_administered? + vaccination_record.nil? || !outcome_administered? + end + + def outcome_administered? + vaccination_record.nil? || vaccination_record.administered? + end + + def reason_did_not_vaccinate + return if vaccination_record.nil? || vaccination_record.administered? + + I18n.t( + vaccination_record.outcome, + scope: "mailers.vaccination_mailer.reasons_did_not_vaccinate", + short_patient_name: + ) + end + + def show_additional_instructions? = + vaccination_record.present? && !vaccination_record.already_had? + + def vaccination + if vaccination_record.present? + # We're sending communication about a specific vaccination that took place. + "#{programme_names.to_sentence} vaccination".pluralize( + programme_names.length + ) + else + # We're sending about a vaccination that will take place. + names = programme_names + + if mmr_second_dose_required? + names = names.map { it == "MMR" ? "2nd dose of the MMR" : it } + end + + "#{names.to_sentence} vaccination".pluralize(names.length) + end + end + + def vaccination_and_dates + if next_or_today_session_dates_or.present? + "#{vaccination} on #{next_or_today_session_dates_or}" + else + vaccination + end + end + + # TODO: Remove this method when schools start offering MMRV. + def vaccination_and_dates_sms + if next_or_today_session_dates_or.present? + "#{vaccination} on #{next_or_today_session_dates_or}" + else + vaccination + end + end + + def vaccination_and_method + "#{programme_names_and_methods.to_sentence} vaccination".pluralize( + programme_names_and_methods.length + ) + end + + def vaccine + "#{programme_names.to_sentence} vaccine".pluralize(programme_names.length) + end + + def vaccine_and_dose + if (dose_sequence = vaccination_record&.dose_sequence) + "#{programme_names.to_sentence} #{dose_sequence.ordinalize} dose" + else + programme_names.to_sentence + end + end + + def vaccine_and_method + "#{programme_names_and_methods.to_sentence} vaccine".pluralize( + programme_names_and_methods.length + ) + end + + def vaccine_is?(method) + if vaccination_record + vaccination_record.vaccine&.method == method + elsif programmes.present? + if patient + programmes.any? do |programme| + patient.vaccine_criteria( + programme:, + academic_year: + ).primary_method == method + end + else + Vaccine.for_programmes(programmes).exists?(method:) + end + end + end + + def vaccine_side_effects + side_effects = + if vaccination_record + vaccination_record.vaccine&.side_effects + elsif programmes.present? + if patient + programmes.flat_map do |programme| + patient.vaccine_criteria(programme:, academic_year:).side_effects + end + else + Vaccine.for_programmes(programmes).active.flat_map(&:side_effects) + end + end + + return if side_effects.nil? + + descriptions = + side_effects.map { Vaccine.human_enum_name(:side_effect, it) }.sort.uniq + + descriptions.map { "- #{it}" }.join("\n") + end + + private + + def short_patient_name + personalisation.short_patient_name + end + + def patient_year_group + @patient_year_group ||= patient.year_group(academic_year:) + end + + def programme_names + @programme_names ||= programmes.map { programme_name_for_parents(it) } + end + + def programme_names_and_methods + @programme_names_and_methods ||= + programmes.map do |programme| + if programme.has_multiple_vaccine_methods? + vaccine_method = + if vaccination_record + Vaccine.delivery_method_to_vaccine_method( + vaccination_record.delivery_method + ) + elsif patient + patient.vaccine_criteria( + programme:, + academic_year: + ).primary_method + end + + method_prefix = + Vaccine.human_enum_name(:method_prefix, vaccine_method) + "#{method_prefix} #{programme.name_in_sentence}".lstrip + else + programme.name_in_sentence + end + end + end + end +end diff --git a/app/lib/graph_records.rb b/app/lib/graph_records.rb index 6b64f76482..7701335a81 100644 --- a/app/lib/graph_records.rb +++ b/app/lib/graph_records.rb @@ -61,30 +61,15 @@ class GraphRecords ALLOWED_TYPES = DEFAULT_NODE_ORDER DEFAULT_TRAVERSALS = { - patient: { - patient: %i[ - parents - consents - cohort_imports - class_imports - vaccination_records - triages - school - patient_locations - ], - parent: %i[patients consents cohort_imports class_imports], - consent: %i[consent_form patient parent], - patient_location: %i[patient location], - location: %i[sessions], - session: %i[location programmes], - vaccination_record: %i[session] + batch: { + batch: %i[team vaccine], + vaccine: %i[programme] }, - parent: { - parent: %i[class_imports cohort_imports consents patients], - class_import: %i[session], - consent: %i[parent patient], - patient: %i[parents sessions consents], - session: %i[location] + class_import: { + class_import: %i[team uploaded_by] + }, + cohort_import: { + cohort_import: %i[team uploaded_by] }, consent: { consent: %i[consent_form parent patient programme], @@ -92,158 +77,168 @@ class GraphRecords patient: %i[parents] }, consent_form: { - consent_form: [:consents] + consent_form: %i[consents] }, - vaccination_record: { - vaccination_record: %i[ - patient - programme - session - vaccine - performed_by_user - ], - patient: [:consents], - session: [:location], - consent: [:programme] + gillick_assessment: { + gillick_assessment: %i[performed_by programme], + session: %i[location] }, location: { - location: %i[sessions team] + location: %i[sessions teams] }, - session: { - session: %i[location programmes session_dates] - }, - session_attendance: { - session_attendance: %i[session_date], - session: %i[location], - session_date: %i[session] + organisation: { + organisation: %i[teams] }, - gillick_assessment: { - gillick_assessment: %i[performed_by programme], + parent: { + class_import: %i[session], + consent: %i[parent patient], + parent: %i[class_imports cohort_imports consents patients], + patient: %i[consents parents sessions], session: %i[location] }, - triage: { - triage: %i[patient performed_by programme] + patient: { + consent: %i[consent_form parent patient], + location: %i[sessions], + parent: %i[class_imports cohort_imports consents patients], + patient: %i[ + class_imports + cohort_imports + consents + parents + patient_locations + school + triages + vaccination_records + ], + patient_location: %i[location patient], + session: %i[location programmes], + vaccination_record: %i[session] + }, + patient_location: { + patient_location: %i[location patient] }, programme: { programme: %i[teams vaccines] }, - organisation: { - organisation: %i[teams] + session: { + session: %i[location programmes] + }, + session_attendance: { + session: %i[location], + session_attendance: %i[session_date], + session_date: %i[session] }, team: { team: %i[organisation programmes] }, - vaccine: { - vaccine: %i[batches programme] - }, - batch: { - batch: %i[team vaccine], - vaccine: [:programme] + triage: { + triage: %i[patient performed_by programme] }, user: { - user: %i[teams programmes], - team: [:programmes] - }, - session_date: { - session_date: [:session], - session: %i[location] - }, - cohort_import: { - cohort_import: %i[team uploaded_by] + team: %i[programmes], + user: %i[programmes teams] }, - class_import: { - class_import: %i[team uploaded_by] + vaccination_record: { + consent: %i[programme], + patient: %i[consents], + session: %i[location], + vaccination_record: %i[ + patient + performed_by_user + programme + session + vaccine + ] }, - patient_location: { - patient_location: %i[patient location] + vaccine: { + vaccine: %i[batches programme] } }.freeze DETAIL_WHITELIST = { - consent: %i[ - response - route - created_at - updated_at - withdrawn_at - invalidated_at - ], - session: %i[slug clinic? academic_year], - session_attendance: %i[attending created_at updated_at], - triage: %i[status created_at updated_at invalidated_at], - vaccination_record: %i[ - outcome - performed_at - created_at - updated_at - discarded_at - uuid - ], - programme: %i[type], - vaccine: %i[upload_name], - organisation: %i[ods_code], - team: %i[name workgroup], - subteam: %i[name], - location: %i[name address_postcode type gias_year_groups], - cohort_import: %i[ + batch: %i[archived_at expiry number], + class_import: %i[ + changed_record_count csv_filename + exact_duplicate_record_count + new_record_count processed_at - status rows_count - new_record_count - exact_duplicate_record_count - changed_record_count + status + year_groups ], - class_import: %i[ + cohort_import: %i[ + changed_record_count csv_filename + exact_duplicate_record_count + new_record_count processed_at - status rows_count - new_record_count - exact_duplicate_record_count - changed_record_count - year_groups + status ], - session_date: %i[value], - patient: %i[ - updated_from_pds_at - date_of_death_recorded_at - restricted_at + consent: %i[ + created_at invalidated_at + response + route + updated_at + withdrawn_at ], - parent: %i[], + consent_form: %i[archived_at recorded_at response], gillick_assessment: %i[ - knows_vaccination - knows_disease + created_at knows_consequences knows_delivery + knows_disease knows_side_effects - created_at + knows_vaccination ], - batch: %i[number expiry archived_at], - user: %i[fallback_role uid], - consent_form: %i[response recorded_at archived_at], + location: %i[address_postcode gias_year_groups name type], + organisation: %i[ods_code], + parent: %i[], parent_relationship: %i[type], - patient_location: %i[academic_year] + patient: %i[ + date_of_death_recorded_at + invalidated_at + restricted_at + updated_from_pds_at + ], + patient_location: %i[academic_year], + programme: %i[type], + session: %i[academic_year clinic? dates slug], + session_attendance: %i[attending created_at updated_at], + subteam: %i[name], + team: %i[name workgroup], + triage: %i[created_at invalidated_at status updated_at], + user: %i[fallback_role uid], + vaccination_record: %i[ + created_at + discarded_at + outcome + performed_at + updated_at + uuid + ], + vaccine: %i[upload_name] }.freeze EXTRA_DETAIL_WHITELIST_WITH_PII = { + consent_form: %i[address_postcode date_of_birth family_name given_name], + parent: %i[email full_name phone], + parent_relationship: %i[other_name], patient: %i[ - nhs_number - given_name - family_name - date_of_birth address_line_1 address_line_2 - address_town address_postcode + address_town + date_of_birth date_of_death + family_name + given_name + nhs_number pending_changes ], - parent: %i[full_name email phone], - user: %i[given_name family_name email fallback_role uid], - consent_form: %i[given_name family_name address_postcode date_of_birth], - parent_relationship: %i[other_name] + user: %i[email fallback_role family_name given_name uid] }.freeze DETAIL_WHITELIST_WITH_PII = diff --git a/app/lib/mavis_cli/reports/export_automated_careplus.rb b/app/lib/mavis_cli/reports/export_automated_careplus.rb index 80d592622b..91f6ba5a31 100644 --- a/app/lib/mavis_cli/reports/export_automated_careplus.rb +++ b/app/lib/mavis_cli/reports/export_automated_careplus.rb @@ -96,14 +96,64 @@ def call( academic_year_value = academic_year&.to_i || AcademicYear.current - csv = - ::Reports::AutomatedCareplusExporter.call( + records = + ::Reports::AutomatedCareplusExporter.vaccination_records_scope( team:, academic_year: academic_year_value, start_date: parsed_start_date, end_date: parsed_end_date ) + programme_types = + records.unscope(:order).distinct.pluck(:programme_type) + + csv = + ::Reports::AutomatedCareplusExporter.from_records( + team:, + academic_year: academic_year_value, + vaccination_records: records + ) + + if records.none? + puts "No records found. No CarePlus report was created." + return + end + + now = Time.current + + # we'll create the export with status "sent" for now + # in the future we'll change this to "pending" or + # prevent this tool from creating database entries at all + + ActiveRecord::Base.transaction do + careplus_export = + CareplusExport.create!( + team:, + academic_year: academic_year_value, + date_from: parsed_start_date, + date_to: parsed_end_date, + programme_types:, + scheduled_at: now, + sent_at: now, + status: :sent, + csv_filename: File.basename(output), + csv_data: csv + ) + + now_iso = now.iso8601(6) + CareplusExportVaccinationRecord.insert_all!( + records.map do |record| + { + careplus_export_id: careplus_export.id, + vaccination_record_id: record.id, + change_type: 0, + created_at: now_iso, + updated_at: now_iso + } + end + ) + end + File.write(output, csv) puts "Exported to #{output}" end diff --git a/app/lib/mavis_cli/schools/add_programme_year_group.rb b/app/lib/mavis_cli/schools/add_programme_year_group.rb index fe5a24f8d0..2222526bce 100644 --- a/app/lib/mavis_cli/schools/add_programme_year_group.rb +++ b/app/lib/mavis_cli/schools/add_programme_year_group.rb @@ -28,7 +28,7 @@ def call(urn_or_id:, programme_type:, year_groups:, id:, **) if id Location.find(urn_or_id) else - Location.school.find_by_urn_and_site(urn_or_id) + Location.gias_school.find_by_urn_and_site(urn_or_id) end if location.nil? diff --git a/app/lib/mavis_cli/schools/add_to_team.rb b/app/lib/mavis_cli/schools/add_to_team.rb index 7036e4a954..8100e4148f 100644 --- a/app/lib/mavis_cli/schools/add_to_team.rb +++ b/app/lib/mavis_cli/schools/add_to_team.rb @@ -83,7 +83,7 @@ def urns_are_valid?(urns, team, academic_year, schools) valid = true urns.each do |urn| - location = Location.school.find_by_urn_and_site(urn) + location = Location.gias_school.find_by_urn_and_site(urn) if location.nil? warn "Could not find school with URN #{urn}." @@ -93,7 +93,7 @@ def urns_are_valid?(urns, team, academic_year, schools) schools << location - all_site_locations = Location.school.where(urn: location.urn) + all_site_locations = Location.gias_school.where(urn: location.urn) # Skip if no sites exist next if all_site_locations.count == 1 diff --git a/app/lib/mavis_cli/schools/create.rb b/app/lib/mavis_cli/schools/create.rb index 4d0f867128..3e669911b4 100644 --- a/app/lib/mavis_cli/schools/create.rb +++ b/app/lib/mavis_cli/schools/create.rb @@ -63,7 +63,7 @@ def call( site:, status:, systm_one_code:, - type: "school", + type: "gias_school", url:, urn: ) diff --git a/app/lib/mavis_cli/schools/move_patients.rb b/app/lib/mavis_cli/schools/move_patients.rb index d418356247..51424a9ce9 100644 --- a/app/lib/mavis_cli/schools/move_patients.rb +++ b/app/lib/mavis_cli/schools/move_patients.rb @@ -13,8 +13,8 @@ def call(source_urn:, target_urn:) academic_year = AcademicYear.pending - old_loc = Location.school.find_by_urn_and_site(source_urn) - new_loc = Location.school.find_by_urn_and_site(target_urn) + old_loc = Location.gias_school.find_by_urn_and_site(source_urn) + new_loc = Location.gias_school.find_by_urn_and_site(target_urn) if old_loc.nil? || new_loc.nil? warn "Could not find one or both schools." diff --git a/app/lib/mavis_cli/schools/remove_from_team.rb b/app/lib/mavis_cli/schools/remove_from_team.rb index b0aa401dc4..63d7fdbd75 100644 --- a/app/lib/mavis_cli/schools/remove_from_team.rb +++ b/app/lib/mavis_cli/schools/remove_from_team.rb @@ -40,7 +40,7 @@ def call(team_workgroup:, subteam_name:, urns:, academic_year: nil, **) locations = urns.filter_map do |urn| - location = Location.school.find_by_urn_and_site(urn) + location = Location.gias_school.find_by_urn_and_site(urn) warn "Could not find school with URN #{urn}" if location.nil? location diff --git a/app/lib/mavis_cli/schools/remove_programme_year_group.rb b/app/lib/mavis_cli/schools/remove_programme_year_group.rb index e1d1e2b060..d8d76149e9 100644 --- a/app/lib/mavis_cli/schools/remove_programme_year_group.rb +++ b/app/lib/mavis_cli/schools/remove_programme_year_group.rb @@ -17,7 +17,7 @@ class RemoveProgrammeYearGroup < Dry::CLI::Command def call(urn:, programme_type:, year_groups:, **) MavisCLI.load_rails - location = Location.school.find_by_urn_and_site(urn) + location = Location.gias_school.find_by_urn_and_site(urn) if location.nil? warn "Could not find school." diff --git a/app/lib/notifier/patient.rb b/app/lib/notifier/patient.rb index 1f9a28a307..9eee4c152d 100644 --- a/app/lib/notifier/patient.rb +++ b/app/lib/notifier/patient.rb @@ -245,7 +245,7 @@ def generate_consent_templates( outbreak:, type: ) - is_school = location.school? + is_school = location.gias_school? base_template = :"consent_#{is_school ? "school" : "clinic"}_#{type}" # We can only handle a single programme group or variant in the template. diff --git a/app/lib/notifier/vaccination_record.rb b/app/lib/notifier/vaccination_record.rb index 79db57976f..5350ec9740 100644 --- a/app/lib/notifier/vaccination_record.rb +++ b/app/lib/notifier/vaccination_record.rb @@ -15,17 +15,10 @@ def send_confirmation(sent_by:) :vaccination_not_administered end - email_template_name = - if vaccination_record.administered? - :"#{template_name}_#{vaccination_record.programme_type}" - else - template_name - end - parents.each do |parent| params = { parent:, vaccination_record:, sent_by: } - EmailDeliveryJob.perform_later(email_template_name, **params) + EmailDeliveryJob.perform_later(template_name, **params) if parent.phone_receive_updates SMSDeliveryJob.perform_later(template_name, **params) diff --git a/app/lib/notify_template.rb b/app/lib/notify_template.rb index fa421f2d43..61ef2e6887 100644 --- a/app/lib/notify_template.rb +++ b/app/lib/notify_template.rb @@ -60,7 +60,7 @@ def initialize(name:, channel:, content:) def render(personalisation) ctx = personalisation.instance_eval { binding } - body = ERB.new(@body, trim_mode: nil).result(ctx) + body = ERB.new(@body, trim_mode: "-").result(ctx) return { body: } if @channel == :sms { subject: ERB.new(@subject, trim_mode: nil).result(ctx), body: } diff --git a/app/lib/patient_status_updater.rb b/app/lib/patient_status_updater.rb index aa460848a1..2ea8d83efb 100644 --- a/app/lib/patient_status_updater.rb +++ b/app/lib/patient_status_updater.rb @@ -85,7 +85,7 @@ def update_registration_statuses! .joins(session: :team_location) .where(team_location: { academic_year: academic_years }) .includes(:attendance_records, :patient, :session, :vaccination_records) - .find_in_batches(batch_size: 10_000) do |batch| + .find_in_batches do |batch| batch.each(&:assign_status) Patient::RegistrationStatus.import!( diff --git a/app/lib/reports/automated_careplus_exporter.rb b/app/lib/reports/automated_careplus_exporter.rb index fa873d1019..4f82acc0de 100644 --- a/app/lib/reports/automated_careplus_exporter.rb +++ b/app/lib/reports/automated_careplus_exporter.rb @@ -1,25 +1,61 @@ # frozen_string_literal: true class Reports::AutomatedCareplusExporter + VACCINE_COLUMNS = %i[ + vaccine + dose + reason_not_given + site + manufacturer + batch_number + ].freeze + def self.call(team:, academic_year:, start_date:, end_date:) Reports::CareplusExporter.call( + **shared_args(team:, academic_year:), + start_date:, + end_date:, + include_missing_nhs_number: false + ) + end + + def self.from_records(vaccination_records:, team:, academic_year:) + Reports::CareplusExporter.from_records( + **shared_args(team:, academic_year:), + vaccination_records: + vaccination_records.includes( + :patient, + :vaccine, + session: %i[location team_location] + ) + ) + end + + def self.vaccination_records_scope( + team:, + academic_year:, + start_date:, + end_date: + ) + Reports::CareplusExporter.vaccination_records_scope( team:, programmes: team.programmes, academic_year:, start_date:, end_date:, - include_gender: false, - include_missing_nhs_number: false, - vaccine_columns: %i[ - vaccine - dose - reason_not_given - site - manufacturer - batch_number - ] + include_missing_nhs_number: false ) end - private_class_method :new + def self.shared_args(team:, academic_year:) + { + team:, + programmes: team.programmes, + academic_year:, + include_gender: false, + vaccine_columns: VACCINE_COLUMNS + } + end + + private_class_method :new, :shared_args end diff --git a/app/lib/reports/careplus_exporter.rb b/app/lib/reports/careplus_exporter.rb index 9e6da8d55c..3e7a4f49f7 100644 --- a/app/lib/reports/careplus_exporter.rb +++ b/app/lib/reports/careplus_exporter.rb @@ -30,19 +30,15 @@ def initialize( team:, programmes:, academic_year:, - start_date:, - end_date:, + vaccination_records:, include_gender:, - include_missing_nhs_number:, vaccine_columns: ) @team = team @programmes = programmes @academic_year = academic_year - @start_date = start_date - @end_date = end_date + @vaccination_records = vaccination_records @include_gender = include_gender - @include_missing_nhs_number = include_missing_nhs_number @vaccine_columns = vaccine_columns end @@ -57,7 +53,79 @@ def call end end - def self.call(...) = new(...).call + def self.call( + team:, + programmes:, + academic_year:, + start_date:, + end_date:, + include_gender:, + include_missing_nhs_number:, + vaccine_columns: + ) + vaccination_records = + vaccination_records_scope( + team:, + programmes:, + academic_year:, + start_date:, + end_date:, + include_missing_nhs_number: + ).includes(:patient, :vaccine, session: %i[location team_location]) + + from_records( + vaccination_records:, + team:, + programmes:, + academic_year:, + include_gender:, + vaccine_columns: + ) + end + + def self.from_records( + vaccination_records:, + team:, + programmes:, + academic_year:, + include_gender:, + vaccine_columns: + ) + new( + vaccination_records:, + team:, + programmes:, + academic_year:, + include_gender:, + vaccine_columns: + ).call + end + + def self.vaccination_records_scope( + team:, + programmes:, + academic_year:, + start_date:, + end_date:, + include_missing_nhs_number: + ) + scope = + team + .vaccination_records + .sourced_from_service + .for_programmes(programmes) + .for_academic_year(academic_year) + .administered + .order_by_performed_at + .created_or_updated_between(start_date, end_date) + + scope = + scope.joins(:patient).merge( + Patient.with_nhs_number + ) unless include_missing_nhs_number + + scope + end private_class_method :new @@ -73,10 +141,8 @@ def self.call(...) = new(...).call attr_reader :team, :programmes, :academic_year, - :start_date, - :end_date, + :vaccination_records, :include_gender, - :include_missing_nhs_number, :vaccine_columns def headers @@ -120,52 +186,6 @@ def gender_row_value(patient) include_gender ? [GENDER_CODE_MAPPINGS[patient.gender_code]] : [] end - def vaccination_records - scope = - VaccinationRecord - .kept - .sourced_from_service - .for_programmes(programmes) - .where(team_location: { team_id: team.id }) - .for_academic_year(academic_year) - .administered - .order_by_performed_at - .includes(:patient, :vaccine, session: %i[location team_location]) - - if start_date.present? - scope = - scope.where( - "vaccination_records.created_at >= ?", - start_date.beginning_of_day - ).or( - scope.where( - "vaccination_records.updated_at >= ?", - start_date.beginning_of_day - ) - ) - end - - if end_date.present? - scope = - scope.where( - "vaccination_records.created_at <= ?", - end_date.end_of_day - ).or( - scope.where( - "vaccination_records.updated_at <= ?", - end_date.end_of_day - ) - ) - end - - scope = - scope.joins(:patient).merge( - Patient.with_nhs_number - ) unless include_missing_nhs_number - - scope - end - def consents @consents ||= Consent @@ -199,7 +219,7 @@ def rows(patient:, vaccination_records:) "", date.strftime("%d/%m/%Y"), records.first.performed_at.strftime("%H:%M"), - session.location.school? ? "SC" : "CL", # Venue Type + session.location.gias_school? ? "SC" : "CL", # Venue Type session.location.dfe_number || team.careplus_venue_code, # Venue Code team.careplus_staff_type, team.careplus_staff_code, diff --git a/app/lib/reports/export_formatters.rb b/app/lib/reports/export_formatters.rb index 604f2c7198..e09b70bafe 100644 --- a/app/lib/reports/export_formatters.rb +++ b/app/lib/reports/export_formatters.rb @@ -4,7 +4,7 @@ module Reports::ExportFormatters extend ActiveSupport::Concern def school_urn(location:, patient:) - if location&.school? + if location&.gias_school? location.urn elsif (school = patient.school) school.urn @@ -14,11 +14,11 @@ def school_urn(location:, patient:) end def school_name(location:, patient:) - location&.school? ? location.name : patient.school&.name || "" + location&.gias_school? ? location.name : patient.school&.name || "" end def care_setting(location:) - if location&.school? + if location&.gias_school? ImmunisationImportRow::CARE_SETTING_SCHOOL else ImmunisationImportRow::CARE_SETTING_COMMUNITY @@ -26,7 +26,7 @@ def care_setting(location:) end def clinic_name(location:, vaccination_record:) - location&.school? ? "" : vaccination_record.location_name + location&.gias_school? ? "" : vaccination_record.location_name end def consent_status(patient:, programme:, academic_year:) diff --git a/app/lib/reports/programme_vaccinations_exporter.rb b/app/lib/reports/programme_vaccinations_exporter.rb index 80ae2fda3a..925dc0b860 100644 --- a/app/lib/reports/programme_vaccinations_exporter.rb +++ b/app/lib/reports/programme_vaccinations_exporter.rb @@ -88,48 +88,20 @@ def headers end def vaccination_records - scope = - team - .vaccination_records - .sourced_from_service - .for_programme(programme) - .for_academic_year(academic_year) - .includes( - :location, - :performed_by_user, - :session, - :supplied_by, - :vaccine, - patient: %i[programme_statuses gp_practice school] - ) - - if start_date.present? - scope = - scope.where( - "vaccination_records.created_at >= ?", - start_date.beginning_of_day - ).or( - scope.where( - "vaccination_records.updated_at >= ?", - start_date.beginning_of_day - ) - ) - end - - if end_date.present? - scope = - scope.where( - "vaccination_records.created_at <= ?", - end_date.end_of_day - ).or( - scope.where( - "vaccination_records.updated_at <= ?", - end_date.end_of_day - ) - ) - end - - scope + team + .vaccination_records + .sourced_from_service + .for_programme(programme) + .for_academic_year(academic_year) + .created_or_updated_between(start_date, end_date) + .includes( + :location, + :performed_by_user, + :session, + :supplied_by, + :vaccine, + patient: %i[programme_statuses gp_practice school] + ) end def consents diff --git a/app/lib/reports/school_moves_exporter.rb b/app/lib/reports/school_moves_exporter.rb index 9ab1826f88..c72bf4ecab 100644 --- a/app/lib/reports/school_moves_exporter.rb +++ b/app/lib/reports/school_moves_exporter.rb @@ -54,14 +54,14 @@ def school_move_log_entries .where( SchoolMoveLogEntry .where("patient_id = patients.id") - .where(school: team.schools) + .where(school: team.gias_schools) .arel .exists ) scope = SchoolMoveLogEntry - .where(school: team.schools) + .where(school: team.gias_schools) .or(SchoolMoveLogEntry.where(patient: team.patients, school: nil)) .or( SchoolMoveLogEntry @@ -94,7 +94,7 @@ def row(log_entry) location = log_entry.school systm_one_school_code = - if location&.school? + if location&.gias_school? location.systm_one_code else patient&.school&.systm_one_code diff --git a/app/lib/reports/systm_one_exporter.rb b/app/lib/reports/systm_one_exporter.rb index 896a64d335..1a712d197f 100644 --- a/app/lib/reports/systm_one_exporter.rb +++ b/app/lib/reports/systm_one_exporter.rb @@ -136,49 +136,21 @@ def headers end def vaccination_records - scope = - team - .vaccination_records - .administered - .sourced_from_service - .for_programme(programme) - .for_academic_year(academic_year) - .includes( - :location, - :patient, - :performed_by_user, - :session, - :supplied_by, - :vaccine - ) - - if start_date.present? - scope = - scope.where( - "vaccination_records.created_at >= ?", - start_date.beginning_of_day - ).or( - scope.where( - "vaccination_records.updated_at >= ?", - start_date.beginning_of_day - ) - ) - end - - if end_date.present? - scope = - scope.where( - "vaccination_records.created_at <= ?", - end_date.end_of_day - ).or( - scope.where( - "vaccination_records.updated_at <= ?", - end_date.end_of_day - ) - ) - end - - scope + team + .vaccination_records + .administered + .sourced_from_service + .for_programme(programme) + .for_academic_year(academic_year) + .created_or_updated_between(start_date, end_date) + .includes( + :location, + :patient, + :performed_by_user, + :session, + :supplied_by, + :vaccine + ) end def row(vaccination_record:) @@ -214,7 +186,7 @@ def practice_code(vaccination_record) if location&.systm_one_code.present? location.systm_one_code - elsif location&.school? + elsif location&.gias_school? location.urn elsif location location.ods_code diff --git a/app/lib/stats/organisations.rb b/app/lib/stats/organisations.rb index c5e442aceb..4b1cc09082 100644 --- a/app/lib/stats/organisations.rb +++ b/app/lib/stats/organisations.rb @@ -60,8 +60,8 @@ def calculate_cohort_total(programme) def calculate_school_total(programme) Team .where(id: teams.pluck(:id)) - .includes(:schools) - .flat_map(&:schools) + .includes(:gias_schools) + .flat_map(&:gias_schools) .uniq .count { |location| location.programmes.include?(programme) } end diff --git a/app/lib/timeline_records.rb b/app/lib/timeline_records.rb index 79627a34ed..8ef975068f 100644 --- a/app/lib/timeline_records.rb +++ b/app/lib/timeline_records.rb @@ -5,7 +5,7 @@ class TimelineRecords changesets: %i[import_id import_type], class_imports: %i[], cohort_imports: %i[], - consents: %i[response route], + consents: %i[programme_type response route], school_move_log_entries: %i[school_id user_id], school_moves: %i[school_id source], sessions: %i[location_id], @@ -35,10 +35,17 @@ class TimelineRecords rows_count status ], - consents: %i[invalidated_at response route updated_at withdrawn_at], + consents: %i[ + invalidated_at + programme_type + response + route + updated_at + withdrawn_at + ], gillick_assessments: %i[], - parent_relationships: %i[], - parents: %i[], + notify_log_entries: %i[purpose], + patient_locations: %i[academic_year date_range location_id], pds_search_results: %i[step], school_move_log_entries: %i[school_id user_id], school_moves: %i[school_id source], @@ -136,12 +143,12 @@ class TimelineRecords ALLOWED_AUDITED_CHANGES_WITH_PII = (ALLOWED_AUDITED_CHANGES + ALLOWED_AUDITED_CHANGES_PII).uniq.freeze - def initialize(patient, detail_config: {}, audit_config: {}, show_pii: false) + def initialize(patient, details_config: {}, audit_config: {}, show_pii: false) @patient = patient @patient_id = @patient.id @patient_events = patient_events(@patient) @additional_events = additional_events(@patient) - @detail_config = extract_detail_config(detail_config) + @detail_config = extract_detail_config(details_config) @events = [] @audit_config = audit_config @show_pii = show_pii @@ -188,7 +195,7 @@ def extract_detail_config(detail_config) end def details - @details ||= DEFAULT_DETAILS_CONFIG.merge(@detail_config) + @detail_config end def audits diff --git a/app/lib/training_onboarding_configuration.rb b/app/lib/training_onboarding_configuration.rb index ddb7e03a20..2a22cb18a6 100644 --- a/app/lib/training_onboarding_configuration.rb +++ b/app/lib/training_onboarding_configuration.rb @@ -87,7 +87,7 @@ def users def schools scope = Location - .school + .gias_school .open .without_team(academic_year:) .order("RANDOM()") diff --git a/app/models/cis2_info.rb b/app/models/cis2_info.rb index d5b888abfb..17e60d09a0 100644 --- a/app/models/cis2_info.rb +++ b/app/models/cis2_info.rb @@ -98,8 +98,7 @@ def is_support_without_pii_access? end def is_support_with_pii_access? - role_code == SUPPORT_ROLE && can_access_sensitive_flagged_records? && - can_view_detailed_health_records? + role_code == SUPPORT_ROLE && can_view_detailed_health_records? end private diff --git a/app/models/cohort_import_row.rb b/app/models/cohort_import_row.rb index 95de2138ba..7f50efcb83 100644 --- a/app/models/cohort_import_row.rb +++ b/app/models/cohort_import_row.rb @@ -23,7 +23,7 @@ def school def schools Location - .where(type: %w[school generic_school]) + .school .joins(:team_locations) .where(team_locations: { team:, academic_year: }) end diff --git a/app/models/concerns/has_many_team_locations.rb b/app/models/concerns/has_many_team_locations.rb index 853b4dd6e4..d82dbf4b4c 100644 --- a/app/models/concerns/has_many_team_locations.rb +++ b/app/models/concerns/has_many_team_locations.rb @@ -23,8 +23,8 @@ module HasManyTeamLocations through: :team_locations, source: :location - has_many :schools, - -> { distinct.school }, + has_many :gias_schools, + -> { distinct.gias_school }, through: :team_locations, source: :location end diff --git a/app/models/concerns/refusable.rb b/app/models/concerns/refusable.rb index 992d86e435..a845ec87fb 100644 --- a/app/models/concerns/refusable.rb +++ b/app/models/concerns/refusable.rb @@ -20,7 +20,8 @@ module Refusable will_be_vaccinated_elsewhere: 2, medical_reasons: 3, personal_choice: 4, - other: 5 + other: 5, + do_not_want_vaccination_at_school: 6 }, prefix: true, validate: { diff --git a/app/models/consent_form.rb b/app/models/consent_form.rb index 2b214742bc..c80cc4069b 100644 --- a/app/models/consent_form.rb +++ b/app/models/consent_form.rb @@ -133,7 +133,7 @@ class ConsentForm < ApplicationRecord source: :team_locations has_many :eligible_schools, - -> { school }, + -> { gias_school }, through: :eligible_team_locations, source: :location @@ -329,6 +329,7 @@ class ConsentForm < ApplicationRecord ETHNICITY_STEPS = %i[ethnicity ethnic_group ethnic_background].freeze FOLLOW_UP_REQUIRED_REASONS = %w[ contains_gelatine + do_not_want_vaccination_at_school medical_reasons personal_choice other @@ -784,7 +785,7 @@ def health_answers_valid? true end - def location_is_school? = location.school? + def location_is_school? = location.gias_school? def location_is_clinic? = location.generic_clinic? || location.generic_school? diff --git a/app/models/draft_session.rb b/app/models/draft_session.rb index a9c74748af..611d6fd2ff 100644 --- a/app/models/draft_session.rb +++ b/app/models/draft_session.rb @@ -128,7 +128,7 @@ def team_location delegate :id, to: :team_location, prefix: true - def school? = location&.school? + def school? = location&.gias_school? def generic_clinic? = location&.generic_clinic? @@ -329,7 +329,7 @@ def valid_school_ids LocationPolicy::Scope .new(@current_user, Location) .resolve - .school + .gias_school .joins(:team_locations) .where(team_locations: { team:, academic_year: }) .pluck(:"locations.id") diff --git a/app/models/draft_vaccination_record.rb b/app/models/draft_vaccination_record.rb index 4ebefa1571..6cfd257d57 100644 --- a/app/models/draft_vaccination_record.rb +++ b/app/models/draft_vaccination_record.rb @@ -307,7 +307,7 @@ def batch_expiry def location_is_school return if location_id.blank? - unless location&.school? + unless location&.gias_school? errors.add(:location_id, "The location must be a school") end end diff --git a/app/models/immunisation_import.rb b/app/models/immunisation_import.rb index facf07934a..d4fac6250e 100644 --- a/app/models/immunisation_import.rb +++ b/app/models/immunisation_import.rb @@ -211,7 +211,7 @@ def postprocess_rows! end PatientTeamUpdater.call(patient_scope: patients) - PatientStatusUpdater.call(patient_scope: patients) + PatientStatusUpdater.call(patient_scope: Patient.where(id: patients.ids)) vaccination_records .includes(:patient, :team, :subteam) diff --git a/app/models/immunisation_import_row.rb b/app/models/immunisation_import_row.rb index d0b69051b0..315b55b6df 100644 --- a/app/models/immunisation_import_row.rb +++ b/app/models/immunisation_import_row.rb @@ -352,7 +352,7 @@ def location_name return unless location.nil? if is_school_setting? || (is_unknown_setting? && clinic_name.blank?) - school&.school? ? school.name : school_name&.to_s || "Unknown" + school&.gias_school? ? school.name : school_name&.to_s || "Unknown" else clinic&.name || clinic_name&.to_s || "Unknown" end @@ -383,8 +383,8 @@ def school elsif school_urn.to_s == Location::URN_UNKNOWN team.unknown_school elsif school_urn.present? - Location.school.find_by_urn_and_site(school_urn.to_s) || - Location.school.find_by(systm_one_code: school_urn.to_s) + Location.gias_school.find_by_urn_and_site(school_urn.to_s) || + Location.gias_school.find_by(systm_one_code: school_urn.to_s) end end @@ -672,7 +672,7 @@ def validate_batch_expiry "must be less than 15 years in the future" ) elsif date < EARLIEST_BATCH_EXPIRY - errors.add(batch_expiry.header, "must be more than 15 years old") + errors.add(batch_expiry.header, "must be less than 15 years old") end else errors.add(batch_expiry.header, "Enter a date in the correct format.") @@ -1173,8 +1173,8 @@ def validate_school_urn school_urn_acceptable = school_urn.to_s.in?( [Location::URN_HOME_EDUCATED, Location::URN_UNKNOWN] - ) || Location.school.where_urn_and_site(school_urn.to_s).exists? || - Location.school.exists?(systm_one_code: school_urn.to_s) + ) || Location.gias_school.where_urn_and_site(school_urn.to_s).exists? || + Location.gias_school.exists?(systm_one_code: school_urn.to_s) unless school_urn_acceptable errors.add( diff --git a/app/models/local_authority.rb b/app/models/local_authority.rb index ba2c250b45..337df18071 100644 --- a/app/models/local_authority.rb +++ b/app/models/local_authority.rb @@ -25,6 +25,7 @@ # index_local_authorities_on_nation_and_short_name (nation,short_name) # index_local_authorities_on_short_name (short_name) # + class LocalAuthority < ApplicationRecord self.primary_key = :mhclg_code @@ -32,10 +33,7 @@ class LocalAuthority < ApplicationRecord validates :gias_code, uniqueness: true, allow_nil: true validates :gss_code, uniqueness: true, allow_nil: true - has_many :postcodes, - foreign_key: :gss_code, - primary_key: :gss_code, - class_name: "LocalAuthority::Postcode" + has_many :postcodes, foreign_key: :gss_code, primary_key: :gss_code enum :nation, { @@ -80,8 +78,6 @@ def self.from_my_society_import_row(data) end def self.for_postcode(postcode) - joins(:postcodes).merge( - LocalAuthority::Postcode.where(value: postcode) - ).first + joins(:postcodes).merge(Postcode.where(value: postcode)).first end end diff --git a/app/models/local_authority/postcode.rb b/app/models/local_authority/postcode.rb index fabfaaff8b..81087af14b 100644 --- a/app/models/local_authority/postcode.rb +++ b/app/models/local_authority/postcode.rb @@ -14,11 +14,9 @@ # index_local_authority_postcodes_on_gss_code (gss_code) # index_local_authority_postcodes_on_value (value) UNIQUE # + class LocalAuthority::Postcode < ApplicationRecord - belongs_to :local_authority, - foreign_key: :gss_code, - primary_key: :gss_code, - optional: true + belongs_to :local_authority, foreign_key: :gss_code, primary_key: :gss_code normalizes :value, with: ->(given_value) do diff --git a/app/models/location.rb b/app/models/location.rb index a378a32dcb..ba629a099b 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -31,6 +31,7 @@ # index_locations_on_urn (urn) UNIQUE WHERE ((type = 0) AND (site IS NULL)) # index_locations_on_urn_and_site (urn,site) UNIQUE # + class Location < ApplicationRecord self.inheritance_column = nil @@ -88,7 +89,7 @@ class Location < ApplicationRecord enum :type, { - school: 0, + gias_school: 0, generic_clinic: 1, community_clinic: 2, gp_practice: 3, @@ -97,6 +98,8 @@ class Location < ApplicationRecord scope :clinic, -> { generic_clinic.or(community_clinic) } + scope :school, -> { gias_school.or(generic_school) } + scope :where_urn_and_site, ->(urn_and_site) do where( @@ -181,6 +184,15 @@ class Location < ApplicationRecord validates :urn, inclusion: [URN_HOME_EDUCATED, URN_UNKNOWN] end + with_options if: :gias_school? do + validates :gias_establishment_number, presence: true + validates :gias_local_authority_code, presence: true + validates :gias_phase, inclusion: Location.gias_phases.keys + validates :ods_code, absence: true + validates :site, uniqueness: { scope: :urn }, allow_nil: true + validates :urn, presence: true, uniqueness: { unless: :site } + end + with_options if: :gp_practice? do validates :gias_establishment_number, absence: true validates :gias_local_authority_code, absence: true @@ -190,15 +202,6 @@ class Location < ApplicationRecord validates :urn, absence: true end - with_options if: :school? do - validates :gias_establishment_number, presence: true - validates :gias_local_authority_code, presence: true - validates :gias_phase, inclusion: Location.gias_phases.keys - validates :ods_code, absence: true - validates :site, uniqueness: { scope: :urn }, allow_nil: true - validates :urn, presence: true, uniqueness: { unless: :site } - end - delegate :fhir_reference, to: :fhir_mapper def self.find_by_urn_and_site(urn_and_site) @@ -229,8 +232,10 @@ def programmes def clinic? = generic_clinic? || community_clinic? + def school? = gias_school? || generic_school? + def dfe_number - "#{gias_local_authority_code}#{gias_establishment_number}" if school? + "#{gias_local_authority_code}#{gias_establishment_number}" if gias_school? end def phase diff --git a/app/models/notify_log_entry.rb b/app/models/notify_log_entry.rb index 12a55bc4d2..eb1bd91fd2 100644 --- a/app/models/notify_log_entry.rb +++ b/app/models/notify_log_entry.rb @@ -72,7 +72,12 @@ class NotifyLogEntry < ApplicationRecord "fa3c8dd5-4688-4b93-960a-1d422c4e5597" => :triage_vaccination_will_happen, "6e4c514d-fcc9-4bc8-b7eb-e222a1445681" => :session_school_reminder, "604ee667-c996-471e-b986-79ab98d0767c" => :consent_confirmation_triage, - "f2921e23-4b73-4e44-abbb-38b0e235db8e" => :consent_confirmation_clinic + "f2921e23-4b73-4e44-abbb-38b0e235db8e" => :consent_confirmation_clinic, + "8a65d7b5-045c-4f26-8f76-6e593c14cb6d" => :vaccination_administered_hpv, + "38727494-9a81-42b3-9c1f-5c31e55333e7" => :vaccination_administered_menacwy, + "3abe7ca8-a889-484b-ab9f-07523302eb6a" => :vaccination_administered_td_ipv, + "7238ee27-5840-40e5-b9b9-3130ba4cd4fa" => :vaccination_administered_flu, + "0b1095db-fb38-4105-9f01-a364fa8bbb1c" => :vaccination_administered_mmr }.freeze self.inheritance_column = nil diff --git a/app/models/onboarding.rb b/app/models/onboarding.rb index af9b4649ba..b9f6c2346f 100644 --- a/app/models/onboarding.rb +++ b/app/models/onboarding.rb @@ -180,7 +180,8 @@ def no_schools_with_existing_team_attachments urn = school.urn next if urn.blank? - locations_with_teams = Location.school.where(urn:).joins(:teams).distinct + locations_with_teams = + Location.gias_school.where(urn:).joins(:teams).distinct next unless locations_with_teams.exists? @@ -304,7 +305,7 @@ class ExistingSchool validates :status, inclusion: %w[open opening] def location - @location ||= Location.school.find_by_urn_and_site(urn) + @location ||= Location.gias_school.find_by_urn_and_site(urn) end delegate :status, to: :location, allow_nil: true @@ -344,7 +345,7 @@ class NewSchoolSite validates :site, presence: true def original_location - @original_location ||= Location.school.find_by_urn_and_site(urn) + @original_location ||= Location.gias_school.find_by_urn_and_site(urn) end def location diff --git a/app/models/patient.rb b/app/models/patient.rb index 4564a5defb..396918584e 100644 --- a/app/models/patient.rb +++ b/app/models/patient.rb @@ -811,7 +811,7 @@ def locations_are_correct_type errors.add(:gp_practice, "must be a GP practice location type") end - if school && !(school.school? || school.generic_school?) + if school && !school.school? errors.add(:school, "must be a school location type") end end diff --git a/app/models/patient/programme_status.rb b/app/models/patient/programme_status.rb index f0bbb68d33..9009503317 100644 --- a/app/models/patient/programme_status.rb +++ b/app/models/patient/programme_status.rb @@ -172,6 +172,11 @@ def assign def vaccine_criteria = VaccineCriteria.from_programme_status(self) + def needs_more_doses? + dose_sequence.present? && + dose_sequence <= Programme.find(programme_type).maximum_dose_sequence + end + def on_last_dose? dose_sequence == Programme.find(programme_type).maximum_dose_sequence end diff --git a/app/models/programme.rb b/app/models/programme.rb index 12e1029e87..6418700e5b 100644 --- a/app/models/programme.rb +++ b/app/models/programme.rb @@ -26,6 +26,14 @@ class InvalidType < StandardError "td_ipv" => (9..11).to_a }.freeze + DEFAULT_YEAR_GROUPS_WITH_OVER_16S_BY_TYPE = { + "flu" => (0..11).to_a, + "hpv" => (8..13).to_a, + "menacwy" => (9..13).to_a, + "mmr" => (0..13).to_a, + "td_ipv" => (9..13).to_a + }.freeze + MAXIMUM_DOSE_SEQUENCES = { "flu" => 2, "hpv" => 3, @@ -155,6 +163,11 @@ def name @name ||= I18n.t(translation_key, scope: :programme_types) end + def name_on_nhs_uk + @name_on_nhs_uk ||= + I18n.t(type, scope: :programme_names_on_nhs_uk, default: nil) + end + def name_in_sentence @name_in_sentence = flu? ? name.downcase : name end @@ -204,7 +217,13 @@ def can_save_to_todays_batch? = !mmr? def triage_on_vaccination_history? = menacwy? || td_ipv? - def default_year_groups = DEFAULT_YEAR_GROUPS_BY_TYPE.fetch(type) + def default_year_groups + if Flipper.enabled?(:vaccinating_16_plus_year_olds) + DEFAULT_YEAR_GROUPS_WITH_OVER_16S_BY_TYPE.fetch(type) + else + DEFAULT_YEAR_GROUPS_BY_TYPE.fetch(type) + end + end def is_catch_up?(year_group:) return nil if seasonal? @@ -242,6 +261,10 @@ def maximum_dose_sequence = MAXIMUM_DOSE_SEQUENCES.fetch(type) def minimum_dose_interval = MINIMUM_DOSE_INTERVALS.fetch(type) + # TODO: Make this more explicit when other programmes start + # administering multiple doses (e.g. immunocompromised schedules). + def multi_dose? = minimum_dose_interval.present? + def import_names = IMPORT_NAMES.fetch(type) def snomed_target_disease_codes = SNOMED_TARGET_DISEASE_CODES.fetch(type) diff --git a/app/models/school_move.rb b/app/models/school_move.rb index f6782d44d1..d280dd221e 100644 --- a/app/models/school_move.rb +++ b/app/models/school_move.rb @@ -17,17 +17,13 @@ # index_school_moves_on_patient_id (patient_id) UNIQUE # index_school_moves_on_patient_id_and_school_id (patient_id,school_id) # index_school_moves_on_school_id (school_id) -# index_school_moves_on_team_id (team_id) # # Foreign Keys # # fk_rails_... (patient_id => patients.id) # fk_rails_... (school_id => locations.id) -# fk_rails_... (team_id => teams.id) # class SchoolMove < ApplicationRecord - self.ignored_columns = %w[home_educated team_id] - audited associated_with: :patient belongs_to :patient diff --git a/app/models/team.rb b/app/models/team.rb index f9c3bd6af2..40ba86e39e 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -44,6 +44,9 @@ class Team < ApplicationRecord NATIONAL_REPORTING_YEAR_GROUPS = (-2..13).to_a.freeze + # The number of days after the national reporting cut-off date when users will lose access to NIVS + NIVS_SWITCH_OFF_DELAY_DAYS = 2 + audited associated_with: :organisation has_associated_audits diff --git a/app/models/vaccination_record.rb b/app/models/vaccination_record.rb index f1aa5f55ff..ca0f90f1ea 100644 --- a/app/models/vaccination_record.rb +++ b/app/models/vaccination_record.rb @@ -4,50 +4,57 @@ # # Table name: vaccination_records # -# id :bigint not null, primary key -# batch_expiry :date -# batch_number :string -# confirmation_sent_at :datetime -# delivery_method :integer -# delivery_site :integer -# discarded_at :datetime -# disease_types :enum not null, is an Array -# dose_sequence :integer -# full_dose :boolean -# local_patient_id_uri :string -# location_name :string -# nhs_immunisations_api_etag :string -# nhs_immunisations_api_identifier_system :string -# nhs_immunisations_api_identifier_value :string -# nhs_immunisations_api_primary_source :boolean -# nhs_immunisations_api_sync_pending_at :datetime -# nhs_immunisations_api_synced_at :datetime -# notes :text -# notify_parents :boolean -# outcome :integer not null -# pending_changes :jsonb not null -# performed_at_date :date not null -# performed_at_time :time -# performed_by_family_name :string -# performed_by_given_name :string -# performed_ods_code :string -# programme_type :enum not null -# protocol :integer -# source :integer not null -# uuid :uuid not null -# created_at :datetime not null -# updated_at :datetime not null -# duplicate_of_vaccination_record_id :bigint -# local_patient_id :string -# location_id :bigint -# next_dose_delay_triage_id :bigint -# nhs_immunisations_api_id :string -# patient_id :bigint not null -# performed_by_user_id :bigint -# reported_by_id :bigint -# session_id :bigint -# supplied_by_user_id :bigint -# vaccine_id :bigint +# id :bigint not null, primary key +# batch_expiry :date +# batch_number :string +# confirmation_sent_at :datetime +# delivery_method :integer +# delivery_site :integer +# discarded_at :datetime +# disease_types :enum not null, is an Array +# dose_sequence :integer +# full_dose :boolean +# local_patient_id_uri :string +# location_name :string +# nhs_immunisations_api_etag :string +# nhs_immunisations_api_identifier_system :string +# nhs_immunisations_api_identifier_value :string +# nhs_immunisations_api_primary_source :boolean +# nhs_immunisations_api_recorded_at :datetime +# nhs_immunisations_api_snomed_procedure_code :string +# nhs_immunisations_api_snomed_procedure_term :string +# nhs_immunisations_api_snomed_product_code :string +# nhs_immunisations_api_snomed_product_term :string +# nhs_immunisations_api_snomed_reason_code :string +# nhs_immunisations_api_snomed_reason_term :string +# nhs_immunisations_api_sync_pending_at :datetime +# nhs_immunisations_api_synced_at :datetime +# notes :text +# notify_parents :boolean +# outcome :integer not null +# pending_changes :jsonb not null +# performed_at_date :date not null +# performed_at_time :time +# performed_by_family_name :string +# performed_by_given_name :string +# performed_ods_code :string +# programme_type :enum not null +# protocol :integer +# source :integer not null +# uuid :uuid not null +# created_at :datetime not null +# updated_at :datetime not null +# duplicate_of_vaccination_record_id :bigint +# local_patient_id :string +# location_id :bigint +# next_dose_delay_triage_id :bigint +# nhs_immunisations_api_id :string +# patient_id :bigint not null +# performed_by_user_id :bigint +# reported_by_id :bigint +# session_id :bigint +# supplied_by_user_id :bigint +# vaccine_id :bigint # # Indexes # @@ -159,6 +166,36 @@ class VaccinationRecord < ApplicationRecord order("performed_at_date DESC, performed_at_time DESC NULLS LAST") end + scope :created_or_updated_between, + ->(start_date, end_date) do + scope = all + if start_date.present? + scope = + scope.where( + "vaccination_records.created_at >= ?", + start_date.beginning_of_day + ).or( + scope.where( + "vaccination_records.updated_at >= ?", + start_date.beginning_of_day + ) + ) + end + if end_date.present? + scope = + scope.where( + "vaccination_records.created_at <= ?", + end_date.end_of_day + ).or( + scope.where( + "vaccination_records.updated_at <= ?", + end_date.end_of_day + ) + ) + end + scope + end + enum :protocol, { pgd: 0, psd: 1, national: 2 }, validate: { allow_nil: true } enum :delivery_method, diff --git a/app/policies/school_move_policy.rb b/app/policies/school_move_policy.rb index 6b964a4774..fdd30cfc80 100644 --- a/app/policies/school_move_policy.rb +++ b/app/policies/school_move_policy.rb @@ -24,7 +24,7 @@ def resolve scope .where(patient_in_team) .where(school: Location.generic_school) - .or(scope.where(school: team.schools)) + .or(scope.where(school: team.gias_schools)) .or(scope.where(school: team.generic_schools)) end end diff --git a/app/views/draft_consents/reason_for_refusal.html.erb b/app/views/draft_consents/reason_for_refusal.html.erb index f37a1cfc25..8b9fc9c9b2 100644 --- a/app/views/draft_consents/reason_for_refusal.html.erb +++ b/app/views/draft_consents/reason_for_refusal.html.erb @@ -17,6 +17,7 @@ <%= f.govuk_radio_button :reason_for_refusal, refusal_reason.value, label: { text: refusal_reason.label }, + hint: { text: refusal_reason.hint }, link_errors: index.zero? %> <% end %> <% end %> diff --git a/app/views/immunisation_imports/new.html.erb b/app/views/immunisation_imports/new.html.erb index 3ad17b8797..d665128338 100644 --- a/app/views/immunisation_imports/new.html.erb +++ b/app/views/immunisation_imports/new.html.erb @@ -6,13 +6,21 @@ <% content_for :page_title, title %> -<% if (cutoff_date = current_team.national_reporting_cut_off_date) && cutoff_date.today? %> - <%= govuk_notification_banner(title_text: "#{cutoff_date.to_fs(:long)} - Check where to upload records") do |notification_banner| %> - <% notification_banner.with_heading(text: "Vaccinations given:") %> - +<% if (cutoff_date = current_team.national_reporting_cut_off_date) && + Date.today <= cutoff_date + Team::NIVS_SWITCH_OFF_DELAY_DAYS.days %> + <%= govuk_notification_banner(title_text: "Important") do %> +

+ Mavis national reporting replaces NIVS on <%= cutoff_date.to_fs(:long) %> +

+ +

Vaccinations given before <%= cutoff_date.to_fs(:long) %>

+

+ Upload to NIVS by <%= (cutoff_date + Team::NIVS_SWITCH_OFF_DELAY_DAYS.days).to_fs(:long) %>. + After this, NIVS will no longer be available. +

+ +

Vaccinations given on or after <%= cutoff_date.to_fs(:long) %>

+

Upload to Mavis national reporting only.

<% end %> <% end %> diff --git a/app/views/imports/show.html.erb b/app/views/imports/show.html.erb index 82bff9e809..b1a52a34c5 100644 --- a/app/views/imports/show.html.erb +++ b/app/views/imports/show.html.erb @@ -25,7 +25,7 @@ <% end %>

- Correct the issues listed, save the changes and upload the file again. + Refer to What your CSV file must include to correct the issues listed below, save the changes and upload the file again. <% if import.changesets_are_invalid? %> This may include errors identified after Mavis added a missing NHS number or replaced an incorrect NHS number. diff --git a/app/views/inspect/graphs/show.html.erb b/app/views/inspect/graphs/show.html.erb index de8c86e443..cfdf925ad2 100644 --- a/app/views/inspect/graphs/show.html.erb +++ b/app/views/inspect/graphs/show.html.erb @@ -10,6 +10,13 @@ <%= link_to "View timeline", inspect_timeline_patient_path(@primary_id), class: "nhsuk-button nhsuk-button--secondary" %> <% end %> +<% if @pii_access_requested_by_user && @sensitive_patient_in_graph %> + <%= render AppWarningCalloutComponent.new( + heading: "Patient is restricted", + description: "One of the patients in the graph has been marked as restricted. You cannot view PII for this graph.", + ) %> +<% end %> +

   <%= @mermaid.html_safe %>
 
diff --git a/app/views/inspect/timeline/patients/show.html.erb b/app/views/inspect/timeline/patients/show.html.erb index bf147cfb88..5599dd50ef 100644 --- a/app/views/inspect/timeline/patients/show.html.erb +++ b/app/views/inspect/timeline/patients/show.html.erb @@ -1,20 +1,19 @@ <%= h1 page_title: @patient.initials do %> <%= "Inspect Patient #{@patient.id}" %> <% end %> - -

- <%= "Patient.find(#{@patient.id})" %> -

- -<%= link_to "View graph", inspect_path(:patient, @patient.id), class: "nhsuk-button nhsuk-button--secondary" %> -
+

+ <%= "Patient.find(#{@patient.id})" %> +

+ + <%= link_to "View graph", inspect_path(:patient, @patient.id), class: "nhsuk-button nhsuk-button--secondary" %> + <%= render AppTimelineFilterComponent.new( url: inspect_timeline_patient_path, patient: @patient, teams: @patient.teams, - event_options: TimelineRecords::DEFAULT_DETAILS_CONFIG, + event_options: TimelineRecords::AVAILABLE_DETAILS_CONFIG, timeline_fields: @show_pii ? TimelineRecords::AVAILABLE_DETAILS_CONFIG_WITH_PII : TimelineRecords::AVAILABLE_DETAILS_CONFIG, class_imports: @patient_events[:class_imports], cohort_imports: @patient_events[:cohort_imports], @@ -43,6 +42,14 @@ ) %>
<% else %> + <% if @patient_accessed_is_sensitive & @pii_access_requested_by_user %> +
+ <%= render AppWarningCalloutComponent.new( + heading: "Patient is restricted", + description: "One of the patients accessed has been marked as restricted. You cannot view PII for this patient.", + ) %> +
+ <% end %> <% if @compare_patient_timeline || @invalid_patient_id || @no_events_compare_message %>

Patient <%= @patient.id %>

diff --git a/app/views/notify_templates/email/triage_vaccination_will_happen_mmr_second_dose.text.erb b/app/views/notify_templates/email/triage_vaccination_will_happen_mmr_second_dose.text.erb index 41daeb60a2..679894fc13 100644 --- a/app/views/notify_templates/email/triage_vaccination_will_happen_mmr_second_dose.text.erb +++ b/app/views/notify_templates/email/triage_vaccination_will_happen_mmr_second_dose.text.erb @@ -1,10 +1,13 @@ --- template_id: "6fd910fd-120c-4e58-9ef3-15ffc5bd6edc" template_name: triage_vaccination_will_happen_mmr_second_dose -subject: "<%= short_patient_name %> needs another dose of the MMR vaccination" +subject: "<%= patient.short_name %> needs another dose of the <%= programme_name_for_parents(mmr_programme.variant_for(patient:)) %> vaccination" --- -We recently gave <%= short_patient_name %> their 1st dose of the MMR vaccination. To be fully vaccinated, they need 2 doses. +<% variant = mmr_programme.variant_for(patient:) -%> +<% previous_dose = patient_previous_dose_label(patient, mmr_programme, academic_year) -%> +<% next_dose = patient_next_dose_label(patient, mmr_programme, academic_year) -%> +We recently gave <%= patient.short_name %> their <%= previous_dose %> dose of the <%= programme_name_for_parents(variant) %> vaccination. To be fully vaccinated, they need <%= mmr_programme.maximum_dose_sequence %> doses. -We’re coming to <%= location_name %> on <%= next_session_dates %> and plan to give <%= short_patient_name %> their 2nd dose then. Please let them know about this. +We’re coming to <%= session.location.name %> on <%= session_future_dates(session) %> and plan to give <%= patient.short_name %> their <%= next_dose %> dose then. Please let them know about this. -If <%= short_patient_name_apos %> health changes, or you arrange for them to get their 2nd dose somewhere else, contact us. You can email <%= subteam_email %> or phone <%= subteam_phone %>. +If <%= patient_short_name_possessive(patient) %> health changes, or you arrange for them to get their <%= next_dose %> dose somewhere else, contact us. You can email <%= team_contact_email(session:) %> or phone <%= team_contact_phone(session:) %>. diff --git a/app/views/notify_templates/email/vaccination_administered.text.erb b/app/views/notify_templates/email/vaccination_administered.text.erb new file mode 100644 index 0000000000..c49abe9dcc --- /dev/null +++ b/app/views/notify_templates/email/vaccination_administered.text.erb @@ -0,0 +1,37 @@ +--- +template_id: "bd51556d-bccc-469c-a822-0b88f26efd10" +template_name: vaccination_administered +subject: "Your child had their <%= programme_name_for_parents(vaccination_record.programme) %> vaccination <%= vaccination_record_today_or_date(vaccination_record) %>" +--- +<%= patient.full_name_with_known_as(context: :parents) %> had their <%= programme_name_for_parents(vaccination_record.programme) %> vaccination at <%= vaccination_record_location(vaccination_record) %> <%= vaccination_record_today_or_date(vaccination_record) %>. + +We suggest you record the following details somewhere. + +Vaccination: <%= programme_name_for_parents(vaccination_record.programme) %> +Vaccine: <%= vaccination_record.vaccine.brand %> +<% if vaccination_record.programme.has_multiple_vaccine_methods? -%> +Method: <%= vaccine_method(vaccination_record.vaccine) %> +<% end -%> +Date of vaccination: <%= vaccination_record_date(vaccination_record) %> +Batch number: <%= vaccination_record.batch_number %> + +## Possible side effects + +Your child might have some of the following side effects: + +<%= vaccine_side_effects_list(vaccination_record.vaccine) %> + +<% if vaccination_record.programme.mmr? %>These side effects are usually mild and only last 2 to 3 days. <% end -%> +If you’re concerned about your child’s reaction to the vaccine, contact your GP in the usual way. +<% if vaccination_record.programme.multi_dose? && patient_needs_more_doses?(patient, vaccination_record.programme, academic_year) -%> + +## Your child still needs a <%= patient_next_dose_label(patient, vaccination_record.programme, academic_year) %> dose of the <%= programme_name_for_parents(vaccination_record.programme) %> vaccine + +To be fully protected against <%= programme_disease_names(vaccination_record.programme) %>, your child needs a <%= patient_next_dose_label(patient, vaccination_record.programme, academic_year) %> dose of the vaccine. Our team will be in touch about this soon. +<% end -%> + +You can [give feedback](https://feedback.digital.nhs.uk/jfe/form/SV_3fICo6frMvUZX1k) about the ’Give or refuse consent’ service by completing our short survey. Your feedback will help us improve the service. + +<%= team_contact_name(vaccination_record:) %> +<%= team_contact_email(vaccination_record:) %> +<%= team_contact_phone(vaccination_record:) %> diff --git a/app/views/notify_templates/email/vaccination_administered_flu.text.erb b/app/views/notify_templates/email/vaccination_administered_flu.text.erb deleted file mode 100644 index 5080b6890c..0000000000 --- a/app/views/notify_templates/email/vaccination_administered_flu.text.erb +++ /dev/null @@ -1,28 +0,0 @@ ---- -template_id: "7238ee27-5840-40e5-b9b9-3130ba4cd4fa" -template_name: vaccination_administered_flu -subject: "Your child had their <%= vaccination %> <%= today_or_date_of_vaccination %>" ---- -<%= full_and_preferred_patient_name %> had their flu vaccination at <%= location_name %> <%= today_or_date_of_vaccination %>. - -We suggest you record the following details somewhere. - -Vaccination: <%= vaccination %> -Vaccine: <%= vaccine_brand %> -Method: <%= if vaccine_is?("injection") then "injection" elsif vaccine_is?("nasal") then "nasal spray" end %> -Date of vaccination: <%= day_month_year_of_vaccination %> -Batch number: <%= batch_name %> - -## Possible side effects - -Your child might have some of the following side effects: - -<%= vaccine_side_effects %> - -If you’re concerned about your child’s reaction to the vaccine, contact your GP in the usual way. - -You can [give feedback](https://feedback.digital.nhs.uk/jfe/form/SV_3fICo6frMvUZX1k) about the ‘Give or refuse consent’ service by completing our short survey. Your feedback will help us improve the service. - -<%= subteam_name %> -<%= subteam_email %> -<%= subteam_phone %> diff --git a/app/views/notify_templates/email/vaccination_administered_hpv.text.erb b/app/views/notify_templates/email/vaccination_administered_hpv.text.erb deleted file mode 100644 index fd3edad7b0..0000000000 --- a/app/views/notify_templates/email/vaccination_administered_hpv.text.erb +++ /dev/null @@ -1,27 +0,0 @@ ---- -template_id: "8a65d7b5-045c-4f26-8f76-6e593c14cb6d" -template_name: vaccination_administered_hpv -subject: "Your child had their HPV vaccination <%= today_or_date_of_vaccination %>" ---- -<%= full_and_preferred_patient_name %> had their HPV vaccination at <%= location_name %> <%= today_or_date_of_vaccination %>. - -We suggest you record the following details somewhere. - -Vaccination: HPV first dose -Vaccine: Gardasil 9 -Date of vaccination: <%= day_month_year_of_vaccination %> -Batch number: <%= batch_name %> - -## Possible side effects - -Your child might have some of the following side effects: - -<%= vaccine_side_effects %> - -If you’re concerned about your child’s reaction to the vaccine, contact your GP in the usual way. - -You can [give feedback](https://feedback.digital.nhs.uk/jfe/form/SV_3fICo6frMvUZX1k) about the ‘Give or refuse consent’ service by completing our short survey. Your feedback will help us improve the service. - -<%= subteam_name %> -<%= subteam_email %> -<%= subteam_phone %> diff --git a/app/views/notify_templates/email/vaccination_administered_menacwy.text.erb b/app/views/notify_templates/email/vaccination_administered_menacwy.text.erb deleted file mode 100644 index 35d3b4ed14..0000000000 --- a/app/views/notify_templates/email/vaccination_administered_menacwy.text.erb +++ /dev/null @@ -1,27 +0,0 @@ ---- -template_id: "38727494-9a81-42b3-9c1f-5c31e55333e7" -template_name: vaccination_administered_menacwy -subject: "Your child had their MenACWY vaccination <%= today_or_date_of_vaccination %>" ---- -<%= full_and_preferred_patient_name %> had their MenACWY vaccination at <%= location_name %> <%= today_or_date_of_vaccination %>. - -We suggest you record the following details somewhere. - -Vaccination: MenACWY -Vaccine: MenQuadfi -Date of vaccination: <%= day_month_year_of_vaccination %> -Batch number: <%= batch_name %> - -## Possible side effects - -Your child might have some of the following side effects: - -<%= vaccine_side_effects %> - -If you’re concerned about your child’s reaction to the vaccine, contact your GP in the usual way. - -You can [give feedback](https://feedback.digital.nhs.uk/jfe/form/SV_3fICo6frMvUZX1k) about the ‘Give or refuse consent’ service by completing our short survey. Your feedback will help us improve the service. - -<%= subteam_name %> -<%= subteam_email %> -<%= subteam_phone %> diff --git a/app/views/notify_templates/email/vaccination_administered_mmr.text.erb b/app/views/notify_templates/email/vaccination_administered_mmr.text.erb deleted file mode 100644 index aa0bcafaf7..0000000000 --- a/app/views/notify_templates/email/vaccination_administered_mmr.text.erb +++ /dev/null @@ -1,29 +0,0 @@ ---- -template_id: "0b1095db-fb38-4105-9f01-a364fa8bbb1c" -template_name: vaccination_administered_mmr -subject: "Your child had their <%= vaccination %> <%= today_or_date_of_vaccination %>" ---- -<%= full_and_preferred_patient_name %> had their <%= vaccination %> at <%= location_name %> <%= today_or_date_of_vaccination %>. - -We suggest you make a note of the following details somewhere. - -Vaccination: <%= vaccination %> -Vaccine: <%= vaccine_brand %> -Date of vaccination: <%= day_month_year_of_vaccination %> -Batch number: <%= batch_name %> - -## Possible side effects - -Your child might have some of the following side effects: - -<%= vaccine_side_effects %> - -These side effects are usually mild and only last 2 to 3 days. If you’re concerned about your child’s reaction to the vaccine, contact your GP in the usual way. - -<%= mmr_second_dose_message %> - -You can [give feedback](https://feedback.digital.nhs.uk/jfe/form/SV_3fICo6frMvUZX1k) about the ‘Give or refuse consent’ service by completing our short survey. Your feedback will help us improve the service. - -<%= subteam_name %> -<%= subteam_email %> -<%= subteam_phone %> diff --git a/app/views/notify_templates/email/vaccination_administered_td_ipv.text.erb b/app/views/notify_templates/email/vaccination_administered_td_ipv.text.erb deleted file mode 100644 index b49f1a2f2d..0000000000 --- a/app/views/notify_templates/email/vaccination_administered_td_ipv.text.erb +++ /dev/null @@ -1,27 +0,0 @@ ---- -template_id: "3abe7ca8-a889-484b-ab9f-07523302eb6a" -template_name: vaccination_administered_td_ipv -subject: "Your child had their Td/IPV (3-in-1 teenage booster) vaccination <%= today_or_date_of_vaccination %>" ---- -<%= full_and_preferred_patient_name %> had their Td/IPV (3-in-1 teenage booster) vaccination at <%= location_name %> <%= today_or_date_of_vaccination %>. - -We suggest you record the following details somewhere. - -Vaccination: Td/IPV -Vaccine: Revaxis -Date of vaccination: <%= day_month_year_of_vaccination %> -Batch number: <%= batch_name %> - -##Possible side effects - -Your child might have some of the following side effects: - -<%= vaccine_side_effects %> - -If you’re concerned about your child’s reaction to the vaccine, contact your GP in the usual way. - -You can [give feedback](https://feedback.digital.nhs.uk/jfe/form/SV_3fICo6frMvUZX1k) about the ‘Give or refuse consent’ service by completing our short survey. Your feedback will help us improve the service. - -<%= subteam_name %> -<%= subteam_email %> -<%= subteam_phone %> diff --git a/app/views/notify_templates/email/vaccination_already_had.text.erb b/app/views/notify_templates/email/vaccination_already_had.text.erb index d0c75d96ec..c42c5b541f 100644 --- a/app/views/notify_templates/email/vaccination_already_had.text.erb +++ b/app/views/notify_templates/email/vaccination_already_had.text.erb @@ -1,13 +1,13 @@ --- template_id: "e37fe0a2-7584-4c25-983a-8f5a11c818a1" template_name: vaccination_already_had -subject: "Cancelled <%= vaccination %> appointment for <%= short_patient_name %>" +subject: "Cancelled <%= programme_name_for_parents(vaccination_record.programme) %> vaccination appointment for <%= short_patient_name %>" --- -We’re contacting you because our records show that <%= short_patient_name %> was vaccinated at another location <%= today_or_date_of_vaccination %> so we’re cancelling their vaccination appointment at school. +We’re contacting you because our records show that <%= short_patient_name %> was vaccinated at another location <%= vaccination_record_today_or_date(vaccination_record) %> so we’re cancelling their vaccination appointment at school. -If you think this is wrong, please tell us immediately on <%= subteam_phone %> or <%= subteam_email %>. +If you think this is wrong, please tell us immediately on <%= team_contact_phone(vaccination_record:) %> or <%= team_contact_email(vaccination_record:) %>. -If <%= short_patient_name %> was vaccinated <%= today_or_date_of_vaccination %>, you do not need +If <%= short_patient_name %> was vaccinated <%= vaccination_record_today_or_date(vaccination_record) %>, you do not need to do anything. -<%= subteam_name %> +<%= team_contact_name(vaccination_record:) %> diff --git a/app/views/notify_templates/email/vaccination_not_administered.text.erb b/app/views/notify_templates/email/vaccination_not_administered.text.erb index 6065c1e27b..f08c934157 100644 --- a/app/views/notify_templates/email/vaccination_not_administered.text.erb +++ b/app/views/notify_templates/email/vaccination_not_administered.text.erb @@ -1,12 +1,12 @@ --- template_id: "130fe52a-014a-45dd-9f53-8e65c1b8bb79" template_name: vaccination_not_administered -subject: "Your child did not have their <%= vaccination %> today" +subject: "Your child did not have their <%= programme_name_for_parents(vaccination_record.programme) %> vaccination today" --- -<%= full_and_preferred_patient_name %> did not have their <%= vaccination %> at school today. This was because <%= reason_did_not_vaccinate %>. +<%= full_and_preferred_patient_name %> did not have their <%= programme_name_for_parents(vaccination_record.programme) %> vaccination at school today. This was because <%= reason_did_not_vaccinate %>. <% if show_additional_instructions? %>If you’d still like your child to be vaccinated, you can book an appointment at a catch-up clinic by contacting our team.<% end %> -<%= subteam_name %> -<%= subteam_email %> -<%= subteam_phone %> +<%= team_contact_name(vaccination_record:) %> +<%= team_contact_email(vaccination_record:) %> +<%= team_contact_phone(vaccination_record:) %> diff --git a/app/views/notify_templates/sms/vaccination_administered.text.erb b/app/views/notify_templates/sms/vaccination_administered.text.erb index 6839aac2b4..467e8e913c 100644 --- a/app/views/notify_templates/sms/vaccination_administered.text.erb +++ b/app/views/notify_templates/sms/vaccination_administered.text.erb @@ -4,7 +4,7 @@ template_name: vaccination_administered --- <%= short_patient_name %> had their <%= vaccination_and_method %> today. They might have some of the following side effects: -<%= vaccine_side_effects %> +<%= vaccine_side_effects_list(vaccination_record.vaccine) %> If you're concerned, contact your GP in the usual way. diff --git a/app/views/notify_templates/sms/vaccination_already_had.text.erb b/app/views/notify_templates/sms/vaccination_already_had.text.erb index 960af3f334..1d9659d476 100644 --- a/app/views/notify_templates/sms/vaccination_already_had.text.erb +++ b/app/views/notify_templates/sms/vaccination_already_had.text.erb @@ -2,5 +2,5 @@ template_id: "fab1e355-bde1-47d5-835c-103bfd232b93" template_name: vaccination_already_had --- -We are cancelling <%= short_patient_name %>'s <%= vaccination %> at school -as our records show <%= short_patient_name %> was vaccinated at another location <%= today_or_date_of_vaccination %>. If this is wrong, contact us. <%= subteam_phone %> +We are cancelling <%= short_patient_name %>'s <%= programme_name_for_parents(vaccination_record.programme) %> vaccination at school +as our records show <%= short_patient_name %> was vaccinated at another location <%= vaccination_record_today_or_date(vaccination_record) %>. If this is wrong, contact us. <%= team_contact_phone(vaccination_record:) %> diff --git a/app/views/notify_templates/sms/vaccination_not_administered.text.erb b/app/views/notify_templates/sms/vaccination_not_administered.text.erb index b6468262bd..9f5b86c0fd 100644 --- a/app/views/notify_templates/sms/vaccination_not_administered.text.erb +++ b/app/views/notify_templates/sms/vaccination_not_administered.text.erb @@ -2,6 +2,6 @@ template_id: "aae061e0-b847-4d4c-a87a-12508f95a302" template_name: vaccination_not_administered --- -<%= short_patient_name %> did not have their <%= vaccination %> at school today. This was because <%= reason_did_not_vaccinate %>. +<%= short_patient_name %> did not have their <%= programme_name_for_parents(vaccination_record.programme) %> vaccination at school today. This was because <%= reason_did_not_vaccinate %>. -If you'd still like them to be vaccinated on a different date, contact the local health team by calling <%= subteam_phone %>, or email <%= subteam_email %>. +If you'd still like them to be vaccinated on a different date, contact the local health team by calling <%= team_contact_phone(vaccination_record:) %>, or email <%= team_contact_email(vaccination_record:) %>. diff --git a/app/views/parent_interface/consent_forms/edit/follow_up_discussion.html.erb b/app/views/parent_interface/consent_forms/edit/follow_up_discussion.html.erb index 0bf1e41eeb..aa27533073 100644 --- a/app/views/parent_interface/consent_forms/edit/follow_up_discussion.html.erb +++ b/app/views/parent_interface/consent_forms/edit/follow_up_discussion.html.erb @@ -8,6 +8,8 @@ <% hint = case @consent_form.reason_for_refusal when "contains_gelatine" "For example, it may be possible to use a vaccine that does not contain gelatine." + when "do_not_want_vaccination_at_school" + "For example, it may be possible to vaccinate your child in a community clinic." when "medical_reasons" "We understand alternatives might not be suitable in some cases." end %> diff --git a/app/views/parent_interface/consent_forms/edit/reason_for_refusal.html.erb b/app/views/parent_interface/consent_forms/edit/reason_for_refusal.html.erb index 27e4332452..8e90aa8ff1 100644 --- a/app/views/parent_interface/consent_forms/edit/reason_for_refusal.html.erb +++ b/app/views/parent_interface/consent_forms/edit/reason_for_refusal.html.erb @@ -18,6 +18,7 @@ <%= f.govuk_radio_button :reason_for_refusal, refusal_reason.value, label: { text: refusal_reason.label }, + hint: { text: refusal_reason.hint }, link_errors: index.zero? %> <% end %> <% end %> diff --git a/app/views/parent_interface/consent_forms/edit/reason_for_refusal_notes.html.erb b/app/views/parent_interface/consent_forms/edit/reason_for_refusal_notes.html.erb index 4b0e27860d..18905aae93 100644 --- a/app/views/parent_interface/consent_forms/edit/reason_for_refusal_notes.html.erb +++ b/app/views/parent_interface/consent_forms/edit/reason_for_refusal_notes.html.erb @@ -9,7 +9,7 @@ <% programme = @consent_form.refused_consent_form_programmes.find { it.programme.mmr? }&.programme %> <% if programme && @consent_form.reason_for_refusal == "already_vaccinated" %> -

Children need 2 doses of the #{programme.name} vaccine to be fully protected. If your child has had more than 1 dose, give details about both doses.

+

Children need 2 doses of the <%= programme.name %> vaccine to be fully protected. If your child has had more than 1 dose, give details about both doses.

<% end %> <% label = "Give details" + (@consent_form.reason_for_refusal_requires_notes? ? "" : " (optional)") %> diff --git a/app/views/patient_sessions/consents/withdraw.html.erb b/app/views/patient_sessions/consents/withdraw.html.erb index 4a43b2212e..3f048e4a49 100644 --- a/app/views/patient_sessions/consents/withdraw.html.erb +++ b/app/views/patient_sessions/consents/withdraw.html.erb @@ -19,6 +19,7 @@ <%= f.govuk_radio_button :reason_for_refusal, refusal_reason.value, label: { text: refusal_reason.label }, + hint: { text: refusal_reason.hint }, link_errors: index.zero? %> <% end %> <% end %> diff --git a/app/views/schools/patients/index.html.erb b/app/views/schools/patients/index.html.erb index d1e4c0b74d..5f2db91d10 100644 --- a/app/views/schools/patients/index.html.erb +++ b/app/views/schools/patients/index.html.erb @@ -8,7 +8,7 @@ <% content_for :page_title, "#{t("schools.patients.title")} – #{@location.name}" %> <%= render "schools/header" %> -<% if @location.school? %> +<% if @location.gias_school? %> <%= govuk_button_link_to "Import class lists", new_school_import_path(@location), secondary: true %> <% elsif @location.generic_school? %>
diff --git a/app/views/sessions/patient_specific_directions/show.html.erb b/app/views/sessions/patient_specific_directions/show.html.erb index 292237e29d..bdd43855d9 100644 --- a/app/views/sessions/patient_specific_directions/show.html.erb +++ b/app/views/sessions/patient_specific_directions/show.html.erb @@ -20,12 +20,10 @@

<% if policy(PatientSpecificDirection).create? %> - <%= link_to new_session_patient_specific_directions_path(@session), class: "nhsuk-action-link" do %> - - Add new PSDs - <% end %> + <%= render AppActionLinkComponent.new( + href: new_session_patient_specific_directions_path(@session), + text: "Add new PSDs", + ) %> <% end %> <% end %> diff --git a/app/views/vaccines/index.html.erb b/app/views/vaccines/index.html.erb index 91b83995fb..9b00240097 100644 --- a/app/views/vaccines/index.html.erb +++ b/app/views/vaccines/index.html.erb @@ -4,12 +4,23 @@ <%= render AppCardComponent.new(section: true) do |card| %> <% card.with_heading(level: 2) { link_to(vaccine_heading(vaccine), vaccine_path(vaccine)) } %> -

<%= vaccine.manufacturer %>

+ <%= govuk_summary_list do |summary_list| + summary_list.with_row do |row| + row.with_key { "Manufacturer" } + row.with_value { vaccine.manufacturer } + end + + summary_list.with_row do |row| + row.with_key { "SNOMED code" } + row.with_value { content_tag(:span, vaccine.snomed_product_code, class: "nhsuk-u-text-break-word app-u-code") } + end + end %> - <%= govuk_button_link_to "Add a new batch", - new_vaccine_batch_path(vaccine), - secondary: true, - aria: { label: "Add a new #{vaccine.brand} batch" } %> + <%= render AppActionLinkComponent.new( + href: new_vaccine_batch_path(vaccine), + text: "Add a new batch", + aria: { label: "Add a new #{vaccine.brand} batch" }, + ) %> <% if (batches = @batches_by_vaccine_id[vaccine.id]).present? %> <%= govuk_table do |table| %> diff --git a/app/views/vaccines/show.html.erb b/app/views/vaccines/show.html.erb index 46926d15bf..c4020a3874 100644 --- a/app/views/vaccines/show.html.erb +++ b/app/views/vaccines/show.html.erb @@ -7,58 +7,57 @@ <%= render AppCardComponent.new do |card| %> <% card.with_heading(level: 2) { "Vaccine details" } %> -
-
-
- Vaccine -
-
- <%= @vaccine.programme.name %> -
-
-
-
- Brand -
-
- <%= @vaccine.brand %> -
-
-
-
- Manufacturer -
-
- <%= @vaccine.manufacturer %> -
-
-
-
- Method -
-
- <%= @vaccine.human_enum_name(:method) %> -
-
-
-
- Dose -
-
- <%= @vaccine.dose_volume_ml %> ml -
-
-
-
- Health questions -
-
-
    - <% @vaccine.health_questions.each do |question| %> -
  • <%= question.title %>
  • - <% end %> -
-
-
-
+ <%= govuk_summary_list do |summary_list| + summary_list.with_row do |row| + row.with_key { "Vaccine" } + row.with_value { @vaccine.programme.name } + end + + summary_list.with_row do |row| + row.with_key { "Brand" } + row.with_value { @vaccine.brand } + end + + summary_list.with_row do |row| + row.with_key { "Manufacturer" } + row.with_value { @vaccine.manufacturer } + end + + summary_list.with_row do |row| + row.with_key { "SNOMED code" } + row.with_value { content_tag(:span, @vaccine.snomed_product_code, class: "nhsuk-u-text-break-word app-u-code") } + end + + summary_list.with_row do |row| + row.with_key { "Method" } + row.with_value { @vaccine.human_enum_name(:method) } + end + + summary_list.with_row do |row| + row.with_key { "Dose" } + row.with_value { "#{@vaccine.dose_volume_ml} ml" } + end + + summary_list.with_row do |row| + row.with_key { "Health questions" } + row.with_value do + content_tag(:ul, class: "nhsuk-list nhsuk-list--bullet") do + safe_join(@vaccine.health_questions.map { |q| + content_tag(:li, q.title) + }) + end + end + end + + summary_list.with_row do |row| + row.with_key { "Side effects" } + row.with_value do + content_tag(:ul, class: "nhsuk-list nhsuk-list--bullet") do + safe_join(@vaccine.side_effects.map { + content_tag(:li, Vaccine.human_enum_name(:side_effect, it).capitalize) + }) + end + end + end + end %> <% end %> diff --git a/config/feature_flags.yml b/config/feature_flags.yml index 8fb7e4d0da..a057b178d5 100644 --- a/config/feature_flags.yml +++ b/config/feature_flags.yml @@ -4,48 +4,49 @@ dev_tools: Developer tools useful for testing and debugging. ethnicity_capture: Enable optional ethnicity capture after parent consent -imms_api_enqueue_rolling_searches: | - Enables enqueueing of vaccination searches for patients not in sessions, - spread over a period of 28 days, in the EnqueueVaccinationsSearchInNHSJob. +imms_api_enqueue_rolling_searches: >- + Enables enqueueing of vaccination searches for patients not in sessions, spread over a period of + 28 days, in the EnqueueVaccinationsSearchInNHSJob. -imms_api_enqueue_session_searches: | +imms_api_enqueue_session_searches: >- Enables enqueueing of vaccination searches for upcoming sessions in the EnqueueVaccinationsSearchInNHSJob. -imms_api_ignore_records_prior_to_2025_academic_year: | - Per-programme flag. When enabled for a programme, prevents vaccination records - dated before 1st September 2025 from being imported. +imms_api_ignore_records_prior_to_2025_academic_year: >- + Per-programme flag. When enabled for a programme, prevents vaccination records dated before 1st + September 2025 from being imported. imms_api_integration: Umbrella flag to switch off ALL API integration (including CLI tools). -imms_api_search_job: +imms_api_search_job: >- Flag to switch off any automated receipt of vaccination records (Search), as part of the search job. -imms_api_sentry_warnings: | - Enable sending non-fatal Immunisations API warnings to Sentry (e.g. OperationOutcome and - Bundle link param mismatches). When disabled, these warnings are logged but not sent to Sentry. +imms_api_sentry_warnings: >- + Enable sending non-fatal Immunisations API warnings to Sentry (e.g. OperationOutcome and Bundle + link param mismatches). When disabled, these warnings are logged but not sent to Sentry. -imms_api_sync_job: +imms_api_sync_job: >- Flag to switch off any automated sending of vaccination records (Create, Update, Delete), triggered by the creation/edit/delete of any vaccination records. This can be controlled on a per-programme basis. import_bulk_remove_parents: Allow user to bulk remove parent relationships from imports. -import_choose_academic_year: +import_choose_academic_year: >- Add an option to choose the academic year when importing patients during the preparation period. -import_concurrency_per_server: | - Controls whether to limit the concurrency of the import jobs to be global or - to be per-server. +import_concurrency_per_server: >- + Controls whether to limit the concurrency of the import jobs to be global or to be per-server. import_search_pds: Perform PDS lookups as part of the patient import processing. ops_tools: Enable the operational support tools; timeline and graph. -sync_national_reporting_to_imms_api: | - Sync immunisations records uploaded as part of national reporting to the NHS - Immunisations API. +sync_national_reporting_to_imms_api: >- + Sync immunisations records uploaded as part of national reporting to the NHS Immunisations API. testing_api: Basic API useful for automated testing. + +vaccinating_16_plus_year_olds: >- + Enable functionality related to recording vaccinations for patients over the age of 16. diff --git a/config/locales/en.yml b/config/locales/en.yml index 03776dd864..6992aba908 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -353,8 +353,13 @@ en: contains_gelatine_mmrv: I’m concerned the vaccine contains gelatine medical: Medical reasons other: Other + do_not_want_vaccination_at_school: Do not want vaccination at school personal_choice: Personal choice will_be_vaccinated_elsewhere: Vaccine will be given elsewhere + reason_for_refusal_hints: + do_not_want_vaccination_at_school: >- + For example, you do not want your child to be vaccinated in a busy environment + will_be_vaccinated_elsewhere: "For example, you've booked your child into a clinic" responses: follow_up_requested: Follow-up requested given: Consent given @@ -1158,6 +1163,7 @@ en: contains_gelatine: of the gelatine in the nasal spray medical_reasons: of medical reasons other: of other reasons + does_not_want_vaccination_at_school: they do not want vaccination at school personal_choice: of personal choice will_be_vaccinated_elsewhere: they will be given the vaccine elsewhere vaccination_mailer: @@ -1204,6 +1210,16 @@ en: sessions: index: title: Sessions + # Only MMR/MMRV disease types are mapped for now. I18n.t! is used + # intentionally so that adding a new multi-dose programme forces you + # to add parent-friendly disease names here. + disease_types: + measles: measles + mumps: mumps + rubella: rubella + varicella: varicella + programme_names_on_nhs_uk: + td_ipv: 3-in-1 teenage booster programme_types: flu: Flu hpv: HPV @@ -1241,7 +1257,7 @@ en: consent: Consent overview: Overview patient_specific_directions: PSDs - patients: Children + patients: Children in session record: Record vaccinations register: Register triage: Triage diff --git a/config/onboarding/enfield.yaml b/config/onboarding/enfield.yaml deleted file mode 100644 index 2598d48d7f..0000000000 --- a/config/onboarding/enfield.yaml +++ /dev/null @@ -1,149 +0,0 @@ -organisation: - ods_code: G6R5B - -team: - workgroup: vaccsukenfieldsais - name: Vaccination UK - Enfield - email: enfield@v-uk.co.uk - phone: "0208 150 7505" - careplus_venue_code: UNKNOWN - privacy_policy_url: https://www.schoolvaccination.uk/policies - privacy_notice_url: https://www.schoolvaccination.uk/policies - reply_to_id: 1db6b63c-cdc3-47d5-abc4-3d7297ab20a1 - type: point_of_care - -programmes: [menacwy, mmr, td_ipv, hpv] - -subteams: - enfield: - name: Vaccination UK - Enfield - email: enfield@v-uk.co.uk - phone: "0208 150 7505" - reply_to_id: 1db6b63c-cdc3-47d5-abc4-3d7297ab20a1 - -# 134580 closed with no successor -schools: - enfield: - - 101972 - - 101981 - - 101983 - - 101984 - - 101989 - - 101992 - - 101993 - - 101994 - - 101996 - - 101997 - - 102002 - - 102003 - - 102004 - - 102005 - - 102006 - - 102014 - - 102024 - - 102026 - - 102027 - - 102028 - - 102029 - - 102030 - - 102031 - - 102032 - - 102033 - - 102034 - - 102035 - - 102036 - - 102037 - - 102038 - - 102039 - - 102040 - - 102041 - - 102045 - - 102048 - - 102049 - - 102052 - - 102053 - - 102054 - - 102055 - - 102056 - - 102058 - - 102060 - - 102061 - - 102062 - - 102063 - - 102064 - - 102065 - - 102066 - - 102067 - - 102069 - - 102070 - - 130438 - - 130958 - - 131407 - - 131657 - - 132203 - - 132256 - - 134307 - - 134311 - - 135839 - - 135958 - - 136147 - - 136327 - - 136952 - - 137094 - - 138203 - - 138381 - - 139599 - - 139815 - - 141017 - - 141247 - - 142727 - - 142874 - - 143036 - - 143197 - - 143198 - - 143199 - - 143200 - - 143201 - - 143202 - - 143924 - - 144084 - - 145201 - - 145207 - - 145232 - - 145522 - - 145955 - - 145968 - - 145984 - - 145985 - - 145986 - - 146663 - - 146728 - - 146862 - - 146868 - - 146882 - - 146900 - - 146935 - - 146946 - - 147536 - - 148358 - - 149100 - - 150114 - - 150663 - - 151401 - - 151543 - - 152093 - -clinics: - enfield: - - name: Evergreen Surgery - address_line_1: 1 Smythe Close - address_town: London - address_postcode: N9 0TW - - name: The Ordnance Unity Centre For Health - address_line_1: 645 Hertford Rd - address_town: Enfield - address_postcode: EN3 6ND - - name: Carlton House Surgery - address_line_1: 28 Tenniswood Rd - address_town: Enfield - address_postcode: EN1 3LL diff --git a/config/onboarding/gloucester.yaml b/config/onboarding/gloucester.yaml new file mode 100644 index 0000000000..67c5b56c18 --- /dev/null +++ b/config/onboarding/gloucester.yaml @@ -0,0 +1,8 @@ +organisation: + ods_code: RTQ + +team: + name: Gloucestershire Health and Care NHS Foundation Trust + workgroup: gloucestershiremavisnr + type: national_reporting + national_reporting_cut_off_date: 2026-04-20 diff --git a/config/onboarding/hullandeastriding.yaml b/config/onboarding/hullandeastriding.yaml deleted file mode 100644 index 1200eefe44..0000000000 --- a/config/onboarding/hullandeastriding.yaml +++ /dev/null @@ -1,290 +0,0 @@ -organisation: - ods_code: O6B9R - -team: - workgroup: vaccsukhullandeastridingsais - name: Vaccination UK - Hull & East Riding - email: hullandeastriding@v-uk.co.uk - phone: "01482 453690" - careplus_venue_code: UNKNOWN - privacy_policy_url: https://www.schoolvaccination.uk/policies - privacy_notice_url: https://www.schoolvaccination.uk/policies - reply_to_id: 8d4721f4-e5db-49db-8eb4-86fd37990391 - type: point_of_care - -programmes: [menacwy, mmr, td_ipv, hpv] - -subteams: - hullandeastriding: - name: Vaccination UK - Hull & East Riding - email: hullandeastriding@v-uk.co.uk - phone: "01482 453690" - reply_to_id: 8d4721f4-e5db-49db-8eb4-86fd37990391 - -schools: - hullandeastriding: - - 144672 - - 139570 - - 144421 - - 140229 - - 139838 - - 139508 - - 140235 - - 144669 - - 140847 - - 139511 - - 143225 - - 140902 - - 140437 - - 144677 - - 139848 - - 140169 - - 139525 - - 139850 - - 140184 - - 140905 - - 140716 - - 141078 - - 118123 - - 147259 - - 144605 - - 138679 - - 139512 - - 141037 - - 143224 - - 140275 - - 140245 - - 144671 - - 140246 - - 139921 - - 144583 - - 140247 - - 138677 - - 141719 - - 147628 - - 144305 - - 144670 - - 142440 - - 142260 - - 140290 - - 145611 - - 145431 - - 140849 - - 140587 - - 118051 - - 144102 - - 144103 - - 143226 - - 141387 - - 140903 - - 140906 - - 141649 - - 144101 - - 140848 - - 143223 - - 141720 - - 145612 - - 139513 - - 144674 - - 138678 - - 139922 - - 139886 - - 142307 - - 144673 - - 139799 - - 140830 - - 139579 - - 139968 - - 144676 - - 148588 - - 146220 - - 140904 - - 118131 - - 135598 - - 118183 - - 118140 - - 130579 - - 138082 - - 144422 - - 143221 - - 139118 - - 142150 - - 144307 - - 144766 - - 138246 - - 135945 - - 144104 - - 139395 - - 143222 - - 142391 - - 139629 - - 139628 - - 117888 - - 145596 - - 117823 - - 147408 - - 147407 - - 132349 - - 117875 - - 117824 - - 118122 - - 151519 - - 117969 - - 117825 - - 117970 - - 150840 - - 149259 - - 117972 - - 117941 - - 117830 - - 117831 - - 117842 - - 117878 - - 118000 - - 117833 - - 117832 - - 117838 - - 117973 - - 117839 - - 118002 - - 118025 - - 145177 - - 117974 - - 117840 - - 140119 - - 145189 - - 117841 - - 117891 - - 117976 - - 117977 - - 144471 - - 135078 - - 151160 - - 135700 - - 117852 - - 145595 - - 133519 - - 117853 - - 149974 - - 149260 - - 142119 - - 117855 - - 117911 - - 133481 - - 147760 - - 117980 - - 117936 - - 117872 - - 117981 - - 117990 - - 118001 - - 117912 - - 117937 - - 151530 - - 146161 - - 117983 - - 117889 - - 117982 - - 117858 - - 117909 - - 117859 - - 117860 - - 147504 - - 117985 - - 117861 - - 117916 - - 147210 - - 117935 - - 145190 - - 117862 - - 137306 - - 117987 - - 143587 - - 118044 - - 117864 - - 140551 - - 117893 - - 117892 - - 117939 - - 146174 - - 117989 - - 143823 - - 117992 - - 117882 - - 117993 - - 118028 - - 117894 - - 118003 - - 117885 - - 144082 - - 117881 - - 147252 - - 148164 - - 118034 - - 147248 - - 118030 - - 147244 - - 149622 - - 143586 - - 118026 - - 137151 - - 117942 - - 132209 - - 135077 - - 117995 - - 117866 - - 117996 - - 117867 - - 117868 - - 117883 - - 117997 - - 149646 - - 135115 - - 141933 - - 118120 - - 117999 - - 133429 - - 137306 - - 118144 - - 136995 - - 118072 - - 118111 - - 136921 - - 143854 - - 118132 - - 145929 - - 118085 - - 145722 - - 118082 - - 146129 - - 118073 - - 118148 - - 118145 - - 148767 - - 136667 - - 118076 - - 140866 - - 150256 - - 118126 - - 118075 - - 143588 - - 144561 - -clinics: - hullandeastriding: - - name: The Deep Business Centre - address_line_1: Tower Street - address_town: - address_postcode: HU1 4BG - - name: Freedom Centre - address_line_1: 97 Preston Road - address_town: - address_postcode: HU9 3QB - - name: Priory Children's Centre - address_line_1: Priory Road - address_town: - address_postcode: HU5 5RU - - name: Lemon Tree - address_line_1: 2 Lothian Way - address_town: - address_postcode: HU7 5DD diff --git a/config/onboarding/kchft.yaml b/config/onboarding/kchft.yaml new file mode 100644 index 0000000000..6709b914a1 --- /dev/null +++ b/config/onboarding/kchft.yaml @@ -0,0 +1,8 @@ +organisation: + ods_code: RYY + +team: + name: Kent Community Health NHS Foundation Trust + workgroup: kchftsaisnr + type: national_reporting + national_reporting_cut_off_date: 2026-04-20 diff --git a/config/onboarding/kernow.yaml b/config/onboarding/kernow.yaml new file mode 100644 index 0000000000..da15ca41e2 --- /dev/null +++ b/config/onboarding/kernow.yaml @@ -0,0 +1,8 @@ +organisation: + ods_code: AYG + +team: + name: Kernow Health CIC - Cornwall + workgroup: kernowsaisnr + type: national_reporting + national_reporting_cut_off_date: 2026-04-20 diff --git a/config/onboarding/lincs.yaml b/config/onboarding/lincs.yaml deleted file mode 100644 index 682392337a..0000000000 --- a/config/onboarding/lincs.yaml +++ /dev/null @@ -1,216 +0,0 @@ -organisation: - ods_code: I4K0Y - -team: - workgroup: vaccsuklincssais - name: Vaccination UK - North & North East Lincolnshire - email: lincs@v-uk.co.uk - phone: "01724 514800" - careplus_venue_code: UNKNOWN - privacy_policy_url: https://www.schoolvaccination.uk/policies - privacy_notice_url: https://www.schoolvaccination.uk/policies - reply_to_id: 69f157fa-c166-47c2-94e3-adb0ae88dd6e - type: point_of_care - -programmes: [menacwy, mmr, td_ipv, hpv] - -subteams: - lincs: - name: Vaccination UK - North & North East Lincolnshire - email: lincs@v-uk.co.uk - phone: "01724 514800" - reply_to_id: 69f157fa-c166-47c2-94e3-adb0ae88dd6e - -# Replaced the following schools with their successors -# 117725 → 151798 -# 117729 → 150025 -# 117730 → 151799 -# 118011 → 151079 -# 137832 → 150867 -# 117739 → 151800 -# 118037 → 151081 -# 118018 → 151080 -# 118021 → 148307 -# 118012 → 149973 -# 117956 → 150965 -# 118015 → 150519 -schools: - lincs: - - 145462 - - 151798 - - 117726 - - 118006 - - 118109 - - 118007 - - 117781 - - 117780 - - 117727 - - 117812 - - 117728 - - 150025 - - 151799 - - 117747 - - 117967 - - 141485 - - 117934 - - 139035 - - 135247 - - 117736 - - 118043 - - 117768 - - 142130 - - 138712 - - 134589 - - 118097 - - 117748 - - 117737 - - 118010 - - 151079 - - 138085 - - 117811 - - 150867 - - 118005 - - 117743 - - 118014 - - 151800 - - 117771 - - 133700 - - 117740 - - 139059 - - 117741 - - 118023 - - 134867 - - 139024 - - 139025 - - 139277 - - 137004 - - 146267 - - 150449 - - 117756 - - 149400 - - 149592 - - 138017 - - 138827 - - 138086 - - 118016 - - 118112 - - 137742 - - 117752 - - 135065 - - 118004 - - 138018 - - 138016 - - 118146 - - 118147 - - 151081 - - 138015 - - 136058 - - 137759 - - 140626 - - 135674 - - 137453 - - 140343 - - 151080 - - 131216 - - 138713 - - 140625 - - 117753 - - 118020 - - 138831 - - 117787 - - 118038 - - 138520 - - 148307 - - 118022 - - 151702 - - 151703 - - 139649 - - 142828 - - 138467 - - 140991 - - 136192 - - 149511 - - 137811 - - 139981 - - 138857 - - 141773 - - 142269 - - 141631 - - 131231 - - 142841 - - 135294 - - 141302 - - 136277 - - 137200 - - 149973 - - 140801 - - 137394 - - 137464 - - 140581 - - 147462 - - 137611 - - 142969 - - 138890 - - 137996 - - 137761 - - 135176 - - 138237 - - 135209 - - 139472 - - 137196 - - 138846 - - 141101 - - 144148 - - 117733 - - 139952 - - 139009 - - 150965 - - 137787 - - 141585 - - 136975 - - 138062 - - 142268 - - 118124 - - 138014 - - 118125 - - 143281 - - 118017 - - 150519 - - 138866 - - 130585 - - 137363 - - 144366 - - 138468 - - 137760 - - 136268 - - 138279 - - 141289 - - 117926 - - 143884 - - 139420 - - 146762 - - 138975 - - 138542 - -clinics: - lincs: - - name: Normanby Gateway - address_line_1: Lysagths Way - address_town: Scunthorpe - address_postcode: DN15 9YG - - name: Grimsby Town Hall - address_line_1: Town Hall Square - address_town: Grimsby - address_postcode: DN31 9ZZ - - name: Immingham Family Hub - address_line_1: Margaret Street - address_town: Immingham - address_postcode: DN40 1LD - - name: Open Door - address_line_1: Albion Street - address_town: - address_postcode: DN32 7DL - - name: Scunthrope Library - address_line_1: Carlton Street - address_town: - address_postcode: DN15 6TX diff --git a/config/onboarding/rollover-training.yaml b/config/onboarding/rollover-training.yaml deleted file mode 100644 index f97f9aa0cc..0000000000 --- a/config/onboarding/rollover-training.yaml +++ /dev/null @@ -1,42 +0,0 @@ -organisation: - ods_code: RLVTRN - -team: - workgroup: rollovertraining - name: Rollover Training - email: rollover@example.com - phone: 01234 567890 - careplus_venue_code: ROLLOVER - privacy_notice_url: https://www.example.com/privacy-notice - privacy_policy_url: https://www.example.com/privacy-policy - -programmes: [flu, hpv, menacwy, mmr, td_ipv] - -subteams: - generic: - name: Rollover Training - email: rollover@example.com - phone: 01234 567890 - -users: - - email: nurse.rollover@example.com - password: nurse.rollover@example.com - given_name: Nurse - family_name: ROLLOVER - -schools: - generic: - # Primary schools - - 115529 - - 131806 - - 145212 - - 118853 - # Secondary schools - - 145482 - - 137236 - - 136642 - - 135970 - -clinics: - generic: - - name: Rollover Training Hospital diff --git a/config/onboarding/sheffield.yaml b/config/onboarding/sheffield.yaml new file mode 100644 index 0000000000..8f5187ae27 --- /dev/null +++ b/config/onboarding/sheffield.yaml @@ -0,0 +1,8 @@ +organisation: + ods_code: RCUEF + +team: + name: Sheffield Children's NHS Foundation Trust + workgroup: sheffieldmavisnr + type: national_reporting + national_reporting_cut_off_date: 2026-04-20 diff --git a/config/onboarding/shropshire.yaml b/config/onboarding/shropshire.yaml new file mode 100644 index 0000000000..e06e7a4f8b --- /dev/null +++ b/config/onboarding/shropshire.yaml @@ -0,0 +1,8 @@ +organisation: + ods_code: R1D + +team: + name: Shropshire Community Health NHS Trust + workgroup: shropshiremavisnr + type: national_reporting + national_reporting_cut_off_date: 2026-04-20 diff --git a/config/onboarding/sirona.yaml b/config/onboarding/sirona.yaml new file mode 100644 index 0000000000..6847899f52 --- /dev/null +++ b/config/onboarding/sirona.yaml @@ -0,0 +1,8 @@ +organisation: + ods_code: NLX + +team: + name: Sirona Care & Health + workgroup: sironamavisnr + type: national_reporting + national_reporting_cut_off_date: 2026-04-20 diff --git a/config/onboarding/south-west-yorkshire.yaml b/config/onboarding/south-west-yorkshire.yaml new file mode 100644 index 0000000000..864f14f949 --- /dev/null +++ b/config/onboarding/south-west-yorkshire.yaml @@ -0,0 +1,8 @@ +organisation: + ods_code: RXG + +team: + name: South West Yorkshire Partnership NHS Foundation Trust + workgroup: swyorkshiremavisnr + type: national_reporting + national_reporting_cut_off_date: 2026-04-20 diff --git a/config/onboarding/sussex.yaml b/config/onboarding/sussex.yaml new file mode 100644 index 0000000000..482a709cc6 --- /dev/null +++ b/config/onboarding/sussex.yaml @@ -0,0 +1,8 @@ +organisation: + ods_code: RDR + +team: + name: Sussex Community NHS Foundation Trust + workgroup: sussexmavisnr + type: national_reporting + national_reporting_cut_off_date: 2026-04-20 diff --git a/config/settings/staging.yml b/config/settings/staging.yml index 56db308482..5ca301704c 100644 --- a/config/settings/staging.yml +++ b/config/settings/staging.yml @@ -12,7 +12,7 @@ nhs_api: cis2: # yamllint disable-line rule:line-length issuer: https://am.nhsint.auth-ptl.cis2.spineservices.nhs.uk:443/openam/oauth2/realms/root/realms/NHSIdentity/realms/Healthcare - support_organisation: RX4 + support_organisation: Y90128 pds: raise_unknown_gp_practice: false diff --git a/db/migrate/20260330095301_remove_home_educated_and_team_from_school_moves.rb b/db/migrate/20260330095301_remove_home_educated_and_team_from_school_moves.rb new file mode 100644 index 0000000000..bedff68423 --- /dev/null +++ b/db/migrate/20260330095301_remove_home_educated_and_team_from_school_moves.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class RemoveHomeEducatedAndTeamFromSchoolMoves < ActiveRecord::Migration[8.1] + def change + change_table :school_moves, bulk: true do |t| + t.remove :home_educated, type: :boolean + t.remove_references :team + end + end +end diff --git a/db/migrate/20260330130232_add_nhs_immunisations_api_snomed_procedure_to_vaccination_records.rb b/db/migrate/20260330130232_add_nhs_immunisations_api_snomed_procedure_to_vaccination_records.rb new file mode 100644 index 0000000000..88f858144c --- /dev/null +++ b/db/migrate/20260330130232_add_nhs_immunisations_api_snomed_procedure_to_vaccination_records.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class AddNHSImmunisationsAPISnomedProcedureToVaccinationRecords < ActiveRecord::Migration[ + 8.1 +] + def change + change_table :vaccination_records, bulk: true do |t| + t.string :nhs_immunisations_api_snomed_procedure_code + t.string :nhs_immunisations_api_snomed_procedure_term + end + end +end diff --git a/db/migrate/20260331100000_add_reason_code_to_vaccination_records.rb b/db/migrate/20260331100000_add_reason_code_to_vaccination_records.rb new file mode 100644 index 0000000000..e087478fb2 --- /dev/null +++ b/db/migrate/20260331100000_add_reason_code_to_vaccination_records.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddReasonCodeToVaccinationRecords < ActiveRecord::Migration[8.1] + def change + change_table :vaccination_records, bulk: true do |t| + t.string :nhs_immunisations_api_snomed_reason_code + t.string :nhs_immunisations_api_snomed_reason_term + end + end +end diff --git a/db/migrate/20260331110000_add_nhs_immunisations_api_recorded_at_to_vaccination_records.rb b/db/migrate/20260331110000_add_nhs_immunisations_api_recorded_at_to_vaccination_records.rb new file mode 100644 index 0000000000..704fa73c17 --- /dev/null +++ b/db/migrate/20260331110000_add_nhs_immunisations_api_recorded_at_to_vaccination_records.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddNHSImmunisationsAPIRecordedAtToVaccinationRecords < ActiveRecord::Migration[ + 8.1 +] + def change + add_column :vaccination_records, + :nhs_immunisations_api_recorded_at, + :datetime + end +end diff --git a/db/migrate/20260407120000_add_nhs_immunisations_api_snomed_product_to_vaccination_records.rb b/db/migrate/20260407120000_add_nhs_immunisations_api_snomed_product_to_vaccination_records.rb new file mode 100644 index 0000000000..6504a218c8 --- /dev/null +++ b/db/migrate/20260407120000_add_nhs_immunisations_api_snomed_product_to_vaccination_records.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class AddNHSImmunisationsAPISnomedProductToVaccinationRecords < ActiveRecord::Migration[ + 8.1 +] + def change + change_table :vaccination_records, bulk: true do |t| + t.string :nhs_immunisations_api_snomed_product_code + t.string :nhs_immunisations_api_snomed_product_term + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 4354b8022a..dab53c97cd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2026_03_30_075714) do +ActiveRecord::Schema[8.1].define(version: 2026_04_07_120000) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" enable_extension "pg_trgm" @@ -834,16 +834,13 @@ create_table "school_moves", force: :cascade do |t| t.integer "academic_year", null: false t.datetime "created_at", null: false - t.boolean "home_educated" t.bigint "patient_id", null: false t.bigint "school_id", null: false t.integer "source", null: false - t.bigint "team_id" t.datetime "updated_at", null: false t.index ["patient_id", "school_id"], name: "index_school_moves_on_patient_id_and_school_id" t.index ["patient_id"], name: "index_school_moves_on_patient_id", unique: true t.index ["school_id"], name: "index_school_moves_on_school_id" - t.index ["team_id"], name: "index_school_moves_on_team_id" end create_table "session_notifications", force: :cascade do |t| @@ -1007,6 +1004,13 @@ t.string "nhs_immunisations_api_identifier_system" t.string "nhs_immunisations_api_identifier_value" t.boolean "nhs_immunisations_api_primary_source" + t.datetime "nhs_immunisations_api_recorded_at" + t.string "nhs_immunisations_api_snomed_procedure_code" + t.string "nhs_immunisations_api_snomed_procedure_term" + t.string "nhs_immunisations_api_snomed_product_code" + t.string "nhs_immunisations_api_snomed_product_term" + t.string "nhs_immunisations_api_snomed_reason_code" + t.string "nhs_immunisations_api_snomed_reason_term" t.datetime "nhs_immunisations_api_sync_pending_at" t.datetime "nhs_immunisations_api_synced_at" t.text "notes" @@ -1178,7 +1182,6 @@ add_foreign_key "school_move_log_entries", "users" 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_notifications", "patients" add_foreign_key "session_notifications", "sessions" add_foreign_key "session_notifications", "users", column: "sent_by_user_id" diff --git a/db/seeds.rb b/db/seeds.rb index 43aa2a0426..dfb3346afa 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -77,7 +77,8 @@ def create_session(user, team, programmes:, completed: false, year_groups: nil) .map { |vaccine| FactoryBot.build(:batch, :not_expired, team:, vaccine:) } ) - location = FactoryBot.create(:school, team:, gias_year_groups: year_groups) + location = + FactoryBot.create(:gias_school, team:, gias_year_groups: year_groups) date = completed ? 1.week.ago.to_date : Date.current academic_year = AcademicYear.current @@ -218,7 +219,7 @@ def create_school_moves(team) :school_move, :to_school, patient:, - school: team.schools.sample + school: team.gias_schools.sample ) end end diff --git a/lib/tasks/locations.rake b/lib/tasks/locations.rake new file mode 100644 index 0000000000..e67ae85ffd --- /dev/null +++ b/lib/tasks/locations.rake @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +namespace :locations do + desc "Import default programme year groups for all schools and clinics managed by a team." + task import_default_programme_year_groups: :environment do + programmes = Programme.all + academic_year = AcademicYear.current + + Location + .where(type: %w[generic_clinic generic_school gias_school]) + .with_team(academic_year:) + .find_each do |location| + location.import_default_programme_year_groups!( + programmes, + academic_year: + ) + end + end +end diff --git a/lib/tasks/smoke.rake b/lib/tasks/smoke.rake index 15e45b9d21..d403da6a6a 100644 --- a/lib/tasks/smoke.rake +++ b/lib/tasks/smoke.rake @@ -6,7 +6,7 @@ namespace :smoke do Location.find_or_create_by!( name: "XXX Smoke Test School XXX", urn: "XXXXXX", - type: :school, + type: "gias_school", address_line_1: "1 Test Street", address_town: "Test Town", address_postcode: "TE1 1ST", @@ -19,7 +19,7 @@ namespace :smoke do Location.find_or_create_by!( name: "XXX Smoke Test GP XXX", ods_code: "Y90001", # https://digital.nhs.uk/developer/api-catalogue/personal-demographics-service-fhir/pds-fhir-api-test-data#production-smoke-testing - type: :gp_practice + type: "gp_practice" ) end end diff --git a/package.json b/package.json index 71c013014b..5d0fabd8c9 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,10 @@ "dependencies": { "@hotwired/turbo-rails": "^8.0.23", "accessible-autocomplete": "^3.0.1", - "esbuild": "^0.27.4", + "esbuild": "^0.28.0", "idb": "^8.0.3", "nhsuk-frontend": "^10.4.2", - "sass": "^1.98.0" + "sass": "^1.99.0" }, "scripts": { "build:css": "sass ./app/assets/stylesheets:./app/assets/builds --no-source-map --pkg-importer=node --quiet-deps --style compressed", diff --git a/script/generate_model_office_data.rb b/script/generate_model_office_data.rb index 7b6ca47bdf..1d4fef32b8 100644 --- a/script/generate_model_office_data.rb +++ b/script/generate_model_office_data.rb @@ -181,7 +181,7 @@ def write_vaccination_records_to_file(vaccination_records) if (school = vaccination_record.patient.school) school.name elsif !vaccination_record.patient.home_educated - vaccination_record.team.schools.all.sample.name + vaccination_record.team.gias_schools.all.sample.name end csv << [ diff --git a/spec/components/app_action_link_component_spec.rb b/spec/components/app_action_link_component_spec.rb new file mode 100644 index 0000000000..a7d095b6ee --- /dev/null +++ b/spec/components/app_action_link_component_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +describe AppActionLinkComponent do + subject(:rendered) { render_inline(component) } + + let(:component) { described_class.new(text:, href:) } + let(:text) { "Get started" } + let(:href) { "/get-started" } + + it { should have_css(".nhsuk-action-link") } + it { should have_css(".nhsuk-action-link__text", text: "Get started") } + + it "links to the correct href" do + expect(rendered).to have_css("a.nhsuk-action-link[href='/get-started']") + end + + it "renders the arrow icon" do + expect(rendered).to have_css( + "svg.nhsuk-icon.nhsuk-icon--arrow-right-circle" + ) + end + + it "marks the icon as decorative" do + expect(rendered).to have_css("svg[aria-hidden='true'][focusable='false']") + end + + context "when passing extra link options" do + let(:component) do + described_class.new( + text:, + href:, + target: "_blank", + rel: "noopener noreferrer" + ) + end + + it "passes target through to the link" do + expect(rendered).to have_css("a[target='_blank']") + end + + it "passes rel through to the link" do + expect(rendered).to have_css("a[rel='noopener noreferrer']") + end + end + + context "when passing an extra class" do + let(:component) do + described_class.new(text:, href:, class: "my-extra-class") + end + + it "includes both the default and extra classes" do + expect(rendered).to have_css("a.nhsuk-action-link.my-extra-class") + end + end + + context "when passing data attributes" do + let(:component) do + described_class.new(text:, href:, data: { turbo_method: :delete }) + end + + it "passes data attributes through to the link" do + expect(rendered).to have_css("a[data-turbo-method='delete']") + end + end +end diff --git a/spec/components/app_activity_log_component_spec.rb b/spec/components/app_activity_log_component_spec.rb index a1906a0579..a8c8285761 100644 --- a/spec/components/app_activity_log_component_spec.rb +++ b/spec/components/app_activity_log_component_spec.rb @@ -10,7 +10,9 @@ let(:programmes) { [Programme.hpv, Programme.flu, Programme.mmr] } let(:team) { create(:team, programmes:) } let(:user) { create(:user, team:, family_name: "Joy", given_name: "Nurse") } - let(:location) { create(:school, :secondary, name: "Hogwarts", programmes:) } + let(:location) do + create(:gias_school, :secondary, name: "Hogwarts", programmes:) + end let(:session) { create(:session, programmes:, location:) } let(:session_last_year) do create(:session, programmes:, location:, date: 1.year.ago.to_date) diff --git a/spec/components/app_child_summary_component_spec.rb b/spec/components/app_child_summary_component_spec.rb index 7d2fd363bf..58c6dec2d5 100644 --- a/spec/components/app_child_summary_component_spec.rb +++ b/spec/components/app_child_summary_component_spec.rb @@ -5,9 +5,9 @@ let(:component) { described_class.new(patient) } - let(:school) { create(:school, name: "Test School") } + let(:school) { create(:gias_school, name: "Test School") } let(:gp_practice) { nil } - let(:other_school) { create(:school, name: "Other School") } + let(:other_school) { create(:gias_school, name: "Other School") } let(:parent) { create(:parent, full_name: "Mark Doe") } let(:restricted) { false } let(:patient) do diff --git a/spec/components/app_compare_consent_form_and_patient_component_spec.rb b/spec/components/app_compare_consent_form_and_patient_component_spec.rb index 54de87e1ff..d8dcd926d1 100644 --- a/spec/components/app_compare_consent_form_and_patient_component_spec.rb +++ b/spec/components/app_compare_consent_form_and_patient_component_spec.rb @@ -15,7 +15,7 @@ address_line_2: "Area", address_town: "Some Town", address_postcode: "SW11 1AA", - school: build(:school, name: "Waterloo Road") + school: build(:gias_school, name: "Waterloo Road") ) end @@ -51,7 +51,7 @@ address_line_2: consent_form.address_line_2, address_town: consent_form.address_town, address_postcode: consent_form.address_postcode, - school: build(:school, name: "Hogwarts") + school: build(:gias_school, name: "Hogwarts") ) end diff --git a/spec/components/app_consent_card_component_spec.rb b/spec/components/app_consent_card_component_spec.rb index 405b908d5d..c8177e3270 100644 --- a/spec/components/app_consent_card_component_spec.rb +++ b/spec/components/app_consent_card_component_spec.rb @@ -18,7 +18,7 @@ submitted_at: Time.zone.local(2024, 1, 1) ) end - let(:school) { create(:school, name: "Waterloo Road", team:) } + let(:school) { create(:gias_school, name: "Waterloo Road", team:) } let(:session) do create(:session, programmes: [programme], team:, location: school) end diff --git a/spec/components/app_consent_patient_summary_component_spec.rb b/spec/components/app_consent_patient_summary_component_spec.rb index 1b9ba70763..f5ce275a06 100644 --- a/spec/components/app_consent_patient_summary_component_spec.rb +++ b/spec/components/app_consent_patient_summary_component_spec.rb @@ -9,7 +9,7 @@ let(:team) { create(:team, programmes: [programme]) } let(:consent) { create(:consent, patient:, consent_form:, programme:, team:) } - let(:school) { create(:school, name: "Waterloo Road", team:) } + let(:school) { create(:gias_school, name: "Waterloo Road", team:) } let(:session) do create(:session, programmes: [programme], team:, location: school) end diff --git a/spec/components/app_import_format_details_component_spec.rb b/spec/components/app_import_format_details_component_spec.rb index 622826503b..655345fe13 100644 --- a/spec/components/app_import_format_details_component_spec.rb +++ b/spec/components/app_import_format_details_component_spec.rb @@ -4,36 +4,10 @@ let(:programme) { Programme.hpv } let(:team) { create(:team, programmes: [programme]) } - it "renders the correct summary text for ClassImport" do + it "renders the correct summary text" do import = ClassImport.new(team:) render_inline(described_class.new(import:)) - expect(page).to have_content( - "How to format your Mavis CSV file for class lists" - ) - end - - it "renders the correct summary text for CohortImport" do - import = CohortImport.new(team:) - render_inline(described_class.new(import:)) - expect(page).to have_content( - "How to format your Mavis CSV file for child records" - ) - end - - it "renders the correct summary text for ImmunisationImport" do - import = ImmunisationImport.new(team:) - render_inline(described_class.new(import:)) - expect(page).to have_content( - "How to format your Mavis CSV file for vaccination records" - ) - end - - it "raises an error for unsupported import types" do - import = Object.new - expect { render_inline(described_class.new(import:)) }.to raise_error( - ArgumentError, - "Unsupported import type: Object" - ) + expect(page).to have_content("What your CSV file must include") end it "renders the correct columns for ClassImport" do @@ -83,14 +57,6 @@ context "with a national reporting team" do let(:team) { create(:team, :national_reporting, programmes: [programme]) } - it "renders the correct summary text for ImmunisationImport" do - import = ImmunisationImport.new(team:) - render_inline(described_class.new(import:)) - expect(page).to have_content( - "How to format your CSV file for vaccination records" - ) - end - it "renders the correct columns for ImmunisationImport" do import = ImmunisationImport.new(team:) render_inline(described_class.new(import:)) diff --git a/spec/components/app_import_review_component_spec.rb b/spec/components/app_import_review_component_spec.rb index b40298a33c..490a36d130 100644 --- a/spec/components/app_import_review_component_spec.rb +++ b/spec/components/app_import_review_component_spec.rb @@ -18,11 +18,13 @@ let(:team) { create(:team) } let(:user) { create(:user, team:) } - let(:location) { create(:school, team:, name: "Test School") } - let(:second_location) { create(:school, team:, name: "Second Test School") } + let(:location) { create(:gias_school, team:, name: "Test School") } + let(:second_location) do + create(:gias_school, team:, name: "Second Test School") + end let(:other_team) { create(:team) } let(:other_location) do - create(:school, team: other_team, name: "Other Test School") + create(:gias_school, team: other_team, name: "Other Test School") end let(:import) { create(:cohort_import, team:, uploaded_by: user) } @@ -327,7 +329,7 @@ describe "with skipped school moves" do let(:other_team_school) do - create(:school, team: other_team, name: "School in Other Team") + create(:gias_school, team: other_team, name: "School in Other Team") end let(:skipped_patient) do create(:patient, school: other_team_school, team: other_team) diff --git a/spec/components/app_import_review_school_moves_summary_component_spec.rb b/spec/components/app_import_review_school_moves_summary_component_spec.rb index 7dcf055ca8..8fdc20bfa1 100644 --- a/spec/components/app_import_review_school_moves_summary_component_spec.rb +++ b/spec/components/app_import_review_school_moves_summary_component_spec.rb @@ -10,7 +10,7 @@ let(:changesets) { [] } describe "table structure" do - let(:school) { create(:school, team:, name: "Test School") } + let(:school) { create(:gias_school, team:, name: "Test School") } let(:patient) { create(:patient, school:) } let(:changesets) do [create(:patient_changeset, import:, patient:, row_number: 1)] @@ -28,8 +28,8 @@ end describe "with school move to different school (same team)" do - let(:current_school) { create(:school, team:, name: "Current School") } - let(:destination_school) { create(:school, team:, name: "New School") } + let(:current_school) { create(:gias_school, team:, name: "Current School") } + let(:destination_school) { create(:gias_school, team:, name: "New School") } let(:patient) do create( :patient, @@ -90,12 +90,12 @@ describe "with inter-team school move" do let(:other_team) { create(:team, name: "Other Team") } let(:current_school) do - create(:school, name: "Current School").tap do |s| + create(:gias_school, name: "Current School").tap do |s| s.attach_to_team!(other_team, academic_year:) end end let(:destination_school) do - create(:school, name: "Destination School").tap do |s| + create(:gias_school, name: "Destination School").tap do |s| s.attach_to_team!(team, academic_year:) end end @@ -127,7 +127,7 @@ end describe "with move to home educated" do - let(:current_school) { create(:school, team:, name: "Current School") } + let(:current_school) { create(:gias_school, team:, name: "Current School") } let(:patient) { create(:patient, school: current_school) } let(:changesets) do [create(:patient_changeset, import:, patient:, row_number: 2)] @@ -151,7 +151,7 @@ end describe "with unknown destination school" do - let(:current_school) { create(:school, team:, name: "Current School") } + let(:current_school) { create(:gias_school, team:, name: "Current School") } let(:patient) { create(:patient, school: current_school) } let(:changesets) do [create(:patient_changeset, import:, patient:, row_number: 1)] @@ -171,7 +171,7 @@ end describe "with changeset not from file (no row_number)" do - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let(:patient) { create(:patient, school:) } let(:changesets) do [create(:patient_changeset, import:, patient:, row_number: nil)] @@ -197,7 +197,7 @@ end describe "with patient without NHS number" do - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let(:patient) { create(:patient, school:, nhs_number: nil) } let(:changesets) do [create(:patient_changeset, import:, patient:, row_number: 1)] @@ -217,7 +217,7 @@ end describe "with patient without school" do - let(:destination_school) { create(:school, team:, name: "New School") } + let(:destination_school) { create(:gias_school, team:, name: "New School") } let(:patient) { create(:patient, school: nil) } let(:changesets) do [create(:patient_changeset, import:, patient:, row_number: 1)] @@ -241,8 +241,8 @@ end describe "with multiple changesets" do - let(:school_a) { create(:school, team:, name: "School A") } - let(:school_b) { create(:school, team:, name: "School B") } + let(:school_a) { create(:gias_school, team:, name: "School A") } + let(:school_b) { create(:gias_school, team:, name: "School B") } let(:patient_a) do create( :patient, @@ -311,12 +311,12 @@ describe "school with multiple teams" do let(:team2) { create(:team, name: "Team 2") } let(:current_school) do - create(:school, name: "Multi-Team School").tap do |s| + create(:gias_school, name: "Multi-Team School").tap do |s| s.attach_to_team!(team, academic_year:) end end let(:destination_school) do - create(:school, name: "Destination School").tap do |s| + create(:gias_school, name: "Destination School").tap do |s| s.attach_to_team!(team2, academic_year:) end end diff --git a/spec/components/app_imports_table_component_spec.rb b/spec/components/app_imports_table_component_spec.rb index b92c8b1cd6..2d9230ceb2 100644 --- a/spec/components/app_imports_table_component_spec.rb +++ b/spec/components/app_imports_table_component_spec.rb @@ -11,7 +11,7 @@ let(:programmes) { [Programme.sample] } let(:team) { create(:team, programmes:) } - let(:school) { create(:school, team:, name: "Test School") } + let(:school) { create(:gias_school, team:, name: "Test School") } let(:session) { create(:session, programmes:, location: school) } let(:uploaded_files) { false } let(:mixnmatch_imports) do diff --git a/spec/components/app_location_card_component_spec.rb b/spec/components/app_location_card_component_spec.rb index 61f26a930f..8f7a896825 100644 --- a/spec/components/app_location_card_component_spec.rb +++ b/spec/components/app_location_card_component_spec.rb @@ -24,7 +24,7 @@ context "with a secondary school" do let(:location) do create( - :school, + :gias_school, :secondary, urn: "123456", name: "Waterloo Road", diff --git a/spec/components/app_parent_summary_component_spec.rb b/spec/components/app_parent_summary_component_spec.rb deleted file mode 100644 index bb90d1a614..0000000000 --- a/spec/components/app_parent_summary_component_spec.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -describe AppParentSummaryComponent do - subject(:rendered) { render_inline(component) } - - let(:component) { described_class.new(parent_relationship:) } - - let(:parent) { create(:parent, full_name: "John Smith") } - let(:patient) { create(:patient) } - let(:parent_relationship) do - create(:parent_relationship, :father, parent:, patient:) - end - - it { should have_content("Name") } - it { should have_content("John Smith") } - - it { should have_content("Relationship") } - it { should have_content("Dad") } - - context "with an email address" do - let(:parent) { create(:parent, email: "test@example.com") } - - it { should have_content("Email address") } - it { should have_content("test@example.com") } - - context "with a delivery failure" do - before do - create( - :notify_log_entry, - :email, - :permanent_failure, - parent:, - recipient: parent.email - ) - end - - it { should have_content("Email address does not exist") } - end - end - - context "with a phone number" do - let(:parent) { create(:parent, phone: "07987654321") } - - it { should have_content("Phone number") } - it { should have_content("07987 654321") } - - context "with a delivery failure" do - before do - create( - :notify_log_entry, - :sms, - :permanent_failure, - parent:, - recipient: parent.phone - ) - end - - it { should have_content("Phone number does not exist") } - end - end - - context "when the patient is restricted" do - let(:patient) { create(:patient, :restricted) } - - it { should_not have_content("Email address") } - it { should_not have_content("Phone number") } - end - - it { should_not have_content("Change") } - - context "with change links" do - let(:component) { described_class.new(parent_relationship:, change_links:) } - - let(:change_links) do - { - name: "/name", - relationship: "/relationship", - email: "/email", - phone: "/phone" - } - end - - it { should have_content("Change name") } - it { should have_content("Change relationship") } - it { should have_content("Change email address") } - it { should have_content("Change phone number") } - end -end diff --git a/spec/components/app_patient_card_component_spec.rb b/spec/components/app_patient_card_component_spec.rb index cc9970dc01..a4817aa20a 100644 --- a/spec/components/app_patient_card_component_spec.rb +++ b/spec/components/app_patient_card_component_spec.rb @@ -13,7 +13,7 @@ let(:programmes) { [Programme.hpv] } let(:team) { create(:team, programmes:) } let(:session) { create(:session, team:, programmes:) } - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let(:patient) { create(:patient, school:, year_group: 8) } @@ -53,7 +53,7 @@ context "with a patient that has moved teams" do let(:other_team) { create(:team, programmes:) } - let(:other_school) { create(:school, team: other_team) } + let(:other_school) { create(:gias_school, team: other_team) } let(:school_move) do create(:school_move, :to_school, patient:, school: other_school) end diff --git a/spec/components/app_patient_programme_sessions_component_spec.rb b/spec/components/app_patient_programme_sessions_component_spec.rb index 956fd1afe7..47a415efd8 100644 --- a/spec/components/app_patient_programme_sessions_component_spec.rb +++ b/spec/components/app_patient_programme_sessions_component_spec.rb @@ -21,7 +21,12 @@ let(:programmes) { [Programme.hpv, Programme.mmr, Programme.flu] } let(:location) do - create(:school, name: "Waterloo Road", programmes:, academic_year: 2024) + create( + :gias_school, + name: "Waterloo Road", + programmes:, + academic_year: 2024 + ) end let(:session) do create( @@ -53,7 +58,7 @@ context "with multiple sessions" do let(:other_location) do create( - :school, + :gias_school, name: "Paddington Road", programmes: other_programmes, academic_year: 2024 diff --git a/spec/components/app_patient_programme_vaccination_component_spec.rb b/spec/components/app_patient_programme_vaccination_component_spec.rb index 8bce2cbb51..20a56cbf68 100644 --- a/spec/components/app_patient_programme_vaccination_component_spec.rb +++ b/spec/components/app_patient_programme_vaccination_component_spec.rb @@ -15,7 +15,7 @@ context "with a vaccination record" do let(:location) do create( - :school, + :gias_school, name: "Test School", address_line_1: "Waterloo Road", address_town: "London", diff --git a/spec/components/app_patient_search_result_card_component_spec.rb b/spec/components/app_patient_search_result_card_component_spec.rb index 3c644ca582..806fff7dca 100644 --- a/spec/components/app_patient_search_result_card_component_spec.rb +++ b/spec/components/app_patient_search_result_card_component_spec.rb @@ -11,7 +11,7 @@ family_name: "Seldon", given_name: "Hari", nhs_number: "9000000009", - school: build(:school, name: "Streeling University") + school: build(:gias_school, name: "Streeling University") ) end diff --git a/spec/components/app_patient_session_search_result_card_component_spec.rb b/spec/components/app_patient_session_search_result_card_component_spec.rb index 3ce59fb2c0..4d54246449 100644 --- a/spec/components/app_patient_session_search_result_card_component_spec.rb +++ b/spec/components/app_patient_session_search_result_card_component_spec.rb @@ -14,7 +14,7 @@ family_name: "Seldon", address_postcode: "SW11 1AA", year_group: 8, - school: build(:school, name: "Streeling University"), + school: build(:gias_school, name: "Streeling University"), session: ) end diff --git a/spec/components/app_patient_vaccination_table_component_spec.rb b/spec/components/app_patient_vaccination_table_component_spec.rb index 01dd9ba2e9..80842b7c8b 100644 --- a/spec/components/app_patient_vaccination_table_component_spec.rb +++ b/spec/components/app_patient_vaccination_table_component_spec.rb @@ -17,7 +17,7 @@ context "with a vaccination record" do let(:location) do create( - :school, + :gias_school, name: "Test School", address_line_1: "Waterloo Road", address_town: "London", diff --git a/spec/components/app_programme_session_table_component_spec.rb b/spec/components/app_programme_session_table_component_spec.rb index 0d5272795b..9724b4066a 100644 --- a/spec/components/app_programme_session_table_component_spec.rb +++ b/spec/components/app_programme_session_table_component_spec.rb @@ -8,7 +8,7 @@ let(:programme) { Programme.sample } let(:academic_year) { AcademicYear.current } let(:location) do - create(:school, name: "Waterloo Road", programmes: [programme]) + create(:gias_school, name: "Waterloo Road", programmes: [programme]) end let(:session) { create(:session, programmes: [programme], location:) } let(:sessions) do diff --git a/spec/components/app_school_card_component_spec.rb b/spec/components/app_school_card_component_spec.rb index 90df6d6ff4..0ba30674f5 100644 --- a/spec/components/app_school_card_component_spec.rb +++ b/spec/components/app_school_card_component_spec.rb @@ -9,7 +9,7 @@ let(:school) do create( - :school, + :gias_school, :secondary, name: "Streeling University", urn: "123456", diff --git a/spec/components/app_school_summary_component_spec.rb b/spec/components/app_school_summary_component_spec.rb index 9594b48184..4430470369 100644 --- a/spec/components/app_school_summary_component_spec.rb +++ b/spec/components/app_school_summary_component_spec.rb @@ -11,7 +11,7 @@ let(:academic_year) { AcademicYear.pending } let(:school) do create( - :school, + :gias_school, :secondary, name: "Streeling University", urn: "123456", diff --git a/spec/components/app_session_overview_component_spec.rb b/spec/components/app_session_overview_component_spec.rb index 4b76255ab6..7432b173b1 100644 --- a/spec/components/app_session_overview_component_spec.rb +++ b/spec/components/app_session_overview_component_spec.rb @@ -171,7 +171,7 @@ create( :vaccination_record, patient:, - location: create(:school, name: "Hogwarts"), + location: create(:gias_school, name: "Hogwarts"), programme: hpv_programme ) @@ -186,7 +186,7 @@ create( :vaccination_record, patient:, - location: create(:school, name: "Hogwarts"), + location: create(:gias_school, name: "Hogwarts"), programme: hpv_programme, outcome: "refused" ) @@ -196,7 +196,7 @@ end context "with multiple patients and one was vaccinated for HPV elsewhere" do - let(:other_school) { create(:school) } + let(:other_school) { create(:gias_school) } before do create( diff --git a/spec/components/app_session_summary_component_spec.rb b/spec/components/app_session_summary_component_spec.rb index 6da11c6ec2..0939981295 100644 --- a/spec/components/app_session_summary_component_spec.rb +++ b/spec/components/app_session_summary_component_spec.rb @@ -8,7 +8,7 @@ let(:programmes) { [Programme.hpv] } let(:location) do create( - :school, + :gias_school, :secondary, name: "Streeling University", urn: 123_456, diff --git a/spec/components/app_vaccination_record_summary_component_spec.rb b/spec/components/app_vaccination_record_summary_component_spec.rb index d3e40398ac..c1d6b85b6b 100644 --- a/spec/components/app_vaccination_record_summary_component_spec.rb +++ b/spec/components/app_vaccination_record_summary_component_spec.rb @@ -8,7 +8,7 @@ let(:current_user) { create(:user) } let(:performed_at) { Time.zone.local(2024, 9, 6, 12) } let(:outcome) { "administered" } - let(:location) { create(:school, name: "Hogwarts") } + let(:location) { create(:gias_school, name: "Hogwarts") } let(:programme) { Programme.hpv } let(:team) { create(:team, programmes: [programme]) } let(:session) { create(:session, programmes: [programme], location:, team:) } diff --git a/spec/controllers/api/reporting/totals_controller_spec.rb b/spec/controllers/api/reporting/totals_controller_spec.rb index de1a3873d0..969d80a533 100644 --- a/spec/controllers/api/reporting/totals_controller_spec.rb +++ b/spec/controllers/api/reporting/totals_controller_spec.rb @@ -65,7 +65,9 @@ expect(not_vaccinated).to eq(1) # patient3 expect(vaccinated + not_vaccinated).to eq(cohort) - expect(parsed_response["vaccinations_given"]).to eq(2) # 2 administered records + expect(parsed_response["vaccinations_given"]).to eq( + { "school_count" => 2, "community_count" => 0 } + ) expect(parsed_response["monthly_vaccinations_given"]).to be_an(Array) end @@ -169,8 +171,8 @@ team.programmes << programme session = create(:session, team:, programmes: [programme]) - school_one = create(:school, name: "School One", urn: "111111") - school_two = create(:school, name: "School Two", urn: "222222") + school_one = create(:gias_school, name: "School One", urn: "111111") + school_two = create(:gias_school, name: "School Two", urn: "222222") create(:patient, session:, school: school_one) patient2 = create(:patient, session:, school: school_two) @@ -226,7 +228,7 @@ team.programmes << programme session = create(:session, team:, programmes: [programme]) - school = create(:school, name: "Test School", urn: "123456") + school = create(:gias_school, name: "Test School", urn: "123456") patient1 = create( @@ -304,8 +306,8 @@ team.programmes << programme session = create(:session, team:, programmes: [programme]) - school_one = create(:school, name: "School One", urn: "111111") - school_two = create(:school, name: "School Two", urn: "222222") + school_one = create(:gias_school, name: "School One", urn: "111111") + school_two = create(:gias_school, name: "School Two", urn: "222222") create(:patient, session:, school: school_one) create(:patient, session:, school: school_two) @@ -335,9 +337,9 @@ session = create(:session, team:, programmes: [programme]) school_one = - create(:school, name: "School One", gias_local_authority_code: 201) + create(:gias_school, name: "School One", gias_local_authority_code: 201) school_two = - create(:school, name: "School Two", gias_local_authority_code: 202) + create(:gias_school, name: "School Two", gias_local_authority_code: 202) create(:patient, session:, school: school_one) create(:patient, session:, school: school_two) @@ -369,7 +371,11 @@ # Patient with school that has LA school = - create(:school, name: "Known School", gias_local_authority_code: 201) + create( + :gias_school, + name: "Known School", + gias_local_authority_code: 201 + ) create(:patient, session:, school:) # Home-educated patient without postcode LA lookup @@ -495,7 +501,9 @@ def refresh_and_get_totals(programme_type: "hpv") expect(cohort).to eq(1) expect(vaccinated).to eq(1) expect(not_vaccinated).to eq(0) - expect(vaccinations_given).to eq(1) + expect(vaccinations_given).to eq( + { "school_count" => 1, "community_count" => 0 } + ) monthly = monthly_vaccinations_given.find do @@ -503,7 +511,8 @@ def refresh_and_get_totals(programme_type: "hpv") it["month"] == Date::MONTHNAMES[Time.current.month] end expect(monthly).to be_present - expect(monthly["count"]).to eq(1) + expect(monthly["school_count"]).to eq(1) + expect(monthly["community_count"]).to eq(0) end it "counts vaccination in correct month when performed during BST" do @@ -525,7 +534,8 @@ def refresh_and_get_totals(programme_type: "hpv") end expect(monthly).to be_present, "Expected vaccination to be counted in September, not August" - expect(monthly["count"]).to eq(1) + expect(monthly["school_count"]).to eq(1) + expect(monthly["community_count"]).to eq(0) end it "child archived after being vaccinated by SAIS" do @@ -613,7 +623,9 @@ def refresh_and_get_totals(programme_type: "hpv") expect(cohort).to eq(1) expect(vaccinated).to eq(1) expect(not_vaccinated).to eq(0) - expect(vaccinations_given).to eq(0) + expect(vaccinations_given).to eq( + { "school_count" => 0, "community_count" => 0 } + ) expect(monthly_vaccinations_given).to be_empty end @@ -634,7 +646,9 @@ def refresh_and_get_totals(programme_type: "hpv") expect(cohort).to eq(1) expect(vaccinated).to eq(1) expect(not_vaccinated).to eq(0) - expect(vaccinations_given).to eq(0) + expect(vaccinations_given).to eq( + { "school_count" => 0, "community_count" => 0 } + ) expect(monthly_vaccinations_given).to be_empty end @@ -658,7 +672,9 @@ def refresh_and_get_totals(programme_type: "hpv") expect(vaccinated).to eq(0) expect(not_vaccinated).to eq(1) - expect(vaccinations_given).to eq(0) + expect(vaccinations_given).to eq( + { "school_count" => 0, "community_count" => 0 } + ) expect(monthly_vaccinations_given).to be_empty end @@ -678,7 +694,9 @@ def refresh_and_get_totals(programme_type: "hpv") expect(cohort).to eq(1) expect(vaccinated).to eq(1) expect(not_vaccinated).to eq(0) - expect(vaccinations_given).to eq(0) + expect(vaccinations_given).to eq( + { "school_count" => 0, "community_count" => 0 } + ) expect(monthly_vaccinations_given).to be_empty end @@ -702,7 +720,9 @@ def refresh_and_get_totals(programme_type: "hpv") expect(cohort).to eq(1) expect(vaccinated).to eq(1) expect(not_vaccinated).to eq(0) - expect(vaccinations_given).to eq(0) + expect(vaccinations_given).to eq( + { "school_count" => 0, "community_count" => 0 } + ) expect(monthly_vaccinations_given).to be_empty end @@ -734,7 +754,9 @@ def refresh_and_get_totals(programme_type: "hpv") expect(cohort).to eq(1) expect(vaccinated).to eq(1) expect(not_vaccinated).to eq(0) - expect(vaccinations_given).to eq(0) + expect(vaccinations_given).to eq( + { "school_count" => 0, "community_count" => 0 } + ) expect(monthly_vaccinations_given).to be_empty end @@ -759,7 +781,9 @@ def refresh_and_get_totals(programme_type: "hpv") # flu is seasonal so vaccinations from a previous year don't count expect(vaccinated).to eq(0) expect(not_vaccinated).to eq(1) - expect(vaccinations_given).to eq(0) + expect(vaccinations_given).to eq( + { "school_count" => 0, "community_count" => 0 } + ) expect(monthly_vaccinations_given).to be_empty end @@ -779,7 +803,9 @@ def refresh_and_get_totals(programme_type: "hpv") expect(cohort).to eq(1) expect(vaccinated).to eq(0) expect(not_vaccinated).to eq(1) - expect(vaccinations_given).to eq(0) + expect(vaccinations_given).to eq( + { "school_count" => 0, "community_count" => 0 } + ) expect(monthly_vaccinations_given).to be_empty end @@ -800,7 +826,9 @@ def refresh_and_get_totals(programme_type: "hpv") expect(cohort).to eq(1) expect(vaccinated).to eq(0) expect(not_vaccinated).to eq(1) - expect(vaccinations_given).to eq(0) + expect(vaccinations_given).to eq( + { "school_count" => 0, "community_count" => 0 } + ) expect(monthly_vaccinations_given).to be_empty end @@ -821,7 +849,9 @@ def refresh_and_get_totals(programme_type: "hpv") expect(cohort).to eq(1) expect(vaccinated).to eq(1) expect(not_vaccinated).to eq(0) - expect(vaccinations_given).to eq(0) + expect(vaccinations_given).to eq( + { "school_count" => 0, "community_count" => 0 } + ) expect(monthly_vaccinations_given).to be_empty end @@ -854,7 +884,9 @@ def refresh_and_get_totals(programme_type: "hpv") expect(cohort).to eq(1) expect(vaccinated).to eq(1) expect(not_vaccinated).to eq(0) - expect(vaccinations_given).to eq(0) + expect(vaccinations_given).to eq( + { "school_count" => 0, "community_count" => 0 } + ) expect(monthly_vaccinations_given).to be_empty end @@ -878,7 +910,9 @@ def refresh_and_get_totals(programme_type: "hpv") expect(cohort).to eq(1) expect(vaccinated).to eq(1) expect(not_vaccinated).to eq(0) - expect(vaccinations_given).to eq(0) + expect(vaccinations_given).to eq( + { "school_count" => 0, "community_count" => 0 } + ) expect(monthly_vaccinations_given).to be_empty end @@ -886,7 +920,7 @@ def refresh_and_get_totals(programme_type: "hpv") other_org = create(:organisation) other_team = create(:team, programmes: [flu_programme], organisation: other_org) - other_school = create(:school, team: other_team) + other_school = create(:gias_school, team: other_team) patient = create(:patient, session: flu_session) create( @@ -913,14 +947,68 @@ def refresh_and_get_totals(programme_type: "hpv") # Child moved out, but vaccination was given by this team # so it should be counted as given - expect(vaccinations_given).to eq(1) + expect(vaccinations_given).to eq( + { "school_count" => 1, "community_count" => 0 } + ) + monthly = + monthly_vaccinations_given.find do + it["year"] == Time.current.year && + it["month"] == Date::MONTHNAMES[Time.current.month] + end + expect(monthly).to be_present + expect(monthly["school_count"]).to eq(1) + expect(monthly["community_count"]).to eq(0) + end + + it "splits vaccinations given by school and community location" do + school_patient = create(:patient, session: hpv_session) + create( + :vaccination_record, + patient: school_patient, + programme: hpv_programme, + session: hpv_session, + outcome: "administered", + performed_at: Time.current + ) + + community_clinic = + create( + :community_clinic, + team:, + programmes: [hpv_programme], + academic_year: AcademicYear.current + ) + community_session = + create( + :session, + team:, + location: community_clinic, + programmes: [hpv_programme] + ) + community_patient = create(:patient, session: community_session) + create( + :vaccination_record, + patient: community_patient, + programme: hpv_programme, + session: community_session, + outcome: "administered", + performed_at: Time.current + ) + + refresh_and_get_totals + + expect(vaccinations_given).to eq( + { "school_count" => 1, "community_count" => 1 } + ) + monthly = monthly_vaccinations_given.find do it["year"] == Time.current.year && it["month"] == Date::MONTHNAMES[Time.current.month] end expect(monthly).to be_present - expect(monthly["count"]).to eq(1) + expect(monthly["school_count"]).to eq(1) + expect(monthly["community_count"]).to eq(1) end it "counts HPV cohort correctly across years 8 to 11" do @@ -981,7 +1069,7 @@ def refresh_and_get_totals(programme_type: "hpv") it "counts year 12 students when session location has year 12 enabled" do send_location = create( - :school, + :gias_school, gias_year_groups: (0..12).to_a, team:, programmes: [flu_programme], diff --git a/spec/controllers/api/testing/teams_controller_locations_spec.rb b/spec/controllers/api/testing/teams_controller_locations_spec.rb index 9f39598b32..aa45fd47ce 100644 --- a/spec/controllers/api/testing/teams_controller_locations_spec.rb +++ b/spec/controllers/api/testing/teams_controller_locations_spec.rb @@ -12,11 +12,11 @@ let(:team) { create(:team, workgroup: "r1l") } let!(:base_location) do - create(:school, team:, name: "Hogwarts", urn: "123456") + create(:gias_school, team:, name: "Hogwarts", urn: "123456") end let!(:site_location) do - create(:school, team:, name: "Hogwarts 2", urn: "123456", site: "B") + create(:gias_school, team:, name: "Hogwarts 2", urn: "123456", site: "B") end context "when keep_base_locations is true" do @@ -48,7 +48,7 @@ end it "deletes all locations" do - expect { call }.to change(Location.school, :count).by(-2) + expect { call }.to change(Location.gias_school, :count).by(-2) expect { Location.find(base_location.id) }.to raise_error( ActiveRecord::RecordNotFound diff --git a/spec/controllers/api/testing/teams_controller_spec.rb b/spec/controllers/api/testing/teams_controller_spec.rb index 664aa1ca6b..6a75ae8db3 100644 --- a/spec/controllers/api/testing/teams_controller_spec.rb +++ b/spec/controllers/api/testing/teams_controller_spec.rb @@ -42,11 +42,11 @@ end end - create(:school, urn: "123456", team:, programmes:) # to match cohort_import/valid.csv - create(:school, urn: "110158", team:, programmes:) # to match valid_hpv.csv + create(:gias_school, urn: "123456", team:, programmes:) # to match cohort_import/valid.csv + create(:gias_school, urn: "110158", team:, programmes:) # to match valid_hpv.csv session = - create(:session, team:, location: team.schools.first, programmes:) + create(:session, team:, location: team.gias_schools.first, programmes:) process_and_approve_import(cohort_import) immunisation_import.process! diff --git a/spec/controllers/consent_forms_controller_spec.rb b/spec/controllers/consent_forms_controller_spec.rb index 4f220b95e2..e19e90808f 100644 --- a/spec/controllers/consent_forms_controller_spec.rb +++ b/spec/controllers/consent_forms_controller_spec.rb @@ -4,7 +4,7 @@ let(:programme) { Programme.hpv } let(:team) { create(:team, programmes: [programme]) } let(:user) { create(:user, :nurse, team:) } - let(:location) { create(:school, team:, programmes: [programme]) } + let(:location) { create(:gias_school, team:, programmes: [programme]) } let(:session) { create(:session, team:, location:, programmes: [programme]) } before { sign_in user } diff --git a/spec/factories/consent_forms.rb b/spec/factories/consent_forms.rb index 5579c8c75c..783008775e 100644 --- a/spec/factories/consent_forms.rb +++ b/spec/factories/consent_forms.rb @@ -117,7 +117,9 @@ session&.team_location || location.attach_to_team!(team, academic_year:) end - school { location.school? ? location : association(:school, team:) } + school do + location.gias_school? ? location : association(:gias_school, team:) + end school_confirmed { true } ethnic_group { ConsentForm.ethnic_backgrounds_by_group.keys.sample } diff --git a/spec/factories/important_notices.rb b/spec/factories/important_notices.rb index 54feeca56d..928e4593e8 100644 --- a/spec/factories/important_notices.rb +++ b/spec/factories/important_notices.rb @@ -45,7 +45,7 @@ if notice.type == "gillick_no_notify" && notice.vaccination_record.nil? notice.vaccination_record = build(:vaccination_record) elsif notice.type == "team_changed" && notice.school_move_log_entry.nil? - school = create(:school, team: Team.find(notice.team_id)) + school = create(:gias_school, team: Team.find(notice.team_id)) notice.school_move_log_entry = build(:school_move_log_entry, school:) end end diff --git a/spec/factories/locations.rb b/spec/factories/locations.rb index a6fd6ec251..6eba909ae0 100644 --- a/spec/factories/locations.rb +++ b/spec/factories/locations.rb @@ -133,25 +133,8 @@ end end - factory :gp_practice do - type { "gp_practice" } - name { "#{Faker::University.name} Practice" } - with_address - - sequence(:ods_code, 100) { "GP#{it}" } - - after(:create) do |location, evaluator| - if (team = evaluator.team) - academic_year = evaluator.academic_year - subteam = evaluator.subteam - - team.team_locations.create!(location:, academic_year:, subteam:) - end - end - end - - factory :school do - type { "school" } + factory :gias_school do + type { "gias_school" } name { Faker::Educator.primary_school } with_address @@ -204,5 +187,22 @@ ) end end + + factory :gp_practice do + type { "gp_practice" } + name { "#{Faker::University.name} Practice" } + with_address + + sequence(:ods_code, 100) { "GP#{it}" } + + after(:create) do |location, evaluator| + if (team = evaluator.team) + academic_year = evaluator.academic_year + subteam = evaluator.subteam + + team.team_locations.create!(location:, academic_year:, subteam:) + end + end + end end end diff --git a/spec/factories/school_move_log_entries.rb b/spec/factories/school_move_log_entries.rb index d173aaed56..217963aaa4 100644 --- a/spec/factories/school_move_log_entries.rb +++ b/spec/factories/school_move_log_entries.rb @@ -32,7 +32,7 @@ user home_educated { nil } - school + school { association(:gias_school) } trait :home_educated do home_educated { true } diff --git a/spec/factories/school_moves.rb b/spec/factories/school_moves.rb index c1087f0d4c..b22f76b0d1 100644 --- a/spec/factories/school_moves.rb +++ b/spec/factories/school_moves.rb @@ -17,13 +17,11 @@ # index_school_moves_on_patient_id (patient_id) UNIQUE # index_school_moves_on_patient_id_and_school_id (patient_id,school_id) # index_school_moves_on_school_id (school_id) -# index_school_moves_on_team_id (team_id) # # Foreign Keys # # fk_rails_... (patient_id => patients.id) # fk_rails_... (school_id => locations.id) -# fk_rails_... (team_id => teams.id) # FactoryBot.define do factory :school_move do @@ -35,7 +33,7 @@ source { SchoolMove.sources.keys.sample } trait :to_school do - school + school { association(:gias_school) } end trait :to_home_educated do diff --git a/spec/factories/sessions.rb b/spec/factories/sessions.rb index e425ae9be6..896277c519 100644 --- a/spec/factories/sessions.rb +++ b/spec/factories/sessions.rb @@ -34,7 +34,7 @@ academic_year { (dates.first || Date.current).academic_year } team { association(:team, programmes:) } - location { association(:school, programmes:, academic_year:) } + location { association(:gias_school, programmes:, academic_year:) } end sequence(:slug) { |n| "session-#{n}" } diff --git a/spec/factories/users.rb b/spec/factories/users.rb index ac9f3745da..f83700df17 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -126,12 +126,24 @@ activity_codes do [ CIS2Info::VIEW_SHARED_NON_PATIENT_IDENTIFIABLE_INFORMATION_ACTIVITY_CODE, - CIS2Info::ACCESS_SENSITIVE_FLAGGED_RECORDS_ACTIVITY_CODE, CIS2Info::VIEW_DETAILED_HEALTH_RECORDS_ACTIVITY_CODE ] end end + trait :support_no_pii do + team { create(:team, ods_code: "Y90128") } + role_code { CIS2Info::SUPPORT_ROLE } + sequence(:email) { |n| "support-no-pii#{n}@example.com" } + role_workgroups { [CIS2Info::SUPPORT_WORKGROUP] } + fallback_role { :support } + activity_codes do + [ + CIS2Info::VIEW_SHARED_NON_PATIENT_IDENTIFIABLE_INFORMATION_ACTIVITY_CODE + ] + end + end + trait :signed_in do current_sign_in_at { Time.current } current_sign_in_ip { "127.0.0.1" } diff --git a/spec/factories/vaccination_records.rb b/spec/factories/vaccination_records.rb index ed7f3a1824..378d305a38 100644 --- a/spec/factories/vaccination_records.rb +++ b/spec/factories/vaccination_records.rb @@ -4,50 +4,57 @@ # # Table name: vaccination_records # -# id :bigint not null, primary key -# batch_expiry :date -# batch_number :string -# confirmation_sent_at :datetime -# delivery_method :integer -# delivery_site :integer -# discarded_at :datetime -# disease_types :enum not null, is an Array -# dose_sequence :integer -# full_dose :boolean -# local_patient_id_uri :string -# location_name :string -# nhs_immunisations_api_etag :string -# nhs_immunisations_api_identifier_system :string -# nhs_immunisations_api_identifier_value :string -# nhs_immunisations_api_primary_source :boolean -# nhs_immunisations_api_sync_pending_at :datetime -# nhs_immunisations_api_synced_at :datetime -# notes :text -# notify_parents :boolean -# outcome :integer not null -# pending_changes :jsonb not null -# performed_at_date :date not null -# performed_at_time :time -# performed_by_family_name :string -# performed_by_given_name :string -# performed_ods_code :string -# programme_type :enum not null -# protocol :integer -# source :integer not null -# uuid :uuid not null -# created_at :datetime not null -# updated_at :datetime not null -# duplicate_of_vaccination_record_id :bigint -# local_patient_id :string -# location_id :bigint -# next_dose_delay_triage_id :bigint -# nhs_immunisations_api_id :string -# patient_id :bigint not null -# performed_by_user_id :bigint -# reported_by_id :bigint -# session_id :bigint -# supplied_by_user_id :bigint -# vaccine_id :bigint +# id :bigint not null, primary key +# batch_expiry :date +# batch_number :string +# confirmation_sent_at :datetime +# delivery_method :integer +# delivery_site :integer +# discarded_at :datetime +# disease_types :enum not null, is an Array +# dose_sequence :integer +# full_dose :boolean +# local_patient_id_uri :string +# location_name :string +# nhs_immunisations_api_etag :string +# nhs_immunisations_api_identifier_system :string +# nhs_immunisations_api_identifier_value :string +# nhs_immunisations_api_primary_source :boolean +# nhs_immunisations_api_recorded_at :datetime +# nhs_immunisations_api_snomed_procedure_code :string +# nhs_immunisations_api_snomed_procedure_term :string +# nhs_immunisations_api_snomed_product_code :string +# nhs_immunisations_api_snomed_product_term :string +# nhs_immunisations_api_snomed_reason_code :string +# nhs_immunisations_api_snomed_reason_term :string +# nhs_immunisations_api_sync_pending_at :datetime +# nhs_immunisations_api_synced_at :datetime +# notes :text +# notify_parents :boolean +# outcome :integer not null +# pending_changes :jsonb not null +# performed_at_date :date not null +# performed_at_time :time +# performed_by_family_name :string +# performed_by_given_name :string +# performed_ods_code :string +# programme_type :enum not null +# protocol :integer +# source :integer not null +# uuid :uuid not null +# created_at :datetime not null +# updated_at :datetime not null +# duplicate_of_vaccination_record_id :bigint +# local_patient_id :string +# location_id :bigint +# next_dose_delay_triage_id :bigint +# nhs_immunisations_api_id :string +# patient_id :bigint not null +# performed_by_user_id :bigint +# reported_by_id :bigint +# session_id :bigint +# supplied_by_user_id :bigint +# vaccine_id :bigint # # Indexes # @@ -97,7 +104,8 @@ patient do association :patient, - school: session&.location&.school? ? session.location : nil + school: + session&.location&.gias_school? ? session.location : nil end delivery_site { "left_arm_upper_position" } diff --git a/spec/features/cis2_backchannel_logout_spec.rb b/spec/features/cis2_backchannel_logout_spec.rb index 706a0c8fe0..841246a50e 100644 --- a/spec/features/cis2_backchannel_logout_spec.rb +++ b/spec/features/cis2_backchannel_logout_spec.rb @@ -23,7 +23,7 @@ def given_the_app_is_setup @team = create(:team, :with_one_nurse) - create(:school, urn: "123456") + create(:gias_school, urn: "123456") @user = @team.users.first end diff --git a/spec/features/cli_gias_check_import_spec.rb b/spec/features/cli_gias_check_import_spec.rb index 5f22f5f9a8..12a325b546 100644 --- a/spec/features/cli_gias_check_import_spec.rb +++ b/spec/features/cli_gias_check_import_spec.rb @@ -18,7 +18,7 @@ def given_an_team_exists def and_there_are_schools_with_future_sessions @school_with_future_session = create( - :school, + :gias_school, name: "The Aldgate School", urn: "100000", gias_year_groups: (-1..6).to_a @@ -33,11 +33,16 @@ def and_there_are_schools_with_future_sessions ) @successor_school = - create(:school, name: "The Aldgate Academy", urn: "100004", team: @team) + create( + :gias_school, + name: "The Aldgate Academy", + urn: "100004", + team: @team + ) @school2_with_future_session = create( - :school, + :gias_school, name: "St Paul's Cathedral School", urn: "100002", gias_year_groups: (0..6).to_a @@ -53,7 +58,7 @@ def and_there_are_schools_with_future_sessions @school_without_future_session = create( - :school, + :gias_school, name: "City of London School for Girls", urn: "100001", gias_year_groups: (3..13).to_a diff --git a/spec/features/cli_gias_import_spec.rb b/spec/features/cli_gias_import_spec.rb index a2dacd732e..1d94d43c6c 100644 --- a/spec/features/cli_gias_import_spec.rb +++ b/spec/features/cli_gias_import_spec.rb @@ -18,18 +18,18 @@ def given_a_gias_file_exists end def and_a_location_already_exists - create(:school, :secondary, urn: "100000", site: nil) + create(:gias_school, :secondary, urn: "100000", site: nil) end def and_sites_exist create( - :school, + :gias_school, urn: "100000", site: "A", name: "The Aldgate School - Site 2" ) create( - :school, + :gias_school, urn: "100000", site: "B", name: "The Aldgate School - Site 3" diff --git a/spec/features/cli_imports_recent_spec.rb b/spec/features/cli_imports_recent_spec.rb index 66295c7e50..10ba5e2584 100644 --- a/spec/features/cli_imports_recent_spec.rb +++ b/spec/features/cli_imports_recent_spec.rb @@ -53,7 +53,7 @@ def given_there_imports team = create(:team, workgroup: "test_team") - location = create(:school, team:) + location = create(:gias_school, team:) @class_import = create(:class_import, :processed, team:, location:) @cohort_import = create(:cohort_import, :processed, team:) @@ -62,7 +62,7 @@ def given_there_imports def given_there_are_multiple_class_and_cohort_imports team = create(:team, workgroup: "multi_team") - location = create(:school, team:) + location = create(:gias_school, team:) # Create 3 of each type with time travel to ensure proper ordering freeze_time do @@ -85,7 +85,7 @@ def given_there_are_imports_in_different_organisations organisation2 = create(:organisation, ods_code: "ORG2") team1 = create(:team, organisation: organisation1, workgroup: "team1") team2 = create(:team, organisation: organisation2, workgroup: "team2") - location = create(:school, team: team1) + location = create(:gias_school, team: team1) create(:class_import, :processed, team: team1, location:) create(:cohort_import, :processed, team: team1) @@ -96,7 +96,7 @@ def given_there_are_imports_in_different_workgroups organisation = create(:organisation) team1 = create(:team, organisation:, workgroup: "wg1") team2 = create(:team, organisation:, workgroup: "wg2") - location = create(:school, team: team1) + location = create(:gias_school, team: team1) create(:class_import, :processed, team: team1, location:) create(:cohort_import, :processed, team: team1) @@ -105,7 +105,7 @@ def given_there_are_imports_in_different_workgroups def given_there_are_interleaved_class_and_cohort_imports team = create(:team, workgroup: "interleaved") - location = create(:school, team:) + location = create(:gias_school, team:) freeze_time do @oldest = create(:cohort_import, :processed, team:) diff --git a/spec/features/cli_reports_export_automated_careplus_spec.rb b/spec/features/cli_reports_export_automated_careplus_spec.rb index 542e82d080..5933d5926f 100644 --- a/spec/features/cli_reports_export_automated_careplus_spec.rb +++ b/spec/features/cli_reports_export_automated_careplus_spec.rb @@ -7,103 +7,135 @@ let(:output_path) { Rails.root.join("tmp/test_automated_export.csv").to_s } - before do - allow(Reports::AutomatedCareplusExporter).to receive(:call).and_return( - "csv content" - ) - end + after { File.delete(output_path) if File.exist?(output_path) } - context "with a valid organisation and single team" do - it "exports the CSV and reports success" do + context "when there are no matching records" do + it "informs the user and does not create an export" do given_an_organisation_with_a_single_team + when_i_run_the_command_with_options( "--ods_code=#{@organisation.ods_code}", "--start_date=2025-09-01", "--end_date=2026-03-10", "--output=#{output_path}" ) - then_the_exporter_is_called_with( - team: @team, - academic_year: AcademicYear.current, - start_date: Date.new(2025, 9, 1), - end_date: Date.new(2026, 3, 10) + + expect(@output).to include( + "No records found. No CarePlus report was created." + ) + and_no_careplus_export_is_created + expect(File.exist?(output_path)).to be(false) + end + end + + context "when there are matching vaccination records" do + it "exports the CSV, creates an export record, links records, and reports success" do + given_an_organisation_with_a_single_team + given_a_vaccination_record_for_the_team + + when_i_run_the_command_with_options( + "--ods_code=#{@organisation.ods_code}", + "--output=#{output_path}" ) - and_the_output_file_contains("csv content") + + export = CareplusExport.last + expect(export.vaccination_records).to include(@vaccination_record) + expect(export.programme_types).to eq([@programme.type]) + and_the_output_file_is_written and_the_success_message_is_displayed end end + context "when inserting vaccination record links fails" do + it "rolls back the export record too" do + given_an_organisation_with_a_single_team + given_a_vaccination_record_for_the_team + + allow(CareplusExportVaccinationRecord).to receive(:insert_all!).and_raise( + ActiveRecord::ActiveRecordError + ) + + expect { + capture_output do + command( + "--ods_code=#{@organisation.ods_code}", + "--output=#{output_path}" + ) + end + }.to raise_error(ActiveRecord::ActiveRecordError).and( + not_change(CareplusExport, :count) + ) + end + end + context "when the organisation does not exist" do - it "warns and does not call the exporter" do + it "warns and does not create an export" do when_i_run_the_command_with_options_and_capture_error( "--ods_code=UNKNOWN" ) then_the_error_output_includes( "Could not find organisation with ODS code 'UNKNOWN'" ) - and_the_exporter_is_not_called + and_no_careplus_export_is_created end end context "when the organisation has multiple teams and no workgroup is given" do - it "warns and does not call the exporter" do + it "warns and does not create an export" do given_an_organisation_with_multiple_teams when_i_run_the_command_with_options_and_capture_error( "--ods_code=#{@organisation.ods_code}" ) then_the_error_output_includes("has multiple teams") - and_the_exporter_is_not_called + and_no_careplus_export_is_created end end context "when a workgroup is specified" do - it "calls the exporter with the matching team" do + it "creates the export for the matching team" do given_an_organisation_with_multiple_teams + given_a_vaccination_record_for_the_team when_i_run_the_command_with_options( "--ods_code=#{@organisation.ods_code}", "--workgroup=#{@team.workgroup}", "--output=#{output_path}" ) - then_the_exporter_is_called_with(team: @team) + then_a_careplus_export_is_created_with(team: @team) end end context "when a custom academic year is specified" do - it "passes the academic year to the exporter" do + it "creates the export with the given academic year" do given_an_organisation_with_a_single_team + programme = Programme.hpv + session = create(:session, team: @team, programmes: [programme]) + create( + :vaccination_record, + session:, + programme:, + performed_at: Date.new(2024, 10, 1) + ) when_i_run_the_command_with_options( "--ods_code=#{@organisation.ods_code}", "--academic_year=2024", "--output=#{output_path}" ) - then_the_exporter_is_called_with(academic_year: 2024) + then_a_careplus_export_is_created_with(academic_year: 2024) end end context "when the team does not have CarePlus enabled" do - it "warns and does not call the exporter" do + it "warns and does not create an export" do given_an_organisation_with_a_team_without_careplus when_i_run_the_command_with_options_and_capture_error( "--ods_code=#{@organisation.ods_code}" ) then_the_error_output_includes("does not have CarePlus enabled") - and_the_exporter_is_not_called - end - end - - context "when the team has CarePlus enabled" do - it "calls the exporter" do - given_an_organisation_with_a_single_team - - when_i_run_the_command_with_options( - "--ods_code=#{@organisation.ods_code}", - "--output=#{output_path}" - ) - then_the_exporter_is_called_with(team: @team) + and_no_careplus_export_is_created end end @@ -120,6 +152,13 @@ def given_an_organisation_with_a_team_without_careplus create(:team, organisation: @organisation, programmes: Programme.all) end + def given_a_vaccination_record_for_the_team + @programme = Programme.hpv + session = create(:session, team: @team, programmes: [@programme]) + @vaccination_record = + create(:vaccination_record, session:, programme: @programme) + end + def given_an_organisation_with_a_single_team @organisation = create(:organisation) @team = @@ -156,14 +195,12 @@ def when_i_run_the_command_with_options_and_capture_error(*args) @error = capture_error { command(*args) } end - def then_the_exporter_is_called_with(**kwargs) - expect(Reports::AutomatedCareplusExporter).to have_received(:call).with( - hash_including(**kwargs) - ) + def then_a_careplus_export_is_created_with(**kwargs) + expect(CareplusExport.last).to have_attributes(**kwargs) end - def and_the_output_file_contains(content) - expect(File.read(output_path)).to eq(content) + def and_the_output_file_is_written + expect(File.exist?(output_path)).to be(true) end def and_the_success_message_is_displayed @@ -174,7 +211,7 @@ def then_the_error_output_includes(message) expect(@error).to include(message) end - def and_the_exporter_is_not_called - expect(Reports::AutomatedCareplusExporter).not_to have_received(:call) + def and_no_careplus_export_is_created + expect(CareplusExport.count).to eq(0) end end diff --git a/spec/features/cli_schools_add_programme_year_groups_spec.rb b/spec/features/cli_schools_add_programme_year_groups_spec.rb index f4986a0025..d658b28adc 100644 --- a/spec/features/cli_schools_add_programme_year_groups_spec.rb +++ b/spec/features/cli_schools_add_programme_year_groups_spec.rb @@ -62,7 +62,7 @@ def command_with_invalid_programme end def given_the_school_exists - @school = create(:school, urn: "123456") + @school = create(:gias_school, urn: "123456") end def and_the_programme_exists diff --git a/spec/features/cli_schools_add_to_team_spec.rb b/spec/features/cli_schools_add_to_team_spec.rb index 1934ade9da..244142344e 100644 --- a/spec/features/cli_schools_add_to_team_spec.rb +++ b/spec/features/cli_schools_add_to_team_spec.rb @@ -128,7 +128,7 @@ def and_the_subteam_exists end def and_the_school_exists - @school = create(:school, name: "School", urn: "123456") + @school = create(:gias_school, name: "School", urn: "123456") end def and_the_school_belongs_to_another_subteam @@ -170,25 +170,26 @@ def then_a_school_belongs_to_another_team_warning_message_is_displayed end def then_the_school_is_added_to_the_team - expect(@team.schools).to include(@school) + expect(@team.gias_schools).to include(@school) expect(@school.programmes).to eq(@programmes) end def then_the_school_is_added_to_the_team_with_flu_only - expect(@team.schools).to include(@school) + expect(@team.gias_schools).to include(@school) expect(@school.programmes).to contain_exactly(@programmes.first) end def and_the_school_remains_in_the_other_team_too - expect(@other_team.schools).to include(@school) + expect(@other_team.gias_schools).to include(@school) end def and_the_school_exists_with_multiple_sites - @school_main = create(:school, name: "School", urn: "123456", site: nil) + @school_main = + create(:gias_school, name: "School", urn: "123456", site: nil) @school_site_a = - create(:school, name: "School Site A", urn: "123456", site: "A") + create(:gias_school, name: "School Site A", urn: "123456", site: "A") @school_site_b = - create(:school, name: "School Site B", urn: "123456", site: "B") + create(:gias_school, name: "School Site B", urn: "123456", site: "B") end def and_site_b_already_belongs_to_the_team @@ -250,13 +251,13 @@ def then_a_missing_site_b_error_message_is_displayed end def then_all_sites_are_added_to_the_team - expect(@team.schools).to include(@school_site_a) - expect(@team.schools).to include(@school_site_b) - expect(@team.schools).not_to include(@school_main) + expect(@team.gias_schools).to include(@school_site_a) + expect(@team.gias_schools).to include(@school_site_b) + expect(@team.gias_schools).not_to include(@school_main) end def then_site_a_is_added_to_the_team - expect(@team.schools).to include(@school_site_a) - expect(@team.schools).to include(@school_site_b) # already was in team + expect(@team.gias_schools).to include(@school_site_a) + expect(@team.gias_schools).to include(@school_site_b) # already was in team end end diff --git a/spec/features/cli_schools_create_site_spec.rb b/spec/features/cli_schools_create_site_spec.rb index 7b7193067b..81d967e12b 100644 --- a/spec/features/cli_schools_create_site_spec.rb +++ b/spec/features/cli_schools_create_site_spec.rb @@ -28,7 +28,7 @@ def given_a_school_exists subteam = create(:subteam, team:) @school = create( - :school, + :gias_school, urn: "123456", name: "MainSchool", site: nil, diff --git a/spec/features/cli_schools_create_spec.rb b/spec/features/cli_schools_create_spec.rb index e6c91da2d5..230c60280c 100644 --- a/spec/features/cli_schools_create_spec.rb +++ b/spec/features/cli_schools_create_spec.rb @@ -23,7 +23,7 @@ def when_i_run_the_command end def then_the_school_is_created - location = Location.school.find_by(urn: "123456") + location = Location.gias_school.find_by(urn: "123456") expect(location).to be_open expect(location.name).to eq("MySchool") expect(location.name).to eq("MySchool") diff --git a/spec/features/cli_schools_move_patients_spec.rb b/spec/features/cli_schools_move_patients_spec.rb index bd59401936..719b54e6b7 100644 --- a/spec/features/cli_schools_move_patients_spec.rb +++ b/spec/features/cli_schools_move_patients_spec.rb @@ -10,8 +10,8 @@ let(:team) { create(:team) } let(:subteam) { create(:subteam, team:) } let(:other_subteam) { create(:subteam, team:) } - let(:source_school) { create(:school, team:, subteam:) } - let(:target_school) { create(:school, team:) } + let(:source_school) { create(:gias_school, team:, subteam:) } + let(:target_school) { create(:gias_school, team:) } let(:programmes) { [Programme.hpv] } let(:location_programme_year_group) do create( @@ -26,7 +26,7 @@ end let!(:school_move) { create(:school_move, patient:, school: source_school) } let!(:consent_form) { create(:consent_form, school: source_school, session:) } - let(:other_org_school) { create(:school, subteam: other_subteam) } + let(:other_org_school) { create(:gias_school, subteam: other_subteam) } let(:source_urn) { source_school.urn.to_s } let(:target_urn) { target_school.urn.to_s } diff --git a/spec/features/cli_schools_remove_from_team_spec.rb b/spec/features/cli_schools_remove_from_team_spec.rb index 0e6fd52a28..1cb7247e30 100644 --- a/spec/features/cli_schools_remove_from_team_spec.rb +++ b/spec/features/cli_schools_remove_from_team_spec.rb @@ -64,7 +64,7 @@ def and_the_subteam_exists def and_the_schools_exist @school_a = create( - :school, + :gias_school, urn: "123456", name: "MainSchool", site: nil, @@ -73,7 +73,7 @@ def and_the_schools_exist ) @school_b = create( - :school, + :gias_school, urn: "654321", name: "OtherSchool", site: nil, @@ -88,7 +88,7 @@ def and_the_schools_exist_but_in_a_different_team @other_team = create(:team, workgroup: "TeamB") @school_a = create( - :school, + :gias_school, urn: "123456", name: "MainSchool", site: nil, @@ -96,7 +96,7 @@ def and_the_schools_exist_but_in_a_different_team ) @school_b = create( - :school, + :gias_school, urn: "654321", name: "OtherSchool", site: nil, diff --git a/spec/features/cli_schools_remove_programme_year_groups_spec.rb b/spec/features/cli_schools_remove_programme_year_groups_spec.rb index 3b4d737c5a..a9a38dc78a 100644 --- a/spec/features/cli_schools_remove_programme_year_groups_spec.rb +++ b/spec/features/cli_schools_remove_programme_year_groups_spec.rb @@ -45,7 +45,7 @@ def command_with_invalid_programme end def given_the_school_exists - @school = create(:school, urn: "123456") + @school = create(:gias_school, urn: "123456") end def and_the_programme_exists diff --git a/spec/features/cli_schools_show_spec.rb b/spec/features/cli_schools_show_spec.rb index e18860e863..bb847d33cf 100644 --- a/spec/features/cli_schools_show_spec.rb +++ b/spec/features/cli_schools_show_spec.rb @@ -56,7 +56,7 @@ def given_a_school_exists team = create(:team, programme_types: %w[flu hpv]) @school = create( - :school, + :gias_school, name: "Test School", urn: "123456", team: @@ -94,7 +94,7 @@ def given_a_clinic_exists def and_a_site_with_the_same_urn_exists @site = - create(:school, name: "Test School Site B", urn: "123456", site: "B") + create(:gias_school, name: "Test School Site B", urn: "123456", site: "B") end def and_the_school_has_patients_across_academic_years @@ -176,8 +176,8 @@ def and_the_school_has_patients_across_academic_years end def given_a_school_with_sites_exists - @school = create(:school, name: "Test School", urn: "123456") - @site = create(:school, name: "Site B", urn: "123456", site: "B") + @school = create(:gias_school, name: "Test School", urn: "123456") + @site = create(:gias_school, name: "Site B", urn: "123456", site: "B") end def when_i_run_the_command diff --git a/spec/features/cli_stats_consents_by_school_spec.rb b/spec/features/cli_stats_consents_by_school_spec.rb index 39bed04412..480c29365f 100644 --- a/spec/features/cli_stats_consents_by_school_spec.rb +++ b/spec/features/cli_stats_consents_by_school_spec.rb @@ -67,9 +67,9 @@ def given_organisation_has_consent_data ) school1 = - create(:school, name: "Primary School", team: @team_a, programmes:) + create(:gias_school, name: "Primary School", team: @team_a, programmes:) school2 = - create(:school, name: "Secondary School", team: @team_b, programmes:) + create(:gias_school, name: "Secondary School", team: @team_b, programmes:) school1.import_year_groups_from_gias!(academic_year: AcademicYear.previous) school1.import_default_programme_year_groups!( diff --git a/spec/features/cli_stats_organisations_spec.rb b/spec/features/cli_stats_organisations_spec.rb index d9ada43bf4..72eebd7884 100644 --- a/spec/features/cli_stats_organisations_spec.rb +++ b/spec/features/cli_stats_organisations_spec.rb @@ -89,14 +89,14 @@ def given_organisation_has_complete_data_with_filters school1 = create( - :school, + :gias_school, name: "Hogwarts", team: @team_a, programmes: [programme_flu] ) school2 = create( - :school, + :gias_school, name: "East High", team: @team_b, programmes: [programme_hpv] diff --git a/spec/features/cli_teams_add_programme_spec.rb b/spec/features/cli_teams_add_programme_spec.rb index 4249e0b624..bf85da2f30 100644 --- a/spec/features/cli_teams_add_programme_spec.rb +++ b/spec/features/cli_teams_add_programme_spec.rb @@ -42,7 +42,7 @@ def command_with_invalid_programme def given_the_team_exists @team = create(:team, workgroup: "abc") - @school = create(:school, :secondary, team: @team) + @school = create(:gias_school, :secondary, team: @team) end def and_is_already_set_up_for_hpv diff --git a/spec/features/cli_teams_onboard_spec.rb b/spec/features/cli_teams_onboard_spec.rb index 6c44518de2..fe212a2949 100644 --- a/spec/features/cli_teams_onboard_spec.rb +++ b/spec/features/cli_teams_onboard_spec.rb @@ -92,7 +92,7 @@ def given_programmes_and_schools_exist @school_a = create( - :school, + :gias_school, :secondary, :open, urn: "123456", @@ -100,7 +100,7 @@ def given_programmes_and_schools_exist ) @school_b = create( - :school, + :gias_school, :secondary, :open, urn: "234567", @@ -108,7 +108,7 @@ def given_programmes_and_schools_exist ) @school_c = create( - :school, + :gias_school, :secondary, :open, urn: "345678", @@ -116,7 +116,7 @@ def given_programmes_and_schools_exist ) @school_d = create( - :school, + :gias_school, :secondary, :open, urn: "456789", @@ -150,10 +150,10 @@ def and_a_new_team_is_created end def and_schools_are_added_to_the_team_appropriately - expect(Team.last.schools.count).to eq(6) + expect(Team.last.gias_schools.count).to eq(6) school_b_sites = Location.where(urn: @school_b.urn).where.not(site: nil) school_d_sites = Location.where(urn: @school_d.urn).where.not(site: nil) - expect(Team.last.schools).to include( + expect(Team.last.gias_schools).to include( @school_a, @school_c, *school_b_sites, diff --git a/spec/features/cli_teams_reset_national_reporting_spec.rb b/spec/features/cli_teams_reset_national_reporting_spec.rb index 9f30e49751..464967d670 100644 --- a/spec/features/cli_teams_reset_national_reporting_spec.rb +++ b/spec/features/cli_teams_reset_national_reporting_spec.rb @@ -187,7 +187,7 @@ def and_a_point_of_care_team_exists_in_the_same_org_as_the_national_reporting_te end def and_i_upload_some_vaccination_records - create(:school, team: @national_reporting_team, urn: 100_000) + create(:gias_school, team: @national_reporting_team, urn: 100_000) @user = @national_reporting_team.users.first sign_in @user diff --git a/spec/features/doubles_vaccination_administered_spec.rb b/spec/features/doubles_vaccination_administered_spec.rb index 37194069a5..9b6574a2a2 100644 --- a/spec/features/doubles_vaccination_administered_spec.rb +++ b/spec/features/doubles_vaccination_administered_spec.rb @@ -28,7 +28,7 @@ def given_a_doubles_session_exists programmes = [Programme.menacwy, Programme.td_ipv] team = create(:team, programmes:) - location = create(:school, team:) + location = create(:gias_school, team:) @menacwy_batch = create( @@ -135,14 +135,14 @@ def then_an_email_is_sent_to_the_parent_confirming_the_vaccinations expect(email_deliveries).to include( matching_notify_email( to: @patient.consents.last.parent.email, - template: :vaccination_administered_menacwy + template: :vaccination_administered ).with_content_including("MenACWY vaccination", "MenQuadfi") ) expect(email_deliveries).to include( matching_notify_email( to: @patient.consents.last.parent.email, - template: :vaccination_administered_td_ipv + template: :vaccination_administered ).with_content_including("3-in-1 teenage booster", "Revaxis") ) end diff --git a/spec/features/doubles_vaccination_cannot_record_as_admin_spec.rb b/spec/features/doubles_vaccination_cannot_record_as_admin_spec.rb index 7812ab5e5b..bc667ab12f 100644 --- a/spec/features/doubles_vaccination_cannot_record_as_admin_spec.rb +++ b/spec/features/doubles_vaccination_cannot_record_as_admin_spec.rb @@ -13,7 +13,7 @@ def given_i_am_signed_in_as_an_admin programmes = [Programme.menacwy, Programme.td_ipv] team = create(:team, :with_one_admin, programmes:) - location = create(:school, team:) + location = create(:gias_school, team:) @session = create(:session, team:, programmes:, location:) @patient = diff --git a/spec/features/e2e_journey_spec.rb b/spec/features/e2e_journey_spec.rb index 002f4d26d0..05c03a2304 100644 --- a/spec/features/e2e_journey_spec.rb +++ b/spec/features/e2e_journey_spec.rb @@ -54,7 +54,8 @@ def given_an_hpv_programme_is_underway programme = Programme.hpv @team = create(:team, :with_one_nurse, programmes: [programme]) - @school = create(:school, :secondary, team: @team, name: "Pilot School") + @school = + create(:gias_school, :secondary, team: @team, name: "Pilot School") @batch = create( :batch, diff --git a/spec/features/edit_session_programmes_spec.rb b/spec/features/edit_session_programmes_spec.rb index d55c631d33..d07d45fd40 100644 --- a/spec/features/edit_session_programmes_spec.rb +++ b/spec/features/edit_session_programmes_spec.rb @@ -69,7 +69,7 @@ def given_a_school_exists @team = create(:team, :with_one_nurse, programmes: @programmes) - @location = create(:school, team: @team, programmes: @programmes) + @location = create(:gias_school, team: @team, programmes: @programmes) end def and_an_hpv_session_already_exists diff --git a/spec/features/edit_vaccination_record_spec.rb b/spec/features/edit_vaccination_record_spec.rb index 1eabb6bc5d..eab61ad1bd 100644 --- a/spec/features/edit_vaccination_record_spec.rb +++ b/spec/features/edit_vaccination_record_spec.rb @@ -486,7 +486,7 @@ def given_an_hpv_programme_is_underway @replacement_batch = create(:batch, :not_expired, team: @team, vaccine: @vaccine) - location = create(:school, team: @team) + location = create(:gias_school, team: @team) @session = create( @@ -532,7 +532,7 @@ def given_a_national_reporting_team_exists year_group: 8 ) - @school = create(:school, name: "A New School", status: "open") + @school = create(:gias_school, name: "A New School", status: "open") @vaccine = @programme.vaccines.first @new_vaccine = @programme.vaccines.second @@ -1001,7 +1001,7 @@ def and_the_parent_receives_a_not_administered_email end def then_the_parent_receives_an_administered_email - expect_email_to(@patient.parents.first.email, :vaccination_administered_hpv) + expect_email_to(@patient.parents.first.email, :vaccination_administered) end alias_method :and_the_parent_receives_an_administered_email, @@ -1093,7 +1093,7 @@ def and_i_should_see_the_session_specific_breadcrumb end def and_a_vaccination_record_with_a_session_exists - location = create(:school, urn: 100_001) + location = create(:gias_school, urn: 100_001) @session = create(:session, :completed, programmes: [@programme], location:) diff --git a/spec/features/flu_vaccination_administered_spec.rb b/spec/features/flu_vaccination_administered_spec.rb index be60fa22ad..491347af0f 100644 --- a/spec/features/flu_vaccination_administered_spec.rb +++ b/spec/features/flu_vaccination_administered_spec.rb @@ -103,7 +103,7 @@ def given_i_am_signed_in_with_flu_programme @programme = Programme.flu @team = create(:team, :with_one_nurse, programmes: [@programme]) - @location = create(:school, team: @team) + @location = create(:gias_school, team: @team) @session = create( :session, @@ -371,7 +371,7 @@ def then_an_email_is_sent_to_the_parent_confirming_the_vaccination matching_notify_email( to: @patient.consents.last.parent.email, subject: "Your child had their flu vaccination today", - template: :vaccination_administered_flu + template: :vaccination_administered ).with_content_including("Vaccination: flu", *method_and_side_effects) ) end diff --git a/spec/features/follow_up_consent_refusal_journey_spec.rb b/spec/features/follow_up_consent_refusal_journey_spec.rb index d1fb826c5f..d6fdcd67ad 100644 --- a/spec/features/follow_up_consent_refusal_journey_spec.rb +++ b/spec/features/follow_up_consent_refusal_journey_spec.rb @@ -75,7 +75,7 @@ def given_an_hpv_programme_is_underway @programme = Programme.hpv @team = create(:team, :with_one_nurse, programmes: [@programme]) - location = create(:school, name: "Pilot School", team: @team) + location = create(:gias_school, name: "Pilot School", team: @team) @session = create( :session, diff --git a/spec/features/hpv_vaccination_administered_spec.rb b/spec/features/hpv_vaccination_administered_spec.rb index f55fcecb07..481ce22fdc 100644 --- a/spec/features/hpv_vaccination_administered_spec.rb +++ b/spec/features/hpv_vaccination_administered_spec.rb @@ -102,7 +102,7 @@ def given_i_am_signed_in programme = Programme.hpv team = create(:team, :with_one_nurse, programmes: [programme]) - location = create(:school, team:) + location = create(:gias_school, team:) programme.vaccines.discontinued.each do |vaccine| create(:batch, team:, vaccine:) @@ -305,7 +305,7 @@ def then_an_email_is_sent_to_the_parent_confirming_the_vaccination matching_notify_email( to: @patient.consents.last.parent.email, subject: "Your child had their HPV vaccination today", - template: :vaccination_administered_hpv + template: :vaccination_administered ).with_content_including( "John Doe had their HPV vaccination at #{@session.location.name} today", "Vaccination: HPV", diff --git a/spec/features/hpv_vaccination_clinic_spec.rb b/spec/features/hpv_vaccination_clinic_spec.rb index 4b41af18c6..372c413ff8 100644 --- a/spec/features/hpv_vaccination_clinic_spec.rb +++ b/spec/features/hpv_vaccination_clinic_spec.rb @@ -126,7 +126,7 @@ def when_vaccination_confirmations_are_sent def then_an_email_is_sent_to_the_parent_confirming_the_vaccination expect_email_to( @patient.consents.last.parent.email, - :vaccination_administered_hpv + :vaccination_administered ) end diff --git a/spec/features/hpv_vaccination_delayed_spec.rb b/spec/features/hpv_vaccination_delayed_spec.rb index c878e32ac7..0c35930819 100644 --- a/spec/features/hpv_vaccination_delayed_spec.rb +++ b/spec/features/hpv_vaccination_delayed_spec.rb @@ -30,7 +30,7 @@ def given_i_am_signed_in programmes = [Programme.hpv] @team = create(:team, :with_one_nurse, programmes:) - location = create(:school, team: @team) + location = create(:gias_school, team: @team) @batch = create(:batch, team: @team, vaccine: programmes.first.vaccines.first) diff --git a/spec/features/identity_check_spec.rb b/spec/features/identity_check_spec.rb index 932441cf1b..36d2bc73f1 100644 --- a/spec/features/identity_check_spec.rb +++ b/spec/features/identity_check_spec.rb @@ -57,7 +57,7 @@ def given_i_am_signed_in programmes = [Programme.hpv] @team = create(:team, :with_one_nurse, programmes:) - location = create(:school, team: @team) + location = create(:gias_school, team: @team) @batch = create( :batch, diff --git a/spec/features/import_child_pds_lookup_extravaganza_spec.rb b/spec/features/import_child_pds_lookup_extravaganza_spec.rb index c7fc88ccd5..a92e61284a 100644 --- a/spec/features/import_child_pds_lookup_extravaganza_spec.rb +++ b/spec/features/import_child_pds_lookup_extravaganza_spec.rb @@ -117,7 +117,7 @@ def given_i_am_signed_in def and_an_hpv_programme_is_underway @school = create( - :school, + :gias_school, urn: "123456", name: "Waterloo Road", gias_year_groups: [7, 8, 9, 10], @@ -165,7 +165,7 @@ def and_an_existing_patient_record_exists session: @session ) - @different_school = create(:school, urn: "456789", team: @team) + @different_school = create(:gias_school, urn: "456789", team: @team) @different_school_session = create( :session, diff --git a/spec/features/import_child_records_preparation_spec.rb b/spec/features/import_child_records_preparation_spec.rb index db8f54a82c..f531ca3ec7 100644 --- a/spec/features/import_child_records_preparation_spec.rb +++ b/spec/features/import_child_records_preparation_spec.rb @@ -150,7 +150,7 @@ def and_the_app_is_setup programmes = [Programme.hpv, Programme.menacwy, Programme.td_ipv] @team = create(:team, :with_one_nurse, programmes:) - @school = create(:school, urn: "123456", team: @team) + @school = create(:gias_school, urn: "123456", team: @team) @user = @team.users.first [AcademicYear.current, AcademicYear.pending].each do |academic_year| diff --git a/spec/features/import_child_records_review_spec.rb b/spec/features/import_child_records_review_spec.rb index 7f6b42066b..19e96c26a6 100644 --- a/spec/features/import_child_records_review_spec.rb +++ b/spec/features/import_child_records_review_spec.rb @@ -29,7 +29,7 @@ def given_i_am_signed_in @user = @team.users.first # Create a school in this team (children can be uploaded here) - create(:school, urn: "123456", team: @team) + create(:gias_school, urn: "123456", team: @team) sign_in @user end @@ -40,7 +40,7 @@ def and_two_teams_exist create(:team, :with_one_nurse, programmes:, name: "Other Team") @other_school = create( - :school, + :gias_school, urn: "111222", name: "Other Team School", team: @other_team diff --git a/spec/features/import_child_records_spec.rb b/spec/features/import_child_records_spec.rb index 88ef110c7a..5bf1c00b17 100644 --- a/spec/features/import_child_records_spec.rb +++ b/spec/features/import_child_records_spec.rb @@ -123,7 +123,7 @@ def given_the_app_is_setup @team = create(:team, :with_one_nurse, programmes:) - create(:school, urn: "123456", team: @team) + create(:gias_school, urn: "123456", team: @team) @user = @team.users.first end @@ -239,9 +239,7 @@ def when_i_upload_a_file_with_invalid_fields end def then_i_should_the_errors_page_with_invalid_fields - expect(page).to have_content( - "How to format your Mavis CSV file for child records" - ) + expect(page).to have_content("What your CSV file must include") expect(page).to have_content("Row 2") end diff --git a/spec/features/import_child_records_with_duplicates_spec.rb b/spec/features/import_child_records_with_duplicates_spec.rb index 4424cfc0c0..0d79e53d15 100644 --- a/spec/features/import_child_records_with_duplicates_spec.rb +++ b/spec/features/import_child_records_with_duplicates_spec.rb @@ -191,7 +191,7 @@ def and_pds_lookup_during_import_is_enabled end def and_an_hpv_programme_is_underway - @school = create(:school, urn: "123456", team: @team) + @school = create(:gias_school, urn: "123456", team: @team) @session = create(:session, team: @team, location: @school, programmes: [@programme]) diff --git a/spec/features/import_child_records_with_twins_spec.rb b/spec/features/import_child_records_with_twins_spec.rb index 93d03d414a..cb5d2c4451 100644 --- a/spec/features/import_child_records_with_twins_spec.rb +++ b/spec/features/import_child_records_with_twins_spec.rb @@ -54,7 +54,7 @@ def and_pds_lookup_during_import_returns_nhs_numbers end def and_an_hpv_programme_is_underway - @school = create(:school, urn: "123456", team: @team) + @school = create(:gias_school, urn: "123456", team: @team) @session = create(:session, team: @team, location: @school, programmes: [@programme]) end diff --git a/spec/features/import_class_list_review_spec.rb b/spec/features/import_class_list_review_spec.rb index 27faffdafb..c82629b15a 100644 --- a/spec/features/import_class_list_review_spec.rb +++ b/spec/features/import_class_list_review_spec.rb @@ -113,7 +113,7 @@ def given_i_am_signed_in def and_an_hpv_programme_is_underway @school = create( - :school, + :gias_school, urn: "123456", name: "Waterloo Road", gias_year_groups: [7, 8, 9, 10], @@ -122,11 +122,12 @@ def and_an_hpv_programme_is_underway @other_school = create( - :school, + :gias_school, team: @team, name: "Liverpool Road", gias_year_groups: [7, 8, 9, 10] ) + @clinic = @team.generic_clinic @session = diff --git a/spec/features/import_class_lists_move_spec.rb b/spec/features/import_class_lists_move_spec.rb index a28d9ee979..810fc11825 100644 --- a/spec/features/import_class_lists_move_spec.rb +++ b/spec/features/import_class_lists_move_spec.rb @@ -141,13 +141,14 @@ def given_an_hpv_programme_is_underway @second_team = create(:team, :with_one_nurse, programmes:) @second_user = @second_team.users.first - location = create(:school, :secondary, name: "Waterloo Road", team: @team) + location = + create(:gias_school, :secondary, name: "Waterloo Road", team: @team) other_location = - create(:school, :secondary, name: "Different Road", team: @team) + create(:gias_school, :secondary, name: "Different Road", team: @team) second_team_location = create( - :school, + :gias_school, :secondary, name: "Second Team School", team: @second_team diff --git a/spec/features/import_class_lists_spec.rb b/spec/features/import_class_lists_spec.rb index 27b0f40311..be5d58c6a3 100644 --- a/spec/features/import_class_lists_spec.rb +++ b/spec/features/import_class_lists_spec.rb @@ -78,7 +78,7 @@ def given_an_hpv_programme_is_underway programmes = [Programme.hpv] @team = create(:team, :with_one_nurse, programmes:) - location = create(:school, name: "Waterloo Road", team: @team) + location = create(:gias_school, name: "Waterloo Road", team: @team) @user = @team.users.first @@ -219,9 +219,7 @@ def when_i_upload_a_file_with_invalid_fields end def then_i_should_the_errors_page_with_invalid_fields - expect(page).to have_content( - "How to format your Mavis CSV file for class lists" - ) + expect(page).to have_content("What your CSV file must include") expect(page).to have_content("Row 1") end diff --git a/spec/features/import_class_lists_with_duplicates_spec.rb b/spec/features/import_class_lists_with_duplicates_spec.rb index b90e1fce0c..22c5e3ef03 100644 --- a/spec/features/import_class_lists_with_duplicates_spec.rb +++ b/spec/features/import_class_lists_with_duplicates_spec.rb @@ -127,7 +127,8 @@ def and_pds_lookup_during_import_is_enabled def and_an_hpv_programme_is_underway programme = Programme.hpv - @location = create(:school, :secondary, name: "Waterloo Road", team: @team) + @location = + create(:gias_school, :secondary, name: "Waterloo Road", team: @team) @session = create( :session, diff --git a/spec/features/import_vaccination_records_national_reporting_spec.rb b/spec/features/import_vaccination_records_national_reporting_spec.rb index 27efa42876..d94c376c7d 100644 --- a/spec/features/import_vaccination_records_national_reporting_spec.rb +++ b/spec/features/import_vaccination_records_national_reporting_spec.rb @@ -56,8 +56,8 @@ context "cutoff date banner" do let(:cutoff_date) { Date.new(2026, 4, 20) } - context "when today is not the cutoff date" do - around { |example| travel_to(cutoff_date - 1.day) { example.run } } + context "when today is more than 2 days after the cutoff date" do + around { |example| travel_to(cutoff_date + 3.days) { example.run } } scenario "banner is not shown" do given_mavis_logins_are_configured( @@ -69,6 +69,38 @@ end end + context "when today is within 2 days after the cutoff date" do + around { |example| travel_to(cutoff_date + 2.days) { example.run } } + + scenario "banner is shown" do + given_mavis_logins_are_configured( + national_reporting_cut_off_date: cutoff_date + ) + given_i_am_signed_in_as_a_national_reporting_user + when_i_navigate_to_the_upload_page + expect(page).to have_css(".nhsuk-notification-banner") + expect(page).to have_content( + "Mavis national reporting replaces NIVS on 20 April 2026" + ) + end + end + + context "when today is before the cutoff date" do + around { |example| travel_to(cutoff_date - 1.day) { example.run } } + + scenario "banner is shown" do + given_mavis_logins_are_configured( + national_reporting_cut_off_date: cutoff_date + ) + given_i_am_signed_in_as_a_national_reporting_user + when_i_navigate_to_the_upload_page + expect(page).to have_css(".nhsuk-notification-banner") + expect(page).to have_content( + "Mavis national reporting replaces NIVS on 20 April 2026" + ) + end + end + context "when today is the cutoff date" do around { |example| travel_to(cutoff_date) { example.run } } @@ -79,14 +111,17 @@ given_i_am_signed_in_as_a_national_reporting_user when_i_navigate_to_the_upload_page expect(page).to have_css(".nhsuk-notification-banner") - expect(page).to have_content("20 April 2026") - expect(page).to have_content("Check where to upload records") expect(page).to have_content( - "before 20 April 2026 must be uploaded to NIVS by the end of today" + "Mavis national reporting replaces NIVS on 20 April 2026" ) + expect(page).to have_content("Vaccinations given before 20 April 2026") expect(page).to have_content( - "on or after 20 April 2026 must be uploaded to Mavis national reporting only" + "Upload to NIVS by 22 April 2026. After this, NIVS will no longer be available." ) + expect(page).to have_content( + "Vaccinations given on or after 20 April 2026" + ) + expect(page).to have_content("Upload to Mavis national reporting only.") end end end @@ -100,7 +135,7 @@ def given_mavis_logins_are_configured(national_reporting_cut_off_date: nil) ods_code: "R1L", national_reporting_cut_off_date: ) - @school = create(:school, team: @team, urn: 100_000) + @school = create(:gias_school, team: @team, urn: 100_000) end def given_i_am_signed_in_as_a_national_reporting_user @@ -183,9 +218,7 @@ def when_i_upload_an_invalid_file end def then_i_should_see_the_errors_page - expect(page).to have_content( - "How to format your CSV file for vaccination records" - ) + expect(page).to have_content("What your CSV file must include") expect(page).to have_content("Row 2") expect(page).to have_content("ANATOMICAL_SITE:") expect(page).to have_content("SCHOOL_URN:") diff --git a/spec/features/import_vaccination_records_point_of_care_spec.rb b/spec/features/import_vaccination_records_point_of_care_spec.rb index 7c00218c63..6a5c68cc85 100644 --- a/spec/features/import_vaccination_records_point_of_care_spec.rb +++ b/spec/features/import_vaccination_records_point_of_care_spec.rb @@ -48,7 +48,7 @@ def given_an_hpv_programme_is_underway programme = Programme.hpv @team = create(:team, :with_one_nurse, ods_code: "R1L", programmes: [programme]) - location = create(:school, team: @team) + location = create(:gias_school, team: @team) @session = create(:session, programmes: [programme], location:, team: @team) end @@ -57,9 +57,9 @@ def and_i_am_signed_in end def and_school_locations_exist - create(:school, urn: "110158") - create(:school, urn: "120026") - create(:school, urn: "144012") + create(:gias_school, urn: "110158") + create(:gias_school, urn: "120026") + create(:gias_school, urn: "144012") end def given_a_patient_exists @@ -121,9 +121,7 @@ def when_i_upload_an_invalid_file end def then_i_should_see_the_errors_page - expect(page).to have_content( - "How to format your Mavis CSV file for vaccination records" - ) + expect(page).to have_content("What your CSV file must include") expect(page).to have_content("Row 3") expect(page).to have_content("VACCINATED:") diff --git a/spec/features/import_vaccination_records_with_duplicates_spec.rb b/spec/features/import_vaccination_records_with_duplicates_spec.rb index afe74c4b15..6287ea7cc5 100644 --- a/spec/features/import_vaccination_records_with_duplicates_spec.rb +++ b/spec/features/import_vaccination_records_with_duplicates_spec.rb @@ -41,7 +41,7 @@ def given_an_hpv_programme_is_underway @team = create(:team, :with_one_nurse, ods_code: "R1L", programmes: [@programme]) - @location = create(:school, urn: "110158", name: "Eton College") + @location = create(:gias_school, urn: "110158", name: "Eton College") @session = create( :session, diff --git a/spec/features/important_notices_spec.rb b/spec/features/important_notices_spec.rb index 7d8a89bbfe..093420deef 100644 --- a/spec/features/important_notices_spec.rb +++ b/spec/features/important_notices_spec.rb @@ -112,7 +112,7 @@ def given_my_team_exists @programme = Programme.hpv @team = create(:team, :with_one_nurse, programmes: [@programme]) - @school = create(:school, :secondary, team: @team, urn: "123456") + @school = create(:gias_school, :secondary, team: @team, urn: "123456") @session = create( :session, @@ -126,7 +126,7 @@ def given_my_team_exists def and_another_team_exists @other_team = create(:team, :with_one_nurse, programmes: [@programme]) @other_school = - create(:school, :secondary, team: @other_team, urn: "111333") + create(:gias_school, :secondary, team: @other_team, urn: "111333") @other_session = create( :session, diff --git a/spec/features/manage_attendance_spec.rb b/spec/features/manage_attendance_spec.rb index ed035c85d9..04369efe08 100644 --- a/spec/features/manage_attendance_spec.rb +++ b/spec/features/manage_attendance_spec.rb @@ -98,7 +98,7 @@ def and_there_is_a_vaccination_session_today :today, programmes: @programmes, team: @team, - location: create(:school, team: @team) + location: create(:gias_school, team: @team) ) end @@ -110,7 +110,7 @@ def and_there_is_a_vaccination_session_today_that_requires_no_registration :requires_no_registration, programmes: @programmes, team: @team, - location: create(:school, team: @team) + location: create(:gias_school, team: @team) ) end @@ -122,7 +122,7 @@ def and_there_is_a_vaccination_session_yesterday_that_requires_no_registration :requires_no_registration, programmes: @programmes, team: @team, - location: create(:school, team: @team) + location: create(:gias_school, team: @team) ) end diff --git a/spec/features/manage_batches_spec.rb b/spec/features/manage_batches_spec.rb index 3aec1fdfbc..65596940d4 100644 --- a/spec/features/manage_batches_spec.rb +++ b/spec/features/manage_batches_spec.rb @@ -30,7 +30,7 @@ def given_my_team_is_running_an_hpv_vaccination_programme end def and_there_is_a_vaccination_session_today_with_one_patient_safe_to_vaccinate - location = create(:school, programmes: @programmes) + location = create(:gias_school, programmes: @programmes) session = create(:session, :today, programmes: @programmes, location:) create( diff --git a/spec/features/manage_children_spec.rb b/spec/features/manage_children_spec.rb index d15d80e7b5..58c1df7a0a 100644 --- a/spec/features/manage_children_spec.rb +++ b/spec/features/manage_children_spec.rb @@ -253,17 +253,17 @@ def given_my_team_exists end def given_patients_exist - school = create(:school, team: @team) + school = create(:gias_school, team: @team) @ineligible_school = create( - :school, + :gias_school, name: "Ineligible School", gias_year_groups: [4], team: @team ) - @new_school = create(:school, name: "New School", team: @team) + @new_school = create(:gias_school, name: "New School", team: @team) @session = create(:session, location: school, team: @team, programmes: [@hpv]) diff --git a/spec/features/manage_teams_spec.rb b/spec/features/manage_teams_spec.rb index ceb9bf495d..74cc2361e3 100644 --- a/spec/features/manage_teams_spec.rb +++ b/spec/features/manage_teams_spec.rb @@ -163,13 +163,15 @@ def given_my_team_exists @team = create(:team, :with_one_nurse) - @school = create(:school, team: @team, urn: "12345", name: "Current school") - @school_last_year = create(:school, urn: "67890", name: "School last year") + @school = + create(:gias_school, team: @team, urn: "12345", name: "Current school") + @school_last_year = + create(:gias_school, urn: "67890", name: "School last year") @school_last_year.attach_to_team!( @team, academic_year: AcademicYear.pending - 1 ) - create(:school, team: @team, urn: "34567") + create(:gias_school, team: @team, urn: "34567") create(:community_clinic, team: @team) end @@ -177,7 +179,13 @@ def and_sites_exist @school.update!(site: "A") @site_b = - create(:school, team: @team, urn: @school.urn, site: "B", name: "Site B") + create( + :gias_school, + team: @team, + urn: @school.urn, + site: "B", + name: "Site B" + ) end def when_i_click_on_team_settings @@ -406,9 +414,9 @@ def then_i_see_the_school_site_confirmation_banner end def and_a_school_site_is_created - expect(Location.school.count).to eq(4) + expect(Location.gias_school.count).to eq(4) - site = Location.school.last + site = Location.gias_school.last expect(site.name).to eq("New School Site") expect(site.address_line_1).to eq("123 Main St") expect(site.address_line_2).to eq("Suite 100") @@ -420,7 +428,7 @@ def and_a_school_site_is_created end def and_the_school_site_has_the_correct_year_groups - site = Location.school.last + site = Location.gias_school.last expect(site.location_year_groups.pluck(:value)).to include(12) end @@ -430,16 +438,17 @@ def when_i_go_back def and_other_schools_exist @closed_school = - create(:school, :closed, urn: "99999", name: "Closed school") + create(:gias_school, :closed, urn: "99999", name: "Closed school") @other_team = create(:team) @other_team_school = create( - :school, + :gias_school, urn: "88888", team: @other_team, name: "Other Team School" ) - @available_school = create(:school, urn: "77777", name: "Available School") + @available_school = + create(:gias_school, urn: "77777", name: "Available School") end def when_i_click_on_add_a_new_school diff --git a/spec/features/menacwy_vaccination_administered_spec.rb b/spec/features/menacwy_vaccination_administered_spec.rb index 6b7af8bdda..3d4672e665 100644 --- a/spec/features/menacwy_vaccination_administered_spec.rb +++ b/spec/features/menacwy_vaccination_administered_spec.rb @@ -57,7 +57,7 @@ def given_i_am_signed_in programme = Programme.menacwy team = create(:team, :with_one_nurse, programmes: [programme]) - location = create(:school, team:) + location = create(:gias_school, team:) programme.vaccines.discontinued.each do |vaccine| create(:batch, team:, vaccine:) @@ -217,7 +217,7 @@ def then_an_email_is_sent_to_the_parent_confirming_the_vaccination matching_notify_email( to: @patient.consents.last.parent.email, subject: "Your child had their MenACWY vaccination today", - template: :vaccination_administered_menacwy + template: :vaccination_administered ).with_content_including( "John Doe had their MenACWY vaccination at #{@session.location.name} today", "Vaccination: MenACWY", diff --git a/spec/features/mmr_triage_spec.rb b/spec/features/mmr_triage_spec.rb index 5b36d36a8e..c921b2a06e 100644 --- a/spec/features/mmr_triage_spec.rb +++ b/spec/features/mmr_triage_spec.rb @@ -50,7 +50,7 @@ def given_i_am_signed_in_with_mmr_programme @programme = Programme.mmr @team = create(:team, :with_one_nurse, programmes: [@programme]) - @location = create(:school, team: @team) + @location = create(:gias_school, team: @team) @session = create( :session, diff --git a/spec/features/mmr_vaccination_administered_spec.rb b/spec/features/mmr_vaccination_administered_spec.rb index 762ace75bf..c420adc51c 100644 --- a/spec/features/mmr_vaccination_administered_spec.rb +++ b/spec/features/mmr_vaccination_administered_spec.rb @@ -94,7 +94,7 @@ def given_i_am_signed_in_with_mmr_programme @programme = Programme.mmr @team = create(:team, :with_one_nurse, programmes: [@programme]) - @location = create(:school, team: @team) + @location = create(:gias_school, team: @team) @session = create( :session, @@ -305,12 +305,12 @@ def then_an_email_is_sent_to_the_parent_confirming_the_vaccination matching_notify_email( to: @patient.consents.last.parent.email, subject: "Your child had their MMR vaccination today", - template: :vaccination_administered_mmr + template: :vaccination_administered ).with_content_including( "MMR vaccination", "a raised, blotchy rash", "swollen glands around the cheeks, neck and jaw", - "Your child still needs a second dose of the MMR vaccine" + "Your child still needs a 2nd dose of the MMR vaccine" ) ) end diff --git a/spec/features/mmrv_triage_spec.rb b/spec/features/mmrv_triage_spec.rb index 18ee8417a1..cf4b838f2d 100644 --- a/spec/features/mmrv_triage_spec.rb +++ b/spec/features/mmrv_triage_spec.rb @@ -50,7 +50,7 @@ def given_i_am_signed_in_with_mmr_programme @programme = Programme.mmr @team = create(:team, :with_one_nurse, programmes: [@programme]) - @location = create(:school, team: @team) + @location = create(:gias_school, team: @team) @session = create( :session, diff --git a/spec/features/mmrv_vaccination_administered_spec.rb b/spec/features/mmrv_vaccination_administered_spec.rb index 820dbe5114..a9b601dfd1 100644 --- a/spec/features/mmrv_vaccination_administered_spec.rb +++ b/spec/features/mmrv_vaccination_administered_spec.rb @@ -268,13 +268,13 @@ def then_an_email_is_sent_to_the_parent_confirming_the_vaccination matching_notify_email( to: @patient.consents.last.parent.email, subject: "Your child had their MMRV vaccination today", - template: :vaccination_administered_mmr + template: :vaccination_administered ).with_content_including( "MMRV vaccination", "ProQuad", "a raised, blotchy rash", "swollen glands around the cheeks, neck and jaw", - "Your child still needs a second dose of the MMR vaccine" + "Your child still needs a 2nd dose of the MMRV vaccine" ) ) end diff --git a/spec/features/national_reporting_team_spec.rb b/spec/features/national_reporting_team_spec.rb index 8bfa9626c2..b00adf178a 100644 --- a/spec/features/national_reporting_team_spec.rb +++ b/spec/features/national_reporting_team_spec.rb @@ -61,7 +61,7 @@ def given_i_am_signed_in_as_an_national_reporting_team programmes: [Programme.flu, Programme.hpv], ods_code: "XX99" ) - @school = create(:school, team: @team, urn: 100_000) + @school = create(:gias_school, team: @team, urn: 100_000) sign_in @team.users.first end diff --git a/spec/features/nhs_vaccination_already_had_notification_spec.rb b/spec/features/nhs_vaccination_already_had_notification_spec.rb new file mode 100644 index 0000000000..c784456fd0 --- /dev/null +++ b/spec/features/nhs_vaccination_already_had_notification_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +describe "NHS vaccination already had notification" do + before do + Flipper.enable(:imms_api_integration) + Flipper.enable(:imms_api_search_job, Programme.flu) + end + + after do + Flipper.disable(:imms_api_integration) + Flipper.disable(:imms_api_search_job) + end + + scenario "parent is notified when NHS API reveals their child was vaccinated elsewhere" do + given_a_patient_with_consent_exists + and_the_nhs_api_returns_a_flu_vaccination_for_the_patient + when_the_nhs_vaccination_search_runs + then_the_parent_receives_a_vaccination_already_had_email + end + + private + + def given_a_patient_with_consent_exists + team = + create( + :team, + programmes: [Programme.flu], + name: "South Hampshire SAIS", + email: "southhampshire@example.com", + phone: "02380 654321" + ) + school = create(:gias_school, team:) + session = + create(:session, programmes: [Programme.flu], team:, location: school) + @parent = create(:parent, email: "parent@example.com") + @patient = + create( + :patient, + nhs_number: "9449308357", + parents: [@parent], + session:, + school: + ) + create( + :consent, + :given, + patient: @patient, + programme: Programme.flu, + parent: @parent + ) + end + + def and_the_nhs_api_returns_a_flu_vaccination_for_the_patient + stub_request( + :get, + "https://sandbox.api.service.nhs.uk/immunisation-fhir-api/FHIR/R4/Immunization" + ).with( + query: { + "patient.identifier" => "https://fhir.nhs.uk/Id/nhs-number|9449308357", + "-immunization.target" => "3IN1,FLU,HPV,MENACWY,MMR,MMRV" + } + ).to_return( + status: 200, + body: + file_fixture( + "fhir/search_responses/1_result_in_academic_year_2025.json" + ).read, + headers: { + "content-type" => "application/fhir+json" + } + ) + end + + def when_the_nhs_vaccination_search_runs + SearchVaccinationRecordsInNHSJob.new.perform(@patient.id) + end + + def then_the_parent_receives_a_vaccination_already_had_email + expect(email_deliveries).to include( + matching_notify_email( + to: "parent@example.com", + template: :vaccination_already_had + ).with_content_including( + "South Hampshire SAIS", + "southhampshire@example.com", + "023 8065 4321" + ) + ) + end +end diff --git a/spec/features/parental_consent_authentication_spec.rb b/spec/features/parental_consent_authentication_spec.rb index 23bb62d83b..3d197eccde 100644 --- a/spec/features/parental_consent_authentication_spec.rb +++ b/spec/features/parental_consent_authentication_spec.rb @@ -22,7 +22,8 @@ def given_an_hpv_programme_is_underway @programme = Programme.hpv @team = create(:team, :with_one_nurse, programmes: [@programme]) - location = create(:school, name: "Pilot School", programmes: [@programme]) + location = + create(:gias_school, name: "Pilot School", programmes: [@programme]) @session = create(:session, :scheduled, programmes: [@programme], location:) @child = create(:patient, session: @session) end diff --git a/spec/features/parental_consent_change_answers_spec.rb b/spec/features/parental_consent_change_answers_spec.rb index d685295ea2..707f26c07a 100644 --- a/spec/features/parental_consent_change_answers_spec.rb +++ b/spec/features/parental_consent_change_answers_spec.rb @@ -81,7 +81,7 @@ def given_a_flu_programme_is_underway programmes = [Programme.flu] @team = create(:team, :with_one_nurse, programmes:) - location = create(:school, name: "Pilot School", team: @team) + location = create(:gias_school, name: "Pilot School", team: @team) @session = create(:session, :scheduled, team: @team, programmes:, location:) @child = create(:patient, session: @session) end diff --git a/spec/features/parental_consent_clinic_spec.rb b/spec/features/parental_consent_clinic_spec.rb index ea9a9a2492..c2070897c5 100644 --- a/spec/features/parental_consent_clinic_spec.rb +++ b/spec/features/parental_consent_clinic_spec.rb @@ -138,7 +138,7 @@ def given_an_hpv_programme_is_underway @child = create(:patient, school: @team.unknown_school, session: @session) - @school = create(:school, team: @team) + @school = create(:gias_school, team: @team) end def and_an_upcoming_school_session_exists diff --git a/spec/features/parental_consent_closed_spec.rb b/spec/features/parental_consent_closed_spec.rb index dbe109931b..f4babe3253 100644 --- a/spec/features/parental_consent_closed_spec.rb +++ b/spec/features/parental_consent_closed_spec.rb @@ -20,7 +20,7 @@ def given_an_hpv_programme_is_underway_with_a_backfilled_session @programme = Programme.hpv @team = create(:team, :with_one_nurse, programmes: [@programme]) @subteam = create(:subteam, team: @team) - location = create(:school, name: "Pilot School", subteam: @subteam) + location = create(:gias_school, name: "Pilot School", subteam: @subteam) @session = create( :session, @@ -36,7 +36,7 @@ def given_an_hpv_programme_is_starting_soon @programme = Programme.hpv @team = create(:team, :with_one_nurse, programmes: [@programme]) @subteam = create(:subteam, team: @team) - location = create(:school, name: "Pilot School", subteam: @subteam) + location = create(:gias_school, name: "Pilot School", subteam: @subteam) @session = create( :session, diff --git a/spec/features/parental_consent_create_patient_spec.rb b/spec/features/parental_consent_create_patient_spec.rb index bf47948b4e..cf712c1714 100644 --- a/spec/features/parental_consent_create_patient_spec.rb +++ b/spec/features/parental_consent_create_patient_spec.rb @@ -85,7 +85,7 @@ def given_the_app_is_setup @programme = Programme.hpv @team = create(:team, :with_one_nurse, programmes: [@programme]) - location = create(:school, name: "Pilot School", team: @team) + location = create(:gias_school, name: "Pilot School", team: @team) @session = create( :session, diff --git a/spec/features/parental_consent_different_school_spec.rb b/spec/features/parental_consent_different_school_spec.rb index 94ac5a2714..2afda938fa 100644 --- a/spec/features/parental_consent_different_school_spec.rb +++ b/spec/features/parental_consent_different_school_spec.rb @@ -27,8 +27,9 @@ def given_an_hpv_programme_is_underway @team = create(:team, :with_one_nurse, programmes: [@programme]) - @original_location = create(:school, team: @team, name: "Original School") - @actual_location = create(:school, team: @team, name: "Actual School") + @original_location = + create(:gias_school, team: @team, name: "Original School") + @actual_location = create(:gias_school, team: @team, name: "Actual School") @session = create( diff --git a/spec/features/parental_consent_doubles_spec.rb b/spec/features/parental_consent_doubles_spec.rb index b169611772..b923dbf90b 100644 --- a/spec/features/parental_consent_doubles_spec.rb +++ b/spec/features/parental_consent_doubles_spec.rb @@ -60,7 +60,7 @@ def given_a_doubles_programme_is_underway programmes = [@programme1, @programme2] @team = create(:team, :with_one_nurse, programmes:) - location = create(:school, name: "Pilot School", programmes:) + location = create(:gias_school, name: "Pilot School", programmes:) @session = create(:session, :scheduled, programmes:, location:) @child = create(:patient, session: @session) end diff --git a/spec/features/parental_consent_flu_spec.rb b/spec/features/parental_consent_flu_spec.rb index 090fe03722..7ca7a132a0 100644 --- a/spec/features/parental_consent_flu_spec.rb +++ b/spec/features/parental_consent_flu_spec.rb @@ -91,7 +91,8 @@ def given_ethnicity_capture_is_enabled def given_a_flu_programme_is_underway @programme = Programme.flu @team = create(:team, :with_one_nurse, programmes: [@programme]) - location = create(:school, name: "Pilot School", programmes: [@programme]) + location = + create(:gias_school, name: "Pilot School", programmes: [@programme]) @session = create(:session, :scheduled, programmes: [@programme], location:) @child = create(:patient, session: @session) stub_pds_search_to_return_a_patient(@child.nhs_number) diff --git a/spec/features/parental_consent_home_educated_spec.rb b/spec/features/parental_consent_home_educated_spec.rb index 8128075462..f5ec3fe13a 100644 --- a/spec/features/parental_consent_home_educated_spec.rb +++ b/spec/features/parental_consent_home_educated_spec.rb @@ -23,7 +23,7 @@ def given_an_hpv_programme_is_underway @programme = Programme.hpv @team = create(:team, :with_one_nurse, programmes: [@programme]) - location = create(:school, team: @team, name: "Pilot School") + location = create(:gias_school, team: @team, name: "Pilot School") @session = create( :session, diff --git a/spec/features/parental_consent_hpv_spec.rb b/spec/features/parental_consent_hpv_spec.rb index 92d9363ced..5fe69a49b9 100644 --- a/spec/features/parental_consent_hpv_spec.rb +++ b/spec/features/parental_consent_hpv_spec.rb @@ -40,7 +40,7 @@ def given_an_hpv_programme_is_underway @programme = Programme.hpv @team = create(:team, :with_one_nurse, programmes: [@programme]) - location = create(:school, name: "Pilot School", team: @team) + location = create(:gias_school, name: "Pilot School", team: @team) @session = create( :session, diff --git a/spec/features/parental_consent_inexact_auto_match_spec.rb b/spec/features/parental_consent_inexact_auto_match_spec.rb index ffa074e4f5..75f46e89aa 100644 --- a/spec/features/parental_consent_inexact_auto_match_spec.rb +++ b/spec/features/parental_consent_inexact_auto_match_spec.rb @@ -18,7 +18,7 @@ def given_an_hpv_programme_is_underway @programme = Programme.hpv @team = create(:team, :with_one_nurse, programmes: [@programme]) - location = create(:school, name: "Pilot School", team: @team) + location = create(:gias_school, name: "Pilot School", team: @team) @session = create( :session, diff --git a/spec/features/parental_consent_manual_consent_reminders_send_spec.rb b/spec/features/parental_consent_manual_consent_reminders_send_spec.rb index 33107d3a7f..de0752e190 100644 --- a/spec/features/parental_consent_manual_consent_reminders_send_spec.rb +++ b/spec/features/parental_consent_manual_consent_reminders_send_spec.rb @@ -32,7 +32,7 @@ def given_a_session_with_patients_having_no_consent_response @team = create(:team, :with_one_nurse, programmes:) @user = @team.users.first - location = create(:school, team: @team) + location = create(:gias_school, team: @team) @session = create( @@ -79,7 +79,7 @@ def given_a_session_with_patients_having_consent_responses @team = create(:team, :with_one_nurse, programmes:) @user = @team.users.first - location = create(:school, team: @team) + location = create(:gias_school, team: @team) @session = create( diff --git a/spec/features/parental_consent_manual_matching_spec.rb b/spec/features/parental_consent_manual_matching_spec.rb index cee9dafbc3..33bd8b39b3 100644 --- a/spec/features/parental_consent_manual_matching_spec.rb +++ b/spec/features/parental_consent_manual_matching_spec.rb @@ -74,7 +74,7 @@ def given_the_app_is_setup @team = create(:team, :with_one_nurse, programmes:) @user = @team.users.first - @school = create(:school, name: "Pilot School", team: @team) + @school = create(:gias_school, name: "Pilot School", team: @team) @session = create(:session, location: @school, team: @team, programmes:) @consent_form = create( diff --git a/spec/features/parental_consent_mmr_spec.rb b/spec/features/parental_consent_mmr_spec.rb index ada2f0a642..c97d0ac385 100644 --- a/spec/features/parental_consent_mmr_spec.rb +++ b/spec/features/parental_consent_mmr_spec.rb @@ -52,7 +52,8 @@ def given_an_mmr_programme_is_underway @programme = Programme.mmr @team = create(:team, :with_one_nurse, programmes: [@programme]) - location = create(:school, name: "Pilot School", programmes: [@programme]) + location = + create(:gias_school, name: "Pilot School", programmes: [@programme]) @session = create(:session, :scheduled, programmes: [@programme], location:) @child = create( diff --git a/spec/features/parental_consent_mmrv_spec.rb b/spec/features/parental_consent_mmrv_spec.rb index 3473eef582..e755682f3a 100644 --- a/spec/features/parental_consent_mmrv_spec.rb +++ b/spec/features/parental_consent_mmrv_spec.rb @@ -54,7 +54,7 @@ def given_an_mmr_programme_is_underway Programme::Variant.new(@mmr_programme, variant_type: "mmrv") @team = create(:team, :with_one_nurse, programmes: [@mmr_programme]) location = - create(:school, name: "Pilot School", programmes: [@mmr_programme]) + create(:gias_school, name: "Pilot School", programmes: [@mmr_programme]) @session = create(:session, :scheduled, programmes: [@mmrv_programme], location:) @child = diff --git a/spec/features/parental_consent_refused_spec.rb b/spec/features/parental_consent_refused_spec.rb index 1f5bbbc7a9..c23620b5bb 100644 --- a/spec/features/parental_consent_refused_spec.rb +++ b/spec/features/parental_consent_refused_spec.rb @@ -58,7 +58,7 @@ def given_an_hpv_programme_is_underway @programme = Programme.hpv @team = create(:team, :with_one_nurse, programmes: [@programme]) - location = create(:school, name: "Pilot School", team: @team) + location = create(:gias_school, name: "Pilot School", team: @team) @session = create( :session, diff --git a/spec/features/parental_consent_school_session_completed_spec.rb b/spec/features/parental_consent_school_session_completed_spec.rb index 945520b236..362c20a7e7 100644 --- a/spec/features/parental_consent_school_session_completed_spec.rb +++ b/spec/features/parental_consent_school_session_completed_spec.rb @@ -33,7 +33,7 @@ def given_an_hpv_programme_is_underway @unscheduled_school = create( - :school, + :gias_school, :secondary, name: "School 1", subteam: @subteam, @@ -42,7 +42,7 @@ def given_an_hpv_programme_is_underway ) @scheduled_school = create( - :school, + :gias_school, :secondary, name: "School 2", subteam: @subteam, @@ -51,7 +51,7 @@ def given_an_hpv_programme_is_underway ) @completed_school = create( - :school, + :gias_school, :secondary, name: "School 3", subteam: @subteam, diff --git a/spec/features/parental_consent_send_request_spec.rb b/spec/features/parental_consent_send_request_spec.rb index ce96a8243e..201037bc87 100644 --- a/spec/features/parental_consent_send_request_spec.rb +++ b/spec/features/parental_consent_send_request_spec.rb @@ -463,7 +463,7 @@ def create_school_session_with_patient(date_of_birth:, outbreak: false) @team = create(:team, :with_one_nurse, programmes: @programmes) @user = @team.users.first - location = create(:school, team: @team, programmes: @programmes) + location = create(:gias_school, team: @team, programmes: @programmes) @session = create( diff --git a/spec/features/patient_search_spec.rb b/spec/features/patient_search_spec.rb index 030cc46fbb..e0b5f8cb08 100644 --- a/spec/features/patient_search_spec.rb +++ b/spec/features/patient_search_spec.rb @@ -53,7 +53,7 @@ def given_that_i_am_signed_in programmes = [Programme.hpv] team = create(:team, :with_one_nurse, programmes:) - location = create(:school, name: "Waterloo Road", team:) + location = create(:gias_school, name: "Waterloo Road", team:) @session = create(:session, location:, team:, programmes:) [ diff --git a/spec/features/pre_screening_spec.rb b/spec/features/pre_screening_spec.rb index ba90a74582..5740a64bbf 100644 --- a/spec/features/pre_screening_spec.rb +++ b/spec/features/pre_screening_spec.rb @@ -72,7 +72,7 @@ def given_a_session_exists(programme, vaccine_method: "injection") :session, team:, programmes: [programme], - location: create(:school, team:) + location: create(:gias_school, team:) ) @patient = diff --git a/spec/features/record_already_vaccinated_hpv_spec.rb b/spec/features/record_already_vaccinated_hpv_spec.rb index a2ec80bc50..ac87295c8f 100644 --- a/spec/features/record_already_vaccinated_hpv_spec.rb +++ b/spec/features/record_already_vaccinated_hpv_spec.rb @@ -28,7 +28,7 @@ def given_i_am_signed_in programmes = [Programme.hpv] team = create(:team, :with_one_nurse, programmes:) - school = create(:school, :secondary, team:, programmes:) + school = create(:gias_school, :secondary, team:, programmes:) @patient = create(:patient, :consent_no_response, school:, programmes:) diff --git a/spec/features/record_already_vaccinated_mmr_spec.rb b/spec/features/record_already_vaccinated_mmr_spec.rb index f0b53bee46..12d912aaf2 100644 --- a/spec/features/record_already_vaccinated_mmr_spec.rb +++ b/spec/features/record_already_vaccinated_mmr_spec.rb @@ -37,7 +37,7 @@ def given_i_am_signed_in programmes = [Programme.mmr] team = create(:team, :with_one_nurse, programmes:) - school = create(:school, :secondary, team:, programmes:) + school = create(:gias_school, :secondary, team:, programmes:) @patient = create( diff --git a/spec/features/record_already_vaccinated_mmrv_spec.rb b/spec/features/record_already_vaccinated_mmrv_spec.rb index 319ac52158..0d21e640a3 100644 --- a/spec/features/record_already_vaccinated_mmrv_spec.rb +++ b/spec/features/record_already_vaccinated_mmrv_spec.rb @@ -73,7 +73,7 @@ def given_i_am_signed_in programmes = [Programme.mmr] team = create(:team, :with_one_nurse, programmes:) - school = create(:school, :primary, team:, programmes:) + school = create(:gias_school, :primary, team:, programmes:) @patient = create( diff --git a/spec/features/record_already_vaccinated_td_ipv_spec.rb b/spec/features/record_already_vaccinated_td_ipv_spec.rb index b087560cd4..749ddd09e7 100644 --- a/spec/features/record_already_vaccinated_td_ipv_spec.rb +++ b/spec/features/record_already_vaccinated_td_ipv_spec.rb @@ -28,7 +28,7 @@ def given_i_am_signed_in programmes = [Programme.menacwy, Programme.td_ipv] team = create(:team, :with_one_nurse, programmes:) - school = create(:school, :secondary, team:, programmes:) + school = create(:gias_school, :secondary, team:, programmes:) @patient = create( diff --git a/spec/features/scheduled_consent_requests_and_reminders_spec.rb b/spec/features/scheduled_consent_requests_and_reminders_spec.rb index 239cf7b2a6..0b2ea4daff 100644 --- a/spec/features/scheduled_consent_requests_and_reminders_spec.rb +++ b/spec/features/scheduled_consent_requests_and_reminders_spec.rb @@ -82,7 +82,7 @@ def given_my_team_is_running_all_vaccination_programmes Programme.td_ipv ] @team = create(:team, :with_one_nurse, programmes:) - @location = create(:school, team: @team) + @location = create(:gias_school, team: @team) @session = create( :session, diff --git a/spec/features/schools_spec.rb b/spec/features/schools_spec.rb index 50000c6459..b6a51b7493 100644 --- a/spec/features/schools_spec.rb +++ b/spec/features/schools_spec.rb @@ -52,8 +52,8 @@ def given_a_team_exists_with_a_few_schools @team = create(:team, programmes:) - @primary_school = create(:school, :primary, team: @team) - @secondary_school = create(:school, :secondary, team: @team) + @primary_school = create(:gias_school, :primary, team: @team) + @secondary_school = create(:gias_school, :secondary, team: @team) @primary_session = create(:session, :yesterday, location: @primary_school, team: @team) diff --git a/spec/features/self_consent_spec.rb b/spec/features/self_consent_spec.rb index 2407ee1568..dd4b4d82a0 100644 --- a/spec/features/self_consent_spec.rb +++ b/spec/features/self_consent_spec.rb @@ -46,7 +46,7 @@ def given_an_hpv_programme_is_underway @team = create(:team, :with_one_nurse, programmes: [@programme]) - @school = create(:school, team: @team) + @school = create(:gias_school, team: @team) @session = create( diff --git a/spec/features/session_school_reminder_spec.rb b/spec/features/session_school_reminder_spec.rb index 18923727f5..741603bdc7 100644 --- a/spec/features/session_school_reminder_spec.rb +++ b/spec/features/session_school_reminder_spec.rb @@ -14,7 +14,7 @@ def given_a_school_session_is_scheduled_for_tomorrow programme = Programme.hpv @team = create(:team, programmes: [programme]) - school = create(:school, team: @team) + school = create(:gias_school, team: @team) @session = create( :session, diff --git a/spec/features/sessions_clinic_spec.rb b/spec/features/sessions_clinic_spec.rb index 9bee615e2c..b68de71ff6 100644 --- a/spec/features/sessions_clinic_spec.rb +++ b/spec/features/sessions_clinic_spec.rb @@ -60,7 +60,9 @@ def when_i_go_to_todays_sessions_as_a_nurse click_link "Sessions", match: :first + choose "Community clinic" choose "In progress" + click_on "Update results" end diff --git a/spec/features/sessions_school_spec.rb b/spec/features/sessions_school_spec.rb index 774cec0720..dd3a361fbc 100644 --- a/spec/features/sessions_school_spec.rb +++ b/spec/features/sessions_school_spec.rb @@ -111,7 +111,7 @@ def given_my_team_is_running_an_hpv_vaccination_programme programmes = [@programme, @other_programme] @team = create(:team, :with_one_nurse, programmes:) - @location = create(:school, :secondary, team: @team, programmes:) + @location = create(:gias_school, :secondary, team: @team, programmes:) @parent = create(:parent) @@ -180,7 +180,9 @@ def when_i_go_to_todays_sessions_as_a_nurse click_link "Sessions", match: :first + choose "School session" choose "In progress" + click_on "Update results" end @@ -481,7 +483,7 @@ def given_my_team_is_running_an_hpv_vaccination_programme_for_ods_code( programmes = [@programme, @other_programme] @team = create(:team, :with_one_nurse, programmes:, ods_code:) - @location = create(:school, :secondary, team: @team, programmes:) + @location = create(:gias_school, :secondary, team: @team, programmes:) @parent = create(:parent) diff --git a/spec/features/td_ipv_vaccination_administered_spec.rb b/spec/features/td_ipv_vaccination_administered_spec.rb index 5ae2abb280..ac507462b5 100644 --- a/spec/features/td_ipv_vaccination_administered_spec.rb +++ b/spec/features/td_ipv_vaccination_administered_spec.rb @@ -57,7 +57,7 @@ def given_i_am_signed_in programme = Programme.td_ipv team = create(:team, :with_one_nurse, programmes: [programme]) - location = create(:school, team:) + location = create(:gias_school, team:) programme.vaccines.discontinued.each do |vaccine| create(:batch, team:, vaccine:) @@ -218,10 +218,10 @@ def then_an_email_is_sent_to_the_parent_confirming_the_vaccination to: @patient.consents.last.parent.email, subject: "Your child had their Td/IPV (3-in-1 teenage booster) vaccination today", - template: :vaccination_administered_td_ipv + template: :vaccination_administered ).with_content_including( "John Doe had their Td/IPV (3-in-1 teenage booster) vaccination at #{@session.location.name} today", - "Vaccination: Td/IPV", + "Vaccination: Td/IPV (3-in-1 teenage booster)", "Vaccine: Revaxis", "Date of vaccination: 01/02/2024", "dizziness", diff --git a/spec/features/triage_delay_vaccination_spec.rb b/spec/features/triage_delay_vaccination_spec.rb index e7ea3f29b4..ab772a57b5 100644 --- a/spec/features/triage_delay_vaccination_spec.rb +++ b/spec/features/triage_delay_vaccination_spec.rb @@ -29,7 +29,7 @@ def given_a_programme_with_a_running_session programmes = [Programme.hpv] @team = create(:team, :with_one_nurse, programmes:) - @school = create(:school, team: @team) + @school = create(:gias_school, team: @team) session = create( :session, @@ -95,7 +95,7 @@ def and_a_vaccination_at_clinic_email_is_sent_to_the_parent end def when_i_filter_by_delay_vaccination - within(".nhsuk-breadcrumb") { click_on "Children" } + within(".nhsuk-breadcrumb") { click_on "Children in session" } choose "Unable to vaccinate" check "Delay vaccination" click_on "Update results" diff --git a/spec/features/triage_invite_to_clinic_spec.rb b/spec/features/triage_invite_to_clinic_spec.rb index 8bbb155236..099bff3eac 100644 --- a/spec/features/triage_invite_to_clinic_spec.rb +++ b/spec/features/triage_invite_to_clinic_spec.rb @@ -79,7 +79,7 @@ def given_a_programme_with_a_running_session programmes = [Programme.hpv] @team = create(:team, :with_one_nurse, programmes:) - @school = create(:school, team: @team) + @school = create(:gias_school, team: @team) @session = create(:session, team: @team, programmes:, location: @school) end @@ -87,7 +87,7 @@ def given_a_programme_with_a_running_session def given_a_programme_with_a_running_session_for_team(ods_code:) programmes = [Programme.hpv] @team = create(:team, :with_one_nurse, programmes:, ods_code:) - @school = create(:school, team: @team) + @school = create(:gias_school, team: @team) @session = create(:session, team: @team, programmes:, location: @school) end @@ -194,7 +194,7 @@ def and_a_vaccination_at_clinic_ryg_email_is_sent_to_the_parent end def when_i_filter_by_invited_to_clinic - within(".nhsuk-breadcrumb") { click_on "Children" } + within(".nhsuk-breadcrumb") { click_on "Children in session" } choose "Needs triage" click_on "Update results" end diff --git a/spec/features/triage_partially_vaccinated_spec.rb b/spec/features/triage_partially_vaccinated_spec.rb index e375b5259f..4a273357d7 100644 --- a/spec/features/triage_partially_vaccinated_spec.rb +++ b/spec/features/triage_partially_vaccinated_spec.rb @@ -32,7 +32,7 @@ def given_a_td_ipv_programme_with_a_session team = create(:team, programmes: [@programme]) @user = create(:nurse, teams: [team]) - location = create(:school, :secondary, urn: 123_456, team:) + location = create(:gias_school, :secondary, urn: 123_456, team:) @session = create( diff --git a/spec/features/triage_required_spec.rb b/spec/features/triage_required_spec.rb index da24a22966..f20e70772c 100644 --- a/spec/features/triage_required_spec.rb +++ b/spec/features/triage_required_spec.rb @@ -446,7 +446,7 @@ def then_i_see_the_triage_options_for_nasal_only_consent end def when_i_go_to_the_second_patient - within(".nhsuk-breadcrumb") { click_on "Children" } + within(".nhsuk-breadcrumb") { click_on "Children in session" } click_link @patient_nasal_only.full_name, match: :first end @@ -481,16 +481,27 @@ def given_an_mmr_programme_with_a_running_session def and_a_partially_vaccinated_patient_who_needs_triage_exists @patient_triage_needed = - create(:patient, :partially_vaccinated_triage_needed, session: @session) + create( + :patient, + :partially_vaccinated_triage_needed, + date_of_birth: Date.new(2019, 6, 1), # before MMRV eligibility cutoff + session: @session + ) end def then_the_mmr_second_dose_will_happen_email_is_sent + patient_name = @patient_triage_needed.short_name + @patient_triage_needed.parents.each do |parent| expect(email_deliveries).to include( matching_notify_email( to: parent.email, - template: :triage_vaccination_will_happen_mmr_second_dose - ).with_content_including("We recently gave", "2nd dose") + template: :triage_vaccination_will_happen_mmr_second_dose, + subject: "#{patient_name} needs another dose of the MMR vaccination" + ).with_content_including( + "We recently gave #{patient_name} their 1st dose of the MMR vaccination", + "plan to give #{patient_name} their 2nd dose then" + ) ) end end diff --git a/spec/features/user_authorisation_spec.rb b/spec/features/user_authorisation_spec.rb index e951fba6af..222dc3f0b5 100644 --- a/spec/features/user_authorisation_spec.rb +++ b/spec/features/user_authorisation_spec.rb @@ -28,8 +28,9 @@ def given_an_hpv_programme_is_underway_with_two_teams @team = create(:team, :with_one_nurse, programmes: [@programme]) @other_team = create(:team, :with_one_nurse, programmes: [@programme]) - location = create(:school, name: "Pilot School", team: @team) - other_location = create(:school, name: "Other School", team: @other_team) + location = create(:gias_school, name: "Pilot School", team: @team) + other_location = + create(:gias_school, name: "Other School", team: @other_team) @session = create( :session, diff --git a/spec/features/vaccination_offline_spec.rb b/spec/features/vaccination_offline_spec.rb index 86fee6515f..bfa452882c 100644 --- a/spec/features/vaccination_offline_spec.rb +++ b/spec/features/vaccination_offline_spec.rb @@ -107,7 +107,7 @@ def given_an_hpv_programme_is_underway(clinic: false) programmes = [Programme.hpv] @team = create(:team, :with_one_nurse, programmes:) - school = clinic ? @team.unknown_school : create(:school, team: @team) + school = clinic ? @team.unknown_school : create(:gias_school, team: @team) previous_date = 1.month.ago.to_date if clinic @@ -188,7 +188,7 @@ def given_an_hpv_programme_is_underway_with_a_single_patient programmes = [Programme.hpv] @team = create(:team, :with_one_nurse, programmes:) - school = create(:school, team: @team) + school = create(:gias_school, team: @team) previous_date = 1.month.ago.to_date vaccine = programmes.first.vaccines.active.first @@ -220,7 +220,7 @@ def given_a_flu_programme_is_underway_with_a_single_patient programmes = [Programme.flu] @team = create(:team, :with_one_nurse, programmes:, ods_code: "B0C4P") - school = create(:school, team: @team) + school = create(:gias_school, team: @team) vaccine = programmes.first.vaccines.active.first @batch = create(:batch, :not_expired, team: @team, vaccine:) @@ -558,7 +558,7 @@ def then_i_see_the_uploaded_vaccination_outcomes_reflected_in_the_session expect(page).to have_content("SiteLeft arm (upper position)") visit patient_url - within(".nhsuk-breadcrumb") { click_on "Children" } + within(".nhsuk-breadcrumb") { click_on "Children in session" } choose "Unable to vaccinate" click_on "Update results" @@ -568,7 +568,7 @@ def then_i_see_the_uploaded_vaccination_outcomes_reflected_in_the_session expect(page).to have_content("Unwell") visit patient_url - within(".nhsuk-breadcrumb") { click_on "Children" } + within(".nhsuk-breadcrumb") { click_on "Children in session" } choose "Vaccinated", match: :first click_on "Update results" @@ -588,7 +588,7 @@ def when_vaccination_confirmations_are_sent def then_an_email_is_sent_to_the_parent_confirming_the_vaccination expect_email_to( @vaccinated_patient.consents.last.parent.email, - :vaccination_administered_hpv, + :vaccination_administered, :any ) diff --git a/spec/features/vaccination_programmes_spec.rb b/spec/features/vaccination_programmes_spec.rb index 2c07687aca..9657c12a1e 100644 --- a/spec/features/vaccination_programmes_spec.rb +++ b/spec/features/vaccination_programmes_spec.rb @@ -51,7 +51,7 @@ def given_my_team_exists end def given_patients_exist_in_year_eleven - school = create(:school, team: @team) + school = create(:gias_school, team: @team) @session = create( diff --git a/spec/features/verbal_consent_given_when_previously_refused_spec.rb b/spec/features/verbal_consent_given_when_previously_refused_spec.rb index 1c54192711..d56f46ddb5 100644 --- a/spec/features/verbal_consent_given_when_previously_refused_spec.rb +++ b/spec/features/verbal_consent_given_when_previously_refused_spec.rb @@ -17,7 +17,7 @@ def given_an_hpv_programme_is_underway @programme = Programme.hpv @team = create(:team, :with_one_nurse, programmes: [@programme]) - location = create(:school, name: "Pilot School", team: @team) + location = create(:gias_school, name: "Pilot School", team: @team) @session = create( :session, diff --git a/spec/features/viewing_child_records_spec.rb b/spec/features/viewing_child_records_spec.rb index 4b2bfc708f..9503edfb5b 100644 --- a/spec/features/viewing_child_records_spec.rb +++ b/spec/features/viewing_child_records_spec.rb @@ -37,17 +37,17 @@ def given_my_team_exists end def given_patients_exist - school = create(:school, team: @team) + school = create(:gias_school, team: @team) @ineligible_school = create( - :school, + :gias_school, name: "Ineligible School", gias_year_groups: [4], team: @team ) - @new_school = create(:school, name: "New School", team: @team) + @new_school = create(:gias_school, name: "New School", team: @team) @session = create(:session, location: school, team: @team, programmes: [@hpv]) diff --git a/spec/fixtures/files/fhir/search_response_0_results.json b/spec/fixtures/files/fhir/search_responses/0_results.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_0_results.json rename to spec/fixtures/files/fhir/search_responses/0_results.json diff --git a/spec/fixtures/files/fhir/search_response_1_result.json b/spec/fixtures/files/fhir/search_responses/1_result.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_1_result.json rename to spec/fixtures/files/fhir/search_responses/1_result.json diff --git a/spec/fixtures/files/fhir/search_response_1_result_in_academic_year_2025.json b/spec/fixtures/files/fhir/search_responses/1_result_in_academic_year_2025.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_1_result_in_academic_year_2025.json rename to spec/fixtures/files/fhir/search_responses/1_result_in_academic_year_2025.json diff --git a/spec/fixtures/files/fhir/search_response_1_result_mavis.json b/spec/fixtures/files/fhir/search_responses/1_result_mavis.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_1_result_mavis.json rename to spec/fixtures/files/fhir/search_responses/1_result_mavis.json diff --git a/spec/fixtures/files/fhir/search_response_1_result_mmrv.json b/spec/fixtures/files/fhir/search_responses/1_result_mmrv.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_1_result_mmrv.json rename to spec/fixtures/files/fhir/search_responses/1_result_mmrv.json diff --git a/spec/fixtures/files/fhir/search_response_1_result_old_date.json b/spec/fixtures/files/fhir/search_responses/1_result_old_date.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_1_result_old_date.json rename to spec/fixtures/files/fhir/search_responses/1_result_old_date.json diff --git a/spec/fixtures/files/fhir/search_responses/1_result_primary_source_false.json b/spec/fixtures/files/fhir/search_responses/1_result_primary_source_false.json new file mode 100644 index 0000000000..bb66141f00 --- /dev/null +++ b/spec/fixtures/files/fhir/search_responses/1_result_primary_source_false.json @@ -0,0 +1,124 @@ +{ + "type": "searchset", + "total": 1, + "link": [ + { + "relation": "self", + "url": "https://api.service.nhs.uk/immunisation-fhir-api/Immunization?-immunization.target=FLU,HPV,MENACWY,3IN1,MMR,MMRV&patient.identifier=https%3A%2F%2Ffhir.nhs.uk%2FId%2Fnhs-number%7C9449308357" + } + ], + "entry": [ + { + "fullUrl": "https://api.service.nhs.uk/immunisation-fhir-api/Immunization/322a54c7-acd8-4eb7-adbc-4006938df8f2", + "resource": { + "id": "322a54c7-acd8-4eb7-adbc-4006938df8f2", + "extension": [ + { + "url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "884861000000100", + "display": "Administration of first intranasal seasonal influenza vaccination" + } + ] + } + } + ], + "identifier": [ + { + "use": "official", + "system": "YGA", + "value": "TPP_V_429814877" + } + ], + "status": "completed", + "vaccineCode": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "46233009", + "display": "Influenza vaccine" + } + ] + }, + "patient": { + "reference": "urn:uuid:c3e7be44-bb52-4df7-8232-7500ed90c137", + "type": "Patient", + "identifier": { + "system": "https://fhir.nhs.uk/Id/nhs-number", + "value": "9449308357" + } + }, + "occurrenceDateTime": "2025-09-24T00:00:00+00:00", + "recorded": "2025-10-02", + "primarySource": false, + "location": { + "identifier": { + "system": "https://fhir.nhs.uk/Id/ods-organization-code", + "value": "100001" + } + }, + "lotNumber": "BU5086", + "site": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "279549004", + "display": "Nasal cavity structure" + } + ] + }, + "performer": [ + { + "actor": { + "type": "Organization", + "identifier": { + "system": "https://fhir.nhs.uk/Id/ods-organization-code", + "value": "R1L" + } + } + } + ], + "protocolApplied": [ + { + "targetDisease": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "6142004", + "display": "Influenza caused by seasonal influenza virus (disorder)" + } + ] + } + ], + "doseNumberPositiveInt": 1 + } + ], + "resourceType": "Immunization" + }, + "search": { + "mode": "match" + } + }, + { + "fullUrl": "urn:uuid:c3e7be44-bb52-4df7-8232-7500ed90c137", + "resource": { + "id": "7101120547", + "identifier": [ + { + "system": "https://fhir.nhs.uk/Id/nhs-number", + "value": "7101120547" + } + ], + "resourceType": "Patient" + }, + "search": { + "mode": "include" + } + } + ], + "resourceType": "Bundle" +} diff --git a/spec/fixtures/files/fhir/search_response_2_results.json b/spec/fixtures/files/fhir/search_responses/2_results.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_2_results.json rename to spec/fixtures/files/fhir/search_responses/2_results.json diff --git a/spec/fixtures/files/fhir/search_response_2_results_mavis_duplicate.json b/spec/fixtures/files/fhir/search_responses/2_results_mavis_duplicate.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_2_results_mavis_duplicate.json rename to spec/fixtures/files/fhir/search_responses/2_results_mavis_duplicate.json diff --git a/spec/fixtures/files/fhir/search_response_2_results_mavis_duplicate_primary_source.json b/spec/fixtures/files/fhir/search_responses/2_results_mavis_duplicate_primary_source.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_2_results_mavis_duplicate_primary_source.json rename to spec/fixtures/files/fhir/search_responses/2_results_mavis_duplicate_primary_source.json diff --git a/spec/fixtures/files/fhir/search_response_all_programmes.json b/spec/fixtures/files/fhir/search_responses/all_programmes.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_all_programmes.json rename to spec/fixtures/files/fhir/search_responses/all_programmes.json diff --git a/spec/fixtures/files/fhir/search_response_bad_immunization_target.json b/spec/fixtures/files/fhir/search_responses/bad_immunization_target.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_bad_immunization_target.json rename to spec/fixtures/files/fhir/search_responses/bad_immunization_target.json diff --git a/spec/fixtures/files/fhir/search_response_duplicate.json b/spec/fixtures/files/fhir/search_responses/duplicate.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_duplicate.json rename to spec/fixtures/files/fhir/search_responses/duplicate.json diff --git a/spec/fixtures/files/fhir/search_response_full_bundle.json b/spec/fixtures/files/fhir/search_responses/full_bundle.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_full_bundle.json rename to spec/fixtures/files/fhir/search_responses/full_bundle.json diff --git a/spec/fixtures/files/fhir/search_response_immunization_target_both.json b/spec/fixtures/files/fhir/search_responses/immunization_target_both.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_immunization_target_both.json rename to spec/fixtures/files/fhir/search_responses/immunization_target_both.json diff --git a/spec/fixtures/files/fhir/search_response_immunization_target_good.json b/spec/fixtures/files/fhir/search_responses/immunization_target_good.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_immunization_target_good.json rename to spec/fixtures/files/fhir/search_responses/immunization_target_good.json diff --git a/spec/fixtures/files/fhir/search_response_mismatching_bundle_link.json b/spec/fixtures/files/fhir/search_responses/mismatching_bundle_link.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_mismatching_bundle_link.json rename to spec/fixtures/files/fhir/search_responses/mismatching_bundle_link.json diff --git a/spec/fixtures/files/fhir/search_response_operation_outcome_error.json b/spec/fixtures/files/fhir/search_responses/operation_outcome_error.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_operation_outcome_error.json rename to spec/fixtures/files/fhir/search_responses/operation_outcome_error.json diff --git a/spec/fixtures/files/fhir/search_response_operation_outcome_fatal.json b/spec/fixtures/files/fhir/search_responses/operation_outcome_fatal.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_operation_outcome_fatal.json rename to spec/fixtures/files/fhir/search_responses/operation_outcome_fatal.json diff --git a/spec/fixtures/files/fhir/search_response_operation_outcome_success.json b/spec/fixtures/files/fhir/search_responses/operation_outcome_success.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_operation_outcome_success.json rename to spec/fixtures/files/fhir/search_responses/operation_outcome_success.json diff --git a/spec/fixtures/files/fhir/search_response_operation_outcome_warning.json b/spec/fixtures/files/fhir/search_responses/operation_outcome_warning.json similarity index 100% rename from spec/fixtures/files/fhir/search_response_operation_outcome_warning.json rename to spec/fixtures/files/fhir/search_responses/operation_outcome_warning.json diff --git a/spec/forms/location_search_form_spec.rb b/spec/forms/location_search_form_spec.rb index 28e262ad80..7bf58edd87 100644 --- a/spec/forms/location_search_form_spec.rb +++ b/spec/forms/location_search_form_spec.rb @@ -9,7 +9,7 @@ let(:request_path) { "/schools" } let(:params) { {} } - let(:scope) { Location.school.all } + let(:scope) { Location.gias_school.all } it "doesn't raise an error" do expect { form.apply(scope) }.not_to raise_error @@ -18,9 +18,9 @@ context "when filtering by name" do let(:params) { { "q" => "Primary" } } - let!(:school_to_include) { create(:school, :primary, name: "Primary") } + let!(:school_to_include) { create(:gias_school, :primary, name: "Primary") } - before { create(:school, :secondary, name: "Secondary") } + before { create(:gias_school, :secondary, name: "Secondary") } it "filters on the schools" do expect(form.apply(scope)).to contain_exactly(school_to_include) @@ -30,9 +30,9 @@ context "when filtering on the phase" do let(:params) { { "phase" => "primary" } } - let!(:school_to_include) { create(:school, :primary, name: "Primary") } + let!(:school_to_include) { create(:gias_school, :primary, name: "Primary") } - before { create(:school, :secondary, name: "Secondary") } + before { create(:gias_school, :secondary, name: "Secondary") } it "filters on the schools" do expect(form.apply(scope)).to contain_exactly(school_to_include) diff --git a/spec/forms/patient_search_form_spec.rb b/spec/forms/patient_search_form_spec.rb index f9697c6fd1..667d8ac5c0 100644 --- a/spec/forms/patient_search_form_spec.rb +++ b/spec/forms/patient_search_form_spec.rb @@ -68,7 +68,7 @@ context "filtering on aged out of programmes" do let(:programmes) { [Programme.flu] } let(:location) do - create(:school, programmes:, gias_year_groups: [11, 12]) + create(:gias_school, programmes:, gias_year_groups: [11, 12]) end let(:session_for_patients) { create(:session, location:, programmes:) } @@ -563,7 +563,7 @@ :school_move, :to_school, patient:, - school: create(:school, subteam:) + school: create(:gias_school, subteam:) ) end diff --git a/spec/forms/session_search_form_spec.rb b/spec/forms/session_search_form_spec.rb index 01a51b336b..bca35cd87a 100644 --- a/spec/forms/session_search_form_spec.rb +++ b/spec/forms/session_search_form_spec.rb @@ -66,7 +66,11 @@ let(:programmes) { [Programme.sample] } let!(:session_to_include) do - create(:session, location: create(:school, name: "School"), programmes:) + create( + :session, + location: create(:gias_school, name: "School"), + programmes: + ) end before do @@ -83,12 +87,16 @@ end context "when filtering on the type" do - let(:params) { { "type" => "school" } } + let(:params) { { "type" => "gias_school" } } let(:programmes) { [Programme.sample] } let!(:session_to_include) do - create(:session, location: create(:school, name: "School"), programmes:) + create( + :session, + location: create(:gias_school, name: "School"), + programmes: + ) end before do diff --git a/spec/helpers/address_helper_spec.rb b/spec/helpers/address_helper_spec.rb index 850bf2ee71..8a1d855d05 100644 --- a/spec/helpers/address_helper_spec.rb +++ b/spec/helpers/address_helper_spec.rb @@ -3,7 +3,7 @@ describe AddressHelper do let(:location) do create( - :school, + :gias_school, name: "School of Politics", address_line_1: "10 Downing Street", address_town: "London", diff --git a/spec/helpers/consents_helper_spec.rb b/spec/helpers/consents_helper_spec.rb index 5f6a190bec..3ef145d309 100644 --- a/spec/helpers/consents_helper_spec.rb +++ b/spec/helpers/consents_helper_spec.rb @@ -3,6 +3,63 @@ describe ConsentsHelper do subject(:reasons) { helper.consent_refusal_reasons(consent) } + describe "#consent_refusal_reasons" do + subject(:reasons) { helper.consent_refusal_reasons(consent) } + + context "with a ConsentForm for a school session" do + let(:session) { create(:session, location: create(:gias_school)) } + let(:consent) { build(:consent_form, session:) } + + it "includes do_not_want_vaccination_at_school" do + expect(reasons.map(&:value)).to include( + "do_not_want_vaccination_at_school" + ) + end + + it "includes the hint for do_not_want_vaccination_at_school" do + reason = + reasons.find { |r| r.value == "do_not_want_vaccination_at_school" } + expect(reason.hint).to eq( + "For example, you do not want your child to be vaccinated in a busy environment" + ) + end + end + + context "with a ConsentForm for a clinic session" do + let(:session) { create(:session, location: create(:generic_clinic)) } + let(:consent) { build(:consent_form, session:) } + + it "does not include do_not_want_vaccination_at_school" do + expect(reasons.map(&:value)).not_to include( + "do_not_want_vaccination_at_school" + ) + end + end + + context "with any consent form" do + let(:session) { create(:session) } + let(:consent) { build(:consent_form, session:) } + + it "includes the hint for will_be_vaccinated_elsewhere" do + reason = reasons.find { |r| r.value == "will_be_vaccinated_elsewhere" } + expect(reason.hint).to eq( + "For example, you've booked your child into a clinic" + ) + end + + it "does not include hints for other reasons" do + reasons_without_hints = + reasons.reject do |r| + %w[ + will_be_vaccinated_elsewhere + do_not_want_vaccination_at_school + ].include?(r.value) + end + expect(reasons_without_hints.map(&:hint)).to all(be_nil) + end + end + end + shared_examples "refusal reason label" do |expected_label| it "uses the programme-specific refusal reason label" do reason = reasons.find { |reason| reason.value == "contains_gelatine" } diff --git a/spec/helpers/patients_helper_spec.rb b/spec/helpers/patients_helper_spec.rb index 9b58e2f498..bbd185c6b5 100644 --- a/spec/helpers/patients_helper_spec.rb +++ b/spec/helpers/patients_helper_spec.rb @@ -67,7 +67,7 @@ end context "with a school" do - let(:school) { create(:school, name: "Waterloo Road") } + let(:school) { create(:gias_school, name: "Waterloo Road") } let(:patient) { create(:patient, school:) } it { should eq("Waterloo Road") } @@ -137,4 +137,104 @@ end end end + + describe "#patient_next_dose_label" do + subject(:next_dose_label) do + helper.patient_next_dose_label(patient, programme, session.academic_year) + end + + let(:programme) { Programme.mmr } + let(:team) { create(:team, programmes: [programme]) } + let(:session) do + create( + :session, + team:, + programmes: [programme], + date: Date.new(2024, 10, 1) + ) + end + let(:patient) { create(:patient, session:, year_group: 9) } + + context "with no vaccinations" do + before { PatientStatusUpdater.call(patient:) } + + it { should eq("1st") } + end + + context "with one vaccination" do + before do + create( + :vaccination_record, + :administered, + programme:, + patient:, + session:, + performed_at: Date.new(2024, 10, 1) + ) + PatientStatusUpdater.call(patient:) + end + + it { should eq("2nd") } + end + end + + describe "#patient_previous_dose_label" do + subject(:previous_dose_label) do + helper.patient_previous_dose_label( + patient, + programme, + session.academic_year + ) + end + + let(:programme) { Programme.mmr } + let(:team) { create(:team, programmes: [programme]) } + let(:session) do + create( + :session, + team:, + programmes: [programme], + date: Date.new(2024, 10, 1) + ) + end + let(:patient) { create(:patient, session:, year_group: 9) } + + context "with no vaccinations" do + before { PatientStatusUpdater.call(patient:) } + + it { should be_nil } + end + + context "with one vaccination" do + before do + create( + :vaccination_record, + :administered, + programme:, + patient:, + session:, + performed_at: Date.new(2024, 10, 1) + ) + PatientStatusUpdater.call(patient:) + end + + it { should eq("1st") } + end + end + + describe "#patient_short_name_possessive" do + subject { helper.patient_short_name_possessive(patient) } + + context "when the name does not end in s" do + let(:patient) { build(:patient, given_name: "Filip") } + + it { should eq("Filip’s") } + end + + context "when the name ends in s" do + let(:patient) { build(:patient, given_name: "James") } + + it { should eq("James’") } + end + end end diff --git a/spec/helpers/programmes_helper_spec.rb b/spec/helpers/programmes_helper_spec.rb new file mode 100644 index 0000000000..9f496c6f35 --- /dev/null +++ b/spec/helpers/programmes_helper_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +describe ProgrammesHelper do + describe "#programme_name_for_parents" do + subject { helper.programme_name_for_parents(programme) } + + context "with a programme that has no NHS.uk name" do + let(:programme) { Programme.hpv } + + it { should eq("HPV") } + end + + context "with flu" do + let(:programme) { Programme.flu } + + it { should eq("flu") } + end + + context "with a programme that has an NHS.uk name" do + let(:programme) { Programme.td_ipv } + + it { should eq("Td/IPV (3-in-1 teenage booster)") } + end + + context "with an MMR variant" do + let(:programme) do + Programme.find("mmr", disease_types: %w[measles mumps rubella]) + end + + it { should eq("MMR") } + end + + context "with an MMRV variant" do + let(:programme) do + Programme.find( + "mmr", + disease_types: %w[measles mumps rubella varicella] + ) + end + + it { should eq("MMRV") } + end + end +end diff --git a/spec/helpers/sessions_helper_spec.rb b/spec/helpers/sessions_helper_spec.rb index f5ff203c9a..a54278cc08 100644 --- a/spec/helpers/sessions_helper_spec.rb +++ b/spec/helpers/sessions_helper_spec.rb @@ -107,6 +107,32 @@ end end + describe "#session_future_dates" do + subject { travel_to(today) { helper.session_future_dates(session) } } + + let(:today) { Date.new(2025, 6, 1) } + + context "with no future dates" do + let(:session) { create(:session, dates: [Date.new(2025, 5, 30)]) } + + it { should eq("") } + end + + context "with one future date" do + let(:session) { create(:session, dates: [Date.new(2025, 6, 10)]) } + + it { should eq("Tuesday 10 June") } + end + + context "with multiple future dates" do + let(:session) do + create(:session, dates: [Date.new(2025, 6, 10), Date.new(2025, 6, 17)]) + end + + it { should eq("Tuesday 10 June and Tuesday 17 June") } + end + end + describe "#session_title" do subject(:session_title) { helper.session_title(session) } @@ -133,7 +159,9 @@ end context "with a school location" do - let(:location) { create(:school, name: "Waterloo Road", programmes:) } + let(:location) do + create(:gias_school, name: "Waterloo Road", programmes:) + end context "when unscheduled" do let(:session) { create(:session, :unscheduled, programmes:, location:) } diff --git a/spec/helpers/teams_helper_spec.rb b/spec/helpers/teams_helper_spec.rb new file mode 100644 index 0000000000..180dbff12b --- /dev/null +++ b/spec/helpers/teams_helper_spec.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +describe TeamsHelper do + let(:team) do + create( + :team, + name: "SAIS Team", + email: "sais@example.com", + phone: "01234 567890" + ) + end + + let(:session) { create(:session, team:) } + + context "with a session" do + describe "#team_contact_name" do + subject { helper.team_contact_name(session:) } + + context "without a subteam" do + it { should eq("SAIS Team") } + end + + context "with a subteam" do + before do + subteam = create(:subteam, team:, name: "SAIS Subteam") + session.team_location.update!(subteam:) + end + + it { should eq("SAIS Subteam") } + end + end + + describe "#team_contact_email" do + subject { helper.team_contact_email(session:) } + + context "without a subteam" do + it { should eq("sais@example.com") } + end + + context "with a subteam" do + before do + subteam = create(:subteam, team:, email: "subteam@example.com") + session.team_location.update!(subteam:) + end + + it { should eq("subteam@example.com") } + end + end + + describe "#team_contact_phone" do + subject { helper.team_contact_phone(session:) } + + context "without a subteam" do + it { should eq("01234 567890") } + end + + context "with a subteam" do + before do + subteam = + create( + :subteam, + team:, + phone: "01234 567890", + phone_instructions: "option 2" + ) + session.team_location.update!(subteam:) + end + + it { should eq("01234 567890 (option 2)") } + end + end + end + + context "with neither session nor vaccination_record" do + it "raises an ArgumentError" do + expect { helper.team_contact_name }.to raise_error(ArgumentError) + end + end + + context "with both session and vaccination_record" do + let(:vaccination_record) do + create(:vaccination_record, programme: Programme.hpv, session:) + end + + it "raises an ArgumentError" do + expect { + helper.team_contact_name(session:, vaccination_record:) + }.to raise_error(ArgumentError) + end + end + + context "with a vaccination_record without a session" do + let(:school) { create(:gias_school, team:) } + let(:patient) { create(:patient, school:) } + let(:vaccination_record) do + create( + :vaccination_record, + :sourced_from_nhs_immunisations_api, + patient:, + session: nil + ) + end + + describe "#team_contact_name" do + subject { helper.team_contact_name(vaccination_record:) } + + it { should eq("SAIS Team") } + end + + describe "#team_contact_email" do + subject { helper.team_contact_email(vaccination_record:) } + + it { should eq("sais@example.com") } + end + + describe "#team_contact_phone" do + subject { helper.team_contact_phone(vaccination_record:) } + + it { should eq("01234 567890") } + end + end +end diff --git a/spec/helpers/vaccination_records_helper_spec.rb b/spec/helpers/vaccination_records_helper_spec.rb new file mode 100644 index 0000000000..187599b50d --- /dev/null +++ b/spec/helpers/vaccination_records_helper_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +describe VaccinationRecordsHelper do + describe "#vaccination_record_date" do + subject { helper.vaccination_record_date(vaccination_record) } + + let(:vaccination_record) do + build(:vaccination_record, performed_at: Time.zone.local(2024, 2, 1, 10)) + end + + it { should eq("01/02/2024") } + end + + describe "#vaccination_record_today_or_date" do + subject { helper.vaccination_record_today_or_date(vaccination_record) } + + context "when performed today" do + let(:vaccination_record) do + build(:vaccination_record, performed_at: Time.current) + end + + it { should eq("today") } + end + + context "when performed on another day" do + let(:vaccination_record) do + build( + :vaccination_record, + performed_at: Time.zone.local(2024, 2, 1, 10) + ) + end + + it { should eq("on 1 February 2024") } + end + end + + describe "#vaccination_record_location" do + subject { helper.vaccination_record_location(vaccination_record) } + + context "with a location_name" do + let(:vaccination_record) do + build(:vaccination_record, location_name: "Springfield School") + end + + it { should eq("Springfield School") } + end + + context "with a location association" do + let(:location) { build(:gias_school, name: "Shelbyville School") } + let(:vaccination_record) do + build(:vaccination_record, location_name: nil, location:) + end + + it { should eq("Shelbyville School") } + end + + context "with no location" do + let(:vaccination_record) do + build(:vaccination_record, location_name: nil, location: nil) + end + + it { should eq("Unknown") } + end + end +end diff --git a/spec/helpers/vaccines_helper_spec.rb b/spec/helpers/vaccines_helper_spec.rb index 9d3fbecd11..03f93b7753 100644 --- a/spec/helpers/vaccines_helper_spec.rb +++ b/spec/helpers/vaccines_helper_spec.rb @@ -8,4 +8,54 @@ it { should eq("Fluenz (Flu)") } end + + describe "#vaccine_method" do + subject { helper.vaccine_method(vaccine) } + + context "with an injection vaccine" do + let(:vaccine) { Vaccine.find_by!(brand: "Gardasil 9") } + + it { should eq("injection") } + end + + context "with a nasal vaccine" do + it { should eq("nasal spray") } + end + + context "with nil" do + let(:vaccine) { nil } + + it { should be_nil } + end + end + + describe "#vaccine_side_effects_list" do + subject(:side_effects_list) { helper.vaccine_side_effects_list(vaccine) } + + context "with a vaccine that has side effects" do + let(:vaccine) do + Vaccine + .find_by!(brand: "Gardasil 9") + .tap { |v| v.update!(side_effects: %w[swelling headache]) } + end + + it do + expect(side_effects_list).to eq( + "- a headache\n- swelling or pain where the injection was given" + ) + end + end + + context "with nil" do + let(:vaccine) { nil } + + it { should be_nil } + end + + context "with a vaccine that has no side effects" do + let(:vaccine) { build(:vaccine, side_effects: []) } + + it { should eq("") } + end + end end diff --git a/spec/jobs/commit_patient_changesets_job_spec.rb b/spec/jobs/commit_patient_changesets_job_spec.rb index 0b19df7f94..b2344f7d18 100644 --- a/spec/jobs/commit_patient_changesets_job_spec.rb +++ b/spec/jobs/commit_patient_changesets_job_spec.rb @@ -5,7 +5,7 @@ let(:programmes) { [Programme.hpv] } let(:team) { create(:team, programmes:) } - let(:location) { create(:school, team:) } + let(:location) { create(:gias_school, team:) } let(:session) { create(:session, location:, programmes:, team:) } let(:file) { "valid.csv" } @@ -328,7 +328,7 @@ address_line_2: nil, year_group: 9, preferred_given_name: "Jenny", - school: create(:school), + school: create(:gias_school), session: create(:session, team:, programmes:) ) end @@ -517,7 +517,7 @@ end context "with a patient currently in the imported school but with an outstanding move to another school" do - let(:other_school) { create(:school, team:) } + let(:other_school) { create(:gias_school, team:) } let(:existing_patient) do create( diff --git a/spec/jobs/enqueue_patients_aged_out_of_schools_job_spec.rb b/spec/jobs/enqueue_patients_aged_out_of_schools_job_spec.rb index 7c16b07b7a..3ecb719e5b 100644 --- a/spec/jobs/enqueue_patients_aged_out_of_schools_job_spec.rb +++ b/spec/jobs/enqueue_patients_aged_out_of_schools_job_spec.rb @@ -3,9 +3,9 @@ describe EnqueuePatientsAgedOutOfSchoolsJob do subject(:perform_now) { described_class.perform_now } - let!(:school_with_team) { create(:school, team: create(:team)) } + let!(:school_with_team) { create(:gias_school, team: create(:team)) } - before { create(:school) } + before { create(:gias_school) } it "queues jobs for the schools with teams" do expect { perform_now }.to enqueue_sidekiq_job( diff --git a/spec/jobs/enqueue_school_consent_reminders_job_spec.rb b/spec/jobs/enqueue_school_consent_reminders_job_spec.rb index f757d544f8..8cc7ff43b5 100644 --- a/spec/jobs/enqueue_school_consent_reminders_job_spec.rb +++ b/spec/jobs/enqueue_school_consent_reminders_job_spec.rb @@ -5,7 +5,7 @@ let(:programmes) { [Programme.sample] } let(:team) { create(:team, programmes:) } - let(:location) { create(:school, team:) } + let(:location) { create(:gias_school, team:) } let(:dates) { [Date.new(2024, 1, 12), Date.new(2024, 1, 15)] } diff --git a/spec/jobs/enqueue_vaccinations_search_in_nhs_job_spec.rb b/spec/jobs/enqueue_vaccinations_search_in_nhs_job_spec.rb index 88afb1760e..202abebbd3 100644 --- a/spec/jobs/enqueue_vaccinations_search_in_nhs_job_spec.rb +++ b/spec/jobs/enqueue_vaccinations_search_in_nhs_job_spec.rb @@ -9,7 +9,7 @@ let(:team) { create(:team) } let(:gias_year_groups) { (0..11).to_a } let(:location) { school } - let(:school) { create(:school, team:, programmes:) } + let(:school) { create(:gias_school, team:, programmes:) } let(:clinic) { create(:generic_clinic, team:, programmes:) } let(:days_before_consent_reminders) { 7 } let(:session) do @@ -211,7 +211,9 @@ def setup_feature_flag allow(SearchVaccinationRecordsInNHSJob).to receive(:perform_bulk) end - let(:school) { create(:school, team:, programmes:, gias_year_groups:) } + let(:school) do + create(:gias_school, team:, programmes:, gias_year_groups:) + end let(:location) { school } let(:session) { create(:session, programmes:, team:, location:) } @@ -469,7 +471,9 @@ def setup_feature_flag patient end - let(:school) { create(:school, team:, programmes:, gias_year_groups:) } + let(:school) do + create(:gias_school, team:, programmes:, gias_year_groups:) + end let(:session) do create( :session, diff --git a/spec/jobs/important_notice_generator_job_spec.rb b/spec/jobs/important_notice_generator_job_spec.rb index 8634aedb17..e44a73b971 100644 --- a/spec/jobs/important_notice_generator_job_spec.rb +++ b/spec/jobs/important_notice_generator_job_spec.rb @@ -166,7 +166,7 @@ end context "team_changed" do - let(:new_school) { create(:school, team: team_b) } + let(:new_school) { create(:gias_school, team: team_b) } let(:team_changed_patient) { create(:patient, school: new_school) } let(:school_move_log_entry) do create( diff --git a/spec/jobs/metrics/export_school_moves_count_job_spec.rb b/spec/jobs/metrics/export_school_moves_count_job_spec.rb index f8ddc0ef13..6f5e1f9914 100644 --- a/spec/jobs/metrics/export_school_moves_count_job_spec.rb +++ b/spec/jobs/metrics/export_school_moves_count_job_spec.rb @@ -46,8 +46,8 @@ end context "with unresolved school moves" do - let(:school_a) { create(:school, team: team_a) } - let(:school_b) { create(:school, team: team_b) } + let(:school_a) { create(:gias_school, team: team_a) } + let(:school_b) { create(:gias_school, team: team_b) } before do create_list(:school_move, 2, school: school_a) diff --git a/spec/jobs/patients_aged_out_of_school_job_spec.rb b/spec/jobs/patients_aged_out_of_school_job_spec.rb index 7c23565771..6eb06c7fd7 100644 --- a/spec/jobs/patients_aged_out_of_school_job_spec.rb +++ b/spec/jobs/patients_aged_out_of_school_job_spec.rb @@ -7,7 +7,7 @@ let(:programme) { Programme.flu } let(:team) { create(:team, programmes: [programme]) } - let(:school) { create(:school, :secondary, team:) } + let(:school) { create(:gias_school, :secondary, team:) } # This date of birth corresponds to year 11 in 2024/25. let(:date_of_birth) { Date.new(2008, 1, 1) } diff --git a/spec/jobs/pds_cascading_search_job_spec.rb b/spec/jobs/pds_cascading_search_job_spec.rb index b7ab2b0139..fd28676d13 100644 --- a/spec/jobs/pds_cascading_search_job_spec.rb +++ b/spec/jobs/pds_cascading_search_job_spec.rb @@ -6,7 +6,7 @@ let(:today) { Time.zone.local(2025, 9, 1, 12, 0, 0) } let(:programme) { Programme.hpv } - let(:school) { create(:school, urn: "123456", team:) } + let(:school) { create(:gias_school, urn: "123456", team:) } let(:team) { create(:team, programmes: [programme]) } let(:session) do create(:session, team:, location: school, programmes: [programme]) diff --git a/spec/jobs/search_vaccination_records_in_nhs_job_spec.rb b/spec/jobs/search_vaccination_records_in_nhs_job_spec.rb index 627e3eafea..933dbb10dd 100644 --- a/spec/jobs/search_vaccination_records_in_nhs_job_spec.rb +++ b/spec/jobs/search_vaccination_records_in_nhs_job_spec.rb @@ -2,7 +2,7 @@ describe SearchVaccinationRecordsInNHSJob do let(:team) { create(:team) } - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let(:patient) { create(:patient, team:, session:, school:, nhs_number:) } let(:session) { create(:session, programmes: [programme], location: school) } let(:nhs_number) { "9449308357" } @@ -21,7 +21,7 @@ describe "#extract_vaccination_records" do let(:bundle) do FHIR.from_contents( - file_fixture("fhir/search_response_2_results.json").read + file_fixture("fhir/search_responses/2_results.json").read ) end @@ -208,6 +208,44 @@ ).to eq(mavis_record) end end + + context "record duplicates a not-administered Mavis record" do + let(:nhs_immunisations_api_primary_source) { true } + + before do + create( + :vaccination_record, + :not_administered, + session:, + programme:, + patient:, + performed_at: + ) + end + + it "returns all incoming records" do + expect(deduplicate).to contain_exactly( + first_vaccination_record, + second_vaccination_record + ) + end + + it "doesn't discard all incoming records" do + deduplicate + expect(first_vaccination_record.discarded_at).to be_nil + expect(second_vaccination_record.discarded_at).to be_nil + end + + it "doesn't point any incoming records at the Mavis record" do + deduplicate + expect( + first_vaccination_record.duplicate_of_vaccination_record + ).to be_nil + expect( + second_vaccination_record.duplicate_of_vaccination_record + ).to be_nil + end + end end let(:vaccination_records) do @@ -675,12 +713,12 @@ perform - ppis = + ppvs = PatientProgrammeVaccinationsSearch.find_by( patient:, programme_type: programme.type ) - expect(ppis.last_searched_at).to eq Time.current + expect(ppvs.last_searched_at).to eq Time.current end end end @@ -688,14 +726,16 @@ shared_examples "does not record the search" do describe "the PatientProgrammeVaccinationsSearch record" do it "is not created or updated" do + freeze_time + perform - expect( + ppvs = PatientProgrammeVaccinationsSearch.find_by( patient:, programme_type: programme.type ) - ).to be_nil + expect(ppvs.last_searched_at).not_to eq Time.current end end end @@ -710,33 +750,37 @@ } end let(:status) { 200 } - let(:body) { file_fixture("fhir/search_response_2_results.json").read } + let(:body) { file_fixture("fhir/search_responses/2_results.json").read } let(:headers) { { "content-type" => "application/fhir+json" } } - let(:existing_bundle) do - FHIR.from_contents( - file_fixture("fhir/search_response_0_results.json").read - ) - end - let!(:existing_records) do - fhir_records = - described_class.new.send( - :extract_fhir_vaccination_records, - existing_bundle + # Simulates a previous job run + let(:existing_records) do + first_run_stub = + stub_request( + :get, + "https://sandbox.api.service.nhs.uk/immunisation-fhir-api/FHIR/R4/Immunization" + ).with(query: expected_query).to_return( + status: 200, + body: existing_bundle_body, + headers: { + "content-type" => "application/fhir+json" + } ) - mapped_records = - fhir_records.map do |fhir_record| - mapped = - FHIRMapper::VaccinationRecord.from_fhir_record( - fhir_record, - patient: - ) - mapped.save! - mapped - end + described_class.new.perform(patient_id) + + WebMock::StubRegistry.instance.remove_request_stub(first_run_stub) + + patient + .vaccination_records + .with_discarded + .sourced_from_nhs_immunisations_api + .reload + .to_a + end - mapped_records + let(:existing_bundle_body) do + file_fixture("fhir/search_responses/0_results.json").read end before do @@ -766,12 +810,12 @@ end context "with 1 existing record and 1 new incoming record" do - let(:existing_bundle) do - FHIR.from_contents( - file_fixture("fhir/search_response_1_result.json").read - ) + let(:existing_bundle_body) do + file_fixture("fhir/search_responses/1_result.json").read end + before { existing_records } + it "updates existing records and creates new records not present" do expect { perform }.to change { patient.vaccination_records.count }.by(1) expect(patient.vaccination_records.map(&:id)).to include( @@ -787,12 +831,12 @@ end context "with 2 existing records and only 1 incoming (edited) record" do - let(:existing_bundle) do - FHIR.from_contents( - file_fixture("fhir/search_response_2_results.json").read - ) + let(:existing_bundle_body) do + file_fixture("fhir/search_responses/2_results.json").read end - let(:body) { file_fixture("fhir/search_response_1_result.json").read } + let(:body) { file_fixture("fhir/search_responses/1_result.json").read } + + before { existing_records } it "deletes the record that is no longer present, and edits the existing record" do expect { perform }.to change { patient.vaccination_records.count }.by( @@ -812,11 +856,11 @@ end context "when re-running after a previous search (patient already has API records in the DB)" do + before { existing_records } + context "with the same 2 records returned again" do - let(:existing_bundle) do - FHIR.from_contents( - file_fixture("fhir/search_response_2_results.json").read - ) + let(:existing_bundle_body) do + file_fixture("fhir/search_responses/2_results.json").read end it "does not create any new records on the second run" do @@ -837,15 +881,13 @@ end context "with the same record returned but with updated attributes" do - # search_response_1_result_old_date.json and search_response_1_result.json + # 1_result_old_date.json and 1_result.json # have the same nhs_immunisations_api_id but different occurrenceDateTimes # (2025-08-22 vs 2025-08-23), simulating a record being corrected in the API. - let(:existing_bundle) do - FHIR.from_contents( - file_fixture("fhir/search_response_1_result_old_date.json").read - ) + let(:existing_bundle_body) do + file_fixture("fhir/search_responses/1_result_old_date.json").read end - let(:body) { file_fixture("fhir/search_response_1_result.json").read } + let(:body) { file_fixture("fhir/search_responses/1_result.json").read } it "does not create a new record" do expect { perform }.not_to(change(VaccinationRecord, :count)) @@ -872,11 +914,6 @@ # Seed just the non-Mavis API record that the fixture will return, # as it would have been after the first search run. - let(:existing_bundle) do - FHIR.from_contents( - file_fixture("fhir/search_response_0_results.json").read - ) - end let!(:existing_api_record) do create( :vaccination_record, @@ -890,7 +927,7 @@ end let(:body) do file_fixture( - "fhir/search_response_2_results_mavis_duplicate.json" + "fhir/search_responses/2_results_mavis_duplicate.json" ).read end let!(:mavis_record) do @@ -931,12 +968,10 @@ # The first run created a kept (primary) record and a discarded # (non-primary) record. On re-run with the same response, both should # be updated in-place with no new records created. - let(:existing_bundle) do - FHIR.from_contents( - file_fixture("fhir/search_response_duplicate.json").read - ) + let(:existing_bundle_body) do + file_fixture("fhir/search_responses/duplicate.json").read end - let(:body) { file_fixture("fhir/search_response_duplicate.json").read } + let(:body) { file_fixture("fhir/search_responses/duplicate.json").read } it "does not create any new records" do expect { perform }.not_to(change(VaccinationRecord, :count)) @@ -967,9 +1002,72 @@ expect(non_primary).to be_discarded end + it "points the non-primary-source record at the primary source record" do + perform + primary = + VaccinationRecord.find_by( + nhs_immunisations_api_primary_source: true + ) + non_primary = + VaccinationRecord.find_by( + nhs_immunisations_api_primary_source: false + ) + expect(non_primary.duplicate_of_vaccination_record).to eq(primary) + end + include_examples "sends discovery comms if required n times", 0 include_examples "calls StatusUpdater" end + + context "when a single non-primary source record exists, but a primary source record has been added" do + # The first run created a kept (non-primary) record. When another search is completed, where there is now + # also a primary record, then the outcome should be the same as if the first search had never happened + + let(:existing_bundle_body) do + file_fixture( + "fhir/search_responses/1_result_primary_source_false.json" + ).read + end + let(:body) { file_fixture("fhir/search_responses/duplicate.json").read } + + it "creates 1 new record" do + expect { perform }.to(change(VaccinationRecord, :count).by(1)) + end + + it "retains the existing record ID" do + perform + expect(VaccinationRecord.all.map(&:id)).to include( + existing_records.map(&:id).sole + ) + end + + it "sets the existing record as discarded" do + perform + expect(existing_records.sole.reload).to be_discarded + end + + it "doesn't set the new record as discarded" do + perform + primary = + VaccinationRecord.find_by( + nhs_immunisations_api_primary_source: true + ) + expect(primary).not_to be_discarded + end + + it "points the non-primary-source record at the primary source record" do + perform + primary = + VaccinationRecord.find_by( + nhs_immunisations_api_primary_source: true + ) + non_primary = + VaccinationRecord.find_by( + nhs_immunisations_api_primary_source: false + ) + expect(non_primary.duplicate_of_vaccination_record).to eq(primary) + end + end end context "with a record for each programme (total 6)" do @@ -1007,7 +1105,7 @@ "3IN1,FLU,HPV,MENACWY,MMR,MMRV" end let(:body) do - file_fixture("fhir/search_response_all_programmes.json").read + file_fixture("fhir/search_responses/all_programmes.json").read end before { Flipper.disable(:imms_api_search_job) } @@ -1051,7 +1149,7 @@ context "with a Mavis-identifier record in the search results" do let(:body) do - file_fixture("fhir/search_response_1_result_mavis.json").read + file_fixture("fhir/search_responses/1_result_mavis.json").read end it "does not create any API record" do @@ -1069,7 +1167,7 @@ context "with a Mavis-identifier record and a non-Mavis duplicate in the search results" do let(:body) do file_fixture( - "fhir/search_response_2_results_mavis_duplicate.json" + "fhir/search_responses/2_results_mavis_duplicate.json" ).read end @@ -1098,7 +1196,7 @@ context "with a Mavis-identifier record and a non-Mavis primary source duplicate in the search results" do let(:body) do file_fixture( - "fhir/search_response_2_results_mavis_duplicate_primary_source.json" + "fhir/search_responses/2_results_mavis_duplicate_primary_source.json" ).read end @@ -1183,7 +1281,7 @@ context "when a record falls on the cutoff date" do let(:body) do file_fixture( - "fhir/search_response_1_result_in_academic_year_2025.json" + "fhir/search_responses/1_result_in_academic_year_2025.json" ).read end @@ -1198,9 +1296,19 @@ end context "when pre-cutoff records were already imported" do - let(:existing_bundle) do - FHIR.from_contents( - file_fixture("fhir/search_response_2_results.json").read + let(:existing_bundle_body) do + file_fixture("fhir/search_responses/2_results.json").read + end + + before do + # The first run happened before the cutoff flag was introduced + Flipper.disable( + :imms_api_ignore_records_prior_to_2025_academic_year + ) + existing_records + Flipper.enable( + :imms_api_ignore_records_prior_to_2025_academic_year, + Programme.flu ) end @@ -1255,12 +1363,13 @@ end context "with no NHS number" do - let(:nhs_number) { nil } + let(:existing_bundle_body) do + file_fixture("fhir/search_responses/2_results.json").read + end - let(:existing_bundle) do - FHIR.from_contents( - file_fixture("fhir/search_response_2_results.json").read - ) + before do + existing_records + patient.update!(nhs_number: nil) end it "deletes all the API records and does not create any new ones" do @@ -1296,7 +1405,7 @@ context "with duplicates" do context "with one primary and one non-primary source record" do - let(:body) { file_fixture("fhir/search_response_duplicate.json").read } + let(:body) { file_fixture("fhir/search_responses/duplicate.json").read } it "adds both vaccination records to the database" do expect { perform }.to change { @@ -1344,7 +1453,7 @@ before { Flipper.enable(:imms_api_sentry_warnings) } let(:body) do - file_fixture("fhir/search_response_mismatching_bundle_link.json").read + file_fixture("fhir/search_responses/mismatching_bundle_link.json").read end it "raises a warning, and sends to Sentry" do diff --git a/spec/jobs/send_automatic_school_consent_reminders_job_spec.rb b/spec/jobs/send_automatic_school_consent_reminders_job_spec.rb index d5ccb35681..265f5521e1 100644 --- a/spec/jobs/send_automatic_school_consent_reminders_job_spec.rb +++ b/spec/jobs/send_automatic_school_consent_reminders_job_spec.rb @@ -70,7 +70,7 @@ let(:dates) { [Date.new(2024, 2, 1), Date.new(2024, 3, 1)] } let(:team) { create(:team, programmes:) } - let(:location) { create(:school, team:) } + let(:location) { create(:gias_school, team:) } let!(:session) do create( diff --git a/spec/jobs/send_manual_school_consent_reminders_job_spec.rb b/spec/jobs/send_manual_school_consent_reminders_job_spec.rb index 17c303b337..088c9c1528 100644 --- a/spec/jobs/send_manual_school_consent_reminders_job_spec.rb +++ b/spec/jobs/send_manual_school_consent_reminders_job_spec.rb @@ -20,7 +20,7 @@ end let(:today) { dates.first - 1.week } let(:team) { create(:team, programmes:) } - let(:location) { create(:school, team:) } + let(:location) { create(:gias_school, team:) } let(:parents) { create_list(:parent, 2) } let(:patient) { create(:patient, team:, parents:) } let(:user) { create(:user, team:) } diff --git a/spec/lib/fhir_mapper/location_spec.rb b/spec/lib/fhir_mapper/location_spec.rb index 9e396d8b0f..c64c50df6b 100644 --- a/spec/lib/fhir_mapper/location_spec.rb +++ b/spec/lib/fhir_mapper/location_spec.rb @@ -10,7 +10,7 @@ subject(:identifier) { fhir_reference.identifier } context "location is a school" do - let(:location) { create(:school, urn: "654321") } + let(:location) { create(:gias_school, urn: "654321") } its(:system) do should eq "https://fhir.hl7.org.uk/Id/urn-school-number" diff --git a/spec/lib/fhir_mapper/vaccination_record_spec.rb b/spec/lib/fhir_mapper/vaccination_record_spec.rb index 5a59c187ec..760024856e 100644 --- a/spec/lib/fhir_mapper/vaccination_record_spec.rb +++ b/spec/lib/fhir_mapper/vaccination_record_spec.rb @@ -7,7 +7,7 @@ let(:organisation) { create(:organisation) } let(:team) { create(:team, organisation:, programmes: [programme]) } let(:programme) { Programme.hpv } - let(:school) { create(:school, urn: "100006") } + let(:school) { create(:gias_school, urn: "100006") } let(:session) do create(:session, location: school, programmes: [programme], team:) end @@ -504,44 +504,97 @@ end end - describe "the parsed dose_sequence value" do + describe "dose sequence parsing" do subject { record.dose_sequence } let(:fixture_file_name) { "fhir/flu/fhir_record_full.json" } + let(:dose_number_positive_int) { nil } + let(:dose_number_string) { nil } before do - allow(fhir_immunization.protocolApplied.sole).to receive( - :doseNumberPositiveInt - ).and_return(dose_number) + allow(fhir_immunization.protocolApplied.sole).to receive_messages( + doseNumberPositiveInt: dose_number_positive_int, + doseNumberString: dose_number_string + ) end - context "when doseNumberPositiveInt is nil" do - let(:dose_number) { nil } - + context "when both fields are nil" do it { should be_nil } - it "does not include a dose sequence note" do - expect(record.notes.to_s).not_to include("Reported dose sequence") + it "does not include any dose notes" do + expect(record.notes.to_s).not_to include("Reported dose number") + expect(record.notes.to_s).not_to include( + "Reported dose number string" + ) end end - context "when doseNumberPositiveInt is less than the maximum dose sequence for flu" do - let(:dose_number) { 1 } + context "when doseNumberPositiveInt is within the maximum dose sequence for flu" do + let(:dose_number_positive_int) { 1 } it { should eq 1 } it "does not include a dose sequence note" do - expect(record.notes.to_s).not_to include("Reported dose sequence") + expect(record.notes.to_s).not_to include("Reported dose number") end end context "when doseNumberPositiveInt exceeds the maximum dose sequence for flu" do - let(:dose_number) { 3 } + let(:dose_number_positive_int) { 3 } it { should be_nil } it "records the out-of-range dose sequence in notes" do - expect(record.notes.to_s).to include("Reported dose sequence: 3") + expect(record.notes.to_s).to include("Reported dose number: 3") + end + end + + context "when doseNumberPositiveInt is present and doseNumberString is also present" do + let(:dose_number_positive_int) { 2 } + let(:dose_number_string) { "first" } + + it { should eq 2 } + + it "does not include a dose number string note" do + expect(record.notes.to_s).not_to include( + "Reported dose number string" + ) + end + end + + context "when doseNumberString is an integer string" do + let(:dose_number_string) { "1" } + + it { should eq 1 } + + it "does not include a dose number string note" do + expect(record.notes.to_s).not_to include( + "Reported dose number string" + ) + end + end + + context "when doseNumberString is a non-integer string" do + let(:dose_number_string) { "first" } + + it { should be_nil } + + it "appends the value to notes" do + expect(record.notes.to_s).to include( + "Reported dose number string: first" + ) + end + end + + context "when doseNumberString is blank" do + let(:dose_number_string) { "" } + + it { should be_nil } + + it "does not include a dose number string note" do + expect(record.notes.to_s).not_to include( + "Reported dose number string" + ) end end end @@ -648,7 +701,36 @@ its(:performed_ods_code) { should eq "B0C4P" } its(:nhs_immunisations_api_primary_source) { should be true } - its(:notes) { should be_nil } + its(:notes) do + should eq "Performing organisation display name: Acme Healthcare" + end + + its(:nhs_immunisations_api_snomed_procedure_code) do + should eq "822851000000102" + end + + its(:nhs_immunisations_api_snomed_procedure_term) do + should eq "Seasonal influenza vaccination 111 (procedure)" + end + + its(:nhs_immunisations_api_recorded_at) do + should eq Time.parse("2025-03-12T13:28:17.12+00:00") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should eq "453684005" } + + its(:nhs_immunisations_api_snomed_reason_term) do + should eq "Disease outbreak (event)" + end + + its(:nhs_immunisations_api_snomed_product_code) do + should eq "43207411000001105" + end + + its(:nhs_immunisations_api_snomed_product_term) do + should eq "Cell-based trivalent influenza vaccine (surface antigen, inactivated) suspension for injection " \ + "0.5ml pre-filled syringes (Seqirus UK Ltd)" + end end context "with a record with not full dose" do @@ -689,7 +771,27 @@ its(:performed_ods_code) { should eq "B0C4P" } its(:nhs_immunisations_api_primary_source) { should be true } - its(:notes) { should be_nil } + its(:nhs_immunisations_api_recorded_at) do + should eq Time.parse("2025-03-12T13:28:17.12+00:00") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should eq "453684005" } + + its(:nhs_immunisations_api_snomed_reason_term) do + should eq "Disease outbreak (event)" + end + + its(:nhs_immunisations_api_snomed_product_code) do + should eq "43208811000001106" + end + + its(:nhs_immunisations_api_snomed_product_term) do + should eq "Fluenz (trivalent) vaccine nasal suspension 0.2ml unit dose (AstraZeneca UK Ltd) (product)" + end + + its(:notes) do + should eq "Performing organisation display name: Acme Healthcare" + end end context "with a record with an unexpected dose unit, and is nasal flu" do @@ -731,7 +833,27 @@ its(:location_name) { should be_nil } its(:performed_ods_code) { should eq "B0C4P" } - its(:notes) { should be_nil } + its(:nhs_immunisations_api_recorded_at) do + should eq Time.parse("2025-03-12T13:28:17.12+00:00") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should eq "453684005" } + + its(:nhs_immunisations_api_snomed_reason_term) do + should eq "Disease outbreak (event)" + end + + its(:nhs_immunisations_api_snomed_product_code) do + should eq "43208811000001106" + end + + its(:nhs_immunisations_api_snomed_product_term) do + should eq "Fluenz (trivalent) vaccine nasal suspension 0.2ml unit dose (AstraZeneca UK Ltd) (product)" + end + + its(:notes) do + should eq "Performing organisation display name: Acme Healthcare" + end end context "with a record with extended milliliter description" do @@ -774,6 +896,29 @@ its(:performed_ods_code) { should eq "B0C4P" } its(:nhs_immunisations_api_primary_source) { should be true } + its(:nhs_immunisations_api_snomed_procedure_code) do + should eq "955651000000100" + end + + its(:nhs_immunisations_api_snomed_procedure_term) do + should eq "Influenza vaccination given by other healthcare provider (situation)" + end + + its(:nhs_immunisations_api_recorded_at) do + should eq Time.zone.parse("2025-10-07") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should be_nil } + its(:nhs_immunisations_api_snomed_reason_term) { should be_nil } + + its(:nhs_immunisations_api_snomed_product_code) do + should eq "43208811000001106" + end + + its(:nhs_immunisations_api_snomed_product_term) do + should eq "Fluenz (trivalent) vaccine nasal suspension 0.2ml unit dose (AstraZeneca UK Ltd)" + end + its(:notes) { should be_nil } end @@ -811,11 +956,31 @@ its(:performed_ods_code) { should eq "B0C4P" } its(:nhs_immunisations_api_primary_source) { should be true } - its(:notes) do - should include( - "SNOMED product code: 43207411000001106", - "SNOMED description: Cell-based trivalent influenza vaccine" - ) + its(:nhs_immunisations_api_snomed_procedure_code) do + should eq "884861000000100" + end + + its(:nhs_immunisations_api_snomed_procedure_term) do + should eq "Administration of first intranasal seasonal influenza vaccination" + end + + its(:nhs_immunisations_api_recorded_at) do + should eq Time.parse("2025-03-12T13:28:17.12+00:00") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should eq "453684005" } + + its(:nhs_immunisations_api_snomed_reason_term) do + should eq "Disease outbreak (event)" + end + + its(:nhs_immunisations_api_snomed_product_code) do + should eq "43207411000001106" + end + + its(:nhs_immunisations_api_snomed_product_term) do + should eq "Cell-based trivalent influenza vaccine (surface antigen, inactivated) suspension for injection " \ + "0.5ml pre-filled syringes (Seqirus UK Ltd)" end end @@ -849,7 +1014,29 @@ its(:performed_ods_code) { should eq "B12345" } its(:nhs_immunisations_api_primary_source) { should be false } - its(:notes) { should be_nil } + its(:nhs_immunisations_api_snomed_procedure_code) do + should eq "955651000000100" + end + + its(:nhs_immunisations_api_snomed_procedure_term) do + should eq "Influenza vaccination given by other healthcare provider (situation)" + end + + its(:nhs_immunisations_api_recorded_at) do + should eq Time.zone.parse("2025-09-08") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should be_nil } + its(:nhs_immunisations_api_snomed_reason_term) { should be_nil } + + its(:nhs_immunisations_api_snomed_product_code) { should be_nil } + its(:nhs_immunisations_api_snomed_product_term) { should be_nil } + + its(:notes) do + should include( + "Reported dose number string: Dose sequence not recorded" + ) + end end context "with a record that is nasal flu, and is missing dose quantity (from real GP)" do @@ -879,6 +1066,29 @@ its(:outcome) { should eq "administered" } its(:performed_ods_code) { should eq "B12345" } + its(:nhs_immunisations_api_snomed_procedure_code) do + should eq "884861000000100" + end + + its(:nhs_immunisations_api_snomed_procedure_term) do + should eq "Administration of first intranasal seasonal influenza vaccination" + end + + its(:nhs_immunisations_api_recorded_at) do + should eq Time.zone.parse("2025-10-09") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should be_nil } + its(:nhs_immunisations_api_snomed_reason_term) { should be_nil } + + its(:nhs_immunisations_api_snomed_product_code) do + should eq "43208811000001106" + end + + its(:nhs_immunisations_api_snomed_product_term) do + should eq "Fluenz (trivalent) vaccine nasal suspension 0.2ml unit dose (AstraZeneca UK Ltd)" + end + its(:notes) { should be_nil } end @@ -907,6 +1117,30 @@ its(:outcome) { should eq "administered" } its(:performed_ods_code) { should eq "B12345" } + its(:nhs_immunisations_api_snomed_procedure_code) do + should eq "884861000000100" + end + + its(:nhs_immunisations_api_snomed_procedure_term) do + should eq "Administration of first intranasal seasonal influenza vaccination" + end + + its(:nhs_immunisations_api_recorded_at) do + should eq Time.zone.parse("2025-10-09") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should be_nil } + its(:nhs_immunisations_api_snomed_reason_term) { should be_nil } + + its(:nhs_immunisations_api_snomed_product_code) do + should eq "43207411000001105" + end + + its(:nhs_immunisations_api_snomed_product_term) do + should eq "Cell-based trivalent influenza vaccine (surface antigen, inactivated) suspension for injection " \ + "0.5ml pre-filled syringes (Seqirus UK Ltd)" + end + its(:notes) { should be_nil } end @@ -939,6 +1173,30 @@ its(:location_name) { should eq "D83013" } its(:performed_ods_code) { should eq "D83013" } + its(:nhs_immunisations_api_snomed_procedure_code) do + should eq "985151000000100" + end + + its(:nhs_immunisations_api_snomed_procedure_term) do + should eq "Administration of first inactivated seasonal influenza vaccination" + end + + its(:nhs_immunisations_api_recorded_at) do + should eq Time.zone.parse("2025-09-22") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should be_nil } + its(:nhs_immunisations_api_snomed_reason_term) { should be_nil } + + its(:nhs_immunisations_api_snomed_product_code) do + should eq "43207411000001105" + end + + its(:nhs_immunisations_api_snomed_product_term) do + should eq "Cell-based trivalent influenza vaccine (surface antigen, inactivated) suspension for injection " \ + "0.5ml pre-filled syringes (Seqirus UK Ltd)" + end + its(:notes) { should be_nil } end @@ -976,6 +1234,29 @@ its(:location_name) { should eq "Unknown" } + its(:nhs_immunisations_api_snomed_procedure_code) do + should eq "884861000000100" + end + + its(:nhs_immunisations_api_snomed_procedure_term) do + should eq "Administration of first intranasal seasonal influenza vaccination" + end + + its(:nhs_immunisations_api_recorded_at) do + should eq Time.parse("2025-08-28T11:45:36.835000+01:00") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should eq "723620004" } + its(:nhs_immunisations_api_snomed_reason_term) { should be_nil } + + its(:nhs_immunisations_api_snomed_product_code) do + should eq "43208811000001106" + end + + its(:nhs_immunisations_api_snomed_product_term) do + should eq "Fluenz (trivalent) vaccine nasal suspension 0.2ml unit dose (AstraZeneca UK Ltd) (product)" + end + its(:notes) { should be_nil } end @@ -1013,6 +1294,24 @@ its(:location) { should have_attributes(urn: "100006") } its(:location_name) { should be_nil } + its(:nhs_immunisations_api_snomed_procedure_code) do + should eq "884861000000100" + end + + its(:nhs_immunisations_api_snomed_procedure_term) do + should eq "Administration of first intranasal seasonal influenza vaccination" + end + + its(:nhs_immunisations_api_recorded_at) do + should eq Time.parse("2025-10-06T07:58:02.836000+01:00") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should be_nil } + its(:nhs_immunisations_api_snomed_reason_term) { should be_nil } + + its(:nhs_immunisations_api_snomed_product_code) { should be_nil } + its(:nhs_immunisations_api_snomed_product_term) { should be_nil } + its(:notes) { should be_nil } end @@ -1047,7 +1346,24 @@ its(:outcome) { should eq "administered" } its(:performed_ods_code) { should eq "B12345" } - its(:notes) { should be_nil } + its(:nhs_immunisations_api_snomed_procedure_code) { should be_nil } + its(:nhs_immunisations_api_snomed_procedure_term) { should be_nil } + + its(:nhs_immunisations_api_recorded_at) do + should eq Time.zone.parse("2025-09-08") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should be_nil } + its(:nhs_immunisations_api_snomed_reason_term) { should be_nil } + + its(:nhs_immunisations_api_snomed_product_code) { should be_nil } + its(:nhs_immunisations_api_snomed_product_term) { should be_nil } + + its(:notes) do + should include( + "Reported dose number string: Dose sequence not recorded" + ) + end end end @@ -1092,6 +1408,30 @@ its(:performed_ods_code) { should eq "R1L" } its(:nhs_immunisations_api_primary_source) { should be true } + its(:nhs_immunisations_api_snomed_procedure_code) do + should eq "761841000" + end + + its(:nhs_immunisations_api_snomed_procedure_term) do + should eq "Administration of vaccine product containing only Human papillomavirus antigen (procedure)" + end + + its(:nhs_immunisations_api_recorded_at) do + should eq Time.parse("2025-11-03T15:30:38.707000+00:00") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should eq "723620004" } + its(:nhs_immunisations_api_snomed_reason_term) { should be_nil } + + its(:nhs_immunisations_api_snomed_product_code) do + should eq "33493111000001108" + end + + its(:nhs_immunisations_api_snomed_product_term) do + should eq "Gardasil 9 vaccine suspension for injection 0.5ml pre-filled syringes " \ + "(Merck Sharp & Dohme (UK) Ltd) (product)" + end + its(:notes) { should be_nil } end end @@ -1137,7 +1477,32 @@ its(:performed_ods_code) { should eq "R1L" } its(:nhs_immunisations_api_primary_source) { should be true } - its(:notes) { should be_nil } + its(:nhs_immunisations_api_snomed_procedure_code) do + should eq "871874000" + end + + its(:nhs_immunisations_api_snomed_procedure_term) do + should eq "Administration of vaccine product containing only " \ + "Neisseria meningitidis serogroup A, C, W135 and Y " \ + "antigens (procedure)" + end + + its(:nhs_immunisations_api_recorded_at) do + should eq Time.parse("2025-11-03T15:31:11.610000+00:00") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should eq "723620004" } + its(:nhs_immunisations_api_snomed_reason_term) { should be_nil } + + its(:nhs_immunisations_api_snomed_product_code) do + should eq "39779611000001104" + end + + its(:nhs_immunisations_api_snomed_product_term) do + should eq "MenQuadfi vaccine solution for injection 0.5ml vials (Sanofi) (product)" + end + + its(:notes) { should include("Reported dose number string: Unknown") } end end @@ -1182,7 +1547,33 @@ its(:performed_ods_code) { should eq "R1L" } its(:nhs_immunisations_api_primary_source) { should be true } - its(:notes) { should be_nil } + its(:nhs_immunisations_api_snomed_procedure_code) do + should eq "866186002" + end + + its(:nhs_immunisations_api_snomed_procedure_term) do + should eq "Administration of vaccine product containing only " \ + "Clostridium tetani and Corynebacterium diphtheriae " \ + "and Human poliovirus antigens (procedure)" + end + + its(:nhs_immunisations_api_recorded_at) do + should eq Time.parse("2025-11-03T15:31:42.228000+00:00") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should eq "723620004" } + its(:nhs_immunisations_api_snomed_reason_term) { should be_nil } + + its(:nhs_immunisations_api_snomed_product_code) do + should eq "7374511000001107" + end + + its(:nhs_immunisations_api_snomed_product_term) do + should eq "Revaxis vaccine suspension for injection 0.5ml pre-filled syringes (Sanofi) " \ + "1 pre-filled disposable injection (product)" + end + + its(:notes) { should include("Reported dose number string: Unknown") } end end @@ -1231,7 +1622,33 @@ its(:performed_ods_code) { should eq "R1L" } its(:nhs_immunisations_api_primary_source) { should be true } - its(:notes) { should be_nil } + its(:nhs_immunisations_api_snomed_procedure_code) do + should eq "38598009" + end + + its(:nhs_immunisations_api_snomed_procedure_term) do + should eq "Administration of vaccine product containing only " \ + "Measles morbillivirus and Mumps orthorubulavirus " \ + "and Rubella virus antigens (procedure)" + end + + its(:nhs_immunisations_api_recorded_at) do + should eq Time.parse("2025-11-03T15:11:15.346000+00:00") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should eq "723620004" } + its(:nhs_immunisations_api_snomed_reason_term) { should be_nil } + + its(:nhs_immunisations_api_snomed_product_code) do + should eq "13968211000001108" + end + + its(:nhs_immunisations_api_snomed_product_term) do + should eq "M-M-RVAXPRO vaccine powder and solvent for suspension for injection 0.5ml pre-filled syringes " \ + "(Merck Sharp & Dohme (UK) Ltd) (product)" + end + + its(:notes) { should include("Reported dose number string: Unknown") } end end @@ -1280,7 +1697,33 @@ its(:performed_ods_code) { should eq "R1L" } its(:nhs_immunisations_api_primary_source) { should be true } - its(:notes) { should be_nil } + its(:nhs_immunisations_api_snomed_procedure_code) do + should eq "432636005" + end + + its(:nhs_immunisations_api_snomed_procedure_term) do + should eq "Administration of vaccine product containing only " \ + "Human alphaherpesvirus 3 and Measles morbillivirus " \ + "and Mumps orthorubulavirus and Rubella virus antigens" + end + + its(:nhs_immunisations_api_recorded_at) do + should eq Time.parse("2025-01-27T08:50:53.257000+00:00") + end + + its(:nhs_immunisations_api_snomed_reason_code) { should eq "723620004" } + its(:nhs_immunisations_api_snomed_reason_term) { should be_nil } + + its(:nhs_immunisations_api_snomed_product_code) do + should eq "45525711000001102" + end + + its(:nhs_immunisations_api_snomed_product_term) do + should eq "Priorix Tetra vaccine powder and solvent for solution for injection 0.5ml pre-filled syringes " \ + "(GlaxoSmithKline UK Ltd) (product)" + end + + its(:notes) { should include("Reported dose number string: Unknown") } end end end diff --git a/spec/lib/generate/cohort_imports_spec.rb b/spec/lib/generate/cohort_imports_spec.rb index 813e8c9e0f..ffd2127328 100644 --- a/spec/lib/generate/cohort_imports_spec.rb +++ b/spec/lib/generate/cohort_imports_spec.rb @@ -10,7 +10,7 @@ before do location = - create(:school, :secondary, team:, name: "Test School", urn: "31337") + create(:gias_school, :secondary, team:, name: "Test School", urn: "31337") create(:session, team:, slug: "slug", location:, programmes: [programme]) end diff --git a/spec/lib/gias_spec.rb b/spec/lib/gias_spec.rb index 931cb498f1..cd40ec08c1 100644 --- a/spec/lib/gias_spec.rb +++ b/spec/lib/gias_spec.rb @@ -89,13 +89,13 @@ end it "updates existing schools" do - create(:school, urn: "100000", name: "Old Name") + create(:gias_school, urn: "100000", name: "Old Name") import expect(Location.find_by(urn: "100000").name).to eq("The Aldgate School") end it "updates sites" do - create(:school, urn: "100000", site: "A", name: "Site A") + create(:gias_school, urn: "100000", site: "A", name: "Site A") import site = Location.find_by(urn: "100000", site: "A") expect(site.status).to eq("closed") # closed, same as main school in CSV @@ -108,7 +108,7 @@ it "returns correct counts" do programme = Programme.hpv team = create(:team, ods_code: "A9A5A", programmes: [programme]) - school = create(:school, urn: "100000", gias_year_groups: [1, 2, 3]) + school = create(:gias_school, urn: "100000", gias_year_groups: [1, 2, 3]) create( :session, location: school, diff --git a/spec/lib/govuk_notify_personalisation/consent_details_presenter_spec.rb b/spec/lib/govuk_notify_personalisation/consent_details_presenter_spec.rb new file mode 100644 index 0000000000..bcc236b2a8 --- /dev/null +++ b/spec/lib/govuk_notify_personalisation/consent_details_presenter_spec.rb @@ -0,0 +1,257 @@ +# frozen_string_literal: true + +describe GovukNotifyPersonalisation::ConsentDetailsPresenter do + subject(:consent_details_presenter) { described_class.new(personalisation) } + + include_context "govuk notify personalisation context" + + context "when session is in the future" do + around { |example| travel_to(Date.new(2025, 9, 1)) { example.run } } + + it do + expect(consent_details_presenter).to have_attributes( + talk_to_your_child_message: + "## Talk to your child about what they want\n\nWe suggest you talk to " \ + "your child about the vaccination before you respond to us. Young " \ + "people have the right to refuse vaccinations.\n\nThey also have " \ + "[the right to consent to their own vaccinations]" \ + "(https://www.nhs.uk/conditions/consent-to-treatment/children/) " \ + "if they show they fully understand what’s involved. Our team might " \ + "give young people this opportunity if they assess them as suitably " \ + "competent.", + consent_deadline: "Wednesday 31 December", + consent_link: "http://localhost:4000/consents/#{session.slug}/hpv/start" + ) + end + end + + context "with a team location and no session" do + let(:location) { create(:gias_school) } + let(:team_location) { create(:team_location, team:, location:) } + let(:session) { nil } + + it do + expect(consent_details_presenter).to have_attributes( + consent_link: + "http://localhost:4000/consents/#{team_location.id}/hpv/start" + ) + end + end + + context "with a patient in primary school" do + let(:date_of_birth) { Date.new(2015, 2, 1) } + let(:patient) { create(:patient, date_of_birth:) } + + it { should have_attributes(talk_to_your_child_message: "") } + + context "when it's an MMR programme and patient is eligible for MMRV" do + let(:programmes) { [Programme.mmr] } + let(:date_of_birth) { Programme::MIN_MMRV_ELIGIBILITY_DATE + 1.month } + + it "generates consent link with mmrv variant" do + expect(consent_details_presenter.consent_link).to end_with( + "/mmrv/start" + ) + end + end + + context "when it's an MMR programme and patient is NOT eligible for MMRV" do + let(:programmes) { [Programme.mmr] } + let(:date_of_birth) { Programme::MIN_MMRV_ELIGIBILITY_DATE - 1.month } + + it "generates consent link with mmr variant" do + expect(consent_details_presenter.consent_link).to end_with("/mmr/start") + end + end + end + + context "with a consent" do + let(:consent) do + create( + :consent, + :refused, + programme: programmes.first, + created_at: Date.new(2024, 1, 1) + ) + end + + it do + expect(consent_details_presenter).to have_attributes( + consented_vaccine_methods_message: "", + reason_for_refusal: "of personal choice", + survey_deadline_date: "8 January 2024" + ) + end + + context "for the flu programme" do + let(:programmes) { [Programme.flu] } + + it do + expect(consent_details_presenter).to have_attributes( + consented_vaccine_methods_message: + "You’ve agreed for John to have the injected flu vaccine." + ) + end + + context "when consented to both nasal and injection" do + before { consent.update!(vaccine_methods: %w[nasal injection]) } + + it do + expect(consent_details_presenter).to have_attributes( + consented_vaccine_methods_message: + "You’ve agreed for John to have the nasal spray flu vaccine, " \ + "or the injected flu vaccine if the nasal spray is not suitable." + ) + end + end + + context "when consented only to nasal" do + before { consent.update!(vaccine_methods: %w[nasal]) } + + it do + expect(consent_details_presenter).to have_attributes( + consented_vaccine_methods_message: + "You’ve agreed for John to have the nasal spray flu vaccine." + ) + end + end + end + + context "for the MMR programme" do + let(:programmes) { [Programme.mmr] } + let(:patient) do + create( + :patient, + session:, + given_name: "John", + family_name: "Smith", + year_group: 9 + ) + end + + it { should have_attributes(consented_vaccine_methods_message: "") } + + context "when consented to vaccine without gelatine" do + before { consent.update!(without_gelatine: true) } + + it do + expect(consent_details_presenter).to have_attributes( + consented_vaccine_methods_message: + "You’ve agreed for John to have the vaccine without gelatine." + ) + end + end + end + end + + context "with a consent form" do + let(:consent_form) do + create( + :consent_form, + :refused, + session:, + recorded_at: Date.new(2024, 1, 1), + given_name: "Tom" + ) + end + + it do + expect(consent_details_presenter).to have_attributes( + consented_vaccine_methods_message: "", + reason_for_refusal: "of personal choice", + survey_deadline_date: "8 January 2024" + ) + end + + describe "#follow_up_discussion" do + subject(:follow_up_discussion) do + described_class.new(personalisation).follow_up_discussion + end + + it "is nil when follow_up_requested is not set" do + expect(follow_up_discussion).to be_nil + end + + context "when follow_up_requested is true" do + before do + consent_form.consent_form_programmes.update!( + follow_up_requested: true + ) + end + + it { should be(true) } + end + + context "when follow_up_requested is false" do + before do + consent_form.consent_form_programmes.update!( + follow_up_requested: false + ) + end + + it { should be(false) } + end + end + + context "for the flu programme" do + let(:programmes) { [Programme.flu] } + + it do + expect(consent_details_presenter).to have_attributes( + consented_vaccine_methods_message: + "You’ve agreed for Tom to have the injected flu vaccine." + ) + end + + context "when consented to both nasal and injection" do + before do + consent_form.consent_form_programmes.update!( + vaccine_methods: %w[nasal injection] + ) + end + + it do + expect(consent_details_presenter).to have_attributes( + consented_vaccine_methods_message: + "You’ve agreed for Tom to have the nasal spray flu vaccine, " \ + "or the injected flu vaccine if the nasal spray is not suitable." + ) + end + end + + context "when consented only to nasal" do + before do + consent_form.consent_form_programmes.update!( + vaccine_methods: %w[nasal] + ) + end + + it do + expect(consent_details_presenter).to have_attributes( + consented_vaccine_methods_message: + "You’ve agreed for Tom to have the nasal spray flu vaccine." + ) + end + end + end + + context "for the MMR programme" do + let(:programmes) { [Programme.mmr] } + + it { should have_attributes(consented_vaccine_methods_message: "") } + + context "when consented to vaccine without gelatine" do + before do + consent_form.consent_form_programmes.update!(without_gelatine: true) + end + + it do + expect(consent_details_presenter).to have_attributes( + consented_vaccine_methods_message: + "You’ve agreed for Tom to have the vaccine without gelatine." + ) + end + end + end + end +end diff --git a/spec/lib/govuk_notify_personalisation/mmr_details_presenter_spec.rb b/spec/lib/govuk_notify_personalisation/mmr_details_presenter_spec.rb new file mode 100644 index 0000000000..5ef6e9d9ee --- /dev/null +++ b/spec/lib/govuk_notify_personalisation/mmr_details_presenter_spec.rb @@ -0,0 +1,184 @@ +# frozen_string_literal: true + +describe GovukNotifyPersonalisation::MmrDetailsPresenter do + subject(:mmr_details_presenter) { described_class.new(personalisation) } + + include_context "govuk notify personalisation context" + + context "when session is in the future" do + around { |example| travel_to(Date.new(2025, 9, 1)) { example.run } } + + it do + expect(mmr_details_presenter).to have_attributes( + invitation_to_clinic_custom_mmr_message: "", + invitation_to_clinic_generic_message: + "They can have this vaccination at a community clinic. If you’d like " \ + "to book a clinic appointment, please contact us using the details " \ + "below.", + mmr_second_dose_required?: false + ) + end + end + + context "with a patient in primary school" do + let(:date_of_birth) { Date.new(2015, 2, 1) } + let(:patient) { create(:patient, date_of_birth:) } + + context "when it's an MMR programme and patient is eligible for MMRV" do + let(:programmes) { [Programme.mmr] } + let(:date_of_birth) { Programme::MIN_MMRV_ELIGIBILITY_DATE + 1.month } + + it { should have_attributes(mmr_or_mmrv_vaccine: "MMR or MMRV vaccine") } + end + + context "when it's an MMR programme and patient is NOT eligible for MMRV" do + let(:programmes) { [Programme.mmr] } + let(:date_of_birth) { Programme::MIN_MMRV_ELIGIBILITY_DATE - 1.month } + + it { should have_attributes(mmr_or_mmrv_vaccine: "MMR vaccine") } + end + end + + context "with a consent" do + let(:consent) do + create( + :consent, + :refused, + programme: programmes.first, + created_at: Date.new(2024, 1, 1) + ) + end + + context "for the flu programme" do + let(:programmes) { [Programme.flu] } + + context "generic message inviting patient to clinic generic" do + it do + expect(mmr_details_presenter).to have_attributes( + invitation_to_clinic_generic_message: + "They can have this vaccination at a community clinic. " \ + "If you’d like to book a clinic appointment, please contact us using " \ + "the details below." + ) + end + end + end + + context "for the MMR programme" do + let(:programmes) { [Programme.mmr] } + let(:patient) do + create( + :patient, + session:, + given_name: "John", + family_name: "Smith", + year_group: 9 + ) + end + + context "generic message inviting patient to generic clinic" do + it do + expect(mmr_details_presenter).to have_attributes( + mmr_second_dose_required?: false, + invitation_to_clinic_generic_message: + "They can have this vaccination at a community clinic. " \ + "If you’d like to book a clinic appointment, please contact us using " \ + "the details below." + ) + end + end + + context "Leicestershire (RT5) message inviting patient to clinic" do + let(:ods_code) { "RT5" } + let(:session) { nil } + + it do + expect(mmr_details_presenter).to have_attributes( + mmr_second_dose_required?: false, + invitation_to_clinic_custom_mmr_message: "" + ) + end + end + + context "Coventry & Warwickshire (RYG) message inviting patient to clinic" do + let(:ods_code) { "RYG" } + let(:session) { nil } + + it do + expect(mmr_details_presenter).to have_attributes( + mmr_second_dose_required?: false, + invitation_to_clinic_custom_mmr_message: "" + ) + end + end + + context "patient has had their 1st dose" do + before do + create( + :vaccination_record, + :administered, + programme: programmes.first, + patient:, + session:, + performed_at: Date.new(2020, 1, 1) + ) + + PatientStatusUpdater.call(patient:) + end + + context "generic message inviting patient to generic clinic for their 2nd dose" do + it do + expect(mmr_details_presenter).to have_attributes( + mmr_second_dose_required?: true, + invitation_to_clinic_generic_message: + "If you would like your local GP surgery to give John their 2nd dose, " \ + "contact the surgery in the usual way.\n\n" \ + "Alternatively, they can have this vaccination at a community clinic. " \ + "If you’d like to book a clinic appointment, please contact us using " \ + "the details below.\n\n" \ + "It’s important to wait at least 28 days after the 1st dose of an MMR " \ + "or MMRV vaccination before getting the 2nd dose. John should not get " \ + "the 2nd dose until 29 January 2020. Please keep this in mind when " \ + "booking the appointment." + ) + end + end + + context "Leicestershire (RT5) message inviting patient to clinic" do + let(:ods_code) { "RT5" } + + it do + expect(mmr_details_presenter).to have_attributes( + mmr_second_dose_required?: true, + invitation_to_clinic_custom_mmr_message: + "It’s important to wait at least 28 days after the 1st dose of an MMR or " \ + "MMRV vaccination before getting the 2nd dose. John should not get the 2nd " \ + "dose until 29 January 2020. Please keep this in mind when booking " \ + "the appointment.\n\n" \ + "It’s also possible for John to be vaccinated at your local GP surgery. " \ + "To book an appointment, contact the surgery in the usual way." + ) + end + end + + context "Coventry & Warwickshire (RYG) message inviting patient to clinic" do + let(:ods_code) { "RYG" } + + it do + expect(mmr_details_presenter).to have_attributes( + mmr_second_dose_required?: true, + invitation_to_clinic_custom_mmr_message: + "It’s important to wait at least 28 days after the 1st dose of an MMR " \ + "or MMRV vaccination before getting the 2nd dose. John should not get the 2nd " \ + "dose until 29 January 2020. Please keep this in mind when booking " \ + "the appointment.\n\n" \ + "## You have 2 options for booking the vaccination\n\n" \ + "You can ask your local GP surgery to give John their 2nd dose. To " \ + "book an appointment, contact the surgery in the usual way." + ) + end + end + end + end + end +end diff --git a/spec/lib/govuk_notify_personalisation/session_dates_presenter_spec.rb b/spec/lib/govuk_notify_personalisation/session_dates_presenter_spec.rb new file mode 100644 index 0000000000..ecbf5a7b79 --- /dev/null +++ b/spec/lib/govuk_notify_personalisation/session_dates_presenter_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +describe GovukNotifyPersonalisation::SessionDatesPresenter do + subject(:session_dates_presenter) { described_class.new(personalisation) } + + include_context "govuk notify personalisation context" + + context "when session is in the future" do + around { |example| travel_to(Date.new(2025, 9, 1)) { example.run } } + + it do + expect(session_dates_presenter).to have_attributes( + has_multiple_dates?: false, + next_or_today_session_date: "Thursday 1 January", + next_or_today_session_dates: "Thursday 1 January", + next_or_today_session_dates_or: "Thursday 1 January", + next_session_date: "Thursday 1 January", + next_session_dates: "Thursday 1 January", + next_session_dates_or: "Thursday 1 January", + subsequent_session_dates_offered_message: "" + ) + end + end + + context "when the session is today" do + let(:session) do + create( + :session, + location:, + team:, + programmes:, + dates: [Date.current, Date.tomorrow] + ) + end + + it "includes today in the next or today date, but not the next date" do + expect(session_dates_presenter).to have_attributes( + next_or_today_session_date: Date.current.to_fs(:short_day_of_week), + next_session_date: Date.tomorrow.to_fs(:short_day_of_week) + ) + end + end + + context "with multiple dates" do + let(:session) do + create( + :session, + location:, + team:, + programmes:, + dates: [Date.new(2026, 1, 1), Date.new(2026, 1, 2)] + ) + end + + context "when today is before the session starts" do + around { |example| travel_to(Date.new(2025, 9, 1)) { example.run } } + + it do + expect(session_dates_presenter).to have_attributes( + has_multiple_dates?: true, + next_or_today_session_date: "Thursday 1 January", + next_or_today_session_dates: + "Thursday 1 January and Friday 2 January", + next_or_today_session_dates_or: + "Thursday 1 January or Friday 2 January", + next_session_date: "Thursday 1 January", + next_session_dates: "Thursday 1 January and Friday 2 January", + next_session_dates_or: "Thursday 1 January or Friday 2 January", + subsequent_session_dates_offered_message: + "If they’re not seen, they’ll be offered the vaccination on Friday 2 January." + ) + end + end + + context "when today is the first date" do + around { |example| travel_to(Date.new(2026, 1, 1)) { example.run } } + + it do + expect(session_dates_presenter).to have_attributes( + has_multiple_dates?: false, + next_or_today_session_date: "Thursday 1 January", + next_or_today_session_dates: + "Thursday 1 January and Friday 2 January", + next_or_today_session_dates_or: + "Thursday 1 January or Friday 2 January", + next_session_date: "Friday 2 January", + next_session_dates: "Friday 2 January", + next_session_dates_or: "Friday 2 January", + subsequent_session_dates_offered_message: "" + ) + end + end + end +end diff --git a/spec/lib/govuk_notify_personalisation/triage_details_presenter_spec.rb b/spec/lib/govuk_notify_personalisation/triage_details_presenter_spec.rb new file mode 100644 index 0000000000..12181321e7 --- /dev/null +++ b/spec/lib/govuk_notify_personalisation/triage_details_presenter_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +describe GovukNotifyPersonalisation::TriageDetailsPresenter do + subject(:triage_details_presenter) { described_class.new(personalisation) } + + include_context "govuk notify personalisation context" + + context "delayed triage" do + context "created on day of session" do + let(:session) { create(:session, :today, location:, team:, programmes:) } + + before do + create( + :triage, + :delay_vaccination, + patient:, + programme: programmes.first + ) + end + + it do + expect(triage_details_presenter).to have_attributes( + delay_vaccination_review_context: + "assessed John in the vaccination session" + ) + end + end + + context "created before session starts" do + let(:session) { create(:session, :today, location:, team:, programmes:) } + + before do + create( + :triage, + :delay_vaccination, + patient:, + created_at: Date.yesterday, + programme: programmes.first + ) + end + + it do + expect(triage_details_presenter).to have_attributes( + delay_vaccination_review_context: + "reviewed the answers you gave to the health questions about John" + ) + end + end + + context "created after session starts" do + let(:session) do + create(:session, :yesterday, location:, team:, programmes:) + end + + before do + create( + :triage, + :delay_vaccination, + patient:, + programme: programmes.first + ) + end + + it do + expect(triage_details_presenter).to have_attributes( + delay_vaccination_review_context: + "reviewed the answers you gave to the health questions about John" + ) + end + end + end +end diff --git a/spec/lib/govuk_notify_personalisation/vaccination_details_presenter_spec.rb b/spec/lib/govuk_notify_personalisation/vaccination_details_presenter_spec.rb new file mode 100644 index 0000000000..3987b80e5e --- /dev/null +++ b/spec/lib/govuk_notify_personalisation/vaccination_details_presenter_spec.rb @@ -0,0 +1,354 @@ +# frozen_string_literal: true + +describe GovukNotifyPersonalisation::VaccinationDetailsPresenter do + subject(:vaccination_details_presenter) do + described_class.new(personalisation) + end + + include_context "govuk notify personalisation context" + + context "when session is in the future" do + around { |example| travel_to(Date.new(2025, 9, 1)) { example.run } } + + it do + expect(vaccination_details_presenter).to have_attributes( + vaccination: "HPV vaccination", + vaccination_and_dates: "HPV vaccination on Thursday 1 January", + vaccination_and_dates_sms: "HPV vaccination on Thursday 1 January", + vaccination_and_method: "HPV vaccination", + vaccine: "HPV vaccine", + vaccine_and_dose: "HPV", + vaccine_and_method: "HPV vaccine", + vaccine_side_effects: "" + ) + end + end + + context "with a patient in primary school" do + let(:date_of_birth) { Date.new(2015, 2, 1) } + let(:patient) { create(:patient, date_of_birth:) } + + context "when it's an MMR programme and patient is eligible for MMRV" do + let(:programmes) { [Programme.mmr] } + let(:date_of_birth) { Programme::MIN_MMRV_ELIGIBILITY_DATE + 1.month } + + it { should have_attributes(vaccination: "MMRV vaccination") } + + it do + expect(vaccination_details_presenter).to have_attributes( + vaccination_and_dates_sms: "MMRV vaccination" + ) + end + end + end + + context "with multiple programmes" do + let(:programmes) { [Programme.menacwy, Programme.td_ipv] } + + it do + expect( + vaccination_details_presenter.vaccination + ).to eq "MenACWY and Td/IPV (3-in-1 teenage booster) vaccinations" + end + end + + context "with a consent" do + let(:consent) do + create( + :consent, + :refused, + programme: programmes.first, + created_at: Date.new(2024, 1, 1) + ) + end + + context "for the MMR programme" do + let(:programmes) { [Programme.mmr] } + let(:patient) do + create( + :patient, + session:, + given_name: "John", + family_name: "Smith", + year_group: 9 + ) + end + + it { should have_attributes(vaccination: "MMR vaccination") } + + context "when receiving their first dose" do + let(:vaccination_record) do + create( + :vaccination_record, + :administered, + programme: programmes.first, + patient:, + session:, + performed_at: Date.new(2020, 1, 1) + ) + end + + it { should have_attributes(vaccination: "MMR vaccination") } + end + end + end + + context "with a consent form" do + let(:consent_form) do + create( + :consent_form, + :refused, + session:, + recorded_at: Date.new(2024, 1, 1), + given_name: "Tom" + ) + end + + context "where the school is different" do + let(:session) { nil } + let(:school) { create(:gias_school, name: "Waterloo Road", team:) } + + let(:consent_form) do + create( + :consent_form, + :given, + :recorded, + session: create(:session, location:, programmes:, team:), + school_confirmed: false, + school: + ) + end + + before { create(:session, location: school, programmes:, team:) } + + it do + expect(vaccination_details_presenter).to have_attributes( + vaccination: "HPV vaccination", + vaccination_and_dates: "HPV vaccination" + ) + end + end + end + + context "with an administered vaccination record" do + let(:vaccine) { Vaccine.find_by!(brand: "Gardasil 9") } + + let(:vaccination_record) do + create( + :vaccination_record, + :administered, + programme: programmes.first, + dose_sequence: 1, + patient:, + performed_at: Date.new(2024, 1, 1), + vaccine: + ) + end + + it do + expect(vaccination_details_presenter).to have_attributes( + vaccine_and_dose: "HPV 1st dose" + ) + end + end + + context "with a not-administered vaccination record" do + let(:vaccination_record) do + create( + :vaccination_record, + :not_administered, + programme: programmes.first, + performed_at: Date.new(2024, 1, 1) + ) + end + + it do + expect(vaccination_details_presenter).to have_attributes( + reason_did_not_vaccinate: "the nurse decided John was not well" + ) + end + end + + context "with vaccine methods" do + let(:personalisation) do + GovukNotifyPersonalisation.new( + patient:, + session:, + programme_types: programmes.map(&:type) + ) + end + + context "and an injection-only programme" do + before do + create( + :patient_programme_status, + :due_injection, + patient:, + academic_year: session.academic_year, + programme: programmes.first + ) + end + + it do + expect( + vaccination_details_presenter.vaccine_is?("injection") + ).to be true + end + + it do + expect(vaccination_details_presenter.vaccine_is?("nasal")).to be false + end + end + + context "and a nasal spray programme" do + let(:programmes) { [Programme.flu] } + + before do + create( + :patient_programme_status, + :due_nasal_injection, + patient:, + programme: programmes.first, + academic_year: session.academic_year + ) + end + + it do + expect( + vaccination_details_presenter.vaccine_is?("injection") + ).to be false + end + + it do + expect(vaccination_details_presenter.vaccine_is?("nasal")).to be true + end + end + + context "and multiple programmes" do + let(:programmes) { [hpv_programme, flu_programme] } + + before do + create( + :patient_programme_status, + :due_nasal_injection, + patient:, + programme: flu_programme, + academic_year: session.academic_year + ) + create( + :patient_programme_status, + :due_injection, + patient:, + programme: hpv_programme, + academic_year: session.academic_year + ) + end + + it do + expect( + vaccination_details_presenter.vaccine_is?("injection") + ).to be true + end + + it do + expect(vaccination_details_presenter.vaccine_is?("nasal")).to be true + end + end + end + + context "with vaccine side effects" do + before do + Vaccine + .for_programme(hpv_programme) + .each { it.update!(side_effects: %w[swelling unwell]) } + end + + it { should have_attributes(vaccine_side_effects: "") } + + context "with injection as an approved vaccine method" do + before do + create( + :patient_programme_status, + :due_injection, + patient:, + programme: hpv_programme, + academic_year: session.academic_year + ) + end + + it do + expect(vaccination_details_presenter).to have_attributes( + vaccine_side_effects: + "- generally feeling unwell\n- swelling or pain where the injection was given" + ) + end + end + end + + context "with a programme that has a different name on NHS.uk" do + let(:programmes) { [Programme.td_ipv] } + + it do + expect(vaccination_details_presenter).to have_attributes( + vaccination: "Td/IPV (3-in-1 teenage booster) vaccination", + vaccine: "Td/IPV (3-in-1 teenage booster) vaccine" + ) + end + end + + context "with the flu programme" do + let(:programmes) { [Programme.flu] } + + it do + expect(vaccination_details_presenter).to have_attributes( + vaccination: "flu vaccination", + vaccination_and_method: "flu vaccination", + vaccine: "flu vaccine", + vaccine_and_method: "flu vaccine" + ) + end + + context "with an administered injected vaccination record" do + let(:vaccination_record) do + vaccine = programmes.first.vaccines.find_by!(method: "injection") + create( + :vaccination_record, + patient:, + programme: programmes.first, + vaccine: + ) + end + + it do + expect(vaccination_details_presenter).to have_attributes( + vaccination: "flu vaccination", + vaccination_and_method: "injected flu vaccination", + vaccine: "flu vaccine", + vaccine_and_method: "injected flu vaccine" + ) + end + end + + context "with an administered nasal spray vaccination record" do + let(:vaccination_record) do + vaccine = programmes.first.vaccines.find_by!(method: "nasal") + create( + :vaccination_record, + patient:, + programme: programmes.first, + vaccine:, + delivery_method: "nasal_spray" + ) + end + + it do + expect(vaccination_details_presenter).to have_attributes( + vaccination: "flu vaccination", + vaccination_and_method: "nasal spray flu vaccination", + vaccine: "flu vaccine", + vaccine_and_method: "nasal spray flu vaccine" + ) + end + end + end +end diff --git a/spec/lib/govuk_notify_personalisation_spec.rb b/spec/lib/govuk_notify_personalisation_spec.rb index 66581a9dba..ae24551174 100644 --- a/spec/lib/govuk_notify_personalisation_spec.rb +++ b/spec/lib/govuk_notify_personalisation_spec.rb @@ -4,122 +4,23 @@ # read the instructions in spec/fixtures/notify_template.txt describe GovukNotifyPersonalisation do - subject(:personalisation) do - described_class.new( - patient:, - session:, - consent:, - consent_form:, - programme_types:, - team_location:, - vaccination_record: - ) - end - - let(:hpv_programme) { Programme.hpv } - let(:flu_programme) { Programme.flu } - let(:programmes) { [hpv_programme] } - let(:programme_types) { programmes.map(&:type) } - let(:ods_code) { "ABC" } - let(:team) do - create( - :team, - name: "Team", - email: "team@example.com", - phone: "01234 567890", - phone_instructions: "option 1", - programmes:, - ods_code: - ) - end - let(:subteam) do - create( - :subteam, - name: "Team", - email: "team@example.com", - phone: "01234 567890", - phone_instructions: "option 1", - team: - ) - end - let(:patient) do - create( - :patient, - given_name: "John", - family_name: "Smith", - date_of_birth: Date.new(2013, 2, 1) - ) - end - let(:location) { create(:school, name: "Hogwarts", subteam:) } - let(:session) do - create(:session, location:, team:, programmes:, date: Date.new(2026, 1, 1)) - end - let(:team_location) { nil } - let(:consent) { nil } - let(:consent_form) { nil } - let(:vaccination_record) { nil } + include_context "govuk notify personalisation context" context "when session is in the future" do around { |example| travel_to(Date.new(2025, 9, 1)) { example.run } } it do expect(personalisation).to have_attributes( - talk_to_your_child_message: - "## Talk to your child about what they want\n\nWe suggest you talk to " \ - "your child about the vaccination before you respond to us. Young " \ - "people have the right to refuse vaccinations.\n\nThey also have " \ - "[the right to consent to their own vaccinations]" \ - "(https://www.nhs.uk/conditions/consent-to-treatment/children/) " \ - "if they show they fully understand what’s involved. Our team might " \ - "give young people this opportunity if they assess them as suitably " \ - "competent.", - consent_deadline: "Wednesday 31 December", - consent_link: - "http://localhost:4000/consents/#{session.slug}/hpv/start", full_and_preferred_patient_name: "John Smith", location_name: "Hogwarts", - invitation_to_clinic_custom_mmr_message: "", - mmr_second_dose_required: false, - invitation_to_clinic_generic_message: - "They can have this vaccination at a community clinic. If you’d like " \ - "to book a clinic appointment, please contact us using the details " \ - "below.", - next_or_today_session_date: "Thursday 1 January", - next_or_today_session_dates: "Thursday 1 January", - next_or_today_session_dates_or: "Thursday 1 January", - next_session_date: "Thursday 1 January", - next_session_dates: "Thursday 1 January", - next_session_dates_or: "Thursday 1 January", patient_date_of_birth: "1 February 2013", short_patient_name: "John", short_patient_name_apos: "John’s", - subsequent_session_dates_offered_message: "", subteam_email: "team@example.com", subteam_name: "Team", subteam_phone: "01234 567890 (option 1)", team_privacy_notice_url: "https://example.com/privacy-notice", - team_privacy_policy_url: "https://example.com/privacy-policy", - vaccination: "HPV vaccination", - vaccination_and_dates: "HPV vaccination on Thursday 1 January", - vaccination_and_dates_sms: "HPV vaccination on Thursday 1 January", - vaccination_and_method: "HPV vaccination", - vaccine: "HPV vaccine", - vaccine_and_dose: "HPV", - vaccine_and_method: "HPV vaccine", - vaccine_side_effects: "" - ) - end - end - - context "with a team location and no session" do - let(:location) { create(:school) } - let(:team_location) { create(:team_location, team:, location:) } - let(:session) { nil } - - it do - expect(personalisation).to have_attributes( - consent_link: - "http://localhost:4000/consents/#{team_location.id}/hpv/start" + team_privacy_policy_url: "https://example.com/privacy-policy" ) end end @@ -128,27 +29,12 @@ let(:date_of_birth) { Date.new(2015, 2, 1) } let(:patient) { create(:patient, date_of_birth:) } - it { should have_attributes(talk_to_your_child_message: "") } - context "when it's an MMR programme and patient is eligible for MMRV" do let(:programmes) { [Programme.mmr] } let(:date_of_birth) { Programme::MIN_MMRV_ELIGIBILITY_DATE + 1.month } - it { should have_attributes(vaccination: "MMRV vaccination") } - - it do - expect(personalisation).to have_attributes( - vaccination_and_dates_sms: "MMRV vaccination" - ) - end - - it { should have_attributes(mmr_or_mmrv_vaccine: "MMR or MMRV vaccine") } it { expect(personalisation.outbreak?).to be false } - it "generates consent link with mmrv variant" do - expect(personalisation.consent_link).to end_with("/mmrv/start") - end - context "when session is setup for outbreak communications" do before { allow(session).to receive(:outbreak).and_return(true) } @@ -160,12 +46,6 @@ let(:programmes) { [Programme.mmr] } let(:date_of_birth) { Programme::MIN_MMRV_ELIGIBILITY_DATE - 1.month } - it { should have_attributes(mmr_or_mmrv_vaccine: "MMR vaccine") } - - it "generates consent link with mmr variant" do - expect(personalisation.consent_link).to end_with("/mmr/start") - end - context "when session is setup for outbreak communications" do before { allow(session).to receive(:outbreak).and_return(true) } @@ -174,373 +54,6 @@ end end - context "when the session is today" do - let(:session) do - create( - :session, - location:, - team:, - programmes:, - dates: [Date.current, Date.tomorrow] - ) - end - - it "doesn't show today's date in next date" do - expect(personalisation).to have_attributes( - next_or_today_session_date: Date.current.to_fs(:short_day_of_week), - next_session_date: Date.tomorrow.to_fs(:short_day_of_week) - ) - end - end - - context "with multiple programmes" do - let(:programmes) { [Programme.menacwy, Programme.td_ipv] } - - it do - expect( - personalisation.vaccination - ).to eq "MenACWY and Td/IPV vaccinations" - end - end - - context "with multiple dates" do - let(:session) do - create( - :session, - location:, - team:, - programmes:, - dates: [Date.new(2026, 1, 1), Date.new(2026, 1, 2)] - ) - end - - context "when today is before the session starts" do - around { |example| travel_to(Date.new(2025, 9, 1)) { example.run } } - - it do - expect(personalisation).to have_attributes( - consent_deadline: "Wednesday 31 December", - next_or_today_session_date: "Thursday 1 January", - next_or_today_session_dates: - "Thursday 1 January and Friday 2 January", - next_or_today_session_dates_or: - "Thursday 1 January or Friday 2 January", - next_session_date: "Thursday 1 January", - next_session_dates: "Thursday 1 January and Friday 2 January", - next_session_dates_or: "Thursday 1 January or Friday 2 January", - subsequent_session_dates_offered_message: - "If they’re not seen, they’ll be offered the vaccination on Friday 2 January." - ) - end - end - - context "when today is the first date" do - around { |example| travel_to(Date.new(2026, 1, 1)) { example.run } } - - it do - expect(personalisation).to have_attributes( - consent_deadline: "Thursday 1 January", - next_or_today_session_date: "Thursday 1 January", - next_or_today_session_dates: - "Thursday 1 January and Friday 2 January", - next_or_today_session_dates_or: - "Thursday 1 January or Friday 2 January", - next_session_date: "Friday 2 January", - subsequent_session_dates_offered_message: "" - ) - end - end - - context "delayed triage" do - context "created on day of session" do - let(:session) do - create(:session, :today, location:, team:, programmes:) - end - - before do - create( - :triage, - :delay_vaccination, - patient:, - programme: programmes.first - ) - end - - it do - expect(personalisation).to have_attributes( - delay_vaccination_review_context: - "assessed John in the vaccination session" - ) - end - end - - context "created before session starts" do - let(:session) do - create(:session, :today, location:, team:, programmes:) - end - - before do - create( - :triage, - :delay_vaccination, - patient:, - created_at: Date.yesterday, - programme: programmes.first - ) - end - - it do - expect(personalisation).to have_attributes( - delay_vaccination_review_context: - "reviewed the answers you gave to the health questions about John" - ) - end - end - - context "created after session starts" do - let(:session) do - create(:session, :yesterday, location:, team:, programmes:) - end - - before do - create( - :triage, - :delay_vaccination, - patient:, - programme: programmes.first - ) - end - - it do - expect(personalisation).to have_attributes( - delay_vaccination_review_context: - "reviewed the answers you gave to the health questions about John" - ) - end - end - end - end - - context "with a consent" do - let(:consent) do - create( - :consent, - :refused, - programme: programmes.first, - created_at: Date.new(2024, 1, 1) - ) - end - - it do - expect(personalisation).to have_attributes( - consented_vaccine_methods_message: "", - reason_for_refusal: "of personal choice", - survey_deadline_date: "8 January 2024" - ) - end - - context "for the flu programme" do - let(:programmes) { [Programme.flu] } - - it do - expect(personalisation).to have_attributes( - consented_vaccine_methods_message: - "You’ve agreed for John to have the injected flu vaccine." - ) - end - - context "when consented to both nasal and injection" do - before { consent.update!(vaccine_methods: %w[nasal injection]) } - - it do - expect(personalisation).to have_attributes( - consented_vaccine_methods_message: - "You’ve agreed for John to have the nasal spray flu vaccine, " \ - "or the injected flu vaccine if the nasal spray is not suitable." - ) - end - end - - context "when consented only to nasal" do - before { consent.update!(vaccine_methods: %w[nasal]) } - - it do - expect(personalisation).to have_attributes( - consented_vaccine_methods_message: - "You’ve agreed for John to have the nasal spray flu vaccine." - ) - end - end - - context "generic message inviting patient to clinic generic" do - it do - expect(personalisation).to have_attributes( - invitation_to_clinic_generic_message: - "They can have this vaccination at a community clinic. " \ - "If you’d like to book a clinic appointment, please contact us using " \ - "the details below." - ) - end - end - end - - context "for the MMR programme" do - let(:programmes) { [Programme.mmr] } - let(:patient) do - create( - :patient, - session:, - given_name: "John", - family_name: "Smith", - year_group: 9 - ) - end - - it do - expect(personalisation).to have_attributes( - consented_vaccine_methods_message: "", - vaccination: "MMR vaccination" - ) - end - - context "when receiving their first dose" do - let(:vaccination_record) do - create( - :vaccination_record, - :administered, - programme: programmes.first, - patient:, - session:, - performed_at: Date.new(2020, 1, 1) - ) - end - - it { should have_attributes(vaccination: "MMR vaccination") } - end - - context "when consented to vaccine without gelatine" do - before { consent.update!(without_gelatine: true) } - - it do - expect(personalisation).to have_attributes( - consented_vaccine_methods_message: - "You’ve agreed for John to have the vaccine without gelatine." - ) - end - end - - context "generic message inviting patient to generic clinic" do - it do - expect(personalisation).to have_attributes( - mmr_second_dose_required: false, - vaccination: "MMR vaccination", - invitation_to_clinic_generic_message: - "They can have this vaccination at a community clinic. " \ - "If you’d like to book a clinic appointment, please contact us using " \ - "the details below." - ) - end - end - - context "Leicestershire (RT5) message inviting patient to clinic" do - let(:ods_code) { "RT5" } - let(:session) { nil } - - it do - expect(personalisation).to have_attributes( - mmr_second_dose_required: false, - vaccination: "MMR vaccination", - invitation_to_clinic_custom_mmr_message: "" - ) - end - end - - context "Coventry & Warwickshire (RYG) message inviting patient to clinic" do - let(:ods_code) { "RYG" } - let(:session) { nil } - - it do - expect(personalisation).to have_attributes( - mmr_second_dose_required: false, - vaccination: "MMR vaccination", - invitation_to_clinic_custom_mmr_message: "" - ) - end - end - - context "patient has had their 1st dose" do - before do - create( - :vaccination_record, - :administered, - programme: programmes.first, - patient:, - session:, - performed_at: Date.new(2020, 1, 1) - ) - - PatientStatusUpdater.call(patient:) - end - - context "generic message inviting patient to generic clinic for their 2nd dose" do - it do - expect(personalisation).to have_attributes( - mmr_second_dose_required: true, - vaccination: "2nd dose of the MMR vaccination", - invitation_to_clinic_generic_message: - "If you would like your local GP surgery to give John their 2nd dose, " \ - "contact the surgery in the usual way.\n\n" \ - "Alternatively, they can have this vaccination at a community clinic. " \ - "If you’d like to book a clinic appointment, please contact us using " \ - "the details below.\n\n" \ - "It’s important to wait at least 28 days after the 1st dose of an MMR " \ - "or MMRV vaccination before getting the 2nd dose. John should not get " \ - "the 2nd dose until 29 January 2020. Please keep this in mind when " \ - "booking the appointment." - ) - end - end - - context "Leicestershire (RT5) message inviting patient to clinic" do - let(:ods_code) { "RT5" } - - it do - expect(personalisation).to have_attributes( - mmr_second_dose_required: true, - vaccination: "2nd dose of the MMR vaccination", - invitation_to_clinic_custom_mmr_message: - "It’s important to wait at least 28 days after the 1st dose of an MMR or " \ - "MMRV vaccination before getting the 2nd dose. John should not get the 2nd " \ - "dose until 29 January 2020. Please keep this in mind when booking " \ - "the appointment.\n\n" \ - "It’s also possible for John to be vaccinated at your local GP surgery. " \ - "To book an appointment, contact the surgery in the usual way." - ) - end - end - - context "Coventry & Warwickshire (RYG) message inviting patient to clinic" do - let(:ods_code) { "RYG" } - - it do - expect(personalisation).to have_attributes( - mmr_second_dose_required: true, - vaccination: "2nd dose of the MMR vaccination", - invitation_to_clinic_custom_mmr_message: - "It’s important to wait at least 28 days after the 1st dose of an MMR " \ - "or MMRV vaccination before getting the 2nd dose. John should not get the 2nd " \ - "dose until 29 January 2020. Please keep this in mind when booking " \ - "the appointment.\n\n" \ - "## You have 2 options for booking the vaccination\n\n" \ - "You can ask your local GP surgery to give John their 2nd dose. To " \ - "book an appointment, contact the surgery in the usual way." - ) - end - end - end - end - end - context "with a consent form" do let(:consent_form) do create( @@ -552,48 +65,11 @@ ) end - it do - expect(personalisation).to have_attributes( - consented_vaccine_methods_message: "", - location_name: "Hogwarts", - reason_for_refusal: "of personal choice", - survey_deadline_date: "8 January 2024" - ) - end - - describe "#follow_up_discussion" do - subject(:follow_up_discussion) do - described_class.new(consent_form:).follow_up_discussion - end - - it "is nil when follow_up_requested is not set" do - expect(follow_up_discussion).to be_nil - end - - context "when follow_up_requested is true" do - before do - consent_form.consent_form_programmes.update!( - follow_up_requested: true - ) - end - - it { should be(true) } - end - - context "when follow_up_requested is false" do - before do - consent_form.consent_form_programmes.update!( - follow_up_requested: false - ) - end - - it { should be(false) } - end - end + it { should have_attributes(location_name: "Hogwarts") } context "where the school is different" do let(:session) { nil } - let(:school) { create(:school, name: "Waterloo Road", team:) } + let(:school) { create(:gias_school, name: "Waterloo Road", team:) } let(:consent_form) do create( @@ -610,354 +86,7 @@ it do expect(personalisation).to have_attributes( - location_name: "Waterloo Road", - vaccination: "HPV vaccination", - vaccination_and_dates: "HPV vaccination" - ) - end - end - - context "for the flu programme" do - let(:programmes) { [Programme.flu] } - - it do - expect(personalisation).to have_attributes( - consented_vaccine_methods_message: - "You’ve agreed for Tom to have the injected flu vaccine." - ) - end - - context "when consented to both nasal and injection" do - before do - consent_form.consent_form_programmes.update!( - vaccine_methods: %w[nasal injection] - ) - end - - it do - expect(personalisation).to have_attributes( - consented_vaccine_methods_message: - "You’ve agreed for Tom to have the nasal spray flu vaccine, " \ - "or the injected flu vaccine if the nasal spray is not suitable." - ) - end - end - - context "when consented only to nasal" do - before do - consent_form.consent_form_programmes.update!( - vaccine_methods: %w[nasal] - ) - end - - it do - expect(personalisation).to have_attributes( - consented_vaccine_methods_message: - "You’ve agreed for Tom to have the nasal spray flu vaccine." - ) - end - end - end - - context "for the MMR programme" do - let(:programmes) { [Programme.mmr] } - - it { should have_attributes(consented_vaccine_methods_message: "") } - - context "when consented to vaccine without gelatine" do - before do - consent_form.consent_form_programmes.update!(without_gelatine: true) - end - - it do - expect(personalisation).to have_attributes( - consented_vaccine_methods_message: - "You’ve agreed for Tom to have the vaccine without gelatine." - ) - end - end - end - end - - context "with an administered vaccination record" do - let(:vaccine) { Vaccine.find_by!(brand: "Gardasil 9") } - - let(:vaccination_record) do - create( - :vaccination_record, - :administered, - programme: programmes.first, - dose_sequence: 1, - patient:, - performed_at: Date.new(2024, 1, 1), - vaccine: - ) - end - - it do - expect(personalisation).to have_attributes( - day_month_year_of_vaccination: "01/01/2024", - today_or_date_of_vaccination: "on 1 January 2024", - vaccine_and_dose: "HPV 1st dose", - vaccine_brand: "Gardasil 9" - ) - end - - context "for the MMR programme" do - let(:programmes) { [Programme.mmr] } - - let(:patient) do - create(:patient, date_of_birth: Date.new(2018, 2, 1), session:) - end - - it do - expect(personalisation).to have_attributes( - mmr_second_dose_message: - "## Your child still needs a second dose of the MMR vaccine\n\n" \ - "To be fully protected against measles, mumps and rubella, " \ - "your child needs a second dose of the vaccine. Our team will " \ - "be in touch about this soon." - ) - end - - context "when fully vaccinated" do - before do - create( - :vaccination_record, - :administered, - programme: programmes.first, - patient:, - performed_at: Date.new(2020, 1, 1), - vaccine: - ) - - vaccination_record # ensure second dose exists - - PatientStatusUpdater.call(patient:) - end - - it { should have_attributes(mmr_second_dose_message: "") } - end - end - end - - context "with a not-administered vaccination record" do - let(:vaccination_record) do - create( - :vaccination_record, - :not_administered, - programme: programmes.first, - performed_at: Date.new(2024, 1, 1) - ) - end - - it do - expect(personalisation).to have_attributes( - day_month_year_of_vaccination: "01/01/2024", - reason_did_not_vaccinate: "the nurse decided John was not well", - today_or_date_of_vaccination: "on 1 January 2024" - ) - end - end - - context "with vaccine methods" do - subject(:personalisation) do - described_class.new( - patient:, - session:, - programme_types: programmes.map(&:type) - ) - end - - context "and an injection-only programme" do - before do - create( - :patient_programme_status, - :due_injection, - patient:, - academic_year: session.academic_year, - programme: programmes.first - ) - end - - it { expect(personalisation.vaccine_is?("injection")).to be true } - it { expect(personalisation.vaccine_is?("nasal")).to be false } - end - - context "and a nasal spray programme" do - let(:programmes) { [Programme.flu] } - - before do - create( - :patient_programme_status, - :due_nasal_injection, - patient:, - programme: programmes.first, - academic_year: session.academic_year - ) - end - - it { expect(personalisation.vaccine_is?("injection")).to be false } - it { expect(personalisation.vaccine_is?("nasal")).to be true } - end - - context "and multiple programmes" do - let(:programmes) { [hpv_programme, flu_programme] } - - before do - create( - :patient_programme_status, - :due_nasal_injection, - patient:, - programme: flu_programme, - academic_year: session.academic_year - ) - create( - :patient_programme_status, - :due_injection, - patient:, - programme: hpv_programme, - academic_year: session.academic_year - ) - end - - it { expect(personalisation.vaccine_is?("injection")).to be true } - it { expect(personalisation.vaccine_is?("nasal")).to be true } - end - end - - context "with vaccine side effects" do - before do - Vaccine - .for_programme(hpv_programme) - .each { it.update!(side_effects: %w[swelling unwell]) } - end - - it { should have_attributes(vaccine_side_effects: "") } - - context "with injection as an approved vaccine method" do - before do - create( - :patient_programme_status, - :due_injection, - patient:, - programme: hpv_programme, - academic_year: session.academic_year - ) - end - - it do - expect(personalisation).to have_attributes( - vaccine_side_effects: - "- generally feeling unwell\n- swelling or pain where the injection was given" - ) - end - - context "when a vaccine is discontinued" do - before do - Vaccine - .for_programme(hpv_programme) - .first - .update!(discontinued: true, side_effects: %w[headache]) - end - - it "excludes side effects from the discontinued vaccine" do - expect(personalisation).not_to have_attributes( - vaccine_side_effects: a_string_matching("headache") - ) - end - end - end - - context "with a vaccination record" do - let(:vaccination_record) do - create( - :vaccination_record, - patient:, - programme: hpv_programme, - session: - ) - end - - it do - expect(personalisation).to have_attributes( - vaccine_side_effects: - "- generally feeling unwell\n- swelling or pain where the injection was given" - ) - end - end - - context "without a patient" do - let(:patient) { nil } - let(:vaccination_record) { nil } - - it do - expect(personalisation).to have_attributes( - vaccine_side_effects: - "- generally feeling unwell\n- swelling or pain where the injection was given" - ) - end - - context "when a vaccine is discontinued" do - before do - Vaccine - .for_programme(hpv_programme) - .first - .update!(discontinued: true, side_effects: %w[headache]) - end - - it "excludes side effects from the discontinued vaccine" do - expect(personalisation).not_to have_attributes( - vaccine_side_effects: a_string_matching("headache") - ) - end - end - end - end - - context "with the flu programme" do - let(:programmes) { [Programme.flu] } - - it do - expect(personalisation).to have_attributes( - vaccination: "flu vaccination", - vaccination_and_method: "flu vaccination", - vaccine: "flu vaccine", - vaccine_and_method: "flu vaccine" - ) - end - - context "with an administered injected vaccination record" do - let(:vaccination_record) do - create(:vaccination_record, patient:, programme: programmes.first) - end - - it do - expect(personalisation).to have_attributes( - vaccination: "flu vaccination", - vaccination_and_method: "injected flu vaccination", - vaccine: "flu vaccine", - vaccine_and_method: "injected flu vaccine" - ) - end - end - - context "with an administered nasal spray vaccination record" do - let(:vaccination_record) do - create( - :vaccination_record, - patient:, - programme: programmes.first, - delivery_method: "nasal_spray" - ) - end - - it do - expect(personalisation).to have_attributes( - vaccination: "flu vaccination", - vaccination_and_method: "nasal spray flu vaccination", - vaccine: "flu vaccine", - vaccine_and_method: "nasal spray flu vaccine" + location_name: "Waterloo Road" ) end end diff --git a/spec/lib/graph_records_spec.rb b/spec/lib/graph_records_spec.rb index a067333f0c..d61b3bc7e0 100644 --- a/spec/lib/graph_records_spec.rb +++ b/spec/lib/graph_records_spec.rb @@ -44,64 +44,63 @@ def non_breaking_text(text) text.gsub(" ", " ").gsub("-", "#8209;") end + def wrap_in_detail_styling(text) + # Wrap in the styling which is output for a line of detail on a model + "
#{non_breaking_text(text)}" + end + it { should start_with "flowchart TB" } it "generates the graph" do # stree-ignore session_details = [ - "slug: #{session.slug}", + "academic_year: #{session.academic_year}", "clinic?: #{session.clinic?}", - "academic_year: #{session.academic_year}" - ].map { - "
#{non_breaking_text(it)}" - } - .join + "dates: #{session.dates}", + "slug: #{session.slug}" + ].map{wrap_in_detail_styling(it)}.join consent_details = [ - non_breaking_text("response: #{consent.response}"), - non_breaking_text("route: #{consent.route}"), - non_breaking_text("created_at: 2024-02-01 00:00:00 +0000"), - non_breaking_text("updated_at: 2024-02-01 00:00:00 +0000"), - non_breaking_text("withdrawn_at: "), - non_breaking_text("invalidated_at: ") - ].map { |d| "
#{d}" }.join + "created_at: 2024-02-01 00:00:00 +0000", + "invalidated_at: ", + "response: #{consent.response}", + "route: #{consent.route}", + "updated_at: 2024-02-01 00:00:00 +0000", + "withdrawn_at: " + ].map { wrap_in_detail_styling(it) }.join cohort_import_details = [ - non_breaking_text("csv_filename: #{cohort_import.csv_filename}"), - non_breaking_text("processed_at: "), - non_breaking_text("status: #{cohort_import.status}"), - non_breaking_text("rows_count: #{cohort_import.rows_count}"), - non_breaking_text("new_record_count: "), - non_breaking_text("exact_duplicate_record_count: "), - non_breaking_text("changed_record_count: ") - ].map { |d| "
#{d}" }.join + "changed_record_count: ", + "csv_filename: #{cohort_import.csv_filename}", + "exact_duplicate_record_count: ", + "new_record_count: ", + "processed_at: ", + "rows_count: #{cohort_import.rows_count}", + "status: #{cohort_import.status}" + ].map { wrap_in_detail_styling(it) }.join class_import_details = [ - non_breaking_text("csv_filename: #{class_import.csv_filename}"), - non_breaking_text("processed_at: "), - non_breaking_text("status: #{class_import.status}"), - non_breaking_text("rows_count: #{class_import.rows_count}"), - non_breaking_text("new_record_count: "), - non_breaking_text("exact_duplicate_record_count: "), - non_breaking_text("changed_record_count: "), - non_breaking_text("year_groups: #{class_import.year_groups}") - ].map { |d| "
#{d}" }.join + "changed_record_count: ", + "csv_filename: #{class_import.csv_filename}", + "exact_duplicate_record_count: ", + "new_record_count: ", + "processed_at: ", + "rows_count: #{class_import.rows_count}", + "status: #{class_import.status}", + "year_groups: #{class_import.year_groups}" + ].map { wrap_in_detail_styling(it) }.join location_details = [ - non_breaking_text("name: #{session.location.name}"), - non_breaking_text( - "address_postcode: #{session.location.address_postcode}" - ), - non_breaking_text("type: #{session.location.type}"), - non_breaking_text( - "gias_year_groups: #{session.location.gias_year_groups}" - ) - ].map { |d| "
#{d}" }.join + "address_postcode: #{session.location.address_postcode}", + "gias_year_groups: #{session.location.gias_year_groups}", + "name: #{session.location.name}", + "type: #{session.location.type}" + ].map { wrap_in_detail_styling(it) }.join patient_location = patient.patient_locations.first @@ -118,9 +117,10 @@ def non_breaking_text(text) " classDef programme fill:#3cb44b,color:white,stroke:#000", " patient-#{patient.id}[\"Patient #{patient.id}
Patient.find(" \ "#{patient.id})
puts GraphRecords.new.graph(patient:" \ - " #{patient.id})
updated_from_pds_at: 
" \ - "date_of_death_recorded_at: 
" \ - "restricted_at: 
invalidated_at: \"]:::" \ + " #{patient.id})
" \ + "date_of_death_recorded_at: 
" \ + "invalidated_at: 
" \ + "restricted_at: 
updated_from_pds_at: \"]:::" \ "patient_focused", " parent-#{parent.id}[\"Parent #{parent.id}
Parent.find(#{parent.id})" \ "
puts GraphRecords.new.graph(parent: #{parent.id})" \ diff --git a/spec/lib/nhs/immunisations_api_spec.rb b/spec/lib/nhs/immunisations_api_spec.rb index 90a3ddeb3f..1437bd0813 100644 --- a/spec/lib/nhs/immunisations_api_spec.rb +++ b/spec/lib/nhs/immunisations_api_spec.rb @@ -809,7 +809,7 @@ end let(:status) { 200 } - let(:body) { file_fixture("fhir/search_response_full_bundle.json").read } + let(:body) { file_fixture("fhir/search_responses/full_bundle.json").read } let(:headers) { { "content-type" => "application/fhir+json" } } let(:diagnostics) { nil } @@ -871,7 +871,9 @@ context "with an operation outcome in bundle" do context "when the severity is `error`" do let(:body) do - file_fixture("fhir/search_response_operation_outcome_error.json").read + file_fixture( + "fhir/search_responses/operation_outcome_error.json" + ).read end it "raises an error" do @@ -883,7 +885,9 @@ context "when the severity is `fatal`" do let(:body) do - file_fixture("fhir/search_response_operation_outcome_fatal.json").read + file_fixture( + "fhir/search_responses/operation_outcome_fatal.json" + ).read end it "raises an error" do @@ -896,7 +900,7 @@ context "when the severity is `warning`" do let(:body) do file_fixture( - "fhir/search_response_operation_outcome_warning.json" + "fhir/search_responses/operation_outcome_warning.json" ).read end @@ -933,7 +937,7 @@ context "when the severity is `success`" do let(:body) do file_fixture( - "fhir/search_response_operation_outcome_success.json" + "fhir/search_responses/operation_outcome_success.json" ).read end @@ -950,7 +954,9 @@ describe "handling `-immunization.target` in `Bundle.link`" do context "with `immunization+target` (incorrect, and unexpected)" do let(:body) do - file_fixture("fhir/search_response_bad_immunization_target.json").read + file_fixture( + "fhir/search_responses/bad_immunization_target.json" + ).read end it "doesn't raise an error" do @@ -974,7 +980,7 @@ context "with both `immunization.target` and `-immunization.target` (as is currently in production)" do let(:body) do file_fixture( - "fhir/search_response_immunization_target_both.json" + "fhir/search_responses/immunization_target_both.json" ).read end @@ -999,7 +1005,7 @@ context "with `-immunization.target` (correct)" do let(:body) do file_fixture( - "fhir/search_response_immunization_target_good.json" + "fhir/search_responses/immunization_target_good.json" ).read end @@ -1040,7 +1046,7 @@ end let(:body) do - file_fixture("fhir/search_response_1_result_mmrv.json").read + file_fixture("fhir/search_responses/1_result_mmrv.json").read end it_behaves_like "continues the request and returns the bundle anyway", diff --git a/spec/lib/notifier/consent_form_spec.rb b/spec/lib/notifier/consent_form_spec.rb index 5416f8d570..35b3d7f45e 100644 --- a/spec/lib/notifier/consent_form_spec.rb +++ b/spec/lib/notifier/consent_form_spec.rb @@ -115,7 +115,7 @@ :consent_form, team:, school_confirmed: false, - school: create(:school, team:), + school: create(:gias_school, team:), session: session ) end diff --git a/spec/lib/notifier/patient_spec.rb b/spec/lib/notifier/patient_spec.rb index 796a2a4fb4..e8fb6b5ad2 100644 --- a/spec/lib/notifier/patient_spec.rb +++ b/spec/lib/notifier/patient_spec.rb @@ -25,7 +25,7 @@ end context "with a school location" do - let(:location) { create(:school, team:) } + let(:location) { create(:gias_school, team:) } it "creates a record" do expect { send_consent_request }.to change( @@ -316,7 +316,7 @@ end context "with a school location" do - let(:location) { create(:school, team:) } + let(:location) { create(:gias_school, team:) } it "creates a record" do expect { send_consent_request }.to change( @@ -579,7 +579,7 @@ let(:disease_types) { programmes.flat_map(&:disease_types).uniq.presence } let(:programme_types) { programmes.map(&:type) } let(:team) { create(:team, programmes:) } - let(:location) { create(:school, team:) } + let(:location) { create(:gias_school, team:) } let(:session) { create(:session, location:, programmes:, team:) } context "without an initial reminder" do @@ -990,7 +990,7 @@ let(:programmes) { [Programme.td_ipv] } let(:programme_types) { programmes.map(&:type) } let(:team) { create(:team, programmes:) } - let(:location) { create(:school, team:) } + let(:location) { create(:gias_school, team:) } let(:session) { create(:session, location:, programmes:, team:) } let(:academic_year) { AcademicYear.current } let(:include_vaccinated_programmes) { false } @@ -1100,7 +1100,7 @@ let(:programmes) { [Programme.td_ipv] } let(:programme_types) { programmes.map(&:type) } let(:team) { create(:team, programmes:) } - let(:location) { create(:school, team:) } + let(:location) { create(:gias_school, team:) } let(:session) { create(:session, location:, programmes:, team:) } let(:academic_year) { AcademicYear.current } let(:include_vaccinated_programmes) { false } diff --git a/spec/lib/notifier/vaccination_record_spec.rb b/spec/lib/notifier/vaccination_record_spec.rb index b80ae2fdcc..eb862a9466 100644 --- a/spec/lib/notifier/vaccination_record_spec.rb +++ b/spec/lib/notifier/vaccination_record_spec.rb @@ -26,7 +26,7 @@ it "sends an email" do expect { send_confirmation }.to have_delivered_email( - :vaccination_administered_hpv + :vaccination_administered ).with(parent:, vaccination_record:, sent_by:) end @@ -90,7 +90,7 @@ it "sends an email" do expect { send_confirmation }.to have_delivered_email( - :vaccination_administered_hpv + :vaccination_administered ).with(parent:, vaccination_record:, sent_by:) end diff --git a/spec/lib/patient_archiver_spec.rb b/spec/lib/patient_archiver_spec.rb index a227967519..de38b6ebb8 100644 --- a/spec/lib/patient_archiver_spec.rb +++ b/spec/lib/patient_archiver_spec.rb @@ -43,7 +43,12 @@ context "with a school move for a school in the same team" do let!(:school_move) do - create(:school_move, :to_school, patient:, school: create(:school, team:)) + create( + :school_move, + :to_school, + patient:, + school: create(:gias_school, team:) + ) end it "deletes the school move" do diff --git a/spec/lib/patient_team_updater_spec.rb b/spec/lib/patient_team_updater_spec.rb index edcddf841b..304a28c8d2 100644 --- a/spec/lib/patient_team_updater_spec.rb +++ b/spec/lib/patient_team_updater_spec.rb @@ -34,7 +34,7 @@ :school_move, :to_school, patient:, - school: create(:school, team:) + school: create(:gias_school, team:) ) PatientTeam.delete_all end diff --git a/spec/lib/reports/automated_careplus_exporter_spec.rb b/spec/lib/reports/automated_careplus_exporter_spec.rb index 7aaa068087..78b020aa8d 100644 --- a/spec/lib/reports/automated_careplus_exporter_spec.rb +++ b/spec/lib/reports/automated_careplus_exporter_spec.rb @@ -27,4 +27,59 @@ described_class.call(team:, academic_year:, start_date:, end_date:) end + + it "delegates vaccination_records_scope to CareplusExporter with the correct parameters" do + team = create(:team) + academic_year = AcademicYear.current + start_date = 1.month.ago.to_date + end_date = Date.current + + expect(Reports::CareplusExporter).to receive( + :vaccination_records_scope + ).with( + team:, + programmes: team.programmes, + academic_year:, + start_date:, + end_date:, + include_missing_nhs_number: false + ) + + described_class.vaccination_records_scope( + team:, + academic_year:, + start_date:, + end_date: + ) + end + + it "passes the correct parameters to CareplusExporter.from_records" do + team = create(:team) + academic_year = AcademicYear.current + vaccination_records = instance_double(ActiveRecord::Relation) + eager_loaded = instance_double(ActiveRecord::Relation) + allow(vaccination_records).to receive(:includes).with( + :patient, + :vaccine, + session: %i[location team_location] + ).and_return(eager_loaded) + + expect(Reports::CareplusExporter).to receive(:from_records).with( + vaccination_records: eager_loaded, + team:, + programmes: team.programmes, + academic_year:, + include_gender: false, + vaccine_columns: %i[ + vaccine + dose + reason_not_given + site + manufacturer + batch_number + ] + ) + + described_class.from_records(vaccination_records:, team:, academic_year:) + end end diff --git a/spec/lib/reports/careplus_exporter_spec.rb b/spec/lib/reports/careplus_exporter_spec.rb index f4883ae550..6fc19c5852 100644 --- a/spec/lib/reports/careplus_exporter_spec.rb +++ b/spec/lib/reports/careplus_exporter_spec.rb @@ -31,6 +31,11 @@ ] end + let(:programme) { Programme.hpv } + let(:team) { create(:team, programmes: [programme]) } + let(:location) { create(:gias_school) } + let(:session) { create(:session, team:, programmes: [programme], location:) } + let(:parsed_csv) { CSV.parse(csv) } let(:headers) { parsed_csv.first } let(:data_rows) { parsed_csv[1..] } @@ -48,7 +53,7 @@ end let(:location) do create( - :school, + :gias_school, gias_local_authority_code: 123, gias_establishment_number: 456 ) @@ -406,13 +411,6 @@ end context "include_missing_nhs_number parameter" do - let(:programme) { Programme.hpv } - let(:team) { create(:team, programmes: [programme]) } - let(:location) { create(:school) } - let(:session) do - create(:session, team:, programmes: [programme], location:) - end - context "when true" do let(:include_missing_nhs_number) { true } @@ -482,13 +480,6 @@ end context "include_gender parameter" do - let(:programme) { Programme.hpv } - let(:team) { create(:team, programmes: [programme]) } - let(:location) { create(:school) } - let(:session) do - create(:session, team:, programmes: [programme], location:) - end - context "when true" do let(:include_gender) { true } @@ -527,13 +518,7 @@ end context "vaccine_columns parameter" do - let(:programme) { Programme.hpv } let(:vaccine) { programme.vaccines.first } - let(:team) { create(:team, programmes: [programme]) } - let(:location) { create(:school) } - let(:session) do - create(:session, team:, programmes: [programme], location:) - end context "with a subset of columns" do let(:vaccine_columns) { %i[vaccine dose] } @@ -734,4 +719,210 @@ end end end + + describe ".call" do + it "delegates to from_records with the built scope" do + scope = + described_class.vaccination_records_scope( + team:, + programmes: [programme], + academic_year:, + start_date: nil, + end_date: nil, + include_missing_nhs_number: true + ) + allow(described_class).to receive(:vaccination_records_scope).and_return( + scope + ) + + expect(described_class).to receive(:from_records).with( + vaccination_records: + scope.includes( + :patient, + :vaccine, + session: %i[location team_location] + ), + team:, + programmes: [programme], + academic_year:, + include_gender: true, + vaccine_columns: %i[vaccine vaccine_code] + ) + + described_class.call( + team:, + programmes: [programme], + academic_year:, + start_date: nil, + end_date: nil, + include_gender: true, + include_missing_nhs_number: true, + vaccine_columns: %i[vaccine vaccine_code] + ) + end + end + + describe ".from_records" do + it "produces the same CSV as call when given the same records" do + patient = create(:patient, session:) + create( + :vaccination_record, + programme:, + patient:, + session:, + performed_at: 2.weeks.ago + ) + + call_args = { + team:, + programmes: [programme], + academic_year:, + include_gender: false, + vaccine_columns: %i[vaccine dose site] + } + + csv_via_call = + described_class.call( + **call_args, + start_date: 1.month.ago.to_date, + end_date: Date.current, + include_missing_nhs_number: true + ) + + vaccination_records = + described_class.vaccination_records_scope( + team:, + programmes: [programme], + academic_year:, + start_date: 1.month.ago.to_date, + end_date: Date.current, + include_missing_nhs_number: true + ).includes(:patient, :vaccine, session: %i[location team_location]) + + expect( + described_class.from_records(**call_args, vaccination_records:) + ).to eq(csv_via_call) + end + end + + describe ".vaccination_records_scope" do + let(:programme) { Programme.hpv } + let(:team) { create(:team, programmes: [programme]) } + let(:session) { create(:session, team:, programmes: [programme]) } + let(:patient) { create(:patient, session:) } + let(:other_team) { create(:team, programmes: [programme]) } + let(:other_session) do + create(:session, team: other_team, programmes: [programme]) + end + + def scope(**overrides) + described_class.vaccination_records_scope( + **{ + team:, + programmes: [programme], + academic_year:, + start_date: nil, + end_date: nil, + include_missing_nhs_number: true + }.merge(overrides) + ) + end + + it "includes administered records for the team" do + record = create(:vaccination_record, programme:, session:, patient:) + expect(scope).to include(record) + end + + it "excludes records belonging to a different team" do + other_patient = create(:patient, session: other_session) + record = + create( + :vaccination_record, + programme:, + session: other_session, + patient: other_patient + ) + expect(scope).not_to include(record) + end + + it "excludes discarded records" do + record = + create(:vaccination_record, :discarded, programme:, session:, patient:) + expect(scope).not_to include(record) + end + + it "excludes non-administered records" do + record = + create( + :vaccination_record, + :not_administered, + programme:, + session:, + patient: + ) + expect(scope).not_to include(record) + end + + context "when include_missing_nhs_number is false" do + let(:patient_without_nhs_number) do + create(:patient, nhs_number: nil, session:) + end + + it "excludes patients without an NHS number" do + create( + :vaccination_record, + programme:, + session:, + patient: patient_without_nhs_number + ) + expect(scope(include_missing_nhs_number: false)).to be_empty + end + + it "includes patients with an NHS number" do + record = create(:vaccination_record, programme:, session:, patient:) + expect(scope(include_missing_nhs_number: false)).to include(record) + end + end + + context "with a start_date" do + let(:record) do + create(:vaccination_record, programme:, session:, patient:) + end + + it "includes records created on or after the start date" do + expect(scope(start_date: record.created_at.to_date)).to include(record) + end + + it "excludes records created and last updated before the start date" do + record.update_columns( + created_at: 2.months.ago, + updated_at: 2.months.ago + ) + expect(scope(start_date: Date.current)).not_to include(record) + end + + it "includes records updated on or after the start date even if created before" do + record.update_columns(created_at: 2.months.ago) + expect(scope(start_date: Date.current)).to include(record) + end + end + + context "with an end_date" do + let(:record) do + create(:vaccination_record, programme:, session:, patient:) + end + + it "includes records created on or before the end date" do + expect(scope(end_date: record.created_at.to_date)).to include(record) + end + + it "excludes records created and last updated after the end date" do + record.update_columns( + created_at: 1.week.from_now, + updated_at: 1.week.from_now + ) + expect(scope(end_date: Date.current)).not_to include(record) + end + end + end end diff --git a/spec/lib/reports/offline_exporter_spec.rb b/spec/lib/reports/offline_exporter_spec.rb index 697a0e8cf0..9524a24906 100644 --- a/spec/lib/reports/offline_exporter_spec.rb +++ b/spec/lib/reports/offline_exporter_spec.rb @@ -53,7 +53,7 @@ def validation_formula(worksheet:, column_name:, row: 1) ) end - let(:location) { create(:school, subteam:) } + let(:location) { create(:gias_school, subteam:) } it { should_not be_blank } @@ -758,7 +758,7 @@ def validation_formula(worksheet:, column_name:, row: 1) end context "a school session" do - let(:location) { create(:school, subteam:) } + let(:location) { create(:gias_school, subteam:) } it { should_not be_blank } @@ -1631,7 +1631,8 @@ def validation_formula(worksheet:, column_name:, row: 1) create( :patient, year_group:, - school: create(:school, urn: "123456", name: "Waterloo Road") + school: + create(:gias_school, urn: "123456", name: "Waterloo Road") ) end let(:batch_number) { build(:batch).number } diff --git a/spec/lib/reports/programme_vaccinations_exporter_spec.rb b/spec/lib/reports/programme_vaccinations_exporter_spec.rb index c50288441e..c7d9a88cfc 100644 --- a/spec/lib/reports/programme_vaccinations_exporter_spec.rb +++ b/spec/lib/reports/programme_vaccinations_exporter_spec.rb @@ -103,7 +103,7 @@ subject(:rows) { CSV.parse(call, headers: true) } context "a school session" do - let(:location) { create(:school, subteam:) } + let(:location) { create(:gias_school, subteam:) } it { should be_empty } diff --git a/spec/lib/reports/school_moves_exporter_spec.rb b/spec/lib/reports/school_moves_exporter_spec.rb index 9b73845abd..dc39f45311 100644 --- a/spec/lib/reports/school_moves_exporter_spec.rb +++ b/spec/lib/reports/school_moves_exporter_spec.rb @@ -13,7 +13,7 @@ let(:one_day_ago) { 1.day.ago } let(:three_days_ago) { 3.days.ago } - let(:school) { create(:school, :secondary, team:) } + let(:school) { create(:gias_school, :secondary, team:) } before do 3.times do @@ -52,8 +52,8 @@ let(:rows) { CSV.parse(csv_data, headers: true) } context "with a standard school move" do - let(:old_school) { create(:school, :secondary, team:) } - let(:new_school) { create(:school, :secondary, team:) } + let(:old_school) { create(:gias_school, :secondary, team:) } + let(:new_school) { create(:gias_school, :secondary, team:) } before do patient = create(:patient, school: old_school, team:) @@ -115,7 +115,7 @@ context "when moving to a school with a SystmOne code" do before do session = create(:session, team:) - school = create(:school, systm_one_code: "ABC") + school = create(:gias_school, systm_one_code: "ABC") patient = create(:patient, school:, session:) create(:school_move_log_entry, :unknown_school, patient:) end @@ -129,7 +129,7 @@ before do session = create(:session, team:) patient = create(:patient, school: nil, session:) - new_school = create(:school, urn: "123456", team: session.team) + new_school = create(:gias_school, urn: "123456", team: session.team) create(:school_move_log_entry, :unknown_school, patient:) create(:school_move_log_entry, :home_educated, patient:) @@ -147,8 +147,8 @@ let(:team_a) { create(:team) } let(:team_b) { create(:team) } - let(:school_a) { create(:school, :secondary, team: team_a) } - let(:school_b) { create(:school, :secondary, team: team_b) } + let(:school_a) { create(:gias_school, :secondary, team: team_a) } + let(:school_b) { create(:gias_school, :secondary, team: team_b) } let(:session) { create(:session, team: team_b) } let(:patient) { create(:patient, session:) } diff --git a/spec/lib/reports/systm_one_exporter_spec.rb b/spec/lib/reports/systm_one_exporter_spec.rb index 86e5a8b623..a68e5ad177 100644 --- a/spec/lib/reports/systm_one_exporter_spec.rb +++ b/spec/lib/reports/systm_one_exporter_spec.rb @@ -19,7 +19,7 @@ let(:programme) { Programme.hpv } let(:academic_year) { AcademicYear.current } let(:team) { create(:team, ods_code: "ABC123", programmes: [programme]) } - let(:location) { create(:school) } + let(:location) { create(:gias_school) } let(:session) { create(:session, team:, programmes: [programme], location:) } let(:patient) { create(:patient) } let(:vaccination_record) do @@ -176,7 +176,7 @@ end context "location has a SystmOne code" do - let(:location) { create(:school, systm_one_code: "A1") } + let(:location) { create(:gias_school, systm_one_code: "A1") } it { should eq("A1") } end diff --git a/spec/lib/stats/consents_by_school_spec.rb b/spec/lib/stats/consents_by_school_spec.rb index c23f639962..f3f1adcd30 100644 --- a/spec/lib/stats/consents_by_school_spec.rb +++ b/spec/lib/stats/consents_by_school_spec.rb @@ -17,7 +17,9 @@ let(:team) { create(:team, organisation:, name: "Test Team", programmes:) } let(:academic_year) { AcademicYear.current } - let(:school) { create(:school, name: "Test School", team:, programmes:) } + let(:school) do + create(:gias_school, name: "Test School", team:, programmes:) + end context "when there are consent responses" do before do diff --git a/spec/lib/stats/organisations_spec.rb b/spec/lib/stats/organisations_spec.rb index cf27875c7e..4d95bb5980 100644 --- a/spec/lib/stats/organisations_spec.rb +++ b/spec/lib/stats/organisations_spec.rb @@ -194,7 +194,7 @@ def setup_complete_organisation_data school1 = create( - :school, + :gias_school, name: "Primary School", team: target_team, programmes: [programme_flu, programme_hpv] @@ -209,7 +209,7 @@ def setup_complete_organisation_data school2 = create( - :school, + :gias_school, name: "Secondary School", team: target_team2, programmes: [programme_flu] diff --git a/spec/lib/status_generator/programme_spec.rb b/spec/lib/status_generator/programme_spec.rb index 8a94335039..59143e9922 100644 --- a/spec/lib/status_generator/programme_spec.rb +++ b/spec/lib/status_generator/programme_spec.rb @@ -22,7 +22,7 @@ let(:session) { create(:session, programmes: [programme]) } let(:patient) { create(:patient, session:, parents:) } let(:parents) { [create(:parent)] } - let(:location) { create(:school) } + let(:location) { create(:gias_school) } context "when already vaccinated" do let(:programme) { Programme.hpv } @@ -406,7 +406,7 @@ end context "when not eligible" do - let(:patient) { create(:patient, year_group: 20, parents:) } + let(:patient) { create(:patient, year_group: 12, parents:) } its(:consent_status) { should be(:no_response) } its(:consent_vaccine_methods) { should be_empty } @@ -417,5 +417,21 @@ its(:status) { should be(:not_eligible) } its(:vaccine_methods) { should be_nil } its(:without_gelatine) { should be_nil } + + context "when vaccination for 16+ is enabled" do + before { Flipper.enable(:vaccinating_16_plus_year_olds) } + + let(:programme) { Programme.hpv } + + its(:consent_status) { should be(:no_response) } + its(:consent_vaccine_methods) { should be_empty } + its(:date) { should be_nil } + its(:disease_types) { should be_nil } + its(:dose_sequence) { should eq(1) } + its(:location_id) { should be_nil } + its(:status) { should be(:needs_consent_no_response) } + its(:vaccine_methods) { should be_nil } + its(:without_gelatine) { should be_nil } + end end end diff --git a/spec/lib/team_merger_spec.rb b/spec/lib/team_merger_spec.rb index cb2a9afb56..5e29dc497c 100644 --- a/spec/lib/team_merger_spec.rb +++ b/spec/lib/team_merger_spec.rb @@ -89,7 +89,7 @@ context "with team_locations for the same location but different subteams" do before do - location = create(:school, :secondary) + location = create(:gias_school, :secondary) sub_a = create(:subteam, team: team_a, name: "Sub A") sub_b = create(:subteam, team: team_b, name: "Sub B") year = AcademicYear.current @@ -387,7 +387,7 @@ end context "team locations" do - let(:location) { create(:school, :secondary) } + let(:location) { create(:gias_school, :secondary) } context "single assignment" do let!(:tl) do diff --git a/spec/lib/timeline_records_spec.rb b/spec/lib/timeline_records_spec.rb index ef8dec0aaf..185c2da026 100644 --- a/spec/lib/timeline_records_spec.rb +++ b/spec/lib/timeline_records_spec.rb @@ -2,15 +2,11 @@ describe TimelineRecords do subject(:timeline) do - described_class.new( - patient, - detail_config: detail_config, - show_pii: show_pii - ) + described_class.new(patient, details_config:, show_pii:) end let(:programme) { Programme.hpv } - let(:detail_config) { {} } + let(:details_config) { TimelineRecords::DEFAULT_DETAILS_CONFIG } let(:show_pii) { false } let(:team) { create(:team, programmes: [programme]) } let(:session) { create(:session, team:, programmes: [programme]) } @@ -140,7 +136,11 @@ event = timeline.instance_variable_get(:@events).first expect(event[:event_type]).to eq "Consent" expect(event[:details]).to eq( - { "response" => consent.response, "route" => consent.route } + { + "programme_type" => consent.programme_type, + "response" => consent.response, + "route" => consent.route + } ) end @@ -211,7 +211,7 @@ end context "with custom details configuration" do - let(:detail_config) { { consents: %i[route], triages: %i[status] } } + let(:details_config) { { consents: %i[route], triages: %i[status] } } before do patient.consents << consent diff --git a/spec/lib/training_onboarding_configuration_spec.rb b/spec/lib/training_onboarding_configuration_spec.rb index 60fc28e9b5..b2a1c9c620 100644 --- a/spec/lib/training_onboarding_configuration_spec.rb +++ b/spec/lib/training_onboarding_configuration_spec.rb @@ -6,10 +6,10 @@ let(:ods_code) { "ABC" } let(:workgroup) { "abc" } - let!(:unattached_primary_school) { create(:school, :open, :primary) } - let!(:unattached_secondary_school) { create(:school, :open, :secondary) } + let!(:unattached_primary_school) { create(:gias_school, :open, :primary) } + let!(:unattached_secondary_school) { create(:gias_school, :open, :secondary) } - before { create(:school, :open, team: create(:team)) } + before { create(:gias_school, :open, team: create(:team)) } context "when cis2 is disabled", cis2: :disabled do context "when type is point_of_care" do diff --git a/spec/models/class_import_row_spec.rb b/spec/models/class_import_row_spec.rb index 884d38b5cc..ea63aa3c52 100644 --- a/spec/models/class_import_row_spec.rb +++ b/spec/models/class_import_row_spec.rb @@ -25,7 +25,7 @@ let(:programmes) { [Programme.sample] } let(:team) { create(:team, programmes:) } let(:academic_year) { AcademicYear.pending } - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let(:session) { create(:session, team:, programmes:, location: school) } let(:valid_data) do diff --git a/spec/models/class_import_spec.rb b/spec/models/class_import_spec.rb index 058b3f9a6c..2896f135c7 100644 --- a/spec/models/class_import_spec.rb +++ b/spec/models/class_import_spec.rb @@ -42,7 +42,7 @@ let(:programmes) { [Programme.hpv] } let(:team) { create(:team, programmes:) } - let(:location) { create(:school, team:) } + let(:location) { create(:gias_school, team:) } let(:session) { create(:session, location:, programmes:, team:) } let(:file) { "valid.csv" } diff --git a/spec/models/cohort_import_row_spec.rb b/spec/models/cohort_import_row_spec.rb index c2a9e660a1..2b23e5dd59 100644 --- a/spec/models/cohort_import_row_spec.rb +++ b/spec/models/cohort_import_row_spec.rb @@ -58,7 +58,7 @@ } end - let!(:school) { create(:school, urn: "123456", team:) } + let!(:school) { create(:gias_school, urn: "123456", team:) } describe "validations" do let(:data) { valid_data } @@ -146,8 +146,8 @@ before do school.team_locations.includes(:team).destroy_all - create(:school, urn: school_urn, site: "A", team:) - create(:school, urn: school_urn, site: "B", team:) + create(:gias_school, urn: school_urn, site: "A", team:) + create(:gias_school, urn: school_urn, site: "B", team:) end it "is invalid" do @@ -163,8 +163,8 @@ before do school.team_locations.includes(:team).destroy_all - create(:school, urn: school_urn, site: "A", team:) - create(:school, urn: school_urn, site: "B", team:) + create(:gias_school, urn: school_urn, site: "A", team:) + create(:gias_school, urn: school_urn, site: "B", team:) end it "is valid" do diff --git a/spec/models/cohort_import_spec.rb b/spec/models/cohort_import_spec.rb index dbf2c4db28..332cb17f97 100644 --- a/spec/models/cohort_import_spec.rb +++ b/spec/models/cohort_import_spec.rb @@ -44,7 +44,7 @@ let(:academic_year) { AcademicYear.current } # Ensure location URN matches the URN in our fixture files - let!(:location) { create(:school, urn: "123456", team:) } # rubocop:disable RSpec/LetSetup + let!(:location) { create(:gias_school, urn: "123456", team:) } # rubocop:disable RSpec/LetSetup it_behaves_like "a CSVImportable model" @@ -159,7 +159,7 @@ let(:location) do Location.find_by_urn_and_site("120026") || - create(:school, urn: "120026", team:) + create(:gias_school, urn: "120026", team:) end it "is valid" do diff --git a/spec/models/consent_form_spec.rb b/spec/models/consent_form_spec.rb index 5246f46fc5..c85233f8f6 100644 --- a/spec/models/consent_form_spec.rb +++ b/spec/models/consent_form_spec.rb @@ -617,7 +617,7 @@ team:, programmes:, academic_year: AcademicYear.current, - location: create(:school, programmes:), + location: create(:gias_school, programmes:), session: nil, health_answers: [ HealthAnswer.new( @@ -683,7 +683,7 @@ let(:programmes) { [Programme.hpv] } let(:team) { create(:team, programmes:) } - let!(:school) { create(:school, team:) } + let!(:school) { create(:gias_school, team:) } let!(:generic_clinic) { team.generic_clinic } context "with a consent form from a school" do @@ -938,7 +938,7 @@ let(:programme) { Programme.sample } let(:team) { create(:team, programmes: [programme]) } - let(:school) { create(:school, programmes: [programme]) } + let(:school) { create(:gias_school, programmes: [programme]) } let(:location) { school } let(:session) do create(:session, team:, programmes: [programme], location:) @@ -1016,7 +1016,7 @@ ) end - let(:new_school) { create(:school) } + let(:new_school) { create(:gias_school) } it "creates a consent" do expect { match_with_patient! }.to change(Consent, :count).by(1) diff --git a/spec/models/draft_school_spec.rb b/spec/models/draft_school_spec.rb index e3f3907972..ab3a02b813 100644 --- a/spec/models/draft_school_spec.rb +++ b/spec/models/draft_school_spec.rb @@ -14,7 +14,7 @@ let(:current_user) { team.users.first } let(:request_session) { {} } - let(:school) { create(:school, :secondary, team:) } + let(:school) { create(:gias_school, :secondary, team:) } let(:valid_attributes) do { @@ -153,7 +153,7 @@ context "when editing and removing existing year groups" do let(:existing_site) do create( - :school, + :gias_school, urn: school.urn, site: "A", gias_year_groups: [8, 9, 10] @@ -178,7 +178,7 @@ context "when editing and keeping all existing year groups" do let(:existing_site) do create( - :school, + :gias_school, urn: school.urn, site: "A", gias_year_groups: [8, 9, 10] @@ -263,7 +263,9 @@ context "when school belongs to a different team" do let(:other_team) { create(:team) } - let(:other_school) { create(:school, :secondary, team: other_team) } + let(:other_school) do + create(:gias_school, :secondary, team: other_team) + end let(:attributes) { { parent_urn_and_site: other_school.urn_and_site } } it { expect(draft_school.source_location).to be_nil } @@ -272,7 +274,7 @@ context "when editing an existing site" do let(:existing_site) do - create(:school, urn: school.urn, site: "A", name: "Site A") + create(:gias_school, urn: school.urn, site: "A", name: "Site A") end let(:attributes) { { editing_id: existing_site.id } } @@ -314,14 +316,14 @@ context "when multiple sites exist with the same URN" do before do create( - :school, + :gias_school, :secondary, urn: school.urn, site: "A", name: "School Site A" ) create( - :school, + :gias_school, :secondary, urn: school.urn, site: "B", @@ -482,8 +484,8 @@ context "when sites already exist" do before do - create(:school, urn: school.urn, site: "A", name: "Site A") - create(:school, urn: school.urn, site: "B", name: "Site B") + create(:gias_school, urn: school.urn, site: "A", name: "Site A") + create(:gias_school, urn: school.urn, site: "B", name: "Site B") end it "returns the URN with the next available site letter" do @@ -494,7 +496,7 @@ context "when editing an existing site" do let(:existing_site) do - create(:school, urn: school.urn, site: "A", name: "Site A") + create(:gias_school, urn: school.urn, site: "A", name: "Site A") end let(:attributes) { { editing_id: existing_site.id } } @@ -515,7 +517,7 @@ context "when editing an existing site" do let(:existing_site) do - create(:school, urn: "654321", site: "A", name: "Site A") + create(:gias_school, urn: "654321", site: "A", name: "Site A") end let(:attributes) { { editing_id: existing_site.id, context: "add_site" } } @@ -535,7 +537,7 @@ end context "when site A exists" do - before { create(:school, urn: school.urn, site: "A") } + before { create(:gias_school, urn: school.urn, site: "A") } it "returns B" do expect(draft_school.next_site_letter).to eq("B") @@ -544,8 +546,8 @@ context "when sites A and B exist" do before do - create(:school, urn: school.urn, site: "A") - create(:school, urn: school.urn, site: "B") + create(:gias_school, urn: school.urn, site: "A") + create(:gias_school, urn: school.urn, site: "B") end it "returns C" do @@ -554,7 +556,7 @@ end context "when site Z exists" do - before { create(:school, urn: school.urn, site: "Z") } + before { create(:gias_school, urn: school.urn, site: "Z") } it "returns AA" do expect(draft_school.next_site_letter).to eq("AA") @@ -573,7 +575,12 @@ context "when editing an existing site" do let(:existing_site) do - create(:school, urn: school.urn, site: "A", gias_year_groups: [10, 11]) + create( + :gias_school, + urn: school.urn, + site: "A", + gias_year_groups: [10, 11] + ) end let(:attributes) { { editing_id: existing_site.id } } @@ -604,7 +611,7 @@ context "when editing an existing site" do let(:programmes) { [Programme.hpv, Programme.flu] } let(:existing_site) do - create(:school, urn: school.urn, site: "A", team:, programmes:) + create(:gias_school, urn: school.urn, site: "A", team:, programmes:) end let(:attributes) { { editing_id: existing_site.id } } @@ -627,7 +634,7 @@ context "when editing an existing site" do let(:existing_site) do - create(:school, :primary, urn: school.urn, site: "A") + create(:gias_school, :primary, urn: school.urn, site: "A") end let(:attributes) { { editing_id: existing_site.id } } diff --git a/spec/models/draft_session_spec.rb b/spec/models/draft_session_spec.rb index 15a01c5564..d9d52912e2 100644 --- a/spec/models/draft_session_spec.rb +++ b/spec/models/draft_session_spec.rb @@ -22,7 +22,9 @@ let(:existing_programmes) { [Programme.menacwy, Programme.td_ipv] } let(:new_programme) { Programme.hpv } - let(:location) { create(:school, team:, programmes: existing_programmes) } + let(:location) do + create(:gias_school, team:, programmes: existing_programmes) + end let(:session) do create(:session, team:, location:, programmes: existing_programmes) end diff --git a/spec/models/immunisation_import_row_spec.rb b/spec/models/immunisation_import_row_spec.rb index 90611d8f1a..e15288d61f 100644 --- a/spec/models/immunisation_import_row_spec.rb +++ b/spec/models/immunisation_import_row_spec.rb @@ -94,7 +94,7 @@ end let!(:location) do - create(:school, urn: "123456", name: "Waterloo Road", programmes:) + create(:gias_school, urn: "123456", name: "Waterloo Road", programmes:) end let(:import_type) { "point_of_care" } diff --git a/spec/models/immunisation_import_spec.rb b/spec/models/immunisation_import_spec.rb index b6fa95426d..2d9f9a808a 100644 --- a/spec/models/immunisation_import_spec.rb +++ b/spec/models/immunisation_import_spec.rb @@ -39,10 +39,10 @@ end before do - create(:school, urn: "110158", systm_one_code: "TT110158") - create(:school, urn: "120026") - create(:school, urn: "144012") - create(:school, urn: "100000") + create(:gias_school, urn: "110158", systm_one_code: "TT110158") + create(:gias_school, urn: "120026") + create(:gias_school, urn: "144012") + create(:gias_school, urn: "100000") end let(:programmes) { [Programme.flu] } @@ -53,7 +53,7 @@ create(:team, ods_code: "R1L", programmes:) end end - let(:school) { create(:school, urn: "123456") } + let(:school) { create(:gias_school, urn: "123456") } let(:file) { "valid_flu.csv" } let(:csv) { fixture_file_upload("immunisation_import/#{type}/#{file}") } diff --git a/spec/models/location/year_group_spec.rb b/spec/models/location/year_group_spec.rb index cbe771aed6..c392c8824b 100644 --- a/spec/models/location/year_group_spec.rb +++ b/spec/models/location/year_group_spec.rb @@ -24,7 +24,7 @@ describe Location::YearGroup do subject(:location_year_group) { build(:location_year_group, location:) } - let(:location) { create(:school) } + let(:location) { create(:gias_school) } describe "associations" do it { should belong_to(:location) } diff --git a/spec/models/location_spec.rb b/spec/models/location_spec.rb index 2b70af0304..02cf9fdf10 100644 --- a/spec/models/location_spec.rb +++ b/spec/models/location_spec.rb @@ -58,8 +58,10 @@ describe "#find_by_urn_and_site" do subject(:scope) { described_class.find_by_urn_and_site(urn_and_site) } - let(:location_without_site) { create(:school, urn: "123456") } - let(:location_with_site) { create(:school, urn: "123456", site: "A") } + let(:location_without_site) { create(:gias_school, urn: "123456") } + let(:location_with_site) do + create(:gias_school, urn: "123456", site: "A") + end context "with just a URN" do let(:urn_and_site) { "123456" } @@ -78,15 +80,19 @@ subject(:scope) { described_class.search_by_name(query) } let(:clinic) { create(:generic_clinic) } - let!(:school_a) { create(:school, name: "St. John's School") } - let!(:school_b) { create(:school, name: "St. John's School for Boys") } + let!(:school_a) { create(:gias_school, name: "St. John's School") } + let!(:school_b) do + create(:gias_school, name: "St. John's School for Boys") + end let!(:school_c) do create( - :school, + :gias_school, name: "St. John's Primary School for Boys with learning disabilities" ) end - let!(:school_d) { create(:school, name: "St. Mary's School for Girls") } + let!(:school_d) do + create(:gias_school, name: "St. Mary's School for Girls") + end context "with an exact match on the name" do let(:query) { "Community clinic" } @@ -145,18 +151,20 @@ describe "#where_phase" do subject { described_class.where_phase(phase) } - let!(:nursery) { create(:school, gias_phase: "nursery") } - let!(:primary) { create(:school, gias_phase: "primary") } + let!(:nursery) { create(:gias_school, gias_phase: "nursery") } + let!(:primary) { create(:gias_school, gias_phase: "primary") } let!(:middle_deemed_primary) do - create(:school, gias_phase: "middle_deemed_primary") + create(:gias_school, gias_phase: "middle_deemed_primary") end - let!(:secondary) { create(:school, gias_phase: "secondary") } + let!(:secondary) { create(:gias_school, gias_phase: "secondary") } let!(:middle_deemed_secondary) do - create(:school, gias_phase: "middle_deemed_secondary") + create(:gias_school, gias_phase: "middle_deemed_secondary") + end + let!(:sixteen_plus) { create(:gias_school, gias_phase: "sixteen_plus") } + let!(:all_through) { create(:gias_school, gias_phase: "all_through") } + let!(:not_applicable) do + create(:gias_school, gias_phase: "not_applicable") end - let!(:sixteen_plus) { create(:school, gias_phase: "sixteen_plus") } - let!(:all_through) { create(:school, gias_phase: "all_through") } - let!(:not_applicable) { create(:school, gias_phase: "not_applicable") } context "with nursery" do let(:phase) { "nursery" } @@ -246,7 +254,7 @@ end context "with a school" do - subject(:location) { build(:school, urn: "abc") } + subject(:location) { build(:gias_school, urn: "abc") } it { should validate_presence_of(:gias_establishment_number) } it { should validate_presence_of(:gias_local_authority_code) } @@ -286,7 +294,7 @@ end context "with a school" do - let(:location) { create(:school) } + let(:location) { create(:gias_school) } it { should be(location.urn_and_site) } end @@ -308,7 +316,7 @@ end context "with a school" do - let(:location) { build(:school) } + let(:location) { build(:gias_school) } it { should be(false) } end @@ -332,7 +340,7 @@ context "with a school" do let(:location) do build( - :school, + :gias_school, gias_local_authority_code: 123, gias_establishment_number: 456 ) @@ -346,49 +354,53 @@ subject { location.phase } context "with a nursery GIAS phase" do - let(:location) { build(:school, gias_phase: "nursery") } + let(:location) { build(:gias_school, gias_phase: "nursery") } it { should eq("nursery") } end context "with a primary GIAS phase" do - let(:location) { build(:school, gias_phase: "primary") } + let(:location) { build(:gias_school, gias_phase: "primary") } it { should eq("primary") } end context "with a middle deemed primary GIAS phase" do - let(:location) { build(:school, gias_phase: "middle_deemed_primary") } + let(:location) do + build(:gias_school, gias_phase: "middle_deemed_primary") + end it { should eq("primary") } end context "with a secondary GIAS phase" do - let(:location) { build(:school, gias_phase: "secondary") } + let(:location) { build(:gias_school, gias_phase: "secondary") } it { should eq("secondary") } end context "with a middle deemed secondary GIAS phase" do - let(:location) { build(:school, gias_phase: "middle_deemed_secondary") } + let(:location) do + build(:gias_school, gias_phase: "middle_deemed_secondary") + end it { should eq("secondary") } end context "with a 16-plus phase" do - let(:location) { build(:school, gias_phase: "sixteen_plus") } + let(:location) { build(:gias_school, gias_phase: "sixteen_plus") } it { should eq("other") } end context "with an all-through phase" do - let(:location) { build(:school, gias_phase: "all_through") } + let(:location) { build(:gias_school, gias_phase: "all_through") } it { should eq("other") } end context "with a not applicable phase" do - let(:location) { build(:school, gias_phase: "not_applicable") } + let(:location) { build(:gias_school, gias_phase: "not_applicable") } it { should eq("other") } end @@ -430,7 +442,7 @@ end context "when the location is not attached to a team" do - let(:location) { create(:school, subteam: nil) } + let(:location) { create(:gias_school, subteam: nil) } it { should include("is_attached_to_team" => false) } end @@ -445,7 +457,7 @@ let(:academic_year) { AcademicYear.pending } context "when the location has no year groups" do - let(:location) { create(:school, gias_year_groups: []) } + let(:location) { create(:gias_school, gias_year_groups: []) } it "doesn't create any programme year groups" do expect { import_default_programme_year_groups! }.not_to change( @@ -456,7 +468,7 @@ end context "when the location has fewer year groups than the default" do - let(:location) { create(:school, gias_year_groups: (0..3).to_a) } + let(:location) { create(:gias_school, gias_year_groups: (0..3).to_a) } it "creates only suitable year groups" do expect { import_default_programme_year_groups! }.to change( @@ -471,7 +483,7 @@ end context "when the location has more year groups than the default" do - let(:location) { create(:school, gias_year_groups: (-1..14).to_a) } + let(:location) { create(:gias_school, gias_year_groups: (-1..14).to_a) } it "creates only suitable year groups" do expect { import_default_programme_year_groups! }.to change( @@ -491,7 +503,7 @@ location.teams_for_academic_year(target_year) end - let(:location) { create(:school) } + let(:location) { create(:gias_school) } let(:team_a) { create(:team) } let(:team_b) { create(:team) } let(:team_c) { create(:team) } @@ -528,7 +540,7 @@ end context "when the location has no teams for the specified year" do - let(:location_without_teams) { create(:school) } + let(:location_without_teams) { create(:gias_school) } let(:pending_year) { AcademicYear.pending } it "returns an empty array" do diff --git a/spec/models/onboarding_spec.rb b/spec/models/onboarding_spec.rb index 2983d16fcc..a7ffaf8807 100644 --- a/spec/models/onboarding_spec.rb +++ b/spec/models/onboarding_spec.rb @@ -9,13 +9,13 @@ let!(:programmes) { [Programme.hpv] } # rubocop:disable RSpec/IndexedLet - let!(:school1) { create(:school, :secondary, :open, urn: "123456") } - let!(:school2) { create(:school, :secondary, :open, urn: "234567") } - let!(:school3) { create(:school, :secondary, :open, urn: "345678") } - let!(:school4) { create(:school, :secondary, :open, urn: "456789") } + let!(:school1) { create(:gias_school, :secondary, :open, urn: "123456") } + let!(:school2) { create(:gias_school, :secondary, :open, urn: "234567") } + let!(:school3) { create(:gias_school, :secondary, :open, urn: "345678") } + let!(:school4) { create(:gias_school, :secondary, :open, urn: "456789") } # rubocop:enable RSpec/IndexedLet - before { create(:school, :secondary, :closed, urn: "567890") } + before { create(:gias_school, :secondary, :closed, urn: "567890") } context "with a valid configuration file for a point of care team" do let(:filename) { "onboarding/point_of_care_valid.yaml" } @@ -42,13 +42,15 @@ expect(generic_clinic.year_groups.count).to eq(19) expect(generic_clinic.location_programme_year_groups.count).to eq(4) - subteam1 = team.subteams.includes(:schools).find_by!(name: "Subteam 1") + subteam1 = + team.subteams.includes(:gias_schools).find_by!(name: "Subteam 1") expect(subteam1.email).to eq("subteam-1@trust.nhs.uk") expect(subteam1.phone).to eq("07700 900816") expect(subteam1.phone_instructions).to eq("option 9") expect(subteam1.reply_to_id).to eq("24af66c3-d6bd-4b9f-8067-3844f49e08d0") - subteam2 = team.subteams.includes(:schools).find_by!(name: "Subteam 2") + subteam2 = + team.subteams.includes(:gias_schools).find_by!(name: "Subteam 2") expect(subteam2.email).to eq("subteam-2@trust.nhs.uk") expect(subteam2.phone).to eq("07700 900817") expect(subteam2.reply_to_id).to be_nil @@ -56,8 +58,8 @@ school2_sites = Location.where(urn: school2.urn).where.not(site: nil) school4_sites = Location.where(urn: school4.urn).where.not(site: nil) - expect(subteam1.schools).to contain_exactly(school1, *school2_sites) - expect(subteam2.schools).to contain_exactly(school3, *school4_sites) + expect(subteam1.gias_schools).to contain_exactly(school1, *school2_sites) + expect(subteam2.gias_schools).to contain_exactly(school3, *school4_sites) expect(school1.location_programme_year_groups.count).to eq(4) expect(school2_sites.first.location_programme_year_groups.count).to eq(4) @@ -104,7 +106,7 @@ expect(team.subteams.count).to eq(0) - expect(team.schools.count).to eq(0) + expect(team.gias_schools.count).to eq(0) end end @@ -114,17 +116,17 @@ let(:team) { create(:team, name: "Existing Team") } before do - create(:school, :secondary, :open, urn: "111111", team:) - create(:school, :secondary, :open, urn: "555555", team:) + create(:gias_school, :secondary, :open, urn: "111111", team:) + create(:gias_school, :secondary, :open, urn: "555555", team:) create( - :school, + :gias_school, :secondary, :open, urn: "222222", name: "School with no site" ) create( - :school, + :gias_school, :secondary, :open, urn: "222222", @@ -133,7 +135,7 @@ team: ) create( - :school, + :gias_school, :secondary, :open, urn: "222222", diff --git a/spec/models/patient/programme_status_spec.rb b/spec/models/patient/programme_status_spec.rb index fe2460184e..9a9f84c6ae 100644 --- a/spec/models/patient/programme_status_spec.rb +++ b/spec/models/patient/programme_status_spec.rb @@ -37,6 +37,73 @@ it { should belong_to(:patient) } end + describe "#needs_more_doses?" do + subject(:needs_more_doses) do + patient.programme_status( + Programme.mmr, + academic_year: session.academic_year + ).needs_more_doses? + end + + let(:programme) { Programme.mmr } + let(:team) { create(:team, programmes: [programme]) } + let(:session) do + create( + :session, + team:, + programmes: [programme], + date: Date.new(2024, 10, 1) + ) + end + let(:patient) { create(:patient, session:, year_group: 9) } + + context "with no vaccinations" do + before { PatientStatusUpdater.call(patient:) } + + it { should be(true) } + end + + context "with one vaccination" do + before do + create( + :vaccination_record, + :administered, + programme:, + patient:, + session:, + performed_at: Date.new(2024, 10, 1) + ) + PatientStatusUpdater.call(patient:) + end + + it { should be(true) } + end + + context "with two vaccinations" do + before do + create( + :vaccination_record, + :administered, + programme:, + patient:, + session:, + performed_at: Date.new(2024, 10, 1) + ) + create( + :vaccination_record, + :administered, + programme:, + patient:, + session:, + performed_at: Date.new(2024, 11, 1) + ) + PatientStatusUpdater.call(patient:) + end + + it { should be(false) } + end + end + describe "#assign" do subject(:assign) { patient_programme_status.assign } diff --git a/spec/models/patient_changeset_spec.rb b/spec/models/patient_changeset_spec.rb index 218d347857..c93badf33d 100644 --- a/spec/models/patient_changeset_spec.rb +++ b/spec/models/patient_changeset_spec.rb @@ -41,7 +41,7 @@ end let(:team) { create(:team) } - let(:school) { create(:school, urn: "123456", team:) } + let(:school) { create(:gias_school, urn: "123456", team:) } let(:valid_data) do { @@ -141,7 +141,7 @@ context "with school change" do before do - create(:patient, nhs_number: "9990000026", school: create(:school)) + create(:patient, nhs_number: "9990000026", school: create(:gias_school)) end it "creates school move record" do @@ -157,11 +157,11 @@ let(:another_team) { create(:team, name: "Another Team") } let(:school_in_other_team) do - create(:school, name: "School in another team", team: another_team) + create(:gias_school, name: "School in another team", team: another_team) end context "when new location is a known school" do - let(:school) { create(:school) } + let(:school) { create(:gias_school) } before do create(:patient, nhs_number: "9990000026", school: school_in_other_team) diff --git a/spec/models/patient_location_spec.rb b/spec/models/patient_location_spec.rb index 74a18d0e38..4e4a3be32f 100644 --- a/spec/models/patient_location_spec.rb +++ b/spec/models/patient_location_spec.rb @@ -60,7 +60,7 @@ end context "in a session with the right year group for the programme but not the location" do - let(:location) { create(:school, :secondary) } + let(:location) { create(:gias_school, :secondary) } let(:session) { create(:session, location:, programmes:) } let(:patient) { create(:patient, year_group: 9) } diff --git a/spec/models/patient_spec.rb b/spec/models/patient_spec.rb index f37a064a6b..12353c368f 100644 --- a/spec/models/patient_spec.rb +++ b/spec/models/patient_spec.rb @@ -249,7 +249,7 @@ end context "in a session with the right year group for the programme but not the location" do - let(:location) { create(:school, :secondary) } + let(:location) { create(:gias_school, :secondary) } let(:patient) { create(:patient, session:, year_group: 9) } let(:session) { create(:session, location:, programmes:) } @@ -272,7 +272,7 @@ let(:hpv_programme) { Programme.hpv } let(:location) do - create(:school, programmes: [flu_programme, hpv_programme]) + create(:gias_school, programmes: [flu_programme, hpv_programme]) end let(:academic_year) { AcademicYear.current } @@ -342,7 +342,7 @@ end context "in a session with the right year group for the programme but not the location" do - let(:location) { create(:school, :secondary) } + let(:location) { create(:gias_school, :secondary) } let(:session) { create(:session, location:, programmes:) } let(:patient) { create(:patient, session:, year_group: 9) } @@ -365,7 +365,7 @@ let(:hpv_programme) { Programme.hpv } let(:location) do - create(:school, programmes: [flu_programme, hpv_programme]) + create(:gias_school, programmes: [flu_programme, hpv_programme]) end let(:academic_year) { AcademicYear.current } @@ -636,7 +636,7 @@ describe "validations" do context "with an invalid GP practice" do - subject(:patient) { build(:patient, gp_practice: create(:school)) } + subject(:patient) { build(:patient, gp_practice: create(:gias_school)) } it "is invalid" do expect(patient).not_to be_valid @@ -774,7 +774,7 @@ let(:patient) { create(:patient) } let(:team) { create(:team) } let(:academic_year) { 2025 } - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } shared_examples "not_in_team? behavior" do context "when the patient is in the team" do @@ -797,7 +797,7 @@ context "when the patient is in a different team" do let(:other_team) { create(:team) } - let(:other_school) { create(:school, team: other_team) } + let(:other_school) { create(:gias_school, team: other_team) } before do SchoolMove.new( @@ -834,7 +834,7 @@ let(:programmes) { [Programme.flu, Programme.hpv] } let(:team) { create(:team, programmes:) } - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } context "outside the preparation period" do around { |example| travel_to(Date.new(2025, 7, 31)) { example.run } } @@ -1124,7 +1124,7 @@ let(:programme) { Programme.sample } let(:team) { create(:team, programmes: [programme]) } - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let(:session) { create(:session, location: school, team:, programme:) } it "marks the patient as not invalidated" do @@ -1283,7 +1283,7 @@ context "when the old patient has upcoming sessions" do let(:team) { create(:team) } - let(:location) { create(:school, team:) } + let(:location) { create(:gias_school, team:) } before do create( @@ -1312,7 +1312,7 @@ context "when the old patient has school moves" do let(:team) { create(:team) } - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } before { create(:school_move, :to_school, patient: old_patient, school:) } diff --git a/spec/models/programme_spec.rb b/spec/models/programme_spec.rb index 53eea49e45..23e7e17c70 100644 --- a/spec/models/programme_spec.rb +++ b/spec/models/programme_spec.rb @@ -252,28 +252,64 @@ describe "#default_year_groups" do subject { programme.default_year_groups } - context "with a flu programme" do + context "for flu" do let(:programme) { described_class.flu } it { should eq([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) } + + context "when vaccination for 16+ is enabled" do + before { Flipper.enable(:vaccinating_16_plus_year_olds) } + + it { should eq([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) } + end end - context "with an HPV programme" do + context "for HPV" do let(:programme) { described_class.hpv } it { should eq([8, 9, 10, 11]) } + + context "when vaccination for 16+ is enabled" do + before { Flipper.enable(:vaccinating_16_plus_year_olds) } + + it { should eq([8, 9, 10, 11, 12, 13]) } + end end - context "with a MenACWY programme" do + context "for MenACWY" do let(:programme) { described_class.menacwy } it { should eq([9, 10, 11]) } + + context "when vaccination for 16+ is enabled" do + before { Flipper.enable(:vaccinating_16_plus_year_olds) } + + it { should eq([9, 10, 11, 12, 13]) } + end end - context "with an Td/IPV programme" do + context "for MMR(V)" do + let(:programme) { described_class.mmr } + + it { should eq([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) } + + context "when vaccination for 16+ is enabled" do + before { Flipper.enable(:vaccinating_16_plus_year_olds) } + + it { should eq([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]) } + end + end + + context "for Td/IPV" do let(:programme) { described_class.td_ipv } it { should eq([9, 10, 11]) } + + context "when vaccination for 16+ is enabled" do + before { Flipper.enable(:vaccinating_16_plus_year_olds) } + + it { should eq([9, 10, 11, 12, 13]) } + end end end @@ -427,6 +463,40 @@ end end + describe "#multi_dose?" do + subject { programme.multi_dose? } + + context "with a flu programme" do + let(:programme) { described_class.flu } + + it { should be(false) } + end + + context "with an HPV programme" do + let(:programme) { described_class.hpv } + + it { should be(false) } + end + + context "with an MenACWY programme" do + let(:programme) { described_class.menacwy } + + it { should be(false) } + end + + context "with an Td/IPV programme" do + let(:programme) { described_class.td_ipv } + + it { should be(false) } + end + + context "with an MMR programme" do + let(:programme) { described_class.mmr } + + it { should be(true) } + end + end + describe "#supports_outbreak?" do subject { programme.supports_outbreak? } diff --git a/spec/models/school_move_spec.rb b/spec/models/school_move_spec.rb index 199a32341e..bedb2792d1 100644 --- a/spec/models/school_move_spec.rb +++ b/spec/models/school_move_spec.rb @@ -17,13 +17,11 @@ # index_school_moves_on_patient_id (patient_id) UNIQUE # index_school_moves_on_patient_id_and_school_id (patient_id,school_id) # index_school_moves_on_school_id (school_id) -# index_school_moves_on_team_id (team_id) # # Foreign Keys # # fk_rails_... (patient_id => patients.id) # fk_rails_... (school_id => locations.id) -# fk_rails_... (team_id => teams.id) # describe SchoolMove do describe "validations" do @@ -171,7 +169,7 @@ create(:school_move, :to_school, patient:, school:) end - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let(:new_sessions) do create_list( :session, @@ -194,7 +192,7 @@ create(:school_move, :to_school, patient:, school:) end - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let(:new_sessions) do create_list( :session, @@ -233,7 +231,7 @@ create(:school_move, :to_school, patient:, school:) end - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let(:new_sessions) do create_list( :session, @@ -257,7 +255,7 @@ create(:school_move, :to_school, patient:, school:) end - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let(:new_sessions) do create_list( :session, @@ -299,7 +297,7 @@ create(:school_move, :to_school, patient:, school:) end - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let(:new_sessions) do create_list( :session, @@ -323,7 +321,7 @@ create(:school_move, :to_school, patient:, school:) end - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let(:new_sessions) do create_list( :session, @@ -359,7 +357,7 @@ end let(:new_team) { create(:team, programmes:) } - let(:school) { create(:school, team: new_team) } + let(:school) { create(:gias_school, team: new_team) } let(:new_sessions) do create_list( :session, @@ -412,7 +410,7 @@ create(:school_move, :to_school, patient:, school:) end - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let!(:new_sessions) do # rubocop:disable RSpec/LetSetup create_list( :session, @@ -435,7 +433,7 @@ create(:school_move, :to_school, patient:, school:) end - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let!(:new_sessions) do # rubocop:disable RSpec/LetSetup create_list( :session, @@ -473,7 +471,7 @@ end let(:new_team) { create(:team, programmes:) } - let(:school) { create(:school, team: new_team) } + let(:school) { create(:gias_school, team: new_team) } let(:new_sessions) do create_list( :session, @@ -543,7 +541,7 @@ create(:school_move, :to_school, patient:, school:) end - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let!(:new_sessions) do # rubocop:disable RSpec/LetSetup create_list( :session, @@ -566,7 +564,7 @@ create(:school_move, :to_school, patient:, school:) end - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let!(:new_sessions) do # rubocop:disable RSpec/LetSetup create_list( :session, @@ -601,7 +599,7 @@ end let(:new_team) { create(:team, programmes:) } - let(:school) { create(:school, team: new_team) } + let(:school) { create(:gias_school, team: new_team) } let(:new_sessions) do create_list( :session, @@ -727,7 +725,7 @@ context "when patient has no current school" do let(:patient) { create(:patient, school: nil) } - let(:new_school) { create(:school, team: team_a) } + let(:new_school) { create(:gias_school, team: team_a) } let(:school_move) do create(:school_move, patient:, school: new_school, academic_year:) end @@ -736,9 +734,9 @@ end context "when patient's current school has no teams" do - let(:current_school) { create(:school) } + let(:current_school) { create(:gias_school) } let(:patient) { create(:patient, school: current_school) } - let(:new_school) { create(:school, team: team_a) } + let(:new_school) { create(:gias_school, team: team_a) } let(:school_move) do create(:school_move, patient:, school: new_school, academic_year:) end @@ -747,7 +745,7 @@ end context "when moving to home educated with same team" do - let(:current_school) { create(:school, team: team_a) } + let(:current_school) { create(:gias_school, team: team_a) } let(:patient) { create(:patient, school: current_school) } let(:school_move) do create( @@ -763,7 +761,7 @@ end context "when moving to home educated with different team" do - let(:current_school) { create(:school, team: team_a) } + let(:current_school) { create(:gias_school, team: team_a) } let(:patient) do create(:patient, school: current_school, location: current_school) end @@ -781,7 +779,7 @@ end context "when moving to unknown school with same team" do - let(:current_school) { create(:school, team: team_a) } + let(:current_school) { create(:gias_school, team: team_a) } let(:patient) { create(:patient, school: current_school) } let(:school_move) do create( @@ -797,7 +795,7 @@ end context "when moving to unknown school with different team" do - let(:current_school) { create(:school, team: team_a) } + let(:current_school) { create(:gias_school, team: team_a) } let(:patient) do create(:patient, school: current_school, location: current_school) end @@ -815,8 +813,8 @@ end context "when moving within the same team" do - let(:current_school) { create(:school, team: team_a) } - let(:new_school) { create(:school, team: team_a) } + let(:current_school) { create(:gias_school, team: team_a) } + let(:new_school) { create(:gias_school, team: team_a) } let(:patient) { create(:patient, school: current_school) } let(:school_move) do create(:school_move, patient:, school: new_school, academic_year:) @@ -826,8 +824,8 @@ end context "when current school is in multiple teams and moving to one of them" do - let(:current_school) { create(:school) } - let(:new_school) { create(:school, team: team_b) } + let(:current_school) { create(:gias_school) } + let(:new_school) { create(:gias_school, team: team_b) } let(:patient) { create(:patient, school: current_school) } let(:school_move) do create(:school_move, patient:, school: new_school, academic_year:) @@ -852,8 +850,8 @@ end context "when moving to a different team" do - let(:current_school) { create(:school, team: team_a) } - let(:new_school) { create(:school, team: team_b) } + let(:current_school) { create(:gias_school, team: team_a) } + let(:new_school) { create(:gias_school, team: team_b) } let(:patient) do create(:patient, school: current_school, location: current_school) end @@ -866,8 +864,8 @@ context "when moving to a school with multiple teams, none matching current" do let(:team_c) { create(:team) } - let(:current_school) { create(:school, team: team_a) } - let(:new_school) { create(:school) } + let(:current_school) { create(:gias_school, team: team_a) } + let(:new_school) { create(:gias_school) } let(:patient) do create(:patient, school: current_school, location: current_school) end diff --git a/spec/models/session_notification_spec.rb b/spec/models/session_notification_spec.rb index da97e0d65f..d8fe03b8b0 100644 --- a/spec/models/session_notification_spec.rb +++ b/spec/models/session_notification_spec.rb @@ -46,7 +46,7 @@ let(:programmes) { [programme] } let(:programme_types) { programmes.map(&:type) } let(:team) { create(:team, programmes:) } - let(:location) { create(:school, team:) } + let(:location) { create(:gias_school, team:) } let(:session) { create(:session, location:, programmes:, team:) } let(:session_date) { session.dates.min } let(:current_user) { create(:user) } diff --git a/spec/models/session_spec.rb b/spec/models/session_spec.rb index 84d2556ea6..0acb270396 100644 --- a/spec/models/session_spec.rb +++ b/spec/models/session_spec.rb @@ -565,7 +565,7 @@ ) end - let(:location) { create(:school, programmes:) } + let(:location) { create(:gias_school, programmes:) } context "when session has both programmes" do let(:session) { create(:session, location:, programmes: []) } diff --git a/spec/models/vaccination_record_spec.rb b/spec/models/vaccination_record_spec.rb index 614ab1939b..3c48ed7c74 100644 --- a/spec/models/vaccination_record_spec.rb +++ b/spec/models/vaccination_record_spec.rb @@ -4,50 +4,57 @@ # # Table name: vaccination_records # -# id :bigint not null, primary key -# batch_expiry :date -# batch_number :string -# confirmation_sent_at :datetime -# delivery_method :integer -# delivery_site :integer -# discarded_at :datetime -# disease_types :enum not null, is an Array -# dose_sequence :integer -# full_dose :boolean -# local_patient_id_uri :string -# location_name :string -# nhs_immunisations_api_etag :string -# nhs_immunisations_api_identifier_system :string -# nhs_immunisations_api_identifier_value :string -# nhs_immunisations_api_primary_source :boolean -# nhs_immunisations_api_sync_pending_at :datetime -# nhs_immunisations_api_synced_at :datetime -# notes :text -# notify_parents :boolean -# outcome :integer not null -# pending_changes :jsonb not null -# performed_at_date :date not null -# performed_at_time :time -# performed_by_family_name :string -# performed_by_given_name :string -# performed_ods_code :string -# programme_type :enum not null -# protocol :integer -# source :integer not null -# uuid :uuid not null -# created_at :datetime not null -# updated_at :datetime not null -# duplicate_of_vaccination_record_id :bigint -# local_patient_id :string -# location_id :bigint -# next_dose_delay_triage_id :bigint -# nhs_immunisations_api_id :string -# patient_id :bigint not null -# performed_by_user_id :bigint -# reported_by_id :bigint -# session_id :bigint -# supplied_by_user_id :bigint -# vaccine_id :bigint +# id :bigint not null, primary key +# batch_expiry :date +# batch_number :string +# confirmation_sent_at :datetime +# delivery_method :integer +# delivery_site :integer +# discarded_at :datetime +# disease_types :enum not null, is an Array +# dose_sequence :integer +# full_dose :boolean +# local_patient_id_uri :string +# location_name :string +# nhs_immunisations_api_etag :string +# nhs_immunisations_api_identifier_system :string +# nhs_immunisations_api_identifier_value :string +# nhs_immunisations_api_primary_source :boolean +# nhs_immunisations_api_recorded_at :datetime +# nhs_immunisations_api_snomed_procedure_code :string +# nhs_immunisations_api_snomed_procedure_term :string +# nhs_immunisations_api_snomed_product_code :string +# nhs_immunisations_api_snomed_product_term :string +# nhs_immunisations_api_snomed_reason_code :string +# nhs_immunisations_api_snomed_reason_term :string +# nhs_immunisations_api_sync_pending_at :datetime +# nhs_immunisations_api_synced_at :datetime +# notes :text +# notify_parents :boolean +# outcome :integer not null +# pending_changes :jsonb not null +# performed_at_date :date not null +# performed_at_time :time +# performed_by_family_name :string +# performed_by_given_name :string +# performed_ods_code :string +# programme_type :enum not null +# protocol :integer +# source :integer not null +# uuid :uuid not null +# created_at :datetime not null +# updated_at :datetime not null +# duplicate_of_vaccination_record_id :bigint +# local_patient_id :string +# location_id :bigint +# next_dose_delay_triage_id :bigint +# nhs_immunisations_api_id :string +# patient_id :bigint not null +# performed_by_user_id :bigint +# reported_by_id :bigint +# session_id :bigint +# supplied_by_user_id :bigint +# vaccine_id :bigint # # Indexes # diff --git a/spec/policies/location_policy_spec.rb b/spec/policies/location_policy_spec.rb index 620cf146b7..4254ad4bf4 100644 --- a/spec/policies/location_policy_spec.rb +++ b/spec/policies/location_policy_spec.rb @@ -9,7 +9,7 @@ let(:national_reporting_user) do create(:nurse, teams: [national_reporting_team]) end - let(:location) { create(:school) } + let(:location) { create(:gias_school) } permissions :index?, :show? do it { should permit(point_of_care_user, location) } diff --git a/spec/policies/patient_policy_spec.rb b/spec/policies/patient_policy_spec.rb index dcc2c4f11b..28129609b3 100644 --- a/spec/policies/patient_policy_spec.rb +++ b/spec/policies/patient_policy_spec.rb @@ -170,7 +170,7 @@ let(:patient_with_move_in_school) { create(:patient) } let!(:patient_with_move_in_another_school) { create(:patient) } - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } before do create( @@ -183,7 +183,7 @@ :school_move, :to_school, patient: patient_with_move_in_another_school, - school: create(:school, team: another_team) + school: create(:gias_school, team: another_team) ) end diff --git a/spec/policies/school_move_policy_spec.rb b/spec/policies/school_move_policy_spec.rb index cca18eafdf..d596a9ac4f 100644 --- a/spec/policies/school_move_policy_spec.rb +++ b/spec/policies/school_move_policy_spec.rb @@ -17,7 +17,7 @@ context "patient belonging to two sessions" do let(:patient) { create(:patient) } - let(:school) { create(:school, team:) } + let(:school) { create(:gias_school, team:) } let(:school_move) do create(:school_move, :to_school, patient:, school:) end @@ -32,7 +32,7 @@ context "school move with school in different team" do let(:patient) { create(:patient) } - let(:school) { create(:school, team: other_team) } + let(:school) { create(:gias_school, team: other_team) } let(:school_move) do create(:school_move, :to_school, patient:, school:) end diff --git a/spec/policies/school_policy_spec.rb b/spec/policies/school_policy_spec.rb index 0752b01152..6158d13b2a 100644 --- a/spec/policies/school_policy_spec.rb +++ b/spec/policies/school_policy_spec.rb @@ -9,7 +9,7 @@ let(:national_reporting_user) do create(:nurse, teams: [national_reporting_team]) end - let(:location) { create(:school) } + let(:location) { create(:gias_school) } permissions :index?, :create?, :edit?, :new?, :show?, :update? do it { should permit(point_of_care_user, location) } diff --git a/spec/requests/api/testing/locations_spec.rb b/spec/requests/api/testing/locations_spec.rb index 6f107c6fa6..6bf0cbc8e6 100644 --- a/spec/requests/api/testing/locations_spec.rb +++ b/spec/requests/api/testing/locations_spec.rb @@ -15,10 +15,10 @@ end let!(:home_educated_school) { team.home_educated_school } let!(:primary_school) do - create(:school, :primary, :closed, name: "Location D") + create(:gias_school, :primary, :closed, name: "Location D") end let!(:secondary_school) do - create(:school, :secondary, :closed, name: "Location E") + create(:gias_school, :secondary, :closed, name: "Location E") end let!(:unknown_school) { team.unknown_school } @@ -79,10 +79,10 @@ end context "with multiple year groups" do - before { create(:school, gias_year_groups: [8, 9]) } + before { create(:gias_school, gias_year_groups: [8, 9]) } let!(:secondary_school) do - create(:school, gias_year_groups: [8, 9, 10]) + create(:gias_school, gias_year_groups: [8, 9, 10]) end it "includes locations with all those year groups" do diff --git a/spec/requests/api/testing/onboard_spec.rb b/spec/requests/api/testing/onboard_spec.rb index 2980c2d7a4..b1750af178 100644 --- a/spec/requests/api/testing/onboard_spec.rb +++ b/spec/requests/api/testing/onboard_spec.rb @@ -22,10 +22,10 @@ before do Programme.hpv - create(:school, :secondary, :open, urn: "123456") - create(:school, :secondary, :open, urn: "234567") - create(:school, :secondary, :open, urn: "345678") - create(:school, :secondary, :open, urn: "456789") + create(:gias_school, :secondary, :open, urn: "123456") + create(:gias_school, :secondary, :open, urn: "234567") + create(:gias_school, :secondary, :open, urn: "345678") + create(:gias_school, :secondary, :open, urn: "456789") end it "responds with created" do diff --git a/spec/support/shared_contexts/govuk_notify_personalisation_context.rb b/spec/support/shared_contexts/govuk_notify_personalisation_context.rb new file mode 100644 index 0000000000..d981104dfd --- /dev/null +++ b/spec/support/shared_contexts/govuk_notify_personalisation_context.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +shared_context "govuk notify personalisation context" do + subject(:personalisation) do + GovukNotifyPersonalisation.new( + patient:, + session:, + consent:, + consent_form:, + programme_types:, + team_location:, + vaccination_record: + ) + end + + let(:hpv_programme) { Programme.hpv } + let(:flu_programme) { Programme.flu } + let(:programmes) { [hpv_programme] } + let(:programme_types) { programmes.map(&:type) } + let(:ods_code) { "ABC" } + let(:team) do + create( + :team, + name: "Team", + email: "team@example.com", + phone: "01234 567890", + phone_instructions: "option 1", + programmes:, + ods_code: + ) + end + let(:subteam) do + create( + :subteam, + name: "Team", + email: "team@example.com", + phone: "01234 567890", + phone_instructions: "option 1", + team: + ) + end + let(:patient) do + create( + :patient, + given_name: "John", + family_name: "Smith", + date_of_birth: Date.new(2013, 2, 1) + ) + end + let(:location) { create(:gias_school, name: "Hogwarts", subteam:) } + let(:session) do + create(:session, location:, team:, programmes:, date: Date.new(2026, 1, 1)) + end + let(:team_location) { nil } + let(:consent) { nil } + let(:consent_form) { nil } + let(:vaccination_record) { nil } +end diff --git a/spec/validators/name_validator_spec.rb b/spec/validators/name_validator_spec.rb index 0a7416de20..8c05cfe29f 100644 --- a/spec/validators/name_validator_spec.rb +++ b/spec/validators/name_validator_spec.rb @@ -62,7 +62,7 @@ let(:validator) do described_class.new(attributes: [:name], school_name: true) end - let(:record) { build(:school) } + let(:record) { build(:gias_school) } context "with valid school names" do context "with valid school names" do diff --git a/spec/views/notify_templates/email/consent_school_request_spec.rb b/spec/views/notify_templates/email/consent_school_request_spec.rb index 4e8646e0ea..63dd3819e3 100644 --- a/spec/views/notify_templates/email/consent_school_request_spec.rb +++ b/spec/views/notify_templates/email/consent_school_request_spec.rb @@ -4,7 +4,7 @@ around { |example| travel_to(Date.new(2024, 1, 1)) { example.run } } let(:team) { create(:team, :with_one_nurse, programmes: [programme]) } - let(:location) { create(:school, team:, programmes: [programme]) } + let(:location) { create(:gias_school, team:, programmes: [programme]) } let(:parent) { create(:parent) } let(:session_dates) { [Date.current + 2.days] } diff --git a/spec/views/notify_templates/email/triage_vaccination_will_happen_mmr_second_dose_spec.rb b/spec/views/notify_templates/email/triage_vaccination_will_happen_mmr_second_dose_spec.rb new file mode 100644 index 0000000000..70e8c43672 --- /dev/null +++ b/spec/views/notify_templates/email/triage_vaccination_will_happen_mmr_second_dose_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +describe "Notify email templates: triage_vaccination_will_happen_mmr_second_dose", + type: :view do + subject(:rendered) { render_template } + + let(:programme) { Programme.mmr } + let(:team) { create(:team, :with_one_nurse, programmes: [programme]) } + let(:session) do + create( + :session, + team:, + programmes: [programme], + date: Date.new(2025, 6, 10) + ) + end + let(:patient) do + create( + :patient, + :partially_vaccinated_triage_needed, + given_name: "Filip", + date_of_birth: Date.new(2015, 6, 1), + session: + ) + end + let(:consent) { patient.consents.first } + + def render_template + PatientStatusUpdater.call(patient:) + patient.programme_statuses.reload + personalisation = GovukNotifyPersonalisation.new(consent:, session:) + NotifyTemplate.find( + :triage_vaccination_will_happen_mmr_second_dose, + channel: :email + ).render(personalisation) + end + + context "with an MMR programme" do + it "includes the programme name in the subject" do + expect(rendered[:subject]).to include("MMR vaccination") + end + + it "describes the first dose given" do + expect(rendered[:body]).to include( + "We recently gave Filip their 1st dose of the MMR vaccination" + ) + end + + it "describes the next dose" do + expect(rendered[:body]).to include( + "plan to give Filip their 2nd dose then" + ) + end + end + + context "with an MMRV programme" do + let(:patient) do + create( + :patient, + :partially_vaccinated_triage_needed, + given_name: "Filip", + date_of_birth: Date.new(2020, 6, 1), + session: + ) + end + + it "includes the programme name in the subject" do + expect(rendered[:subject]).to include("MMRV vaccination") + end + + it "describes the first dose given" do + expect(rendered[:body]).to include( + "We recently gave Filip their 1st dose of the MMRV vaccination" + ) + end + + it "describes the next dose" do + expect(rendered[:body]).to include( + "plan to give Filip their 2nd dose then" + ) + end + end +end diff --git a/spec/views/notify_templates/email/vaccination_administered_spec.rb b/spec/views/notify_templates/email/vaccination_administered_spec.rb index bef3b367c5..51a6f1b21d 100644 --- a/spec/views/notify_templates/email/vaccination_administered_spec.rb +++ b/spec/views/notify_templates/email/vaccination_administered_spec.rb @@ -9,7 +9,7 @@ let(:programme) { nil } let(:team) { create(:team, :with_one_nurse, programmes: [programme]) } - let(:location) { create(:school, team:, programmes: [programme]) } + let(:location) { create(:gias_school, team:, programmes: [programme]) } let(:session) do create( :session, @@ -28,7 +28,7 @@ def render_template(vaccination_record:) describe "body" do context "flu" do - let(:template_name) { :vaccination_administered_flu } + let(:template_name) { :vaccination_administered } let(:programme) { Programme.flu } context "when the vaccination record is injection" do diff --git a/yarn.lock b/yarn.lock index bfab374b91..25e00fe3af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -393,135 +393,135 @@ dependencies: tslib "^2.4.0" -"@esbuild/aix-ppc64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz#4c585002f7ad694d38fe0e8cbf5cfd939ccff327" - integrity sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q== - -"@esbuild/android-arm64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz#7625d0952c3b402d3ede203a16c9f2b78f8a4827" - integrity sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw== - -"@esbuild/android-arm@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.4.tgz#9a0cf1d12997ec46dddfb32ce67e9bca842381ac" - integrity sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ== - -"@esbuild/android-x64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.4.tgz#06e1fdc6283fccd6bc6aadd6754afce6cf96f42e" - integrity sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw== - -"@esbuild/darwin-arm64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz#6c550ee6c0273bcb0fac244478ff727c26755d80" - integrity sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ== - -"@esbuild/darwin-x64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz#ed7a125e9f25ce0091b9aff783ee943f6ba6cb86" - integrity sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw== - -"@esbuild/freebsd-arm64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz#597dc8e7161dba71db4c1656131c1f1e9d7660c6" - integrity sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw== - -"@esbuild/freebsd-x64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz#ea171f9f4f00efaa8e9d3fe8baa1b75d757d1b36" - integrity sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ== - -"@esbuild/linux-arm64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz#e52d57f202369386e6dbcb3370a17a0491ab1464" - integrity sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA== - -"@esbuild/linux-arm@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz#5e0c0b634908adbce0a02cebeba8b3acac263fb6" - integrity sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg== - -"@esbuild/linux-ia32@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz#5f90f01f131652473ec06b038a14c49683e14ec7" - integrity sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA== - -"@esbuild/linux-loong64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz#63bacffdb99574c9318f9afbd0dd4fff76a837e3" - integrity sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA== - -"@esbuild/linux-mips64el@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz#c4b6952eca6a8efff67fee3671a3536c8e67b7eb" - integrity sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw== - -"@esbuild/linux-ppc64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz#6dea67d3d98c6986f1b7769e4f1848e5ae47ad58" - integrity sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA== - -"@esbuild/linux-riscv64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz#9ad2b4c3c0502c6bada9c81997bb56c597853489" - integrity sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw== - -"@esbuild/linux-s390x@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz#c43d3cfd073042ca6f5c52bb9bc313ed2066ce28" - integrity sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA== - -"@esbuild/linux-x64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz#45fa173e0591ac74d80d3cf76704713e14e2a4a6" - integrity sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA== - -"@esbuild/netbsd-arm64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz#366b0ef40cdb986fc751cbdad16e8c25fe1ba879" - integrity sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q== - -"@esbuild/netbsd-x64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz#e985d49a3668fd2044343071d52e1ae815112b3e" - integrity sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg== - -"@esbuild/openbsd-arm64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz#6fb4ab7b73f7e5572ce5ec9cf91c13ff6dd44842" - integrity sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow== - -"@esbuild/openbsd-x64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz#641f052040a0d79843d68898f5791638a026d983" - integrity sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ== - -"@esbuild/openharmony-arm64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz#fc1d33eac9d81ae0a433b3ed1dd6171a20d4e317" - integrity sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg== - -"@esbuild/sunos-x64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz#af2cd5ca842d6d057121f66a192d4f797de28f53" - integrity sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g== - -"@esbuild/win32-arm64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz#78ec7e59bb06404583d4c9511e621db31c760de3" - integrity sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg== - -"@esbuild/win32-ia32@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz#0e616aa488b7ee5d2592ab070ff9ec06a9fddf11" - integrity sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw== - -"@esbuild/win32-x64@0.27.4": - version "0.27.4" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz#1f7ba71a3d6155d44a6faa8dbe249c62ab3e408c" - integrity sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg== +"@esbuild/aix-ppc64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz#7a289c158e29cbf59ea0afc83cc80f06d1c89402" + integrity sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA== + +"@esbuild/android-arm64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz#b8828d9edfa3a92660644eb8de6e4f3c203d7b17" + integrity sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw== + +"@esbuild/android-arm@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.28.0.tgz#5ec1847605e05b5dbe5df90db9ff7e3e4c58dca7" + integrity sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ== + +"@esbuild/android-x64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.28.0.tgz#390642175b88ef82bad4cce03f8ab13fe9b1912e" + integrity sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA== + +"@esbuild/darwin-arm64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz#ae45325960d5950cd6951e4f97396f4e1ff7d8d3" + integrity sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q== + +"@esbuild/darwin-x64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz#c079247d589b6b99449659d94f06951b84bff2e4" + integrity sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ== + +"@esbuild/freebsd-arm64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz#45c456215a486593c94900297202dc11c880a37a" + integrity sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q== + +"@esbuild/freebsd-x64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz#0399494c1c85e4388e9b7040bd60d48f2a5b0d2c" + integrity sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw== + +"@esbuild/linux-arm64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz#d6d9f09ef0de54116bf459a4d53cac7e0952fe39" + integrity sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A== + +"@esbuild/linux-arm@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz#7b42ffa84c288ae94fdc431c1b28a89e3c3b9278" + integrity sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw== + +"@esbuild/linux-ia32@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz#deb15d112ed8dd605346b6b953d23a21ff81253f" + integrity sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ== + +"@esbuild/linux-loong64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz#81fb89d07eecc79b157dea61033757726fce0ca4" + integrity sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg== + +"@esbuild/linux-mips64el@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz#d0e42691b3ff7af9fb2217b70fc01f343bdb62bb" + integrity sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w== + +"@esbuild/linux-ppc64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz#389f3e5e98f17d477c467cc87136e1a076eead87" + integrity sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg== + +"@esbuild/linux-riscv64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz#763bd60d59b242be12da1e67d5729f3024c605fa" + integrity sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ== + +"@esbuild/linux-s390x@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz#aac6061634872e4677de693bce8030d73b1fd055" + integrity sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q== + +"@esbuild/linux-x64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz#4f2917747188fe77632bcec65b2d84b422419779" + integrity sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ== + +"@esbuild/netbsd-arm64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz#814df0ae57a0c386814491b8397eeba82094a947" + integrity sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw== + +"@esbuild/netbsd-x64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz#e01bdf7e60fa1a08e46d46d960b0d9bb8ac210af" + integrity sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw== + +"@esbuild/openbsd-arm64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz#4a15c36aacca68d2d5a4c90b710c06759f4c1ffa" + integrity sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g== + +"@esbuild/openbsd-x64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz#475e6101498a8ecce3008d7c388111d7a27c17bd" + integrity sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA== + +"@esbuild/openharmony-arm64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz#cfdc3957f0b7a69f1bde129aad17fcc2f6fa033e" + integrity sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w== + +"@esbuild/sunos-x64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz#a013c856fecacd1c3aec985c8afe1d1cb017497d" + integrity sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw== + +"@esbuild/win32-arm64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz#eae05e0f35271cad3898b43168d3e9a3bbaf47e5" + integrity sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA== + +"@esbuild/win32-ia32@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz#06161ebc5bf75c08d69feb3c6b22560515913998" + integrity sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA== + +"@esbuild/win32-x64@0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz#04d90d5752b4ce65d2b6ac25eba08ff7624fe07c" + integrity sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw== "@hotwired/turbo-rails@^8.0.23": version "8.0.23" @@ -1989,37 +1989,37 @@ esbuild-jest@^0.5.0: "@babel/plugin-transform-modules-commonjs" "^7.12.13" babel-jest "^26.6.3" -esbuild@^0.27.4: - version "0.27.4" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.4.tgz#b9591dd7e0ab803a11c9c3b602850403bef22f00" - integrity sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ== +esbuild@^0.28.0: + version "0.28.0" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.28.0.tgz#5dee347ffb3e3874212a35a69836b077b1ce6d96" + integrity sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw== optionalDependencies: - "@esbuild/aix-ppc64" "0.27.4" - "@esbuild/android-arm" "0.27.4" - "@esbuild/android-arm64" "0.27.4" - "@esbuild/android-x64" "0.27.4" - "@esbuild/darwin-arm64" "0.27.4" - "@esbuild/darwin-x64" "0.27.4" - "@esbuild/freebsd-arm64" "0.27.4" - "@esbuild/freebsd-x64" "0.27.4" - "@esbuild/linux-arm" "0.27.4" - "@esbuild/linux-arm64" "0.27.4" - "@esbuild/linux-ia32" "0.27.4" - "@esbuild/linux-loong64" "0.27.4" - "@esbuild/linux-mips64el" "0.27.4" - "@esbuild/linux-ppc64" "0.27.4" - "@esbuild/linux-riscv64" "0.27.4" - "@esbuild/linux-s390x" "0.27.4" - "@esbuild/linux-x64" "0.27.4" - "@esbuild/netbsd-arm64" "0.27.4" - "@esbuild/netbsd-x64" "0.27.4" - "@esbuild/openbsd-arm64" "0.27.4" - "@esbuild/openbsd-x64" "0.27.4" - "@esbuild/openharmony-arm64" "0.27.4" - "@esbuild/sunos-x64" "0.27.4" - "@esbuild/win32-arm64" "0.27.4" - "@esbuild/win32-ia32" "0.27.4" - "@esbuild/win32-x64" "0.27.4" + "@esbuild/aix-ppc64" "0.28.0" + "@esbuild/android-arm" "0.28.0" + "@esbuild/android-arm64" "0.28.0" + "@esbuild/android-x64" "0.28.0" + "@esbuild/darwin-arm64" "0.28.0" + "@esbuild/darwin-x64" "0.28.0" + "@esbuild/freebsd-arm64" "0.28.0" + "@esbuild/freebsd-x64" "0.28.0" + "@esbuild/linux-arm" "0.28.0" + "@esbuild/linux-arm64" "0.28.0" + "@esbuild/linux-ia32" "0.28.0" + "@esbuild/linux-loong64" "0.28.0" + "@esbuild/linux-mips64el" "0.28.0" + "@esbuild/linux-ppc64" "0.28.0" + "@esbuild/linux-riscv64" "0.28.0" + "@esbuild/linux-s390x" "0.28.0" + "@esbuild/linux-x64" "0.28.0" + "@esbuild/netbsd-arm64" "0.28.0" + "@esbuild/netbsd-x64" "0.28.0" + "@esbuild/openbsd-arm64" "0.28.0" + "@esbuild/openbsd-x64" "0.28.0" + "@esbuild/openharmony-arm64" "0.28.0" + "@esbuild/sunos-x64" "0.28.0" + "@esbuild/win32-arm64" "0.28.0" + "@esbuild/win32-ia32" "0.28.0" + "@esbuild/win32-x64" "0.28.0" escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" @@ -3355,9 +3355,9 @@ lodash.truncate@^4.4.2: integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== lodash@^4.7.0: - version "4.17.23" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" - integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== lru-cache@^10.2.0, lru-cache@^10.4.3: version "10.4.3" @@ -4005,10 +4005,10 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" -sass@^1.98.0: - version "1.98.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.98.0.tgz#924ce85a3745ccaccd976262fdc1bc0c13aa8e57" - integrity sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A== +sass@^1.99.0: + version "1.99.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.99.0.tgz#ff9d1594da4886249dfaafabbeea2dea2dc74b26" + integrity sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q== dependencies: chokidar "^4.0.0" immutable "^5.1.5"