<%= link_to "Record as already vaccinated",
session_patient_programme_record_already_vaccinated_path(@session, @patient, @programme) %>
diff --git a/app/views/patient_sessions/programmes/show.html.erb b/app/views/patient_sessions/programmes/show.html.erb
index 3a0b79ba8b..b1f5de97d0 100644
--- a/app/views/patient_sessions/programmes/show.html.erb
+++ b/app/views/patient_sessions/programmes/show.html.erb
@@ -10,7 +10,7 @@
<%= render AppPatientSessionTriageComponent.new(@patient_session, programme: @programme, triage_form: @triage_form) %>
- <%= render AppPatientSessionRecordComponent.new(@patient_session, programme: @programme, vaccinate_form: @vaccinate_form) %>
+ <%= render AppPatientSessionRecordComponent.new(@patient_session, programme: @programme, current_user:, vaccinate_form: @vaccinate_form) %>
<%= render AppPatientSessionOutcomeComponent.new(@patient_session, programme: @programme) %>
diff --git a/app/views/session_dates/show.html.erb b/app/views/session_dates/show.html.erb
index 2e601b91f7..dac27db415 100644
--- a/app/views/session_dates/show.html.erb
+++ b/app/views/session_dates/show.html.erb
@@ -1,5 +1,5 @@
<% content_for :before_main do %>
- <%= govuk_back_link(href: edit_session_path(@session)) %>
+ <%= govuk_back_link(href: session_edit_path(@session)) %>
<% end %>
<%= h1 "When will sessions be held?" %>
@@ -42,6 +42,6 @@
<%= f.govuk_submit "Continue" %>
- <%= govuk_link_to "Back", edit_session_path(@session) %>
+ <%= govuk_link_to "Back", session_edit_path(@session) %>
<% end %>
diff --git a/app/views/sessions/_header.html.erb b/app/views/sessions/_header.html.erb
index 5a20fa2854..e181adbf5c 100644
--- a/app/views/sessions/_header.html.erb
+++ b/app/views/sessions/_header.html.erb
@@ -24,6 +24,14 @@
selected: request.path == session_consent_path(@session),
)
+ if @session.psd_enabled?
+ nav.with_item(
+ href: session_patient_specific_directions_path(@session),
+ text: t("sessions.tabs.patient_specific_directions"),
+ selected: request.path == session_patient_specific_directions_path(@session),
+ )
+ end
+
nav.with_item(
href: session_triage_path(@session),
text: t("sessions.tabs.triage"),
diff --git a/app/views/sessions/edit/delegation.html.erb b/app/views/sessions/edit/delegation.html.erb
new file mode 100644
index 0000000000..996bf8d1ba
--- /dev/null
+++ b/app/views/sessions/edit/delegation.html.erb
@@ -0,0 +1,38 @@
+<% content_for :before_main do %>
+ <%= govuk_back_link(href: session_edit_path(@session)) %>
+<% end %>
+
+<%= form_with model: @session, url: delegation_session_edit_path(@session), method: :put do |f| %>
+ <% content_for(:before_content) { f.govuk_error_summary } %>
+
+ <%= @session.location.name %>
+ <%= h1 "Delegation" %>
+
+ <%= f.govuk_radio_buttons_fieldset :psd_enabled,
+ legend: { text: "Can healthcare assistants administer the flu nasal spray vaccine using a patient specific direction (PSD)?" },
+ link_errors: true do %>
+ <%= f.govuk_radio_button :psd_enabled,
+ "false",
+ label: { text: "No" },
+ hint: { text: "Healthcare assistants can only administer a nasal spray when supplied by a nurse" } %>
+ <%= f.govuk_radio_button :psd_enabled,
+ "true",
+ label: { text: "Yes" },
+ hint: { text: "Healthcare assistants can administer the nasal spray vaccine to children who are covered by a PSD" } %>
+ <% end %>
+
+ <%= f.govuk_radio_buttons_fieldset :national_protocol_enabled,
+ legend: { text: "Can healthcare assistants administer the injected flu vaccine using the national protocol?" },
+ link_errors: true do %>
+ <%= f.govuk_radio_button :national_protocol_enabled,
+ "false",
+ label: { text: "No" },
+ hint: { text: "Only nurses can administer the injected flu vaccine" } %>
+ <%= f.govuk_radio_button :national_protocol_enabled,
+ "true",
+ label: { text: "Yes" },
+ hint: { text: "Healthcare assistants can administer an injected flu vaccine when supplied by a nurse" } %>
+ <% end %>
+
+ <%= f.govuk_submit "Continue" %>
+<% end %>
diff --git a/app/views/sessions/edit/programmes.html.erb b/app/views/sessions/edit/programmes.html.erb
index 2b9ce43a7c..91603c3f02 100644
--- a/app/views/sessions/edit/programmes.html.erb
+++ b/app/views/sessions/edit/programmes.html.erb
@@ -1,11 +1,11 @@
<% content_for :before_main do %>
- <%= govuk_back_link(href: edit_session_path(@session)) %>
+ <%= govuk_back_link(href: session_edit_path(@session)) %>
<% end %>
<% legend = "Which programmes is this session part of?" %>
<% content_for :page_title, legend %>
-<%= form_with model: @form, url: edit_programmes_session_path(@session), method: :put do |f| %>
+<%= form_with model: @form, url: programmes_session_edit_path(@session), method: :put do |f| %>
<% content_for(:before_content) { f.govuk_error_summary } %>
<%= f.govuk_collection_check_boxes :programme_ids, policy_scope(Programme), :id, :name,
diff --git a/app/views/sessions/edit/register_attendance.html.erb b/app/views/sessions/edit/register_attendance.html.erb
new file mode 100644
index 0000000000..6dafde2a8c
--- /dev/null
+++ b/app/views/sessions/edit/register_attendance.html.erb
@@ -0,0 +1,20 @@
+<% content_for :before_main do %>
+ <%= govuk_back_link(href: session_edit_path(@session)) %>
+<% end %>
+
+<% legend = "Do you want to register children’s attendance before recording vaccinations?" %>
+<% content_for :page_title, legend %>
+
+<%= form_with model: @session, url: register_attendance_session_edit_path(@session), method: :put do |f| %>
+ <% content_for(:before_content) { f.govuk_error_summary } %>
+
+ <%= f.govuk_radio_buttons_fieldset :requires_registration,
+ legend: { text: legend, size: "l", tag: "h1" },
+ caption: { text: @session.location.name, size: "l" },
+ link_errors: true do %>
+ <%= f.govuk_radio_button :requires_registration, "true", label: { text: "Yes" } %>
+ <%= f.govuk_radio_button :requires_registration, "false", label: { text: "No" } %>
+ <% end %>
+
+ <%= f.govuk_submit "Continue" %>
+<% end %>
diff --git a/app/views/sessions/edit/send_consent_requests_at.html.erb b/app/views/sessions/edit/send_consent_requests_at.html.erb
index e5eb88dd10..dc591990ba 100644
--- a/app/views/sessions/edit/send_consent_requests_at.html.erb
+++ b/app/views/sessions/edit/send_consent_requests_at.html.erb
@@ -1,11 +1,11 @@
<% content_for :before_main do %>
- <%= govuk_back_link(href: edit_session_path(@session)) %>
+ <%= govuk_back_link(href: session_edit_path(@session)) %>
<% end %>
<% legend = "When should parents get a request to give consent?" %>
<% content_for :page_title, legend %>
-<%= form_with model: @session, url: edit_send_consent_requests_at_session_path(@session), method: :put do |f| %>
+<%= form_with model: @session, url: send_consent_requests_at_session_edit_path(@session), method: :put do |f| %>
<% content_for(:before_content) { f.govuk_error_summary } %>
<%= f.govuk_date_field :send_consent_requests_at,
diff --git a/app/views/sessions/edit/send_invitations_at.html.erb b/app/views/sessions/edit/send_invitations_at.html.erb
index 7933b53f7e..bc0148a645 100644
--- a/app/views/sessions/edit/send_invitations_at.html.erb
+++ b/app/views/sessions/edit/send_invitations_at.html.erb
@@ -1,11 +1,11 @@
<% content_for :before_main do %>
- <%= govuk_back_link(href: edit_session_path(@session)) %>
+ <%= govuk_back_link(href: session_edit_path(@session)) %>
<% end %>
<% legend = "When should parents get an invitation?" %>
<% content_for :page_title, legend %>
-<%= form_with model: @session, url: edit_send_invitations_at_session_path(@session), method: :put do |f| %>
+<%= form_with model: @session, url: send_invitations_at_session_edit_path(@session), method: :put do |f| %>
<% content_for(:before_content) { f.govuk_error_summary } %>
<%= f.govuk_date_field :send_invitations_at,
diff --git a/app/views/sessions/edit.html.erb b/app/views/sessions/edit/show.html.erb
similarity index 61%
rename from app/views/sessions/edit.html.erb
rename to app/views/sessions/edit/show.html.erb
index 65781c7425..b0d33bf9c6 100644
--- a/app/views/sessions/edit.html.erb
+++ b/app/views/sessions/edit/show.html.erb
@@ -14,7 +14,7 @@
summary_list.with_row do |row|
row.with_key { "Programmes" }
row.with_value { render AppProgrammeTagsComponent.new(@session.programmes) }
- row.with_action(text: "Change", href: edit_programmes_session_path(@session), visually_hidden_text: "programmes")
+ row.with_action(text: "Change", href: programmes_session_edit_path(@session), visually_hidden_text: "programmes")
end
summary_list.with_row do |row|
@@ -37,7 +37,7 @@
row.with_key { "Consent requests" }
row.with_value { "Send on #{send_consent_requests_at.to_fs(:long_day_of_week)}" }
if @session.can_change_notification_dates?
- row.with_action(text: "Change", href: edit_send_consent_requests_at_session_path(@session), visually_hidden_text: "consent requests")
+ row.with_action(text: "Change", href: send_consent_requests_at_session_edit_path(@session), visually_hidden_text: "consent requests")
end
end
end
@@ -52,7 +52,7 @@
], tag.br)
end
if @session.can_change_notification_dates?
- row.with_action(text: "Change", href: edit_weeks_before_consent_reminders_session_path(@session), visually_hidden_text: "consent reminders")
+ row.with_action(text: "Change", href: weeks_before_consent_reminders_session_edit_path(@session), visually_hidden_text: "consent reminders")
end
end
end
@@ -62,10 +62,30 @@
row.with_key { "Invitations" }
row.with_value { "Send on #{send_invitations_at.to_fs(:long_day_of_week)}" }
if @session.can_change_notification_dates?
- row.with_action(text: "Change", href: edit_send_invitations_at_session_path(@session), visually_hidden_text: "invitations")
+ row.with_action(text: "Change", href: send_invitations_at_session_edit_path(@session), visually_hidden_text: "invitations")
end
end
end
+
+ summary_list.with_row do |row|
+ row.with_key { "Register attendance" }
+ row.with_value { @session.requires_registration? ? "Yes" : "No" }
+ row.with_action(text: "Change", href: register_attendance_session_edit_path(@session), visually_hidden_text: "register attendance")
+ end
+
+ if Flipper.enabled?(:delegation) && @session.supports_delegation?
+ summary_list.with_row do |row|
+ row.with_key { "Use patient specific direction (PSD)" }
+ row.with_value { @session.psd_enabled? ? "Yes" : "No" }
+ row.with_action(text: "Change", href: delegation_session_edit_path(@session), visually_hidden_text: "use patient specific direction (PSD)")
+ end
+
+ summary_list.with_row do |row|
+ row.with_key { "Use national protocol" }
+ row.with_value { @session.national_protocol_enabled? ? "Yes" : "No" }
+ row.with_action(text: "Change", href: delegation_session_edit_path(@session), visually_hidden_text: "use national protocol")
+ end
+ end
end %>
<% end %>
diff --git a/app/views/sessions/edit/weeks_before_consent_reminders.html.erb b/app/views/sessions/edit/weeks_before_consent_reminders.html.erb
index f31f8d2caa..95d6ac52a8 100644
--- a/app/views/sessions/edit/weeks_before_consent_reminders.html.erb
+++ b/app/views/sessions/edit/weeks_before_consent_reminders.html.erb
@@ -1,11 +1,11 @@
<% content_for :before_main do %>
- <%= govuk_back_link(href: edit_session_path(@session)) %>
+ <%= govuk_back_link(href: session_edit_path(@session)) %>
<% end %>
<% legend = "When should parents get a reminder to give consent?" %>
<% content_for :page_title, legend %>
-<%= form_with model: @session, url: edit_weeks_before_consent_reminders_session_path(@session), method: :put do |f| %>
+<%= form_with model: @session, url: weeks_before_consent_reminders_session_edit_path(@session), method: :put do |f| %>
<% content_for(:before_content) { f.govuk_error_summary } %>
<%= f.govuk_text_field :weeks_before_consent_reminders,
diff --git a/app/views/sessions/patient_specific_directions/new.html.erb b/app/views/sessions/patient_specific_directions/new.html.erb
new file mode 100644
index 0000000000..bdc0a4699a
--- /dev/null
+++ b/app/views/sessions/patient_specific_directions/new.html.erb
@@ -0,0 +1,19 @@
+<% content_for :before_main do %>
+ <%= render AppBreadcrumbComponent.new(items: [
+ { text: t("dashboard.index.title"), href: dashboard_path },
+ { text: t("sessions.index.title"), href: sessions_path },
+ { text: @session.location.name, href: session_path(@session) },
+ ]) %>
+<% end %>
+
+<%= h1 "Are you sure you want to add #{@eligible_for_bulk_psd_count} new PSDs?" %>
+
+This cannot be undone.
+
+
+ <%= govuk_button_to "Yes, add PSDs",
+ session_patient_specific_directions_path(@session) %>
+
+ <%= govuk_link_to "No, return to session",
+ new_session_patient_specific_directions_path(@session) %>
+
diff --git a/app/views/sessions/patient_specific_directions/show.html.erb b/app/views/sessions/patient_specific_directions/show.html.erb
new file mode 100644
index 0000000000..788c9df2bb
--- /dev/null
+++ b/app/views/sessions/patient_specific_directions/show.html.erb
@@ -0,0 +1,49 @@
+<% content_for :before_main do %>
+ <%= render AppBreadcrumbComponent.new(items: [
+ { text: t("dashboard.index.title"), href: dashboard_path },
+ { text: t("sessions.index.title"), href: sessions_path },
+ { text: @session.location.name, href: session_path(@session) },
+ ]) %>
+<% end %>
+
+<%= render "sessions/header" %>
+
+
+
+ <%= render AppPatientSearchFormComponent.new(
+ @form,
+ url: session_patient_specific_directions_path(@session),
+ programmes: @session.programmes,
+ patient_specific_direction_statuses: %w[added not_added],
+ year_groups: @session.year_groups,
+ ) %>
+
+
+
+ <%= govuk_inset_text do %>
+
Information:
+
+ There are <%= @eligible_for_bulk_psd_count %> children with consent for the nasal flu vaccine
+ who do not require triage and do not yet have a PSD in place.
+
+ <% if current_user.is_nurse? %>
+
+ <%= link_to new_session_patient_specific_directions_path(@session), class: "nhsuk-action-link__link" do %>
+
+
+
+
+
Add new PSDs
+ <% end %>
+
+ <% end %>
+ <% end %>
+
+ <%= render AppSessionNeedsReviewWarningComponent.new(session: @session) %>
+ <%= render AppSearchResultsComponent.new(@pagy, label: "children", heading: "Review PSDs") do %>
+ <% @patient_sessions.each do |patient_session| %>
+ <%= render AppPatientSessionSearchResultCardComponent.new(patient_session, context: :patient_specific_direction) %>
+ <% end %>
+ <% end %>
+
+
diff --git a/app/views/sessions/record/show.html.erb b/app/views/sessions/record/show.html.erb
index e72878d8be..22696722e1 100644
--- a/app/views/sessions/record/show.html.erb
+++ b/app/views/sessions/record/show.html.erb
@@ -45,7 +45,7 @@
url: session_record_path(@session),
programmes: @session.programmes,
year_groups: @session.year_groups,
- vaccine_methods: @session.vaccine_methods.then { it.length > 1 ? it : [] },
+ vaccine_methods: @vaccine_methods.length > 1 ? @vaccine_methods : [],
) %>
diff --git a/app/views/sessions/show.html.erb b/app/views/sessions/show.html.erb
index 4f885c386f..a2e1792aaf 100644
--- a/app/views/sessions/show.html.erb
+++ b/app/views/sessions/show.html.erb
@@ -31,14 +31,14 @@
<% if @session.unscheduled? %>
<% if policy(@session).edit? %>
- <%= govuk_button_link_to "Schedule sessions", edit_session_path(@session), secondary: true %>
+ <%= govuk_button_link_to "Schedule sessions", session_edit_path(@session), secondary: true %>
<% end %>
<% else %>
<%= render AppSessionActionsComponent.new(@session) %>
<% if policy(@session).edit? %>
- <%= govuk_button_link_to "Edit session", edit_session_path(@session), secondary: true %>
+ <%= govuk_button_link_to "Edit session", session_edit_path(@session), secondary: true %>
<% end %>
<%= govuk_link_to "Record offline", session_path(@session, format: :xlsx) %>
diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb
index fe502d3e48..f0899e9a52 100644
--- a/config/initializers/inflections.rb
+++ b/config/initializers/inflections.rb
@@ -23,6 +23,7 @@
inflect.acronym "FHIR"
inflect.acronym "GIAS"
inflect.acronym "GP"
+ inflect.acronym "HCA"
inflect.acronym "JWKS"
inflect.acronym "NHS"
inflect.acronym "OAuth2"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 2d0a5dbc77..c6f41732c6 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -146,6 +146,9 @@ en:
status_and_vaccine_method:
blank: Choose a status
inclusion: Choose a status
+ add_psd:
+ blank: Select yes or no
+ inclusion: Select yes or no
vaccinate_form:
attributes:
delivery_site:
@@ -162,6 +165,8 @@ en:
blank: Confirm you’ve checked the pre-screening statements are true
pre_screening_notes:
too_long: Enter notes that are less than %{count} characters long
+ supplied_by_user_id:
+ inclusion: Choose which nurse identified and pre-screened the child and supplied the vaccine
vaccine_method:
inclusion: Choose if they are ready to vaccinate
vaccination_report:
@@ -685,6 +690,7 @@ en:
record: Record vaccinations
register: Register
triage: Triage
+ patient_specific_directions: PSDs
table:
no_filtered_results: We couldn’t find any children that matched your filters.
no_results: No results
@@ -739,6 +745,7 @@ en:
route: route
school: school
session: session
+ supplier: supplier
timeline: timeline
triage: triage
type: type
diff --git a/config/locales/status.en.yml b/config/locales/status.en.yml
index c9c7f3f059..24a83e772c 100644
--- a/config/locales/status.en.yml
+++ b/config/locales/status.en.yml
@@ -5,6 +5,7 @@ en:
register: Registration status
triage: Triage status
vaccination: Outcome
+ patient_specific_direction: PSD status
consent:
label:
conflicts: Conflicting consent
@@ -74,3 +75,10 @@ en:
could_not_vaccinate: red
none_yet: white
vaccinated: green
+ patient_specific_direction:
+ label:
+ added: PSD added
+ not_added: PSD not added
+ colour:
+ added: green
+ not_added: grey
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index 90b303a50b..547edae406 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -211,10 +211,14 @@
get "download", on: :member
end
- resources :sessions, only: %i[edit index show], param: :slug do
+ resources :sessions, only: %i[index show], param: :slug do
resource :patients, only: :show, controller: "sessions/patients"
resource :consent, only: :show, controller: "sessions/consent"
resource :triage, only: :show, controller: "sessions/triage"
+ resource :patient_specific_directions,
+ path: "patient-specific-directions",
+ only: %i[show new create],
+ controller: "sessions/patient_specific_directions"
resource :register, only: :show, controller: "sessions/register" do
post ":patient_id/:status", as: :create, action: :create
end
@@ -225,6 +229,27 @@
post "batch/:programme_type/:vaccine_method", action: :update_batch
end
+ resource :edit, only: :show, controller: "sessions/edit" do
+ get "programmes"
+ put "programmes", action: :update_programmes
+
+ get "send-consent-requests-at"
+ put "send-consent-requests-at", action: :update_send_consent_requests_at
+
+ get "send-invitations-at"
+ put "send-invitations-at", action: :update_send_invitations_at
+
+ get "weeks-before-consent-reminders"
+ put "weeks-before-consent-reminders",
+ action: :update_weeks_before_consent_reminders
+
+ get "register-attendance"
+ put "register-attendance", action: :update_register_attendance
+
+ get "delegation"
+ put "delegation", action: :update_delegation
+ end
+
resource :invite_to_clinic,
path: "invite-to-clinic",
only: %i[edit update],
@@ -233,34 +258,6 @@
member do
get "import"
- get "edit/programmes",
- controller: "sessions/edit",
- action: "edit_programmes"
- put "edit/programmes",
- controller: "sessions/edit",
- action: "update_programmes"
-
- get "edit/send-consent-requests-at",
- controller: "sessions/edit",
- action: "edit_send_consent_requests_at"
- put "edit/send-consent-requests-at",
- controller: "sessions/edit",
- action: "update_send_consent_requests_at"
-
- get "edit/send-invitations-at",
- controller: "sessions/edit",
- action: "edit_send_invitations_at"
- put "edit/send-invitations-at",
- controller: "sessions/edit",
- action: "update_send_invitations_at"
-
- get "edit/weeks-before-consent-reminders",
- controller: "sessions/edit",
- action: "edit_weeks_before_consent_reminders"
- put "edit/weeks-before-consent-reminders",
- controller: "sessions/edit",
- action: "update_weeks_before_consent_reminders"
-
constraints -> { Flipper.enabled?(:dev_tools) } do
put "make-in-progress", to: "sessions#make_in_progress"
end
diff --git a/db/migrate/20250825183232_add_supplied_by_user_to_vaccination_records.rb b/db/migrate/20250825183232_add_supplied_by_user_to_vaccination_records.rb
new file mode 100644
index 0000000000..cbcc185e83
--- /dev/null
+++ b/db/migrate/20250825183232_add_supplied_by_user_to_vaccination_records.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddSuppliedByUserToVaccinationRecords < ActiveRecord::Migration[8.0]
+ def change
+ add_reference :vaccination_records,
+ :supplied_by_user,
+ foreign_key: {
+ to_table: :users
+ }
+ end
+end
diff --git a/db/migrate/20250827091512_remove_full_dose_from_patient_specific_directions.rb b/db/migrate/20250827091512_remove_full_dose_from_patient_specific_directions.rb
new file mode 100644
index 0000000000..f3b88e2e54
--- /dev/null
+++ b/db/migrate/20250827091512_remove_full_dose_from_patient_specific_directions.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class RemoveFullDoseFromPatientSpecificDirections < ActiveRecord::Migration[8.0]
+ def change
+ remove_column :patient_specific_directions, :full_dose, :boolean
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 6bdbf8bda5..218947573c 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.0].define(version: 2025_08_26_135132) do
+ActiveRecord::Schema[8.0].define(version: 2025_08_27_091512) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
enable_extension "pg_trgm"
@@ -641,7 +641,6 @@
t.bigint "vaccine_id", null: false
t.integer "vaccine_method", null: false
t.integer "delivery_site", null: false
- t.boolean "full_dose", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "academic_year", null: false
@@ -951,6 +950,7 @@
t.datetime "nhs_immunisations_api_sync_pending_at"
t.boolean "notify_parents"
t.bigint "location_id"
+ t.bigint "supplied_by_user_id"
t.index ["batch_id"], name: "index_vaccination_records_on_batch_id"
t.index ["discarded_at"], name: "index_vaccination_records_on_discarded_at"
t.index ["location_id"], name: "index_vaccination_records_on_location_id"
@@ -959,6 +959,7 @@
t.index ["performed_by_user_id"], name: "index_vaccination_records_on_performed_by_user_id"
t.index ["programme_id"], name: "index_vaccination_records_on_programme_id"
t.index ["session_id"], name: "index_vaccination_records_on_session_id"
+ t.index ["supplied_by_user_id"], name: "index_vaccination_records_on_supplied_by_user_id"
t.index ["uuid"], name: "index_vaccination_records_on_uuid", unique: true
t.index ["vaccine_id"], name: "index_vaccination_records_on_vaccine_id"
end
@@ -1106,6 +1107,7 @@
add_foreign_key "vaccination_records", "programmes"
add_foreign_key "vaccination_records", "sessions"
add_foreign_key "vaccination_records", "users", column: "performed_by_user_id"
+ add_foreign_key "vaccination_records", "users", column: "supplied_by_user_id"
add_foreign_key "vaccination_records", "vaccines"
add_foreign_key "vaccines", "programmes"
end
diff --git a/spec/components/app_patient_session_record_component_spec.rb b/spec/components/app_patient_session_record_component_spec.rb
index 1eb5b27e25..b63caa08fd 100644
--- a/spec/components/app_patient_session_record_component_spec.rb
+++ b/spec/components/app_patient_session_record_component_spec.rb
@@ -7,10 +7,12 @@
described_class.new(
patient_session,
programme: programmes.first,
+ current_user:,
vaccinate_form: VaccinateForm.new
)
end
+ let(:current_user) { create(:user) }
let(:programmes) { [create(:programme, :hpv)] }
let(:session) { create(:session, :today, programmes:) }
let(:patient) do
diff --git a/spec/components/app_vaccinate_form_component_spec.rb b/spec/components/app_vaccinate_form_component_spec.rb
index 40ef233d3a..c7eede75cb 100644
--- a/spec/components/app_vaccinate_form_component_spec.rb
+++ b/spec/components/app_vaccinate_form_component_spec.rb
@@ -5,8 +5,13 @@
let(:programme) { create(:programme) }
let(:programmes) { [programme] }
- let(:session) { create(:session, :today, programmes:) }
+
+ let(:team) { create(:team, programmes:) }
+ let(:current_user) { create(:user, team:) }
+
+ let(:session) { create(:session, :today, team:, programmes:) }
let(:session_date) { session.session_dates.first }
+
let(:patient) do
create(
:patient,
@@ -20,7 +25,7 @@
end
let(:vaccinate_form) do
- VaccinateForm.new(patient:, session_date:, programme:)
+ VaccinateForm.new(current_user:, patient:, session_date:, programme:)
end
let(:component) { described_class.new(vaccinate_form) }
diff --git a/spec/factories/patient_specific_directions.rb b/spec/factories/patient_specific_directions.rb
index e0b73a6c82..a95d663555 100644
--- a/spec/factories/patient_specific_directions.rb
+++ b/spec/factories/patient_specific_directions.rb
@@ -7,7 +7,6 @@
# id :bigint not null, primary key
# academic_year :integer not null
# delivery_site :integer not null
-# full_dose :boolean not null
# vaccine_method :integer not null
# created_at :datetime not null
# updated_at :datetime not null
@@ -38,9 +37,8 @@
programme
vaccine { programme.vaccines.sample || association(:vaccine) }
- delivery_site { "left_arm_upper_position" }
- vaccine_method { "injection" }
- full_dose { true }
+ delivery_site { "nose" }
+ vaccine_method { "nasal" }
academic_year { Time.current.to_date.academic_year }
trait :half_dose do
diff --git a/spec/factories/teams.rb b/spec/factories/teams.rb
index 92c8c12be5..ae06bcc347 100644
--- a/spec/factories/teams.rb
+++ b/spec/factories/teams.rb
@@ -56,6 +56,10 @@
users { [create(:user, :admin, team: instance)] }
end
+ trait :with_one_healthcare_assistant do
+ users { [create(:user, :healthcare_assistant, team: instance)] }
+ end
+
trait :with_generic_clinic do
after(:create) { |team| GenericClinicFactory.call(team:) }
end
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 6874af3a8a..b5d2703cc4 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -34,10 +34,10 @@
factory :user,
aliases: %i[
nurse
- assessor
created_by
- recorded_by
performed_by
+ recorded_by
+ supplied_by
uploaded_by
] do
transient do
@@ -61,6 +61,7 @@
sequence(:email) { |n| "nurse-#{n}@example.com" }
fallback_role { :nurse }
+ show_in_suppliers { true }
given_name { "Test" }
family_name { "User" }
@@ -83,21 +84,31 @@
sequence(:email) { |n| "admin-#{n}@example.com" }
role_code { CIS2Info::ADMIN_ROLE }
fallback_role { :admin }
+ show_in_suppliers { false }
end
trait :superuser do
sequence(:email) { |n| "superuser-#{n}@example.com" }
role_workgroups { [CIS2Info::SUPERUSER_WORKGROUP] }
fallback_role { :superuser }
+ show_in_suppliers { false }
end
trait :healthcare_assistant do
sequence(:email) { |n| "healthcare-assistant-#{n}@example.com" }
- role_code { CIS2Info::ADMIN_ROLE }
+ role_code { nil }
activity_codes do
[CIS2Info::PERSONAL_MEDICATION_ADMINISTRATION_ACTIVITY_CODE]
end
fallback_role { :healthcare_assistant }
+ show_in_suppliers { false }
+ end
+
+ trait :prescriber do
+ sequence(:email) { |n| "prescriber-#{n}@example.com" }
+ role_code { nil }
+ activity_codes { [CIS2Info::INDEPENDENT_PRESCRIBING_ACTIVITY_CODE] }
+ fallback_role { :prescriber }
end
trait :signed_in do
@@ -108,5 +119,6 @@
factory :admin, parent: :user, traits: %i[admin]
factory :healthcare_assistant, parent: :user, traits: %i[healthcare_assistant]
+ factory :prescriber, parent: :user, traits: %i[prescriber]
factory :superuser, parent: :user, traits: %i[superuser]
end
diff --git a/spec/factories/vaccination_records.rb b/spec/factories/vaccination_records.rb
index 69e37ad9dd..b5ef753a34 100644
--- a/spec/factories/vaccination_records.rb
+++ b/spec/factories/vaccination_records.rb
@@ -34,6 +34,7 @@
# performed_by_user_id :bigint
# programme_id :bigint not null
# session_id :bigint
+# supplied_by_user_id :bigint
# vaccine_id :bigint
#
# Indexes
@@ -46,6 +47,7 @@
# index_vaccination_records_on_performed_by_user_id (performed_by_user_id)
# index_vaccination_records_on_programme_id (programme_id)
# index_vaccination_records_on_session_id (session_id)
+# index_vaccination_records_on_supplied_by_user_id (supplied_by_user_id)
# index_vaccination_records_on_uuid (uuid) UNIQUE
# index_vaccination_records_on_vaccine_id (vaccine_id)
#
@@ -56,6 +58,7 @@
# fk_rails_... (performed_by_user_id => users.id)
# fk_rails_... (programme_id => programmes.id)
# fk_rails_... (session_id => sessions.id)
+# fk_rails_... (supplied_by_user_id => users.id)
# fk_rails_... (vaccine_id => vaccines.id)
#
FactoryBot.define do
diff --git a/spec/features/cli_sessions_configure_spec.rb b/spec/features/cli_sessions_configure_spec.rb
new file mode 100644
index 0000000000..c39c6d5819
--- /dev/null
+++ b/spec/features/cli_sessions_configure_spec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+require_relative "../../app/lib/mavis_cli"
+
+describe "mavis sessions delegation" do
+ context "with valid arguments" do
+ it "runs successfully" do
+ given_a_team_exists
+ and_sessions_exist
+
+ when_i_run_the_command
+ then_the_team_sessions_are_updated
+ and_the_non_team_sessions_are_left
+ end
+ end
+
+ private
+
+ def command
+ Dry::CLI.new(MavisCLI).call(
+ arguments: %w[
+ sessions
+ configure
+ team
+ flu
+ --psd-enabled
+ --national-protocol-enabled
+ --no-requires-registration
+ ]
+ )
+ end
+
+ def given_a_team_exists
+ @programmes = [create(:programme, :hpv), create(:programme, :flu)]
+ @team = create(:team, workgroup: "team", programmes: @programmes)
+ end
+
+ def and_sessions_exist
+ academic_year = AcademicYear.pending
+
+ @team_hpv_session =
+ create(
+ :session,
+ :unscheduled,
+ team: @team,
+ programmes: [@programmes.first],
+ academic_year:
+ )
+ @non_team_hpv_session =
+ create(
+ :session,
+ :unscheduled,
+ programmes: [@programmes.first],
+ academic_year:
+ )
+ @team_flu_session =
+ create(
+ :session,
+ :unscheduled,
+ team: @team,
+ programmes: [@programmes.second],
+ academic_year:
+ )
+ @non_team_flu_session =
+ create(
+ :session,
+ :unscheduled,
+ programmes: [@programmes.second],
+ academic_year:
+ )
+ end
+
+ def when_i_run_the_command
+ @output = capture_output { command }
+ end
+
+ def then_the_team_sessions_are_updated
+ expect(@team_flu_session.reload).to have_attributes(
+ psd_enabled: true,
+ national_protocol_enabled: true,
+ requires_registration: false
+ )
+
+ expect(@team_hpv_session.reload).to have_attributes(
+ psd_enabled: false,
+ national_protocol_enabled: false,
+ requires_registration: true
+ )
+ end
+
+ def and_the_non_team_sessions_are_left
+ expect(@non_team_flu_session.reload).to have_attributes(
+ psd_enabled: false,
+ national_protocol_enabled: false,
+ requires_registration: true
+ )
+
+ expect(@non_team_hpv_session.reload).to have_attributes(
+ psd_enabled: false,
+ national_protocol_enabled: false,
+ requires_registration: true
+ )
+ end
+end
diff --git a/spec/features/delegation_spec.rb b/spec/features/delegation_spec.rb
new file mode 100644
index 0000000000..ca57c80089
--- /dev/null
+++ b/spec/features/delegation_spec.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+describe "Delegation" do
+ after { given_the_feature_flag_is_disabled }
+
+ scenario "feature flag off and in a flu session" do
+ given_the_feature_flag_is_disabled
+ and_a_flu_session_exists
+
+ when_i_visit_the_session_as_a_nurse
+ and_i_go_to_the_edit_page
+ then_i_see_nothing_about_delegation
+ end
+
+ scenario "feature flag on and in a HPV session" do
+ given_the_feature_flag_is_enabled
+ and_an_hpv_session_exists
+
+ when_i_visit_the_session_as_a_nurse
+ and_i_go_to_the_edit_page
+ then_i_see_nothing_about_delegation
+ end
+
+ scenario "feature flag on and in a flu session" do
+ given_the_feature_flag_is_enabled
+ and_a_flu_session_exists
+
+ when_i_visit_the_session_as_a_nurse
+ and_i_go_to_the_edit_page
+ then_i_see_the_delegation_options
+
+ when_i_enable_delegation
+ then_i_see_the_options_are_enabled
+ end
+
+ def given_the_feature_flag_is_enabled
+ Flipper.enable(:delegation)
+ end
+
+ def given_the_feature_flag_is_disabled
+ Flipper.disable(:delegation)
+ end
+
+ def and_a_flu_session_exists
+ @programme = create(:programme, :flu)
+ @team = create(:team, programmes: [@programme])
+ @nurse = create(:nurse, teams: [@team])
+
+ @session = create(:session, programmes: [@programme], team: @team)
+ end
+
+ def and_an_hpv_session_exists
+ @programme = create(:programme, :hpv)
+ @team = create(:team, programmes: [@programme])
+ @nurse = create(:nurse, teams: [@team])
+
+ @session = create(:session, programmes: [@programme], team: @team)
+ end
+
+ def when_i_visit_the_session_as_a_nurse
+ sign_in @nurse
+ visit session_path(@session)
+ end
+
+ def and_i_go_to_the_edit_page
+ click_on "Edit session"
+ end
+
+ def then_i_see_nothing_about_delegation
+ expect(page).not_to have_content("patient specific direction")
+ expect(page).not_to have_content("national protocol")
+ end
+
+ def then_i_see_the_delegation_options
+ expect(page).to have_content("Use patient specific direction (PSD)")
+ expect(page).to have_content("Use national protocol")
+ end
+
+ def when_i_enable_delegation
+ click_on "Change use patient specific direction (PSD)"
+
+ # PSD
+ within all(".nhsuk-fieldset")[0] do
+ choose "Yes"
+ end
+
+ # National protocol
+ within all(".nhsuk-fieldset")[1] do
+ choose "Yes"
+ end
+
+ click_on "Continue"
+ end
+
+ def then_i_see_the_options_are_enabled
+ expect(page).to have_content("Use patient specific direction (PSD)Yes")
+ expect(page).to have_content("Use national protocolYes")
+ end
+end
diff --git a/spec/features/flu_vaccination_hca_pgd_supply_spec.rb b/spec/features/flu_vaccination_hca_pgd_supply_spec.rb
new file mode 100644
index 0000000000..91613f5407
--- /dev/null
+++ b/spec/features/flu_vaccination_hca_pgd_supply_spec.rb
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+
+describe "Flu vaccination" do
+ around { |example| travel_to(Time.zone.local(2024, 10, 1)) { example.run } }
+
+ scenario "Administered by HCA" do
+ given_a_session_exists
+ and_patients_exist
+
+ when_i_visit_the_session_record_tab
+ then_i_only_see_nasal_spray_patients
+
+ when_i_click_on_the_nasal_only_patient
+ then_i_am_able_to_vaccinate_them
+
+ when_i_click_on_the_nasal_and_injection_patient
+ then_i_am_able_to_vaccinate_them
+
+ when_i_click_on_the_injection_patient
+ then_i_am_not_able_to_vaccinate_them
+ end
+
+ def given_a_session_exists
+ @programme = create(:programme, :flu)
+ programmes = [@programme]
+
+ @team = create(:team, programmes:)
+
+ @batch =
+ create(
+ :batch,
+ :not_expired,
+ team: @team,
+ vaccine: @programme.vaccines.nasal.first
+ )
+
+ @nurse = create(:nurse, team: @team)
+ @user = create(:healthcare_assistant, team: @team)
+
+ @session =
+ create(
+ :session,
+ :today,
+ :requires_no_registration,
+ team: @team,
+ programmes:
+ )
+ end
+
+ def and_patients_exist
+ @patient_nasal_only =
+ create(
+ :patient,
+ :consent_given_nasal_only_triage_not_needed,
+ session: @session
+ )
+ @patient_nasal_and_injection =
+ create(
+ :patient,
+ :consent_given_nasal_or_injection_triage_not_needed,
+ session: @session
+ )
+ @patient_injection_only =
+ create(
+ :patient,
+ :consent_given_injection_only_triage_not_needed,
+ session: @session
+ )
+ end
+
+ def when_i_visit_the_session_record_tab
+ sign_in @user, role: :healthcare_assistant
+ visit session_record_path(@session)
+ end
+
+ def then_i_only_see_nasal_spray_patients
+ expect(page).to have_content(@patient_nasal_only.full_name)
+ expect(page).to have_content(@patient_nasal_and_injection.full_name)
+ expect(page).not_to have_content(@patient_injection_only.full_name)
+ end
+
+ def when_i_click_on_the_nasal_only_patient
+ click_on @patient_nasal_only.full_name
+ end
+
+ def when_i_click_on_the_nasal_and_injection_patient
+ click_on @patient_nasal_and_injection.full_name
+ end
+
+ def when_i_click_on_the_injection_patient
+ # This patient won't be in the "Record" tab.
+ expect(page).not_to have_content(@patient_injection_only.full_name)
+
+ within(".app-secondary-navigation") { click_on "Children" }
+ click_on @patient_injection_only.full_name
+ end
+
+ def then_i_am_able_to_vaccinate_them
+ check "I have checked that the above statements are true"
+ select @nurse.full_name
+ within all("section")[1] do
+ choose "Yes"
+ end
+ click_on "Continue"
+
+ choose @batch.name
+ click_on "Continue"
+
+ click_on "Change supplier"
+ choose @nurse.full_name
+ 4.times { click_on "Continue" }
+
+ click_on "Confirm"
+ click_on "Record vaccinations"
+ end
+
+ def then_i_am_not_able_to_vaccinate_them
+ expect(page).not_to have_content("Pre-screening checks")
+ expect(page).not_to have_content("ready for their")
+ end
+end
diff --git a/spec/features/manage_attendance_spec.rb b/spec/features/manage_attendance_spec.rb
index 419f2272e5..2abbb5bfcc 100644
--- a/spec/features/manage_attendance_spec.rb
+++ b/spec/features/manage_attendance_spec.rb
@@ -47,13 +47,30 @@
and_the_session_has_patients
when_i_go_to_the_session
- then_i_should_not_see_the_register_tab
+ then_i_do_not_see_the_register_tab
when_i_go_to_the_session_patients
and_i_go_to_a_patient
then_i_should_not_see_link_to_update_attendance
end
+ scenario "Turning off attendance" do
+ given_my_team_is_running_an_hpv_vaccination_programme
+ and_there_is_a_vaccination_session_today
+ and_the_session_has_patients
+
+ when_i_go_to_the_session
+ and_i_click_on_the_register_tab
+ then_i_see_the_register_tab
+
+ when_i_go_to_the_session
+ and_i_edit_the_session
+ and_i_turn_off_register_attendance
+
+ when_i_go_to_the_session
+ then_i_do_not_see_the_register_tab
+ end
+
def given_my_team_is_running_an_hpv_vaccination_programme
@programmes = [create(:programme, :hpv_all_vaccines)]
@team = create(:team, :with_one_nurse, programmes: @programmes)
@@ -103,7 +120,7 @@ def when_i_click_on_the_record_vaccinations_tab
click_link "Record vaccinations"
end
- def then_i_should_not_see_the_register_tab
+ def then_i_do_not_see_the_register_tab
expect(page).not_to have_content("Register")
end
@@ -204,4 +221,14 @@ def when_i_go_to_the_activity_log
def then_i_see_the_attendance_event
expect(page).to have_content("Attended session")
end
+
+ def and_i_edit_the_session
+ click_on "Edit session"
+ end
+
+ def and_i_turn_off_register_attendance
+ click_on "Change register attendance"
+ choose "No"
+ click_on "Continue"
+ end
end
diff --git a/spec/features/patient_specific_directions_spec.rb b/spec/features/patient_specific_directions_spec.rb
new file mode 100644
index 0000000000..232cc6fd06
--- /dev/null
+++ b/spec/features/patient_specific_directions_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+describe "Patient Specific Directions" do
+ before { given_delegation_feature_flag_is_enabled }
+
+ scenario "prescriber can bulk add PSDs to patients that don't require triage" do
+ given_a_flu_programme_with_a_running_session(user_type: :with_one_nurse)
+ and_a_patient_who_doesnt_need_triage_exists
+ and_i_am_signed_in
+
+ when_i_go_to_the_session_psds_tab
+ then_the_patient_should_have_psd_status_not_added
+ and_i_should_see_one_child_eligible_for_psd
+
+ when_i_click_add_new_psds
+ and_should_see_again_one_child_eligible_for_psd
+
+ when_i_click_on_button_to_bulk_add_psds
+ then_the_patient_should_have_psd_status_added
+ and_zero_children_should_be_eligible_for_psd
+ end
+
+ scenario "admin cannot bulk add PSDs to patients" do
+ given_a_flu_programme_with_a_running_session(user_type: :with_one_admin)
+ and_a_patient_who_doesnt_need_triage_exists
+ and_i_am_signed_in(role: :admin)
+
+ when_i_go_to_the_session_psds_tab
+ then_i_should_not_see_link_to_bulk_add_psds
+ end
+
+ scenario "healthcare assistant cannot bulk add PSDs to patients" do
+ given_a_flu_programme_with_a_running_session(
+ user_type: :with_one_healthcare_assistant
+ )
+ and_a_patient_who_doesnt_need_triage_exists
+ and_i_am_signed_in(role: :healthcare_assistant)
+
+ when_i_go_to_the_session_psds_tab
+ then_i_should_not_see_link_to_bulk_add_psds
+ end
+
+ def given_delegation_feature_flag_is_enabled
+ Flipper.enable(:delegation)
+ end
+
+ def given_a_flu_programme_with_a_running_session(user_type:)
+ programmes = [create(:programme, :flu)]
+ @team = create(:team, user_type, programmes:)
+
+ @batch =
+ create(:batch, team: @team, vaccine: programmes.first.vaccines.first)
+
+ @session = create(:session, team: @team, programmes:)
+ end
+
+ def and_a_patient_who_doesnt_need_triage_exists
+ @patient_triage_not_needed =
+ create(
+ :patient_session,
+ :consent_given_triage_not_needed,
+ session: @session
+ ).patient
+ end
+
+ def and_i_am_signed_in(role: :nurse)
+ @user = @team.users.first
+ sign_in @user, role:
+ end
+
+ def when_i_go_to_the_session_psds_tab
+ visit session_patient_specific_directions_path(@session)
+ end
+
+ def then_the_patient_should_have_psd_status_not_added
+ expect(page).to have_text("PSD not added")
+ end
+
+ def then_the_patient_should_have_psd_status_added
+ expect(page).to have_text("PSD added")
+ end
+
+ def and_i_should_see_one_child_eligible_for_psd
+ expect(page).to have_text("There are 1 children")
+ end
+
+ def and_should_see_again_one_child_eligible_for_psd
+ expect(page).to have_text("1 new PSDs?")
+ end
+
+ def and_zero_children_should_be_eligible_for_psd
+ expect(page).to have_text("There are 0 children")
+ end
+
+ def when_i_click_add_new_psds
+ click_link "Add new PSDs"
+ end
+
+ def when_i_click_on_button_to_bulk_add_psds
+ click_button "Yes, add PSDs"
+ end
+
+ def then_i_should_not_see_link_to_bulk_add_psds
+ expect(page).not_to have_text("Add new PSDs")
+ end
+end
diff --git a/spec/features/triage_spec.rb b/spec/features/triage_spec.rb
index f9b88e7d00..1ddfa878d0 100644
--- a/spec/features/triage_spec.rb
+++ b/spec/features/triage_spec.rb
@@ -72,6 +72,23 @@
and_the_vaccine_method_is_recorded_as_nasal
end
+ scenario "adding PSD instruction" do
+ given_delegation_feature_flag_is_enabled
+ and_a_flu_programme_with_a_running_session_with_psd_enabled
+ and_a_patient_with_nasal_consent_who_needs_triage_exists
+ and_i_am_signed_in
+
+ when_i_go_to_the_session_triage_tab
+ then_i_see_the_patient_who_needs_triage
+
+ when_i_go_to_the_patient_that_needs_triage
+ and_i_choose_that_they_are_safe_to_vaccinate_with_nasal
+ and_i_choose_to_add_psd
+ and_i_save_triage
+
+ then_i_should_see_the_patient_tagged_psd_added
+ end
+
def given_a_programme_with_a_running_session
programmes = [create(:programme, :hpv)]
@team = create(:team, :with_one_nurse, programmes:)
@@ -92,6 +109,16 @@ def given_a_flu_programme_with_a_running_session
@session = create(:session, team: @team, programmes:)
end
+ def and_a_flu_programme_with_a_running_session_with_psd_enabled
+ programmes = [create(:programme, :flu)]
+ @team = create(:team, :with_one_nurse, programmes:)
+
+ @batch =
+ create(:batch, team: @team, vaccine: programmes.first.vaccines.first)
+
+ @session = create(:session, :psd_enabled, team: @team, programmes:)
+ end
+
def and_a_patient_who_needs_triage_exists
@patient_triage_needed =
create(
@@ -112,6 +139,17 @@ def and_a_patient_who_needs_triage_exists
@patient_triage_needed.reload # Make sure both consents are accessible
end
+ def and_a_patient_with_nasal_consent_who_needs_triage_exists
+ @patient_triage_needed =
+ create(
+ :patient_session,
+ :consent_given_nasal_only_triage_needed,
+ session: @session
+ ).patient
+
+ @patient_triage_needed.reload # Make sure both consents are accessible
+ end
+
def and_patients_with_different_flu_consent_types_exist
@patient_injection_only =
create(
@@ -140,6 +178,10 @@ def and_a_patient_who_doesnt_need_triage_exists
).patient
end
+ def given_delegation_feature_flag_is_enabled
+ Flipper.enable(:delegation)
+ end
+
def and_i_am_signed_in
@user = @team.users.first
sign_in @user
@@ -149,6 +191,10 @@ def when_i_go_to_the_session_triage_tab
visit session_triage_path(@session)
end
+ def when_i_visit_the_register_tab
+ visit session_register_path(@session)
+ end
+
def then_i_see_the_patient_who_needs_triage
expect(page).to have_content(@patient_triage_needed.full_name)
end
@@ -172,6 +218,10 @@ def then_i_see_the_triage_options
expect(page).to have_selector :heading, "Is it safe to vaccinate"
end
+ def and_i_save_triage
+ click_button "Save triage"
+ end
+
def when_i_record_that_they_need_triage
choose "No, keep in triage"
click_button "Save triage"
@@ -194,10 +244,18 @@ def when_i_record_that_they_are_safe_to_vaccinate_with_injection
end
def and_i_record_that_they_are_safe_to_vaccinate_with_nasal
- choose "Yes, it’s safe to vaccinate with nasal spray"
+ and_i_choose_that_they_are_safe_to_vaccinate_with_nasal
click_button "Save triage"
end
+ def and_i_choose_that_they_are_safe_to_vaccinate_with_nasal
+ choose "Yes, it’s safe to vaccinate with nasal spray"
+ end
+
+ def and_i_choose_to_add_psd
+ choose "Yes"
+ end
+
def when_i_do_not_vaccinate
choose "No, do not vaccinate"
click_button "Save triage"
@@ -324,4 +382,12 @@ def and_the_vaccine_method_is_recorded_as_nasal
triage = @patient_nasal_only.triages.last
expect(triage.vaccine_method).to eq("nasal")
end
+
+ def then_i_should_see_the_patient_tagged_psd_added
+ within(".app-action-list") { expect(page).to have_content("PSD added") }
+ end
+
+ def then_i_should_see_the_patient_with_status_psd_added
+ expect(page).to have_content("PSD added")
+ end
end
diff --git a/spec/forms/patient_search_form_spec.rb b/spec/forms/patient_search_form_spec.rb
index 7ca085eb95..41110305bf 100644
--- a/spec/forms/patient_search_form_spec.rb
+++ b/spec/forms/patient_search_form_spec.rb
@@ -34,6 +34,7 @@
let(:register_status) { nil }
let(:triage_status) { nil }
let(:vaccine_method) { nil }
+ let(:patient_specific_direction_status) { nil }
let(:year_groups) { %w[8 9 10 11] }
let(:params) do
@@ -46,6 +47,7 @@
date_of_birth_year:,
missing_nhs_number:,
vaccination_status:,
+ patient_specific_direction_status:,
programme_types:,
q:,
register_status:,
@@ -458,6 +460,50 @@
end
end
+ context "filtering on patient specific direction status" do
+ let(:consent_statuses) { nil }
+ let(:date_of_birth_day) { nil }
+ let(:date_of_birth_month) { nil }
+ let(:date_of_birth_year) { nil }
+ let(:missing_nhs_number) { nil }
+ let(:vaccination_status) { nil }
+ let(:programme_types) { nil }
+ let(:q) { nil }
+ let(:register_status) { nil }
+ let(:triage_status) { nil }
+ let(:year_groups) { nil }
+
+ let!(:patient_session_with_psd) do
+ create(:patient_session, session:).tap do |patient_session|
+ create(
+ :patient_specific_direction,
+ patient: patient_session.patient,
+ programme: programmes.first
+ )
+ end
+ end
+
+ let!(:patient_session_without_psd) { create(:patient_session, session:) }
+
+ context "when status is 'added'" do
+ let(:patient_specific_direction_status) { "added" }
+
+ it "finds the patient with the PSD" do
+ expect(form.apply(scope)).to contain_exactly(patient_session_with_psd)
+ end
+ end
+
+ context "when status is 'not_added'" do
+ let(:patient_specific_direction_status) { "not_added" }
+
+ it "finds the patient that has no PSD" do
+ expect(form.apply(scope)).to contain_exactly(
+ patient_session_without_psd
+ )
+ end
+ end
+ end
+
context "filtering on vaccine method" do
let(:consent_statuses) { nil }
let(:date_of_birth_day) { nil }
diff --git a/spec/forms/triage_form_spec.rb b/spec/forms/triage_form_spec.rb
index fc8477f477..28ea502cd6 100644
--- a/spec/forms/triage_form_spec.rb
+++ b/spec/forms/triage_form_spec.rb
@@ -16,6 +16,12 @@
it { should_not validate_presence_of(:notes) }
it { should_not validate_presence_of(:vaccine_methods) }
it { should validate_length_of(:notes).is_at_most(1000) }
+
+ it do
+ expect(form).to validate_inclusion_of(:add_psd).in_array(
+ [true, false]
+ ).allow_nil
+ end
end
describe "when the patient is safe to vaccinate for HPV" do
diff --git a/spec/forms/vaccinate_form_spec.rb b/spec/forms/vaccinate_form_spec.rb
index e638da39fc..012b89465e 100644
--- a/spec/forms/vaccinate_form_spec.rb
+++ b/spec/forms/vaccinate_form_spec.rb
@@ -1,9 +1,10 @@
# frozen_string_literal: true
describe VaccinateForm do
- subject(:form) { described_class.new(programme:) }
+ subject(:form) { described_class.new(programme:, current_user:) }
let(:programme) { create(:programme) }
+ let(:current_user) { create(:user) }
describe "validations" do
it do
@@ -28,7 +29,8 @@
subject(:form) do
described_class.new(
identity_check_confirmed_by_patient: false,
- programme:
+ programme:,
+ current_user:
)
end
diff --git a/spec/models/patient_specific_direction_spec.rb b/spec/models/patient_specific_direction_spec.rb
index 861f94957c..1ee1fc1dff 100644
--- a/spec/models/patient_specific_direction_spec.rb
+++ b/spec/models/patient_specific_direction_spec.rb
@@ -7,7 +7,6 @@
# id :bigint not null, primary key
# academic_year :integer not null
# delivery_site :integer not null
-# full_dose :boolean not null
# vaccine_method :integer not null
# created_at :datetime not null
# updated_at :datetime not null
@@ -70,7 +69,5 @@
:vaccine_method
).in_array(%w[injection nasal])
end
-
- it { should allow_values(true, false).for(:full_dose) }
end
end
diff --git a/spec/models/session_spec.rb b/spec/models/session_spec.rb
index e4cf9925b0..18ea1316ce 100644
--- a/spec/models/session_spec.rb
+++ b/spec/models/session_spec.rb
@@ -296,6 +296,32 @@
it { should contain_exactly("injection", "nasal") }
end
+ describe "#vaccine_methods_for" do
+ subject { session.vaccine_methods_for(user:) }
+
+ let(:programmes) { [create(:programme, :flu)] }
+
+ let(:session) { create(:session, programmes:) }
+
+ context "with a nurse" do
+ let(:user) { create(:nurse) }
+
+ it { should match_array(%w[nasal injection]) }
+ end
+
+ context "with a healthcare assistant" do
+ let(:user) { create(:healthcare_assistant) }
+
+ it { should eq(%w[nasal]) }
+ end
+
+ context "with an admin staff" do
+ let(:user) { create(:admin) }
+
+ it { should be_empty }
+ end
+ end
+
describe "#today_or_future_dates" do
subject(:today_or_future_dates) do
travel_to(today) { session.today_or_future_dates }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index ecf02f3694..9ecfe871a3 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -196,6 +196,50 @@
end
end
+ describe "#is_prescriber?" do
+ subject { user.is_prescriber? }
+
+ context "cis2 is enabled", cis2: :enabled do
+ context "when the user is a nurse" do
+ let(:user) { build(:nurse) }
+
+ it { should be(false) }
+ end
+
+ context "when the user is admin staff" do
+ let(:user) { build(:admin) }
+
+ it { should be(false) }
+ end
+
+ context "when the user is a prescriber" do
+ let(:user) { build(:prescriber) }
+
+ it { should be(true) }
+ end
+ end
+
+ context "cis2 is disabled", cis2: :disabled do
+ context "when the user is a nurse" do
+ let(:user) { build(:nurse) }
+
+ it { should be(false) }
+ end
+
+ context "when the user is admin staff" do
+ let(:user) { build(:admin) }
+
+ it { should be(false) }
+ end
+
+ context "when the user is a prescriber" do
+ let(:user) { build(:prescriber) }
+
+ it { should be(true) }
+ end
+ end
+ end
+
describe "#is_superuser?" do
subject { user.is_superuser? }
diff --git a/spec/models/vaccination_record_spec.rb b/spec/models/vaccination_record_spec.rb
index 2774f7a723..16baeb88ab 100644
--- a/spec/models/vaccination_record_spec.rb
+++ b/spec/models/vaccination_record_spec.rb
@@ -34,6 +34,7 @@
# performed_by_user_id :bigint
# programme_id :bigint not null
# session_id :bigint
+# supplied_by_user_id :bigint
# vaccine_id :bigint
#
# Indexes
@@ -46,6 +47,7 @@
# index_vaccination_records_on_performed_by_user_id (performed_by_user_id)
# index_vaccination_records_on_programme_id (programme_id)
# index_vaccination_records_on_session_id (session_id)
+# index_vaccination_records_on_supplied_by_user_id (supplied_by_user_id)
# index_vaccination_records_on_uuid (uuid) UNIQUE
# index_vaccination_records_on_vaccine_id (vaccine_id)
#
@@ -56,6 +58,7 @@
# fk_rails_... (performed_by_user_id => users.id)
# fk_rails_... (programme_id => programmes.id)
# fk_rails_... (session_id => sessions.id)
+# fk_rails_... (supplied_by_user_id => users.id)
# fk_rails_... (vaccine_id => vaccines.id)
#
describe VaccinationRecord do
@@ -63,6 +66,7 @@
describe "associations" do
it { should have_one(:identity_check).autosave(true).dependent(:destroy) }
+ it { should belong_to(:supplied_by).optional }
end
describe "validations" do
diff --git a/spec/policies/patient_specific_direction_policy_spec.rb b/spec/policies/patient_specific_direction_policy_spec.rb
new file mode 100644
index 0000000000..ef3680203e
--- /dev/null
+++ b/spec/policies/patient_specific_direction_policy_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+RSpec.describe PatientSpecificDirectionPolicy do
+ subject(:policy) { described_class.new(user, PatientSpecificDirection) }
+
+ context "cis2 is disabled", cis2: :disabled do
+ describe "#create?" do
+ context "when user is a nurse" do
+ let(:user) { build(:user, :nurse) }
+
+ it "permits creation" do
+ expect(policy.create?).to be(true)
+ end
+ end
+
+ context "when user is not a nurse" do
+ let(:user) { build(:user, :healthcare_assistant) }
+
+ it "denies creation" do
+ expect(policy.create?).to be(false)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/policies/programme_policy_spec.rb b/spec/policies/programme_policy_spec.rb
new file mode 100644
index 0000000000..6f36f2e663
--- /dev/null
+++ b/spec/policies/programme_policy_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+describe ProgrammePolicy do
+ describe ProgrammePolicy::Scope do
+ describe "#resolve" do
+ subject { described_class.new(user, Programme).resolve }
+
+ let(:flu_programme) { create(:programme, :flu) }
+ let(:hpv_programme) { create(:programme, :hpv) }
+
+ let(:team) { create(:team, programmes: [flu_programme, hpv_programme]) }
+
+ context "with an admin user" do
+ let(:user) { create(:admin, team:) }
+
+ it { should contain_exactly(flu_programme, hpv_programme) }
+ end
+
+ context "with a nurse user" do
+ let(:user) { create(:nurse, team:) }
+
+ it { should contain_exactly(flu_programme, hpv_programme) }
+ end
+
+ context "with a healthcare assistant user" do
+ let(:user) { create(:healthcare_assistant, team:) }
+
+ it { should contain_exactly(flu_programme) }
+ end
+ end
+ end
+end
diff --git a/spec/policies/session_policy_spec.rb b/spec/policies/session_policy_spec.rb
index a8454738e6..d93fffd38c 100644
--- a/spec/policies/session_policy_spec.rb
+++ b/spec/policies/session_policy_spec.rb
@@ -35,6 +35,22 @@
it { should be(true) }
end
end
+
+ context "with a healthcare assistant" do
+ let(:user) { create(:healthcare_assistant) }
+
+ context "with a scheduled session" do
+ let(:session) { create(:session, :scheduled) }
+
+ it { should be(false) }
+ end
+
+ context "with an unscheduled session" do
+ let(:session) { create(:session, :unscheduled) }
+
+ it { should be(false) }
+ end
+ end
end
describe "#edit?" do
@@ -49,17 +65,55 @@
include_examples "edit/update session"
end
- describe "Scope#resolve" do
- subject { SessionPolicy::Scope.new(user, Session).resolve }
+ describe SessionPolicy::Scope do
+ describe "#resolve" do
+ subject { described_class.new(user, Session).resolve }
+
+ let(:user) { create(:user, team:) }
+
+ let!(:flu_programme) { create(:programme, :flu) }
+ let!(:hpv_programme) { create(:programme, :hpv) }
- let(:programmes) { [create(:programme)] }
- let(:team) { create(:team, programmes:) }
- let(:user) { create(:user, team:) }
+ let(:users_teams_session) { create(:session, team:, programmes:) }
+ let(:another_teams_session) { create(:session, programmes:) }
- let(:users_teams_session) { create(:session, team:, programmes:) }
- let(:another_teams_session) { create(:session, programmes:) }
+ let(:programmes) { [hpv_programme] }
+ let(:team) { create(:team, programmes:) }
- it { should include(users_teams_session) }
- it { should_not include(another_teams_session) }
+ context "with a session part of the user's teams" do
+ let(:session) { create(:session, team:, programmes:) }
+
+ context "and an admin user" do
+ let(:user) { create(:admin, team:) }
+
+ it { should include(session) }
+ end
+
+ context "and a nurse user" do
+ let(:user) { create(:nurse, team:) }
+
+ it { should include(session) }
+ end
+
+ context "and a healthcare assistant user" do
+ let(:user) { create(:healthcare_assistant, team:) }
+
+ it { should_not include(session) }
+
+ context "and a flu session" do
+ let(:programmes) { [flu_programme] }
+
+ it { should include(session) }
+ end
+ end
+ end
+
+ context "with a session not part of the user's teams" do
+ let(:session) { create(:session, programmes:) }
+ let(:user) { create(:user, team:) }
+
+ it { should_not include(session) }
+ end
+ end
end
end