diff --git a/app/components/app_location_search_form_component.rb b/app/components/app_location_search_form_component.rb index c88e4def7d..b2d766f233 100644 --- a/app/components/app_location_search_form_component.rb +++ b/app/components/app_location_search_form_component.rb @@ -8,7 +8,7 @@ def initialize(form, url:) private - PHASES = %w[nursery primary secondary other].freeze + PHASES = Location::PHASES attr_reader :form, :url diff --git a/app/components/app_school_summary_component.rb b/app/components/app_school_summary_component.rb index d4fe3f3606..24bb2d3325 100644 --- a/app/components/app_school_summary_component.rb +++ b/app/components/app_school_summary_component.rb @@ -62,7 +62,7 @@ def name_row end def phase_row - { + row = { key: { text: "Phase" }, @@ -70,6 +70,18 @@ def phase_row text: schoolable.human_enum_name(:phase) } } + + if change_links[:phase] + row[:actions] = [ + { + text: change_links[:phase][:text] || "Change", + href: change_links[:phase][:link], + visually_hidden_text: "phase" + } + ] + end + + row end def programmes_row diff --git a/app/controllers/draft_schools_controller.rb b/app/controllers/draft_schools_controller.rb index 3157dfa77a..02e6f3e7e9 100644 --- a/app/controllers/draft_schools_controller.rb +++ b/app/controllers/draft_schools_controller.rb @@ -12,6 +12,7 @@ class DraftSchoolsController < ApplicationController before_action :set_name, if: -> { current_step == :confirm_urn } before_action :set_address, if: -> { %i[details confirm_urn].include?(current_step) } + before_action :set_phases, if: -> { current_step == :phase } before_action :set_year_groups, if: -> { current_step == :year_groups } before_action :set_back_link_path @@ -91,6 +92,10 @@ def set_name @draft_school.name ||= @draft_school.source_location&.name end + def set_phases + @phases = Location::PHASES + end + def set_year_groups @year_groups = Location::YearGroup::DEFAULT_VALUE_RANGE.map do |n| @@ -231,6 +236,7 @@ def update_params address_town address_postcode ], + phase: [:phase], year_groups: [{ year_groups: [] }], confirm: [] }.fetch(current_step) diff --git a/app/models/draft_school.rb b/app/models/draft_school.rb index c7dae78c50..28543dbeee 100644 --- a/app/models/draft_school.rb +++ b/app/models/draft_school.rb @@ -14,6 +14,7 @@ class DraftSchool attribute :address_town, :string attribute :address_postcode, :string attribute :selected_year_groups, default: [] + attribute :phase, :string attribute :context, :string attr_reader :current_team @@ -51,6 +52,7 @@ def wizard_steps (:details if add_site_context? || editing?), (:urn if add_school_context?), (:confirm_urn if add_school_context?), + :phase, :year_groups, :confirm ].compact @@ -93,6 +95,10 @@ def add_site_context? validates :address_postcode, postcode: true end + on_wizard_step :phase, exact: true do + validates :phase, presence: true, inclusion: { in: Location::PHASES } + end + on_wizard_step :year_groups, exact: true do validates :selected_year_groups, presence: true validate :cannot_remove_year_groups @@ -141,7 +147,7 @@ def readable_attribute_names end def writable_attribute_names - %w[name address_line_1 address_line_2 address_town address_postcode] + %w[name address_line_1 address_line_2 address_town address_postcode phase] end def resolved_urn @@ -167,6 +173,10 @@ def next_site_letter existing_sites.max_by { [it.length, it] }.next end + def phase + super.presence || source_location&.phase + end + def existing_year_groups source_location&.year_groups.presence || source_location&.gias_year_groups || [] @@ -190,7 +200,7 @@ def programmes end def human_enum_name(attr) - source_location&.human_enum_name(attr) + Location.human_enum_name(attr, public_send(attr)) end def schools_with_urn diff --git a/app/models/location.rb b/app/models/location.rb index a4c73f0296..2b610e7b58 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -15,6 +15,7 @@ # gias_year_groups :integer default([]), not null, is an Array # name :text not null # ods_code :string +# phase :string # position :geography point, 4326 # site :string # status :integer default("unknown"), not null @@ -125,8 +126,23 @@ class Location < ApplicationRecord ) end + GIAS_PHASE_MAPPINGS = { + "nursery" => %w[nursery], + "primary" => %w[primary middle_deemed_primary], + "secondary" => %w[secondary middle_deemed_secondary], + "other" => %w[sixteen_plus all_through not_applicable] + }.freeze + + PHASES = GIAS_PHASE_MAPPINGS.keys.freeze + scope :where_phase, - ->(phase) { where(gias_phase: GIAS_PHASE_MAPPINGS.fetch(phase)) } + ->(phase) do + where(phase:).or( + where(phase: nil).where( + gias_phase: GIAS_PHASE_MAPPINGS.fetch(phase) + ) + ) + end scope :with_team, ->(academic_year:) do @@ -190,6 +206,7 @@ class Location < ApplicationRecord validates :gias_local_authority_code, presence: true validates :gias_phase, inclusion: Location.gias_phases.keys validates :ods_code, absence: true + validates :phase, inclusion: { in: PHASES }, allow_nil: true validates :site, uniqueness: { scope: :urn }, allow_nil: true validates :urn, presence: true, uniqueness: { unless: :site } end @@ -240,11 +257,10 @@ def dfe_number end def phase - if gias_phase + super.presence || GIAS_PHASE_MAPPINGS .find { |_, values| values.include?(gias_phase) } &.first - end end def as_json @@ -306,13 +322,6 @@ def import_default_programme_year_groups!(programmes, academic_year:) def organisation_ods_codes = Organisation.pluck(:ods_code) - GIAS_PHASE_MAPPINGS = { - "nursery" => %w[nursery], - "primary" => %w[primary middle_deemed_primary], - "secondary" => %w[secondary middle_deemed_secondary], - "other" => %w[sixteen_plus all_through not_applicable] - }.freeze - def fhir_mapper @fhir_mapper ||= FHIRMapper::Location.new(self) end diff --git a/app/views/draft_schools/confirm.html.erb b/app/views/draft_schools/confirm.html.erb index 3f208d2ef2..7dcd98eb28 100644 --- a/app/views/draft_schools/confirm.html.erb +++ b/app/views/draft_schools/confirm.html.erb @@ -24,10 +24,14 @@ { name: { link: wizard_path(:details) }, address: { link: wizard_path(:details) }, + phase: { link: wizard_path(:phase) }, year_groups: { link: wizard_path("year-groups") }, } else - { year_groups: { link: wizard_path("year-groups") } } + { + phase: { link: wizard_path(:phase) }, + year_groups: { link: wizard_path("year-groups") }, + } end elsif @draft_school.add_site_context? { urn: { link: wizard_path(:school), text: "Change parent school" } } diff --git a/app/views/draft_schools/phase.html.erb b/app/views/draft_schools/phase.html.erb new file mode 100644 index 0000000000..01a00592e0 --- /dev/null +++ b/app/views/draft_schools/phase.html.erb @@ -0,0 +1,15 @@ +<% content_for :before_main do %> + <%= govuk_back_link(href: @back_link_path) %> +<% end %> + +<% legend = "Phase" %> +<% content_for :page_title, legend %> + +<%= form_with model: @draft_school, url: wizard_path, method: :put do |f| %> + <%= f.mavis_error_summary %> + <%= f.govuk_collection_radio_buttons :phase, @phases, :itself, ->(v) { Location.human_enum_name(:phase, v) }, + legend: { text: legend, size: "l", tag: "h1" }, + caption: { text: @draft_school.name || @draft_school.source_location&.name, size: "l" } %> + + <%= f.govuk_submit "Continue" %> +<% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 1c046e9f31..06c1fbdd3f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -94,6 +94,9 @@ en: inclusion: Choose if this is the correct school name: blank: Enter a name + phase: + blank: Choose a phase + inclusion: Choose a phase parent_urn_and_site: blank: Choose a school selected_year_groups: @@ -778,6 +781,8 @@ en: inclusion: Choose a GIAS phase name: blank: Enter a name + phase: + inclusion: Choose a phase ods_code: blank: Enter an ODS code exclusion: This ODS code is not allowed diff --git a/config/locales/wicked.en.yml b/config/locales/wicked.en.yml index 882cfc0090..c13e916848 100644 --- a/config/locales/wicked.en.yml +++ b/config/locales/wicked.en.yml @@ -38,6 +38,7 @@ en: outcome: outcome parent: parent parent_details: parent-details + phase: phase programmes: programmes programmes_check: programmes-check questions: questions diff --git a/db/migrate/20260422100000_add_phase_to_locations.rb b/db/migrate/20260422100000_add_phase_to_locations.rb new file mode 100644 index 0000000000..d4e46636eb --- /dev/null +++ b/db/migrate/20260422100000_add_phase_to_locations.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddPhaseToLocations < ActiveRecord::Migration[8.0] + def change + add_column :locations, :phase, :string + end +end diff --git a/spec/components/app_school_summary_component_spec.rb b/spec/components/app_school_summary_component_spec.rb index 4430470369..2c8806583e 100644 --- a/spec/components/app_school_summary_component_spec.rb +++ b/spec/components/app_school_summary_component_spec.rb @@ -53,6 +53,9 @@ link: "/address", text: "Change address" }, + phase: { + link: "/phase" + }, year_groups: { link: "/year-groups" } @@ -62,6 +65,7 @@ it { should have_link("Change name", href: "/name") } it { should have_link("Change address", href: "/address") } + it { should have_link("Change phase", href: "/phase") } it { should have_link("Change year groups", href: "/year-groups") } end @@ -142,6 +146,12 @@ expect(rendered).to have_content("Secondary") end + it "shows the selected phase when overridden in the draft" do + draft_school.phase = "primary" + expect(rendered).to have_content("Primary") + expect(rendered).not_to have_content("Secondary") + end + it "shows programmes from the underlying location" do expect(rendered).to have_content("Programmes") expect(rendered).to have_content("HPV") diff --git a/spec/factories/locations.rb b/spec/factories/locations.rb index 7723a17e11..35396f56f6 100644 --- a/spec/factories/locations.rb +++ b/spec/factories/locations.rb @@ -15,6 +15,7 @@ # gias_year_groups :integer default([]), not null, is an Array # name :text not null # ods_code :string +# phase :string # position :geography point, 4326 # site :string # status :integer default("unknown"), not null diff --git a/spec/features/manage_teams_spec.rb b/spec/features/manage_teams_spec.rb index 74cc2361e3..991a579a41 100644 --- a/spec/features/manage_teams_spec.rb +++ b/spec/features/manage_teams_spec.rb @@ -50,6 +50,10 @@ when_i_confirm_the_school and_i_continue + then_i_see_the_phase_screen + + when_i_select_a_phase + and_i_continue then_i_see_the_year_groups_screen and_year_groups_are_pre_selected @@ -80,6 +84,10 @@ when_i_fill_in_the_school_site_details and_i_continue + then_i_see_the_phase_screen + + when_i_select_a_phase + and_i_continue then_i_see_the_year_groups_screen and_year_groups_are_pre_selected @@ -136,6 +144,25 @@ and_the_site_details_are_updated end + scenario "Editing a school phase" do + given_my_team_exists + + when_i_click_on_team_settings + when_i_click_on_schools + + when_i_click_on_edit_a_school + then_i_see_the_school_summary_with_phase_and_year_groups_links + + when_i_click_on_change_phase + and_i_select_a_new_phase + and_i_continue + then_i_see_the_phase_is_updated + + when_i_click_save_changes + then_i_see_the_team_schools + and_the_school_phase_is_updated + end + scenario "Editing a school" do given_my_team_exists @@ -144,7 +171,7 @@ then_i_see_the_team_schools when_i_click_on_edit_a_school - then_i_see_the_school_summary_with_only_year_groups_link + then_i_see_the_school_summary_with_phase_and_year_groups_links when_i_click_on_change_year_groups when_i_try_to_remove_a_year_group @@ -241,11 +268,13 @@ def then_i_see_the_school_summary_with_edit_links expect(page).to have_content("Site B") expect(page).to have_link("Change", text: /name/i) expect(page).to have_link("Change", text: /address/i) + expect(page).to have_link("Change", text: /phase/i) expect(page).to have_link("Change", text: /year groups/i) end - def then_i_see_the_school_summary_with_only_year_groups_link + def then_i_see_the_school_summary_with_phase_and_year_groups_links expect(page).to have_content(@school.name) + expect(page).to have_link("Change", text: /phase/i) expect(page).to have_link("Change", text: /year groups/i) expect(page).not_to have_link("Change", text: /name/i) expect(page).not_to have_link("Change", text: /address/i) @@ -529,4 +558,31 @@ def and_the_school_has_the_correct_year_groups @available_school.reload expect(@available_school.location_year_groups.pluck(:value)).to include(12) end + + def then_i_see_the_phase_screen + expect(page).to have_content("Phase") + expect(page).to have_field("Primary", type: :radio) + expect(page).to have_field("Secondary", type: :radio) + end + + def when_i_select_a_phase + choose "Secondary" + end + + def when_i_click_on_change_phase + click_on "Change phase" + end + + def and_i_select_a_new_phase + choose "Primary" + end + + def then_i_see_the_phase_is_updated + expect(page).to have_content("Primary") + end + + def and_the_school_phase_is_updated + @school.reload + expect(@school.phase).to eq("primary") + end end diff --git a/spec/models/draft_school_spec.rb b/spec/models/draft_school_spec.rb index ab3a02b813..557d5da3af 100644 --- a/spec/models/draft_school_spec.rb +++ b/spec/models/draft_school_spec.rb @@ -209,7 +209,7 @@ it "returns all steps including school selection" do expect(draft_school.wizard_steps).to eq( - %i[school details year_groups confirm] + %i[school details phase year_groups confirm] ) end end @@ -219,7 +219,7 @@ it "returns URN flow steps" do expect(draft_school.wizard_steps).to eq( - %i[urn confirm_urn year_groups confirm] + %i[urn confirm_urn phase year_groups confirm] ) end end @@ -228,7 +228,9 @@ let(:attributes) { { editing_id: school.id, context: "add_site" } } it "skips the school selection step" do - expect(draft_school.wizard_steps).to eq(%i[details year_groups confirm]) + expect(draft_school.wizard_steps).to eq( + %i[details phase year_groups confirm] + ) end end @@ -236,7 +238,9 @@ let(:attributes) { { editing_id: school.id } } it "includes only details, year_groups and confirm" do - expect(draft_school.wizard_steps).to eq(%i[details year_groups confirm]) + expect(draft_school.wizard_steps).to eq( + %i[details phase year_groups confirm] + ) end end end @@ -447,6 +451,7 @@ "editing_id" => nil, "name" => "New Site Name", "parent_urn_and_site" => school.urn_and_site, + "phase" => nil, "selected_year_groups" => [], "urn" => nil } @@ -467,6 +472,7 @@ "editing_id" => nil, "name" => nil, "parent_urn_and_site" => nil, + "phase" => nil, "selected_year_groups" => nil, "urn" => nil } @@ -621,35 +627,19 @@ end end - describe "#human_enum_name" do - context "when creating a new site" do - let(:attributes) { valid_attributes } - - it "delegates to the parent school" do - expect(draft_school.human_enum_name(:phase)).to eq( - school.human_enum_name(:phase) - ) - end - end - - context "when editing an existing site" do - let(:existing_site) do - create(:gias_school, :primary, urn: school.urn, site: "A") - end - let(:attributes) { { editing_id: existing_site.id } } - - it "delegates to the location being edited" do - expect(draft_school.human_enum_name(:phase)).to eq("Primary") - end - end - end - describe "#readable_attribute_names" do let(:attributes) { {} } it "returns the list of readable attributes" do expect(draft_school.readable_attribute_names).to eq( - %w[name address_line_1 address_line_2 address_town address_postcode] + %w[ + name + address_line_1 + address_line_2 + address_town + address_postcode + phase + ] ) end end @@ -659,7 +649,14 @@ it "returns the list of writable attributes" do expect(draft_school.writable_attribute_names).to eq( - %w[name address_line_1 address_line_2 address_town address_postcode] + %w[ + name + address_line_1 + address_line_2 + address_town + address_postcode + phase + ] ) end end diff --git a/spec/models/location_spec.rb b/spec/models/location_spec.rb index 6ac193a507..741ce846d3 100644 --- a/spec/models/location_spec.rb +++ b/spec/models/location_spec.rb @@ -15,6 +15,7 @@ # gias_year_groups :integer default([]), not null, is an Array # name :text not null # ods_code :string +# phase :string # position :geography point, 4326 # site :string # status :integer default("unknown"), not null @@ -406,6 +407,14 @@ it { should eq("other") } end + context "with a phase override" do + let(:location) do + build(:gias_school, gias_phase: "secondary", phase: "primary") + end + + it { should eq("primary") } + end + context "with something other than a school" do let(:location) { build(:community_clinic) } @@ -433,6 +442,7 @@ "is_attached_to_team" => false, "name" => location.name, "ods_code" => location.ods_code, + "phase" => nil, "position" => [location.position.x, location.position.y], "site" => location.site, "status" => "unknown",