From ad4a7dcfc9a127f01c63244a685f12690918bf55 Mon Sep 17 00:00:00 2001 From: Manny Dinssa <44172848+Dinssa@users.noreply.github.com> Date: Mon, 2 Dec 2024 11:05:40 +0000 Subject: [PATCH 1/6] CLDC-3723: Add validation on location units question (#2798) * Update location units question validation * Update value * Change nil value in incomplete locations to another attribute * Update validation * Update test * Update test with new additional error message * Updates * Update test --- app/models/location.rb | 3 +- config/locales/en.yml | 4 ++- spec/models/location_spec.rb | 36 ++++++++++++++++--- .../validations/setup_validations_spec.rb | 2 +- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/app/models/location.rb b/app/models/location.rb index 58afc76e8..c333f653f 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -2,7 +2,8 @@ class Location < ApplicationRecord validates :postcode, on: :postcode, presence: { message: I18n.t("validations.location.postcode_blank") } validate :validate_postcode, on: :postcode, if: proc { |model| model.postcode.presence } validates :location_admin_district, on: :location_admin_district, presence: { message: I18n.t("validations.location_admin_district") } - validates :units, on: :units, presence: { message: I18n.t("validations.location.units") } + validates :units, on: :units, presence: { message: I18n.t("validations.location.units.must_be_number") } + validates :units, on: :units, numericality: { greater_than_or_equal_to: 1, message: I18n.t("validations.location.units.must_be_one_or_more") } validates :type_of_unit, on: :type_of_unit, presence: { message: I18n.t("validations.location.type_of_unit") } validates :mobility_type, on: :mobility_type, presence: { message: I18n.t("validations.location.mobility_standards") } validates :startdate, on: :startdate, presence: { message: I18n.t("validations.location.startdate_invalid") } diff --git a/config/locales/en.yml b/config/locales/en.yml index 80711129e..851a9ea2c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -361,7 +361,9 @@ en: location: postcode_blank: "Enter a postcode." - units: "The units at this location must be a number." + units: + must_be_number: "The units at this location must be a number." + must_be_one_or_more: "Number of units must be at least 1." type_of_unit: "Select the most common type of unit at this location." mobility_standards: "Select the mobility standard for the majority of the units at this location." startdate_invalid: "Enter a valid day, month and year when the first property became available at this location." diff --git a/spec/models/location_spec.rb b/spec/models/location_spec.rb index 18581fb6e..c220914b7 100644 --- a/spec/models/location_spec.rb +++ b/spec/models/location_spec.rb @@ -740,10 +740,38 @@ RSpec.describe Location, type: :model do describe "#units" do let(:location) { FactoryBot.build(:location) } - it "does add an error when the number of units is invalid" do - location.units = nil - location.valid?(:units) - expect(location.errors.count).to eq(1) + context "when the number of units is invalid" do + it "adds an error when units is nil" do + location.units = nil + location.valid?(:units) + expect(location.errors.count).to eq(2) + end + + it "adds an error when units is 0" do + location.units = 0 + location.valid?(:units) + expect(location.errors.count).to eq(1) + end + end + + context "when the number of units is valid" do + it "does not add an error when units is 1" do + location.units = 1 + location.valid?(:units) + expect(location.errors.count).to eq(0) + end + + it "does not add an error when units is 10" do + location.units = 10 + location.valid?(:units) + expect(location.errors.count).to eq(0) + end + + it "does not add an error when units is 200" do + location.units = 200 + location.valid?(:units) + expect(location.errors.count).to eq(0) + end end end diff --git a/spec/models/validations/setup_validations_spec.rb b/spec/models/validations/setup_validations_spec.rb index 5b4f03365..1104cc23d 100644 --- a/spec/models/validations/setup_validations_spec.rb +++ b/spec/models/validations/setup_validations_spec.rb @@ -703,7 +703,7 @@ RSpec.describe Validations::SetupValidations do .to include("This location is incomplete. Select another location or update this one.") end - it "produces no error when location is completes" do + it "produces no error when location is complete" do location.update!(units: 1) location.reload record.location = location From dcbfb70c463b1af2c571cbb1531556bbe71d5a02 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Mon, 2 Dec 2024 12:46:22 +0000 Subject: [PATCH 2/6] CLCD-3759 Update reset password copy and move the link (#2832) * Update reset password copy and move the link * lint --- app/views/devise/sessions/new.html.erb | 4 ++-- app/views/devise/shared/_links.html.erb | 2 +- spec/features/user_spec.rb | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 440eeb624..04b436aa5 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -12,6 +12,8 @@ <%= content_for(:title) %> + <%= render "devise/shared/links" %> + <%= f.govuk_email_field :email, label: { text: "Email address" }, autocomplete: "email", @@ -25,5 +27,3 @@ <% end %> - -<%= render "devise/shared/links" %> diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb index e463f5deb..f48e1b3af 100644 --- a/app/views/devise/shared/_links.html.erb +++ b/app/views/devise/shared/_links.html.erb @@ -7,7 +7,7 @@ <% end %> <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> -

You can <%= govuk_link_to "reset your password", new_password_path(resource_name) %> if you’ve forgotten it.

+

<%= govuk_link_to "Forgot password", new_password_path(resource_name) %>

<% end %> <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> diff --git a/spec/features/user_spec.rb b/spec/features/user_spec.rb index bc562824c..2e837abe2 100644 --- a/spec/features/user_spec.rb +++ b/spec/features/user_spec.rb @@ -61,7 +61,7 @@ RSpec.describe "User Features" do context "when the user has forgotten their password" do it "is redirected to the reset password page when they click the reset password link" do visit("/lettings-logs") - click_link("reset your password") + click_link("Forgot password") expect(page).to have_current_path("/account/password/new") end @@ -744,7 +744,7 @@ RSpec.describe "User Features" do it "is redirected to the reset password page when they click the reset password link" do visit("/account/sign-in") - click_link("reset your password") + click_link("Forgot password") expect(page).to have_current_path("/account/password/new") end From 128ac5d57ad719d36705cefaa6936d38dd15dfcc Mon Sep 17 00:00:00 2001 From: Manny Dinssa <44172848+Dinssa@users.noreply.github.com> Date: Mon, 2 Dec 2024 14:06:59 +0000 Subject: [PATCH 3/6] CLDC-3758: Sales - Add shared ownership staircasing transaction section (#2788) * db migration, new sales log fields * Add new staircasing transaction section * Update tests * Lint * Add tests * Use log methods * Lint * Update test * Remove compact * Change section display condition * CLDC-3760: Sales - firststair and numstair staircasing questions validations (#2793) * Add validation to new questions firststair and numstair * Remove page headers on single question page * Remove page * More useful page header * Show don't know option from 2025 onwards * Add page header to monthly rent only for the version with two questions on the page * Remove page header staircase initial date only page * Update displayed answer options * Fix lint * Updates after merge * Update test * Update tests --- .../derived_variables/sales_log_variables.rb | 3 + .../form/sales/pages/about_staircase.rb | 2 +- app/models/form/sales/pages/equity.rb | 2 +- .../sales/pages/monthly_rent_staircasing.rb | 17 ++++ .../pages/monthly_rent_staircasing_owned.rb | 17 ++++ .../form/sales/pages/staircase_first_time.rb | 15 +++ .../sales/pages/staircase_initial_date.rb | 16 +++ .../form/sales/pages/staircase_previous.rb | 18 ++++ app/models/form/sales/pages/staircase_sale.rb | 17 ++++ .../sales/pages/value_shared_ownership.rb | 2 +- app/models/form/sales/questions/equity.rb | 1 + .../monthly_rent_after_staircasing.rb | 15 +++ .../monthly_rent_before_staircasing.rb | 15 +++ .../form/sales/questions/mortgageused.rb | 2 +- .../form/sales/questions/staircase_count.rb | 15 +++ .../sales/questions/staircase_first_time.rb | 16 +++ .../sales/questions/staircase_initial_date.rb | 11 +++ .../sales/questions/staircase_last_date.rb | 11 +++ .../form/sales/questions/staircase_sale.rb | 2 +- app/models/form/sales/questions/value.rb | 1 + .../form/sales/sections/sale_information.rb | 16 +-- .../shared_ownership_initial_purchase.rb | 5 +- .../subsections/shared_ownership_scheme.rb | 4 +- ...hared_ownership_staircasing_transaction.rb | 35 +++++++ app/models/sales_log.rb | 4 + .../sales/sale_information_validations.rb | 17 ++++ .../forms/2025/sales/sale_information.en.yml | 92 ++++++++++++----- .../validations/sales/sale_information.en.yml | 6 ++ .../20241114173226_add_fields_to_sales_log.rb | 11 +++ db/schema.rb | 5 + .../form/sales/pages/about_staircase_spec.rb | 4 + spec/models/form/sales/pages/equity_spec.rb | 4 - .../monthly_rent_staircasing_owned_spec.rb | 31 ++++++ .../pages/monthly_rent_staircasing_spec.rb | 31 ++++++ .../sales/pages/staircase_first_time_spec.rb | 31 ++++++ .../pages/staircase_initial_date_spec.rb | 31 ++++++ .../sales/pages/staircase_previous_spec.rb | 31 ++++++ .../form/sales/pages/staircase_sale_spec.rb | 38 +++++++ .../pages/value_shared_ownership_spec.rb | 2 +- .../form/sales/questions/equity_spec.rb | 2 +- .../monthly_rent_after_staircasing_spec.rb | 37 +++++++ .../monthly_rent_before_staircasing_spec.rb | 37 +++++++ .../form/sales/questions/mortgageused_spec.rb | 98 +++++++++++++++---- .../sales/questions/staircase_count_spec.rb | 33 +++++++ .../questions/staircase_first_time_spec.rb | 40 ++++++++ .../questions/staircase_initial_date_spec.rb | 33 +++++++ .../questions/staircase_last_date_spec.rb | 33 +++++++ .../sales/questions/staircase_sale_spec.rb | 4 + .../models/form/sales/questions/value_spec.rb | 6 +- .../sales/sections/sale_information_spec.rb | 11 ++- .../shared_ownership_initial_purchase_spec.rb | 2 +- .../sale_information_validations_spec.rb | 40 ++++++++ 52 files changed, 902 insertions(+), 70 deletions(-) create mode 100644 app/models/form/sales/pages/monthly_rent_staircasing.rb create mode 100644 app/models/form/sales/pages/monthly_rent_staircasing_owned.rb create mode 100644 app/models/form/sales/pages/staircase_first_time.rb create mode 100644 app/models/form/sales/pages/staircase_initial_date.rb create mode 100644 app/models/form/sales/pages/staircase_previous.rb create mode 100644 app/models/form/sales/pages/staircase_sale.rb create mode 100644 app/models/form/sales/questions/monthly_rent_after_staircasing.rb create mode 100644 app/models/form/sales/questions/monthly_rent_before_staircasing.rb create mode 100644 app/models/form/sales/questions/staircase_count.rb create mode 100644 app/models/form/sales/questions/staircase_first_time.rb create mode 100644 app/models/form/sales/questions/staircase_initial_date.rb create mode 100644 app/models/form/sales/questions/staircase_last_date.rb create mode 100644 app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb create mode 100644 db/migrate/20241114173226_add_fields_to_sales_log.rb create mode 100644 spec/models/form/sales/pages/monthly_rent_staircasing_owned_spec.rb create mode 100644 spec/models/form/sales/pages/monthly_rent_staircasing_spec.rb create mode 100644 spec/models/form/sales/pages/staircase_first_time_spec.rb create mode 100644 spec/models/form/sales/pages/staircase_initial_date_spec.rb create mode 100644 spec/models/form/sales/pages/staircase_previous_spec.rb create mode 100644 spec/models/form/sales/pages/staircase_sale_spec.rb create mode 100644 spec/models/form/sales/questions/monthly_rent_after_staircasing_spec.rb create mode 100644 spec/models/form/sales/questions/monthly_rent_before_staircasing_spec.rb create mode 100644 spec/models/form/sales/questions/staircase_count_spec.rb create mode 100644 spec/models/form/sales/questions/staircase_first_time_spec.rb create mode 100644 spec/models/form/sales/questions/staircase_initial_date_spec.rb create mode 100644 spec/models/form/sales/questions/staircase_last_date_spec.rb diff --git a/app/models/derived_variables/sales_log_variables.rb b/app/models/derived_variables/sales_log_variables.rb index 0c13d4fdf..4f4c3105d 100644 --- a/app/models/derived_variables/sales_log_variables.rb +++ b/app/models/derived_variables/sales_log_variables.rb @@ -80,6 +80,9 @@ module DerivedVariables::SalesLogVariables self.is_la_inferred = false end + self.numstair = is_firststair? ? 1 : nil if numstair == 1 && firststair_changed? + self.mrent = 0 if stairowned_100? + set_encoded_derived_values!(DEPENDENCIES) end diff --git a/app/models/form/sales/pages/about_staircase.rb b/app/models/form/sales/pages/about_staircase.rb index d736bae15..2d42c7456 100644 --- a/app/models/form/sales/pages/about_staircase.rb +++ b/app/models/form/sales/pages/about_staircase.rb @@ -18,7 +18,7 @@ class Form::Sales::Pages::AboutStaircase < ::Form::Page end def staircase_sale_question - if form.start_date.year >= 2023 + if [2023, 2024].include?(form.start_date.year) Form::Sales::Questions::StaircaseSale.new(nil, nil, self) end end diff --git a/app/models/form/sales/pages/equity.rb b/app/models/form/sales/pages/equity.rb index 46eec40a3..12d3c0a1b 100644 --- a/app/models/form/sales/pages/equity.rb +++ b/app/models/form/sales/pages/equity.rb @@ -1,7 +1,7 @@ class Form::Sales::Pages::Equity < ::Form::Page def initialize(id, hsh, subsection) super - @id = "equity" + @copy_key = "sales.sale_information.equity" end def questions diff --git a/app/models/form/sales/pages/monthly_rent_staircasing.rb b/app/models/form/sales/pages/monthly_rent_staircasing.rb new file mode 100644 index 000000000..062439c52 --- /dev/null +++ b/app/models/form/sales/pages/monthly_rent_staircasing.rb @@ -0,0 +1,17 @@ +class Form::Sales::Pages::MonthlyRentStaircasing < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "monthly_rent_staircasing" + @copy_key = "sales.sale_information.mrent_staircasing" + @depends_on = [{ + "stairowned_100?" => false, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::MonthlyRentBeforeStaircasing.new(nil, nil, self), + Form::Sales::Questions::MonthlyRentAfterStaircasing.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/monthly_rent_staircasing_owned.rb b/app/models/form/sales/pages/monthly_rent_staircasing_owned.rb new file mode 100644 index 000000000..b772d129f --- /dev/null +++ b/app/models/form/sales/pages/monthly_rent_staircasing_owned.rb @@ -0,0 +1,17 @@ +class Form::Sales::Pages::MonthlyRentStaircasingOwned < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "monthly_rent_staircasing_owned" + @copy_key = "sales.sale_information.mrent_staircasing" + @header = "" + @depends_on = [{ + "stairowned_100?" => true, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::MonthlyRentBeforeStaircasing.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/staircase_first_time.rb b/app/models/form/sales/pages/staircase_first_time.rb new file mode 100644 index 000000000..239a2a930 --- /dev/null +++ b/app/models/form/sales/pages/staircase_first_time.rb @@ -0,0 +1,15 @@ +class Form::Sales::Pages::StaircaseFirstTime < ::Form::Page + def initialize(id, hsh, subsection) + super(id, hsh, subsection) + @id = "staircase_first_time" + @depends_on = [{ + "staircase" => 1, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::StaircaseFirstTime.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/staircase_initial_date.rb b/app/models/form/sales/pages/staircase_initial_date.rb new file mode 100644 index 000000000..9404440f4 --- /dev/null +++ b/app/models/form/sales/pages/staircase_initial_date.rb @@ -0,0 +1,16 @@ +class Form::Sales::Pages::StaircaseInitialDate < ::Form::Page + def initialize(id, hsh, subsection) + super(id, hsh, subsection) + @id = "staircase_initial_date" + @header = "" + @depends_on = [{ + "is_firststair?" => true, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::StaircaseInitialDate.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/staircase_previous.rb b/app/models/form/sales/pages/staircase_previous.rb new file mode 100644 index 000000000..30d0139ab --- /dev/null +++ b/app/models/form/sales/pages/staircase_previous.rb @@ -0,0 +1,18 @@ +class Form::Sales::Pages::StaircasePrevious < ::Form::Page + def initialize(id, hsh, subsection) + super(id, hsh, subsection) + @id = "staircase_previous" + @copy_key = "sales.sale_information.stairprevious" + @depends_on = [{ + "is_firststair?" => false, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::StaircaseCount.new(nil, nil, self), + Form::Sales::Questions::StaircaseLastDate.new(nil, nil, self), + Form::Sales::Questions::StaircaseInitialDate.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/staircase_sale.rb b/app/models/form/sales/pages/staircase_sale.rb new file mode 100644 index 000000000..116db72b5 --- /dev/null +++ b/app/models/form/sales/pages/staircase_sale.rb @@ -0,0 +1,17 @@ +class Form::Sales::Pages::StaircaseSale < ::Form::Page + def initialize(id, hsh, subsection) + super(id, hsh, subsection) + @id = "staircase_sale" + @copy_key = form.start_year_2025_or_later? ? "sales.sale_information.staircasesale" : "sales.sale_information.about_staircasing.staircasesale" + @depends_on = [{ + "staircase" => 1, + "stairowned" => 100, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::StaircaseSale.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/value_shared_ownership.rb b/app/models/form/sales/pages/value_shared_ownership.rb index 200563053..bf628b3f8 100644 --- a/app/models/form/sales/pages/value_shared_ownership.rb +++ b/app/models/form/sales/pages/value_shared_ownership.rb @@ -1,7 +1,7 @@ class Form::Sales::Pages::ValueSharedOwnership < ::Form::Page def initialize(id, hsh, subsection) super - @id = "value_shared_ownership" + @copy_key = "sales.sale_information.value" end def questions diff --git a/app/models/form/sales/questions/equity.rb b/app/models/form/sales/questions/equity.rb index 7a2a4ce5b..e39e77ebb 100644 --- a/app/models/form/sales/questions/equity.rb +++ b/app/models/form/sales/questions/equity.rb @@ -2,6 +2,7 @@ class Form::Sales::Questions::Equity < ::Form::Question def initialize(id, hsh, page) super @id = "equity" + @copy_key = "sales.sale_information.equity.#{page.id}" @type = "numeric" @min = 0 @max = 100 diff --git a/app/models/form/sales/questions/monthly_rent_after_staircasing.rb b/app/models/form/sales/questions/monthly_rent_after_staircasing.rb new file mode 100644 index 000000000..1116abb7b --- /dev/null +++ b/app/models/form/sales/questions/monthly_rent_after_staircasing.rb @@ -0,0 +1,15 @@ +class Form::Sales::Questions::MonthlyRentAfterStaircasing < ::Form::Question + def initialize(id, hsh, page) + super + @id = "mrent" + @copy_key = "sales.sale_information.mrent_staircasing.poststaircasing" + @type = "numeric" + @min = 0 + @step = 0.01 + @width = 5 + @prefix = "£" + @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + end + + QUESTION_NUMBER_FROM_YEAR = { 2025 => 99 }.freeze +end diff --git a/app/models/form/sales/questions/monthly_rent_before_staircasing.rb b/app/models/form/sales/questions/monthly_rent_before_staircasing.rb new file mode 100644 index 000000000..a7966447a --- /dev/null +++ b/app/models/form/sales/questions/monthly_rent_before_staircasing.rb @@ -0,0 +1,15 @@ +class Form::Sales::Questions::MonthlyRentBeforeStaircasing < ::Form::Question + def initialize(id, hsh, page) + super + @id = "mrentprestaircasing" + @copy_key = "sales.sale_information.mrent_staircasing.prestaircasing" + @type = "numeric" + @min = 0 + @step = 0.01 + @width = 5 + @prefix = "£" + @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + end + + QUESTION_NUMBER_FROM_YEAR = { 2025 => 98 }.freeze +end diff --git a/app/models/form/sales/questions/mortgageused.rb b/app/models/form/sales/questions/mortgageused.rb index 3c3c42840..1c683384b 100644 --- a/app/models/form/sales/questions/mortgageused.rb +++ b/app/models/form/sales/questions/mortgageused.rb @@ -12,7 +12,7 @@ class Form::Sales::Questions::Mortgageused < ::Form::Question def displayed_answer_options(log, _user = nil) if log.outright_sale? && log.saledate && !form.start_year_2024_or_later? answer_options_without_dont_know - elsif log.stairowned == 100 || log.outright_sale? + elsif log.stairowned_100? || log.outright_sale? || (log.is_staircase? && form.start_year_2025_or_later?) ANSWER_OPTIONS else answer_options_without_dont_know diff --git a/app/models/form/sales/questions/staircase_count.rb b/app/models/form/sales/questions/staircase_count.rb new file mode 100644 index 000000000..07095cd6a --- /dev/null +++ b/app/models/form/sales/questions/staircase_count.rb @@ -0,0 +1,15 @@ +class Form::Sales::Questions::StaircaseCount < ::Form::Question + def initialize(id, hsh, page) + super + @id = "numstair" + @copy_key = "sales.sale_information.stairprevious.numstair" + @type = "numeric" + @width = 2 + @min = 2 + @max = 10 + @step = 1 + @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + end + + QUESTION_NUMBER_FROM_YEAR = { 2025 => 82 }.freeze +end diff --git a/app/models/form/sales/questions/staircase_first_time.rb b/app/models/form/sales/questions/staircase_first_time.rb new file mode 100644 index 000000000..fed2c34fb --- /dev/null +++ b/app/models/form/sales/questions/staircase_first_time.rb @@ -0,0 +1,16 @@ +class Form::Sales::Questions::StaircaseFirstTime < ::Form::Question + def initialize(id, hsh, page) + super + @id = "firststair" + @type = "radio" + @answer_options = ANSWER_OPTIONS + @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + end + + ANSWER_OPTIONS = { + "1" => { "value" => "Yes" }, + "2" => { "value" => "No" }, + }.freeze + + QUESTION_NUMBER_FROM_YEAR = { 2025 => 81 }.freeze +end diff --git a/app/models/form/sales/questions/staircase_initial_date.rb b/app/models/form/sales/questions/staircase_initial_date.rb new file mode 100644 index 000000000..b810d6689 --- /dev/null +++ b/app/models/form/sales/questions/staircase_initial_date.rb @@ -0,0 +1,11 @@ +class Form::Sales::Questions::StaircaseInitialDate < ::Form::Question + def initialize(id, hsh, page) + super + @id = "initialpurchase" + @copy_key = "sales.sale_information.stairprevious.initialpurchase" + @type = "date" + @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + end + + QUESTION_NUMBER_FROM_YEAR = { 2025 => 83 }.freeze +end diff --git a/app/models/form/sales/questions/staircase_last_date.rb b/app/models/form/sales/questions/staircase_last_date.rb new file mode 100644 index 000000000..edb75e823 --- /dev/null +++ b/app/models/form/sales/questions/staircase_last_date.rb @@ -0,0 +1,11 @@ +class Form::Sales::Questions::StaircaseLastDate < ::Form::Question + def initialize(id, hsh, page) + super + @id = "lasttransaction" + @copy_key = "sales.sale_information.stairprevious.lasttransaction" + @type = "date" + @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + end + + QUESTION_NUMBER_FROM_YEAR = { 2025 => 83 }.freeze +end diff --git a/app/models/form/sales/questions/staircase_sale.rb b/app/models/form/sales/questions/staircase_sale.rb index ac54084f5..de0977ecb 100644 --- a/app/models/form/sales/questions/staircase_sale.rb +++ b/app/models/form/sales/questions/staircase_sale.rb @@ -2,7 +2,7 @@ class Form::Sales::Questions::StaircaseSale < ::Form::Question def initialize(id, hsh, page) super @id = "staircasesale" - @copy_key = "sales.sale_information.about_staircasing.staircasesale" + @copy_key = form.start_year_2025_or_later? ? "sales.sale_information.staircasesale" : "sales.sale_information.about_staircasing.staircasesale" @type = "radio" @answer_options = ANSWER_OPTIONS @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] diff --git a/app/models/form/sales/questions/value.rb b/app/models/form/sales/questions/value.rb index 1d258899d..ad021e920 100644 --- a/app/models/form/sales/questions/value.rb +++ b/app/models/form/sales/questions/value.rb @@ -2,6 +2,7 @@ class Form::Sales::Questions::Value < ::Form::Question def initialize(id, hsh, page) super @id = "value" + @copy_key = "sales.sale_information.value.#{page.id}" @type = "numeric" @min = 0 @step = 1 diff --git a/app/models/form/sales/sections/sale_information.rb b/app/models/form/sales/sections/sale_information.rb index 22dbbef5a..fc2180529 100644 --- a/app/models/form/sales/sections/sale_information.rb +++ b/app/models/form/sales/sections/sale_information.rb @@ -4,18 +4,20 @@ class Form::Sales::Sections::SaleInformation < ::Form::Section @id = "sale_information" @label = "Sale information" @description = "" - @subsections = [ - shared_ownership_scheme_subsection, - Form::Sales::Subsections::DiscountedOwnershipScheme.new(nil, nil, self), - Form::Sales::Subsections::OutrightSale.new(nil, nil, self), - ] || [] + @subsections = [] + @subsections.concat(shared_ownership_scheme_subsection) + @subsections << Form::Sales::Subsections::DiscountedOwnershipScheme.new(nil, nil, self) + @subsections << Form::Sales::Subsections::OutrightSale.new(nil, nil, self) end def shared_ownership_scheme_subsection if form.start_year_2025_or_later? - Form::Sales::Subsections::SharedOwnershipInitialPurchase.new(nil, nil, self) + [ + Form::Sales::Subsections::SharedOwnershipInitialPurchase.new(nil, nil, self), + Form::Sales::Subsections::SharedOwnershipStaircasingTransaction.new(nil, nil, self), + ] else - Form::Sales::Subsections::SharedOwnershipScheme.new(nil, nil, self) + [Form::Sales::Subsections::SharedOwnershipScheme.new(nil, nil, self)] end end end diff --git a/app/models/form/sales/subsections/shared_ownership_initial_purchase.rb b/app/models/form/sales/subsections/shared_ownership_initial_purchase.rb index 5dfb322a2..175994b0b 100644 --- a/app/models/form/sales/subsections/shared_ownership_initial_purchase.rb +++ b/app/models/form/sales/subsections/shared_ownership_initial_purchase.rb @@ -4,6 +4,7 @@ class Form::Sales::Subsections::SharedOwnershipInitialPurchase < ::Form::Subsect @id = "shared_ownership_initial_purchase" @label = "Shared ownership - initial purchase" @depends_on = [{ "ownershipsch" => 1, "setup_completed?" => true, "staircase" => 2 }] + @copy_key = "sale_information" end def pages @@ -18,9 +19,9 @@ class Form::Sales::Subsections::SharedOwnershipInitialPurchase < ::Form::Subsect Form::Sales::Pages::PreviousBedrooms.new(nil, nil, self), Form::Sales::Pages::PreviousPropertyType.new(nil, nil, self), Form::Sales::Pages::PreviousTenure.new(nil, nil, self), - Form::Sales::Pages::ValueSharedOwnership.new(nil, nil, self), + Form::Sales::Pages::ValueSharedOwnership.new("value_shared_ownership", nil, self), Form::Sales::Pages::AboutPriceValueCheck.new("about_price_shared_ownership_value_check", nil, self), - Form::Sales::Pages::Equity.new(nil, nil, self), + Form::Sales::Pages::Equity.new("initial_equity", nil, self), Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_equity_value_check", nil, self), Form::Sales::Pages::Mortgageused.new("mortgage_used_shared_ownership", nil, self, ownershipsch: 1), Form::Sales::Pages::MortgageValueCheck.new("mortgage_used_mortgage_value_check", nil, self), diff --git a/app/models/form/sales/subsections/shared_ownership_scheme.rb b/app/models/form/sales/subsections/shared_ownership_scheme.rb index 20a088eae..c0718e009 100644 --- a/app/models/form/sales/subsections/shared_ownership_scheme.rb +++ b/app/models/form/sales/subsections/shared_ownership_scheme.rb @@ -27,9 +27,9 @@ class Form::Sales::Subsections::SharedOwnershipScheme < ::Form::Subsection Form::Sales::Pages::PreviousBedrooms.new(nil, nil, self), Form::Sales::Pages::PreviousPropertyType.new(nil, nil, self), Form::Sales::Pages::PreviousTenure.new(nil, nil, self), - Form::Sales::Pages::ValueSharedOwnership.new(nil, nil, self), + Form::Sales::Pages::ValueSharedOwnership.new("value_shared_ownership", nil, self), Form::Sales::Pages::AboutPriceValueCheck.new("about_price_shared_ownership_value_check", nil, self), - Form::Sales::Pages::Equity.new(nil, nil, self), + Form::Sales::Pages::Equity.new("equity", nil, self), Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_equity_value_check", nil, self), Form::Sales::Pages::Mortgageused.new("mortgage_used_shared_ownership", nil, self, ownershipsch: 1), Form::Sales::Pages::MortgageValueCheck.new("mortgage_used_mortgage_value_check", nil, self), diff --git a/app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb b/app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb new file mode 100644 index 000000000..000a0c800 --- /dev/null +++ b/app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb @@ -0,0 +1,35 @@ +class Form::Sales::Subsections::SharedOwnershipStaircasingTransaction < ::Form::Subsection + def initialize(id, hsh, section) + super + @id = "shared_ownership_staircasing_transaction" + @label = "Shared ownership - staircasing transaction" + @depends_on = [{ "ownershipsch" => 1, "setup_completed?" => true, "staircase" => 1 }] + @copy_key = "sale_information" + end + + def pages + @pages ||= [ + Form::Sales::Pages::AboutStaircase.new("about_staircasing_joint_purchase", nil, self, joint_purchase: true), + Form::Sales::Pages::AboutStaircase.new("about_staircasing_not_joint_purchase", nil, self, joint_purchase: false), + Form::Sales::Pages::StaircaseSale.new(nil, nil, self), + Form::Sales::Pages::StaircaseBoughtValueCheck.new(nil, nil, self), + Form::Sales::Pages::StaircaseOwnedValueCheck.new("staircase_owned_value_check_joint_purchase", nil, self, joint_purchase: true), + Form::Sales::Pages::StaircaseOwnedValueCheck.new("staircase_owned_value_check_not_joint_purchase", nil, self, joint_purchase: false), + Form::Sales::Pages::StaircaseFirstTime.new(nil, nil, self), + Form::Sales::Pages::StaircasePrevious.new(nil, nil, self), + Form::Sales::Pages::StaircaseInitialDate.new(nil, nil, self), + Form::Sales::Pages::ValueSharedOwnership.new("value_shared_ownership_staircase", nil, self), + Form::Sales::Pages::AboutPriceValueCheck.new("about_price_shared_ownership_value_check", nil, self), + Form::Sales::Pages::Equity.new("staircase_equity", nil, self), + Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_equity_value_check", nil, self), + Form::Sales::Pages::Mortgageused.new("staircase_mortgage_used_shared_ownership", nil, self, ownershipsch: 1), + Form::Sales::Pages::MonthlyRentStaircasingOwned.new(nil, nil, self), + Form::Sales::Pages::MonthlyRentStaircasing.new(nil, nil, self), + Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_shared_ownership_value_check", nil, self), + ].compact + end + + def displayed_in_tasklist?(log) + log.staircase == 1 && (log.ownershipsch.nil? || log.ownershipsch == 1) + end +end diff --git a/app/models/sales_log.rb b/app/models/sales_log.rb index 01741fbc5..361aab6f6 100644 --- a/app/models/sales_log.rb +++ b/app/models/sales_log.rb @@ -557,4 +557,8 @@ class SalesLog < Log def is_resale? resale == 1 end + + def is_firststair? + firststair == 1 + end end diff --git a/app/models/validations/sales/sale_information_validations.rb b/app/models/validations/sales/sale_information_validations.rb index fa095a5e2..3825271c5 100644 --- a/app/models/validations/sales/sale_information_validations.rb +++ b/app/models/validations/sales/sale_information_validations.rb @@ -35,6 +35,14 @@ module Validations::Sales::SaleInformationValidations end end + def validate_staircasing_initial_purchase_date(record) + return unless record.initialpurchase + + if record.initialpurchase < Time.zone.local(1980, 1, 1) + record.errors.add :initialpurchase, I18n.t("validations.sales.sale_information.initialpurchase.must_be_after_1980") + end + end + def validate_previous_property_unit_type(record) return unless record.fromprop && record.frombeds @@ -351,6 +359,15 @@ module Validations::Sales::SaleInformationValidations end end + def validate_number_of_staircase_transactions(record) + return unless record.numstair + + if record.firststair == 2 && record.numstair < 2 + record.errors.add :numstair, I18n.t("validations.sales.sale_information.numstair.must_be_greater_than_one") + record.errors.add :firststair, I18n.t("validations.sales.sale_information.firststair.cannot_be_no") + end + end + def over_tolerance?(expected, actual, tolerance, strict: false) if strict (expected - actual).abs > tolerance diff --git a/config/locales/forms/2025/sales/sale_information.en.yml b/config/locales/forms/2025/sales/sale_information.en.yml index 9a273d1c3..0535caca1 100644 --- a/config/locales/forms/2025/sales/sale_information.en.yml +++ b/config/locales/forms/2025/sales/sale_information.en.yml @@ -26,24 +26,47 @@ en: question_text: "Did the buyer live in the property before purchasing it?" about_staircasing: - page_header: "About the staircasing transaction" - stairbought: - check_answer_label: "Percentage bought in this staircasing transaction" + page_header: "About the staircasing transaction" + stairbought: + check_answer_label: "Percentage bought in this staircasing transaction" + hint_text: "" + question_text: "What percentage of the property has been bought in this staircasing transaction?" + stairowned: + joint_purchase: + check_answer_label: "Percentage the buyers now own in total" hint_text: "" - question_text: "What percentage of the property has been bought in this staircasing transaction?" - stairowned: - joint_purchase: - check_answer_label: "Percentage the buyers now own in total" - hint_text: "" - question_text: "What percentage of the property do the buyers now own in total?" - not_joint_purchase: - check_answer_label: "Percentage the buyer now owns in total" - hint_text: "" - question_text: "What percentage of the property does the buyer now own in total?" - staircasesale: - check_answer_label: "Part of a back-to-back staircasing transaction" + question_text: "What percentage of the property do the buyers now own in total?" + not_joint_purchase: + check_answer_label: "Percentage the buyer now owns in total" hint_text: "" - question_text: "Is this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?" + question_text: "What percentage of the property does the buyer now own in total?" + + staircasesale: + page_header: "" + check_answer_label: "Part of a back-to-back staircasing transaction?" + hint_text: "" + question_text: "Is this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?" + + firststair: + page_header: "" + check_answer_label: "First time staircasing?" + hint_text: "" + question_text: "Is this the first time the shared owner has engaged in staircasing in the home?" + + stairprevious: + page_header: "About previous staircasing transactions" + numstair: + check_answer_label: "Number of staircasing transactions" + hint_text: "" + question_text: "Including this time, how many times has the shared owner engaged in staircasing in the home?" + initialpurchase: + check_answer_label: "Initial staircasing transaction" + hint_text: "" + question_text: "What was the date of the initial purchase of a share in the property?" + lasttransaction: + check_answer_label: "Last staircasing transaction" + hint_text: "" + question_text: "What was the date of the last staircasing transaction?" resale: page_header: "" @@ -101,19 +124,29 @@ en: value: page_header: "About the price of the property" - check_answer_label: "Full purchase price" - hint_text: "Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser)" - question_text: "What was the full purchase price?" + value_shared_ownership: + check_answer_label: "Full purchase price" + hint_text: "Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser)." + question_text: "What was the full purchase price?" + value_shared_ownership_staircase: + check_answer_label: "Full purchase price" + hint_text: "Enter the full purchase price paid for the equity bought in this staircasing transaction (this is equal to the value of the share bought by the purchaser)." + question_text: "What was the full purchase price for this staircasing transaction?" equity: page_header: "About the price of the property" - check_answer_label: "Initial percentage equity share" - hint_text: "Enter the amount of initial equity share held by the purchaser (for example, 25% or 50%)" - question_text: "What was the initial percentage share purchased?" + initial_equity: + check_answer_label: "Initial percentage equity share" + hint_text: "Enter the amount of initial equity share held by the purchaser (for example, 25% or 50%)" + question_text: "What was the initial percentage share purchased?" + staircase_equity: + check_answer_label: "Initial percentage equity share" + hint_text: "Enter the amount of initial equity share held by the purchaser (for example, 25% or 50%)" + question_text: "What was the percentage shared purchased in the initial transaction?" mortgageused: page_header: "Mortgage Amount" - check_answer_label: "Mortgage used" + check_answer_label: "Mortgage used?" hint_text: "" question_text: "Was a mortgage used for the purchase of this property?" @@ -165,6 +198,17 @@ en: hint_text: "Amount paid before any charges" question_text: "What is the basic monthly rent?" + mrent_staircasing: + page_header: "Monthly rent" + prestaircasing: + check_answer_label: "Monthly rent prior to staircasing" + hint_text: "Amount paid before any charges" + question_text: "What was the basic monthly rent prior to staircasing?" + poststaircasing: + check_answer_label: "Monthly rent after staircasing" + hint_text: "Amount paid before any charges" + question_text: "What is the basic monthly rent after staircasing?" + leaseholdcharges: page_header: "" has_mscharge: @@ -199,7 +243,7 @@ en: check_answer_label: "Amount of any loan, grant or subsidy" hint_text: "For all schemes except Right to Buy (RTB), Preserved Right to Buy (PRTB), Voluntary Right to Buy (VRTB) and Rent to Buy" question_text: "What was the amount of any loan, grant, discount or subsidy given?" - + management_fee: page_header: "" has_management_fee: diff --git a/config/locales/validations/sales/sale_information.en.yml b/config/locales/validations/sales/sale_information.en.yml index 8fb7d02d4..ea17953fb 100644 --- a/config/locales/validations/sales/sale_information.en.yml +++ b/config/locales/validations/sales/sale_information.en.yml @@ -19,6 +19,8 @@ en: exdate: must_be_before_saledate: "Contract exchange date must be before sale completion date." must_be_less_than_1_year_from_saledate: "Contract exchange date must be less than 1 year before sale completion date." + initialpurchase: + must_be_after_1980: "The initial purchase date must be after January 1, 1980." fromprop: previous_property_type_bedsit: "A bedsit cannot have more than 1 bedroom." frombeds: @@ -125,3 +127,7 @@ en: postcode_full: value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London." value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London." + numstair: + must_be_greater_than_one: "The number of staircasing transactions must be greater than 1 when this is not the first staircasing transaction." + firststair: + cannot_be_no: "The answer to 'Is this the first staircasing transaction?' cannot be 'no' if the number of staircasing transactions is 1." diff --git a/db/migrate/20241114173226_add_fields_to_sales_log.rb b/db/migrate/20241114173226_add_fields_to_sales_log.rb new file mode 100644 index 000000000..2f7dbbd2b --- /dev/null +++ b/db/migrate/20241114173226_add_fields_to_sales_log.rb @@ -0,0 +1,11 @@ +class AddFieldsToSalesLog < ActiveRecord::Migration[7.0] + def change + change_table :sales_logs, bulk: true do |t| + t.column :firststair, :integer + t.column :numstair, :integer + t.column :mrentprestaircasing, :decimal, precision: 10, scale: 2 + t.column :lasttransaction, :datetime + t.column :initialpurchase, :datetime + end + end +end diff --git a/db/schema.rb b/db/schema.rb index c53872020..0cdc15e9f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -761,6 +761,11 @@ ActiveRecord::Schema[7.0].define(version: 2024_11_22_154743) do t.bigint "created_by_id" t.integer "has_management_fee" t.decimal "management_fee", precision: 10, scale: 2 + t.integer "firststair" + t.integer "numstair" + t.decimal "mrentprestaircasing", precision: 10, scale: 2 + t.datetime "lasttransaction" + t.datetime "initialpurchase" t.index ["assigned_to_id"], name: "index_sales_logs_on_assigned_to_id" t.index ["bulk_upload_id"], name: "index_sales_logs_on_bulk_upload_id" t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id" diff --git a/spec/models/form/sales/pages/about_staircase_spec.rb b/spec/models/form/sales/pages/about_staircase_spec.rb index 181c3f695..7d8254d4b 100644 --- a/spec/models/form/sales/pages/about_staircase_spec.rb +++ b/spec/models/form/sales/pages/about_staircase_spec.rb @@ -15,6 +15,10 @@ RSpec.describe Form::Sales::Pages::AboutStaircase, type: :model do describe "questions" do let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date:)) } + before do + allow(subsection.form).to receive(:start_year_2025_or_later?).and_return(false) + end + context "when 2022" do let(:start_date) { Time.utc(2022, 2, 8) } diff --git a/spec/models/form/sales/pages/equity_spec.rb b/spec/models/form/sales/pages/equity_spec.rb index a44085101..83a5dfaa3 100644 --- a/spec/models/form/sales/pages/equity_spec.rb +++ b/spec/models/form/sales/pages/equity_spec.rb @@ -15,10 +15,6 @@ RSpec.describe Form::Sales::Pages::Equity, type: :model do expect(page.questions.map(&:id)).to eq(%w[equity]) end - it "has the correct id" do - expect(page.id).to eq("equity") - end - it "has the correct description" do expect(page.description).to be_nil end diff --git a/spec/models/form/sales/pages/monthly_rent_staircasing_owned_spec.rb b/spec/models/form/sales/pages/monthly_rent_staircasing_owned_spec.rb new file mode 100644 index 000000000..21f0e0ee6 --- /dev/null +++ b/spec/models/form/sales/pages/monthly_rent_staircasing_owned_spec.rb @@ -0,0 +1,31 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::MonthlyRentStaircasingOwned, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[mrentprestaircasing]) + end + + it "has the correct id" do + expect(page.id).to eq("monthly_rent_staircasing_owned") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([ + { "stairowned_100?" => true }, + ]) + end +end diff --git a/spec/models/form/sales/pages/monthly_rent_staircasing_spec.rb b/spec/models/form/sales/pages/monthly_rent_staircasing_spec.rb new file mode 100644 index 000000000..347e105fd --- /dev/null +++ b/spec/models/form/sales/pages/monthly_rent_staircasing_spec.rb @@ -0,0 +1,31 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::MonthlyRentStaircasing, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[mrentprestaircasing mrent]) + end + + it "has the correct id" do + expect(page.id).to eq("monthly_rent_staircasing") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([ + { "stairowned_100?" => false }, + ]) + end +end diff --git a/spec/models/form/sales/pages/staircase_first_time_spec.rb b/spec/models/form/sales/pages/staircase_first_time_spec.rb new file mode 100644 index 000000000..9c7d713af --- /dev/null +++ b/spec/models/form/sales/pages/staircase_first_time_spec.rb @@ -0,0 +1,31 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::StaircaseFirstTime, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[firststair]) + end + + it "has the correct id" do + expect(page.id).to eq("staircase_first_time") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([ + { "staircase" => 1 }, + ]) + end +end diff --git a/spec/models/form/sales/pages/staircase_initial_date_spec.rb b/spec/models/form/sales/pages/staircase_initial_date_spec.rb new file mode 100644 index 000000000..de5806500 --- /dev/null +++ b/spec/models/form/sales/pages/staircase_initial_date_spec.rb @@ -0,0 +1,31 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::StaircaseInitialDate, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[initialpurchase]) + end + + it "has the correct id" do + expect(page.id).to eq("staircase_initial_date") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([ + { "is_firststair?" => true }, + ]) + end +end diff --git a/spec/models/form/sales/pages/staircase_previous_spec.rb b/spec/models/form/sales/pages/staircase_previous_spec.rb new file mode 100644 index 000000000..336cf0454 --- /dev/null +++ b/spec/models/form/sales/pages/staircase_previous_spec.rb @@ -0,0 +1,31 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::StaircasePrevious, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[numstair lasttransaction initialpurchase]) + end + + it "has the correct id" do + expect(page.id).to eq("staircase_previous") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([ + { "is_firststair?" => false }, + ]) + end +end diff --git a/spec/models/form/sales/pages/staircase_sale_spec.rb b/spec/models/form/sales/pages/staircase_sale_spec.rb new file mode 100644 index 000000000..17a140797 --- /dev/null +++ b/spec/models/form/sales/pages/staircase_sale_spec.rb @@ -0,0 +1,38 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::StaircaseSale, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) } + + before do + allow(subsection.form).to receive(:start_year_2025_or_later?).and_return(true) + end + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[staircasesale]) + end + + it "has the correct id" do + expect(page.id).to eq("staircase_sale") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([ + { + "staircase" => 1, + "stairowned" => 100, + }, + ]) + end +end diff --git a/spec/models/form/sales/pages/value_shared_ownership_spec.rb b/spec/models/form/sales/pages/value_shared_ownership_spec.rb index f8232894b..eb1b1099f 100644 --- a/spec/models/form/sales/pages/value_shared_ownership_spec.rb +++ b/spec/models/form/sales/pages/value_shared_ownership_spec.rb @@ -3,7 +3,7 @@ require "rails_helper" RSpec.describe Form::Sales::Pages::ValueSharedOwnership, type: :model do subject(:page) { described_class.new(page_id, page_definition, subsection) } - let(:page_id) { nil } + let(:page_id) { "value_shared_ownership" } let(:page_definition) { nil } let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1))) } diff --git a/spec/models/form/sales/questions/equity_spec.rb b/spec/models/form/sales/questions/equity_spec.rb index f58c042f5..5083af9e8 100644 --- a/spec/models/form/sales/questions/equity_spec.rb +++ b/spec/models/form/sales/questions/equity_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Form::Sales::Questions::Equity, type: :model do let(:question_id) { nil } let(:question_definition) { nil } - let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } + let(:page) { instance_double(Form::Page, id: "initial_equity", subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } it "has correct page" do expect(question.page).to eq(page) diff --git a/spec/models/form/sales/questions/monthly_rent_after_staircasing_spec.rb b/spec/models/form/sales/questions/monthly_rent_after_staircasing_spec.rb new file mode 100644 index 000000000..4ceb3df00 --- /dev/null +++ b/spec/models/form/sales/questions/monthly_rent_after_staircasing_spec.rb @@ -0,0 +1,37 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::MonthlyRentAfterStaircasing, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("mrent") + end + + it "has the correct type" do + expect(question.type).to eq("numeric") + end + + it "is not marked as derived" do + expect(question.derived?(nil)).to be false + end + + it "has correct width" do + expect(question.width).to eq(5) + end + + it "has correct prefix" do + expect(question.prefix).to eq("£") + end + + it "has correct min" do + expect(question.min).to eq(0) + end +end diff --git a/spec/models/form/sales/questions/monthly_rent_before_staircasing_spec.rb b/spec/models/form/sales/questions/monthly_rent_before_staircasing_spec.rb new file mode 100644 index 000000000..8d7d864a8 --- /dev/null +++ b/spec/models/form/sales/questions/monthly_rent_before_staircasing_spec.rb @@ -0,0 +1,37 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::MonthlyRentBeforeStaircasing, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("mrentprestaircasing") + end + + it "has the correct type" do + expect(question.type).to eq("numeric") + end + + it "is not marked as derived" do + expect(question.derived?(nil)).to be false + end + + it "has correct width" do + expect(question.width).to eq(5) + end + + it "has correct prefix" do + expect(question.prefix).to eq("£") + end + + it "has correct min" do + expect(question.min).to eq(0) + end +end diff --git a/spec/models/form/sales/questions/mortgageused_spec.rb b/spec/models/form/sales/questions/mortgageused_spec.rb index e85238a4d..971eb2909 100644 --- a/spec/models/form/sales/questions/mortgageused_spec.rb +++ b/spec/models/form/sales/questions/mortgageused_spec.rb @@ -3,28 +3,39 @@ require "rails_helper" RSpec.describe Form::Sales::Questions::Mortgageused, type: :model do subject(:question) { described_class.new(question_id, question_definition, page, ownershipsch:) } - let(:ownershipsch) { 1 } let(:question_id) { nil } let(:question_definition) { nil } - let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) } - let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form:)) } let(:stairowned) { nil } let(:staircase) { nil } let(:saledate) { Time.zone.today } let(:log) { build(:sales_log, :in_progress, ownershipsch:, stairowned:, staircase:) } - it "has the correct answer_options" do - expect(question.answer_options).to eq({ - "1" => { "value" => "Yes" }, - "2" => { "value" => "No" }, - "3" => { "value" => "Don’t know" }, - }) - end + context "when the form start year is 2024" do + let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form:)) } + let(:saledate) { Time.zone.local(2024, 5, 1) } + let(:ownershipsch) { 1 } + + before do + allow(form).to receive(:start_year_2024_or_later?).and_return true + allow(form).to receive(:start_year_2025_or_later?).and_return false + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq({ + "1" => { "value" => "Yes" }, + "2" => { "value" => "No" }, + "3" => { "value" => "Don’t know" }, + }) + end - describe "the displayed answer options" do context "when it is a discounted ownership sale" do let(:ownershipsch) { 2 } + it "shows the correct question number" do + expect(question.question_number).to eq 104 + end + it "does not show the don't know option" do expect_the_question_not_to_show_dont_know end @@ -34,20 +45,14 @@ RSpec.describe Form::Sales::Questions::Mortgageused, type: :model do let(:ownershipsch) { 3 } context "and the saledate is before 24/25" do - before do - allow(form).to receive(:start_year_2024_or_later?).and_return false - end + let(:saledate) { Time.zone.local(2023, 5, 1) }\ - it "does not show the don't know option" do - expect_the_question_not_to_show_dont_know + it "does show the don't know option" do + expect_the_question_to_show_dont_know end end - context "and the saledate is 24/25 or after" do - before do - allow(form).to receive(:start_year_2024_or_later?).and_return true - end - + context "and the saledate is 24/25" do it "shows the don't know option" do expect_the_question_to_show_dont_know end @@ -87,6 +92,57 @@ RSpec.describe Form::Sales::Questions::Mortgageused, type: :model do end end + context "when the form start year is 2025" do + let(:form) { instance_double(Form, start_date: Time.zone.local(2025, 4, 1)) } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form:)) } + let(:saledate) { Time.zone.local(2025, 5, 1) } + + before do + allow(form).to receive(:start_year_2024_or_later?).and_return true + allow(form).to receive(:start_year_2025_or_later?).and_return true + end + + context "when it is a discounted ownership sale" do + let(:ownershipsch) { 2 } + + it "shows the correct question number" do + expect(question.question_number).to eq 104 + end + + it "does not show the don't know option" do + expect_the_question_not_to_show_dont_know + end + end + + context "when it is a shared ownership scheme" do + let(:ownershipsch) { 1 } + + context "and it is a staircasing transaction" do + let(:staircase) { 1 } + + it "does show the don't know option" do + expect_the_question_to_show_dont_know + end + + context "and stairowned is 100" do + let(:stairowned) { 100 } + + it "shows the don't know option" do + expect_the_question_to_show_dont_know + end + end + end + + context "and it is not a staircasing transaction" do + let(:staircase) { 2 } + + it "does not show the don't know option" do + expect_the_question_not_to_show_dont_know + end + end + end + end + private def expect_the_question_not_to_show_dont_know diff --git a/spec/models/form/sales/questions/staircase_count_spec.rb b/spec/models/form/sales/questions/staircase_count_spec.rb new file mode 100644 index 000000000..06f39b3d0 --- /dev/null +++ b/spec/models/form/sales/questions/staircase_count_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::StaircaseCount, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))) } + + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(true) + end + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("numstair") + end + + it "has the correct type" do + expect(question.type).to eq("numeric") + end + + it "is not marked as derived" do + expect(question.derived?(nil)).to be false + end + + it "has correct conditional for" do + expect(question.conditional_for).to eq(nil) + end +end diff --git a/spec/models/form/sales/questions/staircase_first_time_spec.rb b/spec/models/form/sales/questions/staircase_first_time_spec.rb new file mode 100644 index 000000000..59d0281a2 --- /dev/null +++ b/spec/models/form/sales/questions/staircase_first_time_spec.rb @@ -0,0 +1,40 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::StaircaseFirstTime, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))) } + + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(true) + end + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("firststair") + end + + it "has the correct type" do + expect(question.type).to eq("radio") + end + + it "is not marked as derived" do + expect(question.derived?(nil)).to be false + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq({ + "1" => { "value" => "Yes" }, + "2" => { "value" => "No" }, + }) + end + + it "has correct conditional for" do + expect(question.conditional_for).to eq(nil) + end +end diff --git a/spec/models/form/sales/questions/staircase_initial_date_spec.rb b/spec/models/form/sales/questions/staircase_initial_date_spec.rb new file mode 100644 index 000000000..3c244e512 --- /dev/null +++ b/spec/models/form/sales/questions/staircase_initial_date_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::StaircaseInitialDate, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))) } + + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(true) + end + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("initialpurchase") + end + + it "has the correct type" do + expect(question.type).to eq("date") + end + + it "is not marked as derived" do + expect(question.derived?(nil)).to be false + end + + it "has correct conditional for" do + expect(question.conditional_for).to eq(nil) + end +end diff --git a/spec/models/form/sales/questions/staircase_last_date_spec.rb b/spec/models/form/sales/questions/staircase_last_date_spec.rb new file mode 100644 index 000000000..2efa5ccc7 --- /dev/null +++ b/spec/models/form/sales/questions/staircase_last_date_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::StaircaseLastDate, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))) } + + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(true) + end + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("lasttransaction") + end + + it "has the correct type" do + expect(question.type).to eq("date") + end + + it "is not marked as derived" do + expect(question.derived?(nil)).to be false + end + + it "has correct conditional for" do + expect(question.conditional_for).to eq(nil) + end +end diff --git a/spec/models/form/sales/questions/staircase_sale_spec.rb b/spec/models/form/sales/questions/staircase_sale_spec.rb index d330aecb3..8d3d1e5da 100644 --- a/spec/models/form/sales/questions/staircase_sale_spec.rb +++ b/spec/models/form/sales/questions/staircase_sale_spec.rb @@ -7,6 +7,10 @@ RSpec.describe Form::Sales::Questions::StaircaseSale, type: :model do let(:question_definition) { nil } let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(false) + end + it "has correct page" do expect(question.page).to eq(page) end diff --git a/spec/models/form/sales/questions/value_spec.rb b/spec/models/form/sales/questions/value_spec.rb index 771607c16..10f281e63 100644 --- a/spec/models/form/sales/questions/value_spec.rb +++ b/spec/models/form/sales/questions/value_spec.rb @@ -5,7 +5,11 @@ RSpec.describe Form::Sales::Questions::Value, type: :model do let(:question_id) { nil } let(:question_definition) { nil } - let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } + let(:page) { instance_double(Form::Page, id: "value_shared_ownership", subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } + + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(false) + end it "has correct page" do expect(question.page).to eq(page) diff --git a/spec/models/form/sales/sections/sale_information_spec.rb b/spec/models/form/sales/sections/sale_information_spec.rb index 8167c92a3..191bca4bb 100644 --- a/spec/models/form/sales/sections/sale_information_spec.rb +++ b/spec/models/form/sales/sections/sale_information_spec.rb @@ -7,6 +7,10 @@ RSpec.describe Form::Sales::Sections::SaleInformation, type: :model do let(:section_definition) { nil } let(:form) { instance_double(Form, start_year_2025_or_later?: false) } + before do + allow(form).to receive(:start_year_2025_or_later?).and_return(false) + end + it "has correct form" do expect(sale_information.form).to eq(form) end @@ -22,11 +26,16 @@ RSpec.describe Form::Sales::Sections::SaleInformation, type: :model do end context "when form is 2025 or later" do - let(:form) { instance_double(Form, start_year_2025_or_later?: true) } + let(:form) { instance_double(Form) } + + before do + allow(form).to receive(:start_year_2025_or_later?).and_return(true) + end it "has correct subsections" do expect(sale_information.subsections.map(&:id)).to eq(%w[ shared_ownership_initial_purchase + shared_ownership_staircasing_transaction discounted_ownership_scheme outright_sale ]) diff --git a/spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb b/spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb index 3b2d72b01..3e473a162 100644 --- a/spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb +++ b/spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb @@ -30,7 +30,7 @@ RSpec.describe Form::Sales::Subsections::SharedOwnershipInitialPurchase, type: : shared_ownership_previous_tenure value_shared_ownership about_price_shared_ownership_value_check - equity + initial_equity shared_ownership_equity_value_check mortgage_used_shared_ownership mortgage_used_mortgage_value_check diff --git a/spec/models/validations/sales/sale_information_validations_spec.rb b/spec/models/validations/sales/sale_information_validations_spec.rb index 5cc0cdf07..fabe3c8c5 100644 --- a/spec/models/validations/sales/sale_information_validations_spec.rb +++ b/spec/models/validations/sales/sale_information_validations_spec.rb @@ -1407,4 +1407,44 @@ RSpec.describe Validations::Sales::SaleInformationValidations do end end end + + describe "#validate_number_of_staircase_transactions" do + let(:record) { build(:sales_log, numstair:, firststair:) } + + before do + sale_information_validator.validate_number_of_staircase_transactions(record) + end + + context "when it is not the first staircasing transaction" do + context "and the number of staircasing transactions is between 2 and 10" do + let(:numstair) { 6 } + let(:firststair) { 2 } + + it "does not add an error" do + expect(record.errors).to be_empty + end + end + + context "and the number of staircasing transactions is less than 2" do + let(:numstair) { 1 } + let(:firststair) { 2 } + + it "adds an error" do + expect(record.errors[:numstair]).to include(I18n.t("validations.sales.sale_information.numstair.must_be_greater_than_one")) + expect(record.errors[:firststair]).to include(I18n.t("validations.sales.sale_information.firststair.cannot_be_no")) + end + end + end + + context "when it is the first staircasing transaction" do + context "and numstair is also 1" do + let(:numstair) { 1 } + let(:firststair) { 1 } + + it "does not add an error" do + expect(record.errors).to be_empty + end + end + end + end end From 5aff4d9989d09b9ab89bfe2bc2d2cd847ff9b608 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:24:36 +0000 Subject: [PATCH 4/6] CLDC-1180 Link to radio/checkbox errors from summary (#2835) * try linking errors * Change question ids to symbols and update checkboxes --- app/views/form/_checkbox_question.html.erb | 5 +++-- app/views/form/_radio_question.html.erb | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/views/form/_checkbox_question.html.erb b/app/views/form/_checkbox_question.html.erb index 2e8585e15..b4feb12bd 100644 --- a/app/views/form/_checkbox_question.html.erb +++ b/app/views/form/_checkbox_question.html.erb @@ -6,16 +6,17 @@ hint: { text: question.hint_text&.html_safe } do %> <% after_divider = false %> - <% question.displayed_answer_options(@log).map do |key, option| %> + <% question.displayed_answer_options(@log).each_with_index do |(key, option), index| %> <% if key.starts_with?("divider") %> <% after_divider = true %> <%= f.govuk_check_box_divider %> <% else %> - <%= f.govuk_check_box question.id, key, + <%= f.govuk_check_box question.id.to_sym, key, label: { text: option["value"] }, hint: { text: option["hint"] }, checked: @log[key] == 1, exclusive: after_divider, + link_errors: index.zero? ? true : nil, **stimulus_html_attributes(question) %> <% end %> <% end %> diff --git a/app/views/form/_radio_question.html.erb b/app/views/form/_radio_question.html.erb index 41e98a1cc..bf6abb0d0 100644 --- a/app/views/form/_radio_question.html.erb +++ b/app/views/form/_radio_question.html.erb @@ -18,22 +18,24 @@ legend: legend(question, page_header, conditional), hint: { text: question.hint_text&.html_safe } do %> - <% question.displayed_answer_options(@log, current_user).map do |key, options| %> + <% question.displayed_answer_options(@log, current_user).each_with_index do |(key, options), index| %> <% if key.starts_with?("divider") %> <%= f.govuk_radio_divider %> <% else %> <% conditional_question = find_conditional_question(@page, question, key) %> <% if conditional_question.nil? %> - <%= f.govuk_radio_button question.id, + <%= f.govuk_radio_button question.id.to_sym, key, label: { text: options["value"] }, hint: { text: options["hint"] }, + link_errors: index.zero? ? true : nil, **stimulus_html_attributes(question) %> <% else %> - <%= f.govuk_radio_button question.id, + <%= f.govuk_radio_button question.id.to_sym, key, label: { text: options["value"] }, hint: { text: options["hint"] }, + link_errors: index.zero? ? true : nil, **stimulus_html_attributes(question) do %> <%= render partial: "#{conditional_question.type}_question", locals: { question: conditional_question, From 838ad04e631419b0dbf3b459c47e9a53d5df63f0 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:08:54 +0000 Subject: [PATCH 5/6] CLDC-2141 Update form submit flow (#2838) * Redirect after update * Fix world * Only check type if question is found * Remove empty line * Clear cache --- Dockerfile | 2 +- app/controllers/form_controller.rb | 36 +++++++++++++++++++++------ spec/requests/form_controller_spec.rb | 11 ++++++-- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 74faebfd8..814582011 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ RUN apk add --update --no-cache tzdata && \ # build-base: compilation tools for bundle # yarn: node package manager # postgresql-dev: postgres driver and libraries -RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r7 nodejs-current=20.8.1-r0 yarn=1.22.19-r0 postgresql13-dev=13.17-r0 git=2.40.3-r0 bash=5.2.15-r5 +RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r7 nodejs-current=20.8.1-r0 yarn=1.22.19-r0 postgresql13-dev=13.18-r0 git=2.40.3-r0 bash=5.2.15-r5 # Bundler version should be the same version as what the Gemfile.lock was bundled with RUN gem install bundler:2.3.14 --no-document diff --git a/app/controllers/form_controller.rb b/app/controllers/form_controller.rb index 70b6f892b..7ce63e609 100644 --- a/app/controllers/form_controller.rb +++ b/app/controllers/form_controller.rb @@ -5,6 +5,7 @@ class FormController < ApplicationController before_action :find_resource, only: %i[review] before_action :find_resource_by_named_id, except: %i[review] before_action :check_collection_period, only: %i[submit_form show_page] + before_action :set_cache_headers, only: [:show_page] def submit_form if @log @@ -37,8 +38,9 @@ class FormController < ApplicationController error_attributes = @log.errors.map(&:attribute) Rails.logger.info "User triggered validation(s) on: #{error_attributes.join(', ')}" @subsection = form.subsection_for_page(@page) - restore_error_field_values(@page&.questions) - render "form/page" + flash[:errors] = @log.errors + flash[:log_data] = responses_for_page + redirect_to send("#{@log.class.name.underscore}_#{@page.id}_path", @log, { referrer: request.params["referrer"], original_page_id: request.params["original_page_id"], related_question_ids: request.params["related_question_ids"] }) end else render_not_found @@ -84,6 +86,10 @@ class FormController < ApplicationController @questions = request.params["related_question_ids"].map { |id| @log.form.get_question(id, @log) } render "form/check_errors" else + if flash[:errors].present? + restore_previous_errors(flash[:errors]) + restore_error_field_values(flash[:log_data]) + end render "form/page" end else @@ -96,13 +102,21 @@ class FormController < ApplicationController private - def restore_error_field_values(questions) - return unless questions + def restore_error_field_values(previous_responses) + return unless previous_responses - questions.each do |question| - if question&.type == "date" && @log.attributes.key?(question.id) - @log[question.id] = @log.send("#{question.id}_was") - end + previous_responses_to_reset = previous_responses.reject do |key, _| + @log.form.get_question(key, @log)&.type == "date" + end + + @log.assign_attributes(previous_responses_to_reset) + end + + def restore_previous_errors(previous_errors) + return unless previous_errors + + previous_errors.each do |attribute, message| + @log.errors.add attribute, message.first end end @@ -431,4 +445,10 @@ private def updated_answer_from_check_errors_page? params["check_errors"] end + + def set_cache_headers + response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0" + response.headers["Pragma"] = "no-cache" + response.headers["Expires"] = "Mon, 01 Jan 1990 00:00:00 GMT" + end end diff --git a/spec/requests/form_controller_spec.rb b/spec/requests/form_controller_spec.rb index 13d711c20..e727d8acc 100644 --- a/spec/requests/form_controller_spec.rb +++ b/spec/requests/form_controller_spec.rb @@ -582,8 +582,9 @@ RSpec.describe FormController, type: :request do allow(Rails.logger).to receive(:info) end - it "re-renders the same page with errors if validation fails" do + it "redirects to the same page with errors if validation fails" do post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params + follow_redirect! expect(page).to have_content("There is a problem") expect(page).to have_content("Error: What is the tenant’s age?") end @@ -591,6 +592,8 @@ RSpec.describe FormController, type: :request do it "resets errors when fixed" do post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: valid_params + follow_redirect! + expect(page).not_to have_content("There is a problem") get "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}" expect(page).not_to have_content("There is a problem") end @@ -616,6 +619,7 @@ RSpec.describe FormController, type: :request do it "validates the date correctly" do post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params + follow_redirect! expect(page).to have_content("There is a problem") end end @@ -693,6 +697,7 @@ RSpec.describe FormController, type: :request do it "validates the date correctly" do post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params + follow_redirect! expect(page).to have_content("There is a problem") end end @@ -713,6 +718,7 @@ RSpec.describe FormController, type: :request do it "validates the date correctly" do post "/sales-logs/#{sales_log.id}/#{page_id.dasherize}", params: params + follow_redirect! expect(page).to have_content("There is a problem") end end @@ -748,8 +754,9 @@ RSpec.describe FormController, type: :request do Timecop.unfreeze end - it "re-renders the same page with errors if validation fails" do + it "redirects the same page with errors if validation fails" do post "/lettings-logs/#{lettings_log.id}/managing-organisation", params: params + follow_redirect! expect(page).to have_content("There is a problem") expect(page).to have_content("Error: Which organisation manages this letting?") end