diff --git a/app/controllers/case_logs_controller.rb b/app/controllers/case_logs_controller.rb index 48fe5726d..41d81ca04 100644 --- a/app/controllers/case_logs_controller.rb +++ b/app/controllers/case_logs_controller.rb @@ -56,16 +56,15 @@ class CaseLogsController < ApplicationController def submit_form form = FormHandler.instance.get_form("2021_2022") @case_log = CaseLog.find(params[:id]) - previous_page = params[:case_log][:previous_page] - questions_for_page = form.questions_for_page(previous_page) - responses_for_page = question_responses(questions_for_page) - @case_log.previous_page = previous_page - if @case_log.update(responses_for_page) - redirect_path = get_next_page_path(form, previous_page, @case_log) + page = params[:case_log][:previous_page] + @case_log.previous_page = page + responses_for_page = responses_for_page(page) + if @case_log.update(responses_for_page) && @case_log.soft_errors.empty? + redirect_path = get_next_page_path(form, page, @case_log) redirect_to(send(redirect_path, @case_log)) else - page_info = form.all_pages[previous_page] - render "form/page", locals: { form: form, page_key: previous_page, page_info: page_info }, status: :unprocessable_entity + page_info = form.all_pages[page] + render "form/page", locals: { form: form, page_key: page, page_info: page_info }, status: :unprocessable_entity end end @@ -101,9 +100,16 @@ private API_ACTIONS = %w[create show update destroy].freeze - def question_responses(questions_for_page) + def responses_for_page(page) + form = FormHandler.instance.get_form("2021_2022") + initial_questions_for_page = form.questions_for_page(page) + soft_validations = form.soft_validations_for_page(page) + questions_for_page = initial_questions_for_page.merge(soft_validations || {}) + questions_for_page.each_with_object({}) do |(question_key, question_info), result| question_params = params["case_log"][question_key] + next unless question_params + if question_info["type"] == "checkbox" question_info["answer_options"].keys.reject { |x| x.match(/divider/) }.each do |option| result[option] = question_params.include?(option) diff --git a/app/models/case_log.rb b/app/models/case_log.rb index 1e59c0819..d0da357a6 100644 --- a/app/models/case_log.rb +++ b/app/models/case_log.rb @@ -111,6 +111,10 @@ class CaseLogValidator < ActiveModel::Validator if record.weekly_net_income < record.applicable_income_range.hard_min record.errors.add :net_income, "Net income cannot be less than #{record.applicable_income_range.hard_min} given the tenant's working situation" end + + if record.soft_errors.present? && record.override_net_income_validation.blank? + record.errors.add :override_net_income_validation, "For net incomes that fall outside the expected range you must confirm they're correct" + end end def validate_other_tenancy_type(record) @@ -211,6 +215,24 @@ class CaseLog < ApplicationRecord IncomeRange::ALLOWED[person_1_economic_status.to_sym] end + def soft_errors() + soft_errors = {} + if weekly_net_income && person_1_economic_status && override_net_income_validation.blank? + if weekly_net_income < applicable_income_range.soft_min && weekly_net_income > applicable_income_range.hard_min + soft_errors["weekly_net_income"] = OpenStruct.new( + message: "Net income is lower than expected based on the main tenant's working situation. Are you sure this is correct?", + hint_text: "This is based on the tenant's work situation: #{person_1_economic_status}" + ) + elsif weekly_net_income > applicable_income_range.soft_max && weekly_net_income < applicable_income_range.hard_max + soft_errors["weekly_net_income"] = OpenStruct.new( + message: "Net income is higher than expected based on the main tenant's working situation. Are you sure this is correct?", + hint_text: "This is based on the tenant's work situation: #{person_1_economic_status}" + ) + end + end + soft_errors + end + private def update_status! @@ -252,6 +274,10 @@ private dynamically_not_required << "other_tenancy_type" end + if soft_errors.empty? + dynamically_not_required << "override_net_income_validation" + end + required.delete_if { |key, _value| dynamically_not_required.include?(key) } end end diff --git a/app/models/form.rb b/app/models/form.rb index fc11d2b36..d819a1b6c 100644 --- a/app/models/form.rb +++ b/app/models/form.rb @@ -41,6 +41,11 @@ class Form pages_for_subsection(subsection).map { |title, _value| questions_for_page(title) }.reduce(:merge) end + # Returns a hash with soft validation questions as keys + def soft_validations_for_page(page) + all_pages[page]["soft_validations"] + end + def first_page_for_subsection(subsection) pages_for_subsection(subsection).keys.first end diff --git a/app/views/form/_validation_override_question.html.erb b/app/views/form/_validation_override_question.html.erb new file mode 100644 index 000000000..e1858ecb2 --- /dev/null +++ b/app/views/form/_validation_override_question.html.erb @@ -0,0 +1,9 @@ +<%= f.govuk_check_boxes_fieldset :override_net_income_validation, + legend: { text: @case_log.soft_errors.values.first.message, size: "l" }, + hint: { text: @case_log.soft_errors.values.first.hint_text } do %> + + <%= f.govuk_check_box :override_net_income_validation, "override_net_income_validation", + label: { text: "Yes" }, + checked: f.object.send("override_net_income_validation") + %> +<% end %> diff --git a/app/views/form/page.html.erb b/app/views/form/page.html.erb index 878fefd66..1b1da86fa 100644 --- a/app/views/form/page.html.erb +++ b/app/views/form/page.html.erb @@ -17,6 +17,10 @@ <% end %> + <% if @case_log.soft_errors.present? %> + <%= render partial: "form/validation_override_question", locals: { f: f } %> + <% end %> + <%= f.hidden_field :previous_page, value: page_key %> <%= f.govuk_submit "Save and continue" %> <% end %> diff --git a/config/forms/2021_2022.json b/config/forms/2021_2022.json index f4cf447c3..04fbf06ec 100644 --- a/config/forms/2021_2022.json +++ b/config/forms/2021_2022.json @@ -1477,6 +1477,15 @@ "2": "Yearly" } } + }, + "soft_validations": { + "override_net_income_validation": { + "check_answer_label": "Net income confirmed?", + "type": "validation_override", + "answer_options": { + "override_net_income_validation": "Yes" + } + } } }, "net_income_uc_proportion": { diff --git a/db/migrate/20211028090049_add_net_income_override.rb b/db/migrate/20211028090049_add_net_income_override.rb new file mode 100644 index 000000000..f776ada87 --- /dev/null +++ b/db/migrate/20211028090049_add_net_income_override.rb @@ -0,0 +1,5 @@ +class AddNetIncomeOverride < ActiveRecord::Migration[6.1] + def change + add_column :case_logs, :override_net_income_validation, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index e7038ce2a..c286aa284 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_10_28_083105) do +ActiveRecord::Schema.define(version: 2021_10_28_090049) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -133,6 +133,7 @@ ActiveRecord::Schema.define(version: 2021_10_28_083105) do t.boolean "reasonable_preference_reason_do_not_know" t.datetime "discarded_at" t.string "other_tenancy_type" + t.boolean "override_net_income_validation" t.index ["discarded_at"], name: "index_case_logs_on_discarded_at" end diff --git a/spec/features/case_log_spec.rb b/spec/features/case_log_spec.rb index 587fc80f1..809200363 100644 --- a/spec/features/case_log_spec.rb +++ b/spec/features/case_log_spec.rb @@ -377,6 +377,22 @@ RSpec.describe "Test Features" do end end + describe "Soft Validation" do + context "given a weekly net income that is above the expected amount for the given economic status but below the hard max" do + let!(:case_log){ FactoryBot.create(:case_log, :in_progress, person_1_economic_status: "Full-time - 30 hours or more") } + let(:income_over_soft_limit) { 750 } + + it "prompts the user to confirm the value is correct" do + visit("/case_logs/#{case_log.id}/net_income") + fill_in("case-log-net-income-field", with: income_over_soft_limit) + choose("case-log-net-income-frequency-weekly-field", allow_label_click: true) + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{case_log.id}/net_income") + expect(page).to have_unchecked_field("case-log-override-net-income-validation-override-net-income-validation-field") + end + end + end + describe "conditional page routing", js: true do it "can route the user to a different page based on their answer on the current page" do visit("case_logs/#{id}/conditional_question")