diff --git a/app/models/form.rb b/app/models/form.rb index 80fed4444..cc73d2ca6 100644 --- a/app/models/form.rb +++ b/app/models/form.rb @@ -178,48 +178,78 @@ class Form pages.reject { |p| p.routed_to?(log, current_user) } end - def invalidated_questions(log) - invalidated_page_questions(log) + invalidated_conditional_questions(log) - end - - def invalidated_page_questions(log, current_user = nil) - # we're already treating these fields as a special case and reset their values upon saving a log - callback_questions = %w[postcode_known la ppcodenk previous_la_known prevloc postcode_full ppostcode_full location_id address_line1 address_line2 town_or_city county] - questions.reject { |q| q.page.routed_to?(log, current_user) || q.derived? || callback_questions.include?(q.id) } || [] - end + def reset_not_routed_questions_and_invalid_answers(log) + reset_checkbox_questions_if_not_routed(log) - def reset_not_routed_questions(log) - enabled_questions = enabled_page_questions(log) - enabled_question_ids = enabled_questions.map(&:id) + reset_radio_questions_if_not_routed_or_invalid_answers(log) - invalidated_page_questions(log).each do |question| - if %w[radio checkbox].include?(question.type) - enabled_answer_options = enabled_question_ids.include?(question.id) ? enabled_questions.find { |q| q.id == question.id }.answer_options : {} - current_answer_option_valid = enabled_answer_options.present? ? enabled_answer_options.key?(log.public_send(question.id).to_s) : false + reset_free_user_input_questions_if_not_routed(log) + end - if !current_answer_option_valid && log.respond_to?(question.id.to_s) - Rails.logger.debug("Cleared #{question.id} value") - log.public_send("#{question.id}=", nil) + def reset_checkbox_questions_if_not_routed(log) + checkbox_questions = routed_and_not_routed_questions_by_type(log, type: "checkbox") + checkbox_questions[:not_routed].each do |not_routed_question| + valid_options = checkbox_questions[:routed] + .select { |q| q.id == not_routed_question.id } + .flat_map { |q| q.answer_options.keys } + not_routed_question.answer_options.each_key do |invalid_option| + if !log.respond_to?(invalid_option) || valid_options.include?(invalid_option) || log.public_send(invalid_option).nil? + next else - - (question.answer_options.keys - enabled_answer_options.keys).map do |invalid_answer_option| - Rails.logger.debug("Cleared #{invalid_answer_option} value") - log.public_send("#{invalid_answer_option}=", nil) if log.respond_to?(invalid_answer_option) - end + clear_attribute(log, invalid_option) end + end + end + end + + def reset_radio_questions_if_not_routed_or_invalid_answers(log) + radio_questions = routed_and_not_routed_questions_by_type(log, type: "radio") + valid_radio_options = radio_questions[:routed] + .group_by(&:id) + .transform_values! { |q_array| q_array.flat_map { |q| q.answer_options.keys } } + radio_questions[:not_routed].each do |not_routed_question| + question_id = not_routed_question.id + if !log.respond_to?(question_id) || log.public_send(question_id).nil? || valid_radio_options.key?(question_id) + next + else + clear_attribute(log, question_id) + end + end + valid_radio_options.each do |question_id, valid_options| + if !log.respond_to?(question_id) || valid_options.include?(log.public_send(question_id).to_s) + next + else + clear_attribute(log, question_id) + end + end + end + + def reset_free_user_input_questions_if_not_routed(log) + non_radio_checkbox_questions = routed_and_not_routed_questions_by_type(log) + enabled_question_ids = non_radio_checkbox_questions[:routed].map(&:id) + non_radio_checkbox_questions[:not_routed].each do |not_routed_question| + question_id = not_routed_question.id + if log.public_send(question_id).nil? || enabled_question_ids.include?(question_id) + next else - Rails.logger.debug("Cleared #{question.id} value") - log.public_send("#{question.id}=", nil) unless enabled_question_ids.include?(question.id) + clear_attribute(log, question_id) end end end - def enabled_page_questions(log) - questions - invalidated_page_questions(log) + def routed_and_not_routed_questions_by_type(log, type: nil, current_user: nil) + questions_by_type = if type + questions.reject { |q| q.type != type || q.disable_clearing_if_not_routed_or_dynamic_answer_options } + else + questions.reject { |q| %w[radio checkbox].include?(q.type) || q.disable_clearing_if_not_routed_or_dynamic_answer_options } + end + routed, not_routed = questions_by_type.partition { |q| q.page.routed_to?(log, current_user) || q.derived? } + { routed:, not_routed: } end - def invalidated_conditional_questions(log) - questions.reject { |q| q.enabled?(log) } || [] + def clear_attribute(log, attribute) + Rails.logger.debug("Cleared #{attribute} value") + log.public_send("#{attribute}=", nil) end def readonly_questions @@ -230,6 +260,13 @@ class Form questions.select { |q| q.type == "numeric" } end + def previous_page(page_ids, page_index, log, current_user) + prev_page = get_page(page_ids[page_index - 1]) + return prev_page.id if prev_page.routed_to?(log, current_user) + + previous_page(page_ids, page_index - 1, log, current_user) + end + def send_chain(arr, log) Array(arr).inject(log) { |o, a| o.public_send(*a) } end diff --git a/app/models/form/lettings/questions/address_line1.rb b/app/models/form/lettings/questions/address_line1.rb index 4c8b4151b..ef197e4fd 100644 --- a/app/models/form/lettings/questions/address_line1.rb +++ b/app/models/form/lettings/questions/address_line1.rb @@ -7,6 +7,7 @@ class Form::Lettings::Questions::AddressLine1 < ::Form::Question @type = "text" @plain_label = true @check_answer_label = "Q12 - Address" + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def answer_label(log, _current_user = nil) diff --git a/app/models/form/lettings/questions/address_line2.rb b/app/models/form/lettings/questions/address_line2.rb index 16f7c8336..3b2c36dbc 100644 --- a/app/models/form/lettings/questions/address_line2.rb +++ b/app/models/form/lettings/questions/address_line2.rb @@ -5,6 +5,7 @@ class Form::Lettings::Questions::AddressLine2 < ::Form::Question @header = "Address line 2 (optional)" @type = "text" @plain_label = true + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def hidden_in_check_answers?(_log = nil, _current_user = nil) diff --git a/app/models/form/lettings/questions/county.rb b/app/models/form/lettings/questions/county.rb index 360c0966c..9f0dc7138 100644 --- a/app/models/form/lettings/questions/county.rb +++ b/app/models/form/lettings/questions/county.rb @@ -5,6 +5,7 @@ class Form::Lettings::Questions::County < ::Form::Question @header = "County (optional)" @type = "text" @plain_label = true + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def hidden_in_check_answers?(_log = nil, _current_user = nil) diff --git a/app/models/form/lettings/questions/la.rb b/app/models/form/lettings/questions/la.rb index 3cafda054..5c483d786 100644 --- a/app/models/form/lettings/questions/la.rb +++ b/app/models/form/lettings/questions/la.rb @@ -8,6 +8,7 @@ class Form::Lettings::Questions::La < ::Form::Question @check_answers_card_number = 0 @hint_text = "" @question_number = 13 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def answer_options diff --git a/app/models/form/lettings/questions/location_id.rb b/app/models/form/lettings/questions/location_id.rb index 94196534f..80c2516e8 100644 --- a/app/models/form/lettings/questions/location_id.rb +++ b/app/models/form/lettings/questions/location_id.rb @@ -1,6 +1,7 @@ class Form::Lettings::Questions::LocationId < ::Form::Question - def initialize(_id, hsh, page) - super("location_id", hsh, page) + def initialize(id, hsh, page) + super + @id = "location_id" @check_answer_label = "Location" @header = header_text @type = "radio" @@ -11,6 +12,7 @@ class Form::Lettings::Questions::LocationId < ::Form::Question }, } @question_number = 10 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def answer_options diff --git a/app/models/form/lettings/questions/postcode_for_full_address.rb b/app/models/form/lettings/questions/postcode_for_full_address.rb index 015abc2e8..4f41867d7 100644 --- a/app/models/form/lettings/questions/postcode_for_full_address.rb +++ b/app/models/form/lettings/questions/postcode_for_full_address.rb @@ -17,6 +17,7 @@ class Form::Lettings::Questions::PostcodeForFullAddress < ::Form::Question }, } @plain_label = true + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def hidden_in_check_answers?(_log = nil, _current_user = nil) diff --git a/app/models/form/lettings/questions/postcode_full.rb b/app/models/form/lettings/questions/postcode_full.rb index 1f0a25c49..fc7d7691b 100644 --- a/app/models/form/lettings/questions/postcode_full.rb +++ b/app/models/form/lettings/questions/postcode_full.rb @@ -10,5 +10,6 @@ class Form::Lettings::Questions::PostcodeFull < ::Form::Question @check_answers_card_number = 0 @hint_text = "" @inferred_answers = { "la" => { "is_la_inferred" => true } } + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end end diff --git a/app/models/form/lettings/questions/postcode_known.rb b/app/models/form/lettings/questions/postcode_known.rb index 6af30bb19..ea9adb06b 100644 --- a/app/models/form/lettings/questions/postcode_known.rb +++ b/app/models/form/lettings/questions/postcode_known.rb @@ -8,6 +8,7 @@ class Form::Lettings::Questions::PostcodeKnown < ::Form::Question @check_answers_card_number = 0 @hint_text = "" @answer_options = ANSWER_OPTIONS + @disable_clearing_if_not_routed_or_dynamic_answer_options = true @conditional_for = { "postcode_full" => [1] } @hidden_in_check_answers = { "depends_on" => [{ "postcode_known" => 0 }, { "postcode_known" => 1 }] } end diff --git a/app/models/form/lettings/questions/ppcodenk.rb b/app/models/form/lettings/questions/ppcodenk.rb index 33b03d959..25557a166 100644 --- a/app/models/form/lettings/questions/ppcodenk.rb +++ b/app/models/form/lettings/questions/ppcodenk.rb @@ -11,6 +11,7 @@ class Form::Lettings::Questions::Ppcodenk < ::Form::Question @conditional_for = { "ppostcode_full" => [1] } @hidden_in_check_answers = { "depends_on" => [{ "ppcodenk" => 0 }, { "ppcodenk" => 1 }] } @question_number = 80 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end ANSWER_OPTIONS = { "1" => { "value" => "Yes" }, "0" => { "value" => "No" } }.freeze diff --git a/app/models/form/lettings/questions/ppostcode_full.rb b/app/models/form/lettings/questions/ppostcode_full.rb index 0432d1b5b..86b4c7b31 100644 --- a/app/models/form/lettings/questions/ppostcode_full.rb +++ b/app/models/form/lettings/questions/ppostcode_full.rb @@ -11,5 +11,6 @@ class Form::Lettings::Questions::PpostcodeFull < ::Form::Question @hint_text = "" @inferred_answers = { "prevloc" => { "is_previous_la_inferred" => true } } @question_number = 80 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end end diff --git a/app/models/form/lettings/questions/previous_la_known.rb b/app/models/form/lettings/questions/previous_la_known.rb index a9ff11e55..eb80eda4a 100644 --- a/app/models/form/lettings/questions/previous_la_known.rb +++ b/app/models/form/lettings/questions/previous_la_known.rb @@ -11,6 +11,7 @@ class Form::Lettings::Questions::PreviousLaKnown < ::Form::Question @conditional_for = { "prevloc" => [1] } @hidden_in_check_answers = { "depends_on" => [{ "previous_la_known" => 0 }, { "previous_la_known" => 1 }] } @question_number = 81 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end ANSWER_OPTIONS = { "1" => { "value" => "Yes" }, "0" => { "value" => "No" } }.freeze diff --git a/app/models/form/lettings/questions/prevloc.rb b/app/models/form/lettings/questions/prevloc.rb index 229af2c18..e0082602e 100644 --- a/app/models/form/lettings/questions/prevloc.rb +++ b/app/models/form/lettings/questions/prevloc.rb @@ -9,6 +9,7 @@ class Form::Lettings::Questions::Prevloc < ::Form::Question @check_answers_card_number = 0 @hint_text = "Select ‘Northern Ireland’, ‘Scotland’, ‘Wales’ or ‘Outside the UK’ if the household’s last settled home was outside England." @question_number = 81 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def answer_options diff --git a/app/models/form/lettings/questions/town_or_city.rb b/app/models/form/lettings/questions/town_or_city.rb index f1eac8dff..501e9bec4 100644 --- a/app/models/form/lettings/questions/town_or_city.rb +++ b/app/models/form/lettings/questions/town_or_city.rb @@ -5,6 +5,7 @@ class Form::Lettings::Questions::TownOrCity < ::Form::Question @header = "Town or city" @type = "text" @plain_label = true + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def hidden_in_check_answers?(_log = nil, _current_user = nil) diff --git a/app/models/form/question.rb b/app/models/form/question.rb index 8e8c26739..d4b7c9d76 100644 --- a/app/models/form/question.rb +++ b/app/models/form/question.rb @@ -1,5 +1,5 @@ class Form::Question - attr_accessor :id, :header, :hint_text, :description, :questions, + attr_accessor :id, :header, :hint_text, :description, :questions, :disable_clearing_if_not_routed_or_dynamic_answer_options, :type, :min, :max, :step, :width, :fields_to_add, :result_field, :conditional_for, :readonly, :answer_options, :page, :check_answer_label, :inferred_answers, :hidden_in_check_answers, :inferred_check_answers_value, @@ -42,6 +42,7 @@ class Form::Question @unresolved_hint_text = hsh["unresolved_hint_text"] @question_number = hsh["question_number"] @plain_label = hsh["plain_label"] + @disable_clearing_if_not_routed_or_dynamic_answer_options = hsh["disable_clearing_if_not_routed_or_dynamic_answer_options"] end end diff --git a/app/models/form/sales/pages/extra_borrowing_value_check.rb b/app/models/form/sales/pages/extra_borrowing_value_check.rb index 18f975b8d..5fff74db2 100644 --- a/app/models/form/sales/pages/extra_borrowing_value_check.rb +++ b/app/models/form/sales/pages/extra_borrowing_value_check.rb @@ -9,8 +9,7 @@ class Form::Sales::Pages::ExtraBorrowingValueCheck < Form::Page @title_text = { "translation" => "soft_validations.extra_borrowing.title", } - @informative_text = { - } + @informative_text = {} end def questions diff --git a/app/models/form/sales/questions/address_line1.rb b/app/models/form/sales/questions/address_line1.rb index a95051116..edee2e7ee 100644 --- a/app/models/form/sales/questions/address_line1.rb +++ b/app/models/form/sales/questions/address_line1.rb @@ -7,6 +7,7 @@ class Form::Sales::Questions::AddressLine1 < ::Form::Question @type = "text" @plain_label = true @check_answer_label = "Q15 - Address" + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def answer_label(log, _current_user = nil) diff --git a/app/models/form/sales/questions/address_line2.rb b/app/models/form/sales/questions/address_line2.rb index 0b4ff661c..94396a2af 100644 --- a/app/models/form/sales/questions/address_line2.rb +++ b/app/models/form/sales/questions/address_line2.rb @@ -5,6 +5,7 @@ class Form::Sales::Questions::AddressLine2 < ::Form::Question @header = "Address line 2 (optional)" @type = "text" @plain_label = true + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def hidden_in_check_answers?(_log = nil, _current_user = nil) diff --git a/app/models/form/sales/questions/age2.rb b/app/models/form/sales/questions/age2.rb index c0dc567a1..c003eb02e 100644 --- a/app/models/form/sales/questions/age2.rb +++ b/app/models/form/sales/questions/age2.rb @@ -13,6 +13,7 @@ class Form::Sales::Questions::Age2 < ::Form::Question @check_answers_card_number = 2 @max = 110 @min = 0 + @step = 1 @question_number = 28 end end diff --git a/app/models/form/sales/questions/county.rb b/app/models/form/sales/questions/county.rb index 080ac809f..6586cb9e6 100644 --- a/app/models/form/sales/questions/county.rb +++ b/app/models/form/sales/questions/county.rb @@ -5,6 +5,7 @@ class Form::Sales::Questions::County < ::Form::Question @header = "County (optional)" @type = "text" @plain_label = true + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def hidden_in_check_answers?(_log = nil, _current_user = nil) diff --git a/app/models/form/sales/questions/person_age.rb b/app/models/form/sales/questions/person_age.rb index 779e09669..cdbc9f80b 100644 --- a/app/models/form/sales/questions/person_age.rb +++ b/app/models/form/sales/questions/person_age.rb @@ -12,6 +12,7 @@ class Form::Sales::Questions::PersonAge < ::Form::Question @check_answers_card_number = person_index @min = 0 @max = 110 + @step = 1 @question_number = 29 + (4 * person_index) end end diff --git a/app/models/form/sales/questions/postcode.rb b/app/models/form/sales/questions/postcode.rb index 0f72a9585..55e33199a 100644 --- a/app/models/form/sales/questions/postcode.rb +++ b/app/models/form/sales/questions/postcode.rb @@ -17,5 +17,6 @@ class Form::Sales::Questions::Postcode < ::Form::Question "is_la_inferred" => true, }, } + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end end diff --git a/app/models/form/sales/questions/postcode_for_full_address.rb b/app/models/form/sales/questions/postcode_for_full_address.rb index a1e6f8633..5d3b9f122 100644 --- a/app/models/form/sales/questions/postcode_for_full_address.rb +++ b/app/models/form/sales/questions/postcode_for_full_address.rb @@ -17,6 +17,7 @@ class Form::Sales::Questions::PostcodeForFullAddress < ::Form::Question }, } @plain_label = true + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def hidden_in_check_answers?(_log = nil, _current_user = nil) diff --git a/app/models/form/sales/questions/previous_la_known.rb b/app/models/form/sales/questions/previous_la_known.rb index 1a9a646f3..1a4a2f438 100644 --- a/app/models/form/sales/questions/previous_la_known.rb +++ b/app/models/form/sales/questions/previous_la_known.rb @@ -21,6 +21,7 @@ class Form::Sales::Questions::PreviousLaKnown < ::Form::Question "prevloc" => [1], } @question_number = 58 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end ANSWER_OPTIONS = { diff --git a/app/models/form/sales/questions/previous_postcode.rb b/app/models/form/sales/questions/previous_postcode.rb index 568ba66ea..5eb8e6d54 100644 --- a/app/models/form/sales/questions/previous_postcode.rb +++ b/app/models/form/sales/questions/previous_postcode.rb @@ -18,5 +18,6 @@ class Form::Sales::Questions::PreviousPostcode < ::Form::Question }, } @question_number = 57 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end end diff --git a/app/models/form/sales/questions/previous_postcode_known.rb b/app/models/form/sales/questions/previous_postcode_known.rb index e83fda8d3..f53f52bf0 100644 --- a/app/models/form/sales/questions/previous_postcode_known.rb +++ b/app/models/form/sales/questions/previous_postcode_known.rb @@ -21,6 +21,7 @@ class Form::Sales::Questions::PreviousPostcodeKnown < ::Form::Question ], } @question_number = 57 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end ANSWER_OPTIONS = { diff --git a/app/models/form/sales/questions/prevloc.rb b/app/models/form/sales/questions/prevloc.rb index 75bb50e81..71606aa34 100644 --- a/app/models/form/sales/questions/prevloc.rb +++ b/app/models/form/sales/questions/prevloc.rb @@ -12,6 +12,7 @@ class Form::Sales::Questions::Prevloc < ::Form::Question "value" => "Not known", }] @question_number = 58 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def answer_options diff --git a/app/models/form/sales/questions/property_local_authority.rb b/app/models/form/sales/questions/property_local_authority.rb index 04dbed347..df3fe5e88 100644 --- a/app/models/form/sales/questions/property_local_authority.rb +++ b/app/models/form/sales/questions/property_local_authority.rb @@ -6,6 +6,7 @@ class Form::Sales::Questions::PropertyLocalAuthority < ::Form::Question @header = "What is the property’s local authority?" @type = "select" @question_number = 16 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def answer_options diff --git a/app/models/form/sales/questions/town_or_city.rb b/app/models/form/sales/questions/town_or_city.rb index 9dde3aeb8..25acfe036 100644 --- a/app/models/form/sales/questions/town_or_city.rb +++ b/app/models/form/sales/questions/town_or_city.rb @@ -5,6 +5,7 @@ class Form::Sales::Questions::TownOrCity < ::Form::Question @header = "Town or city" @type = "text" @plain_label = true + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def hidden_in_check_answers?(_log = nil, _current_user = nil) diff --git a/app/models/log.rb b/app/models/log.rb index 33d3c4547..fc125fd46 100644 --- a/app/models/log.rb +++ b/app/models/log.rb @@ -174,7 +174,7 @@ private def reset_invalidated_dependent_fields! return unless form - form.reset_not_routed_questions(self) + form.reset_not_routed_questions_and_invalid_answers(self) reset_created_by! end diff --git a/app/views/form/_checkbox_question.html.erb b/app/views/form/_checkbox_question.html.erb index c855e2d88..2a9c33941 100644 --- a/app/views/form/_checkbox_question.html.erb +++ b/app/views/form/_checkbox_question.html.erb @@ -6,14 +6,14 @@ hint: { text: question.hint_text&.html_safe } do %> <% after_divider = false %> - <% question.displayed_answer_options(@log).map do |key, options| %> + <% question.displayed_answer_options(@log).map do |key, option| %> <% if key.starts_with?("divider") %> - <% after_divider = true %> - <%= f.govuk_check_box_divider %> + <% after_divider = true %> + <%= f.govuk_check_box_divider %> <% else %> <%= f.govuk_check_box question.id, key, - label: { text: options["value"] }, - hint: { text: options["hint"] }, + label: { text: option["value"] }, + hint: { text: option["hint"] }, checked: @log[key] == 1, exclusive: after_divider, **stimulus_html_attributes(question) %> diff --git a/config/forms/2021_2022.json b/config/forms/2021_2022.json index 741195f01..3caedd8cb 100644 --- a/config/forms/2021_2022.json +++ b/config/forms/2021_2022.json @@ -24,6 +24,7 @@ "header": "Do you know the property’s postcode?", "hint_text": "", "type": "radio", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "1": { "value": "Yes" @@ -54,6 +55,7 @@ "hint_text": "", "type": "text", "width": 5, + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "inferred_answers": { "la": { "is_la_inferred": true @@ -82,6 +84,7 @@ "header": "What is the local authority of the property?", "hint_text": "", "type": "select", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "": "Select an option", "E07000223": "Adur", @@ -6482,6 +6485,7 @@ "header": "Do you know the postcode of the household’s last settled accommodation?", "hint_text": "This is also known as the household’s ‘last settled home’.", "type": "radio", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "1": { "value": "Yes" @@ -6512,6 +6516,7 @@ "hint_text": "", "type": "text", "width": 5, + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "inferred_answers": { "prevloc": { "is_previous_la_inferred": true @@ -6535,6 +6540,7 @@ "header": "Do you know the local authority of the household’s last settled accommodation?", "hint_text": "This is also known as the household’s ‘last settled home’.", "type": "radio", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "hidden_in_check_answers": { "depends_on": [ { @@ -6564,6 +6570,7 @@ "header": "Select a local authority", "hint_text": "Select ‘Northern Ireland’, ‘Scotland’, ‘Wales’ or ‘Outside the UK’ if the household’s last settled home was outside England.", "type": "select", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "": "Select an option", "S12000033": "Aberdeen City", diff --git a/config/forms/2022_2023.json b/config/forms/2022_2023.json index 9b5aa6bb8..abb9064fe 100644 --- a/config/forms/2022_2023.json +++ b/config/forms/2022_2023.json @@ -24,6 +24,7 @@ "header": "Do you know the property’s postcode?", "hint_text": "", "type": "radio", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "1": { "value": "Yes" @@ -54,6 +55,7 @@ "hint_text": "", "type": "text", "width": 5, + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "inferred_answers": { "la": { "is_la_inferred": true @@ -82,6 +84,7 @@ "header": "What is the local authority of the property?", "hint_text": "", "type": "select", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "": "Select an option", "E07000223": "Adur", @@ -6435,6 +6438,7 @@ "header": "Do you know the postcode of the household’s last settled accommodation?", "hint_text": "This is also known as the household’s ‘last settled home’.", "type": "radio", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "1": { "value": "Yes" @@ -6465,6 +6469,7 @@ "hint_text": "", "type": "text", "width": 5, + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "inferred_answers": { "prevloc": { "is_previous_la_inferred": true @@ -6488,6 +6493,7 @@ "header": "Do you know the local authority of the household’s last settled accommodation?", "hint_text": "This is also known as the household’s ‘last settled home’.", "type": "radio", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "hidden_in_check_answers": { "depends_on": [ { @@ -6517,6 +6523,7 @@ "header": "Select a local authority", "hint_text": "Select ‘Northern Ireland’, ‘Scotland’, ‘Wales’ or ‘Outside the UK’ if the household’s last settled home was outside England.", "type": "select", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "": "Select an option", "S12000033": "Aberdeen City", diff --git a/config/locales/en.yml b/config/locales/en.yml index eef342674..16583a911 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -365,7 +365,7 @@ en: over_20: "The lead tenant must be under 20 as you told us their housing situation immediately before this letting was a children’s home or foster care" ecstat: retired_over_70: "Person %{person_num} must be retired if over 70" - child_under_16: "Person’s %{person_num} working situation must be ’child under 16‘ as you told us they’re under 16" + child_under_16: "Person %{person_num}’s working situation must be ‘child under 16’ as you told us they’re under 16" child_over_16: "Answer cannot be ‘child under 16’ as you told us the person %{person_num} is older than 16" not_student_16_19: "Person’s %{person_num} working situation must be full-time student or prefers not to say as you told us they’re between 16 and 19." student_16_19: diff --git a/spec/factories/lettings_log.rb b/spec/factories/lettings_log.rb index 311e5fecc..58c81e8f3 100644 --- a/spec/factories/lettings_log.rb +++ b/spec/factories/lettings_log.rb @@ -158,6 +158,12 @@ FactoryBot.define do sheltered { 0 } household_charge { 0 } end + trait :sheltered_housing do + needstype { 2 } + end + trait :startdate_today do + startdate { Time.zone.today } + end created_at { Time.zone.today } updated_at { Time.zone.today } end diff --git a/spec/factories/sales_log.rb b/spec/factories/sales_log.rb index 4bea77a65..c61b0cd93 100644 --- a/spec/factories/sales_log.rb +++ b/spec/factories/sales_log.rb @@ -10,6 +10,30 @@ FactoryBot.define do type { 8 } saledate { Time.utc(2023, 2, 2, 10, 36, 49) } end + trait :shared_ownership do + ownershipsch { 1 } + type { 30 } + end + trait :privacy_notice_seen do + privacynotice { 1 } + end + trait :saledate_today do + saledate { Time.zone.today } + end + trait :shared_ownership_setup_complete do + saledate_today + ownershipsch { 1 } + type { 30 } + jointpur { 2 } + end + trait :outright_sale_setup_complete do + saledate_today + ownershipsch { 3 } + type { 10 } + companybuy { 2 } + buylivein { 1 } + jointpur { 2 } + end trait :completed do ownershipsch { 2 } type { 8 } diff --git a/spec/fixtures/forms/2021_2022.json b/spec/fixtures/forms/2021_2022.json index 97999d8a3..69f23bbd6 100644 --- a/spec/fixtures/forms/2021_2022.json +++ b/spec/fixtures/forms/2021_2022.json @@ -411,6 +411,7 @@ "hint_text": "Type ahead to filter the options", "type": "select", "check_answer_label": "Accessible Select", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "": "Select an option", "E07000223": "Adur", @@ -473,6 +474,7 @@ "hint_text": "Type ahead to filter the options", "type": "select", "check_answer_label": "Accessible Select", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "": "Select an option", "E07000223": "Adur", @@ -500,6 +502,7 @@ "header": "Do you know the property’s postcode?", "hint_text": "", "type": "radio", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "1": { "value": "Yes" @@ -521,6 +524,7 @@ "hint_text": "", "type": "text", "width": 5, + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "inferred_answers": { "la": { "is_la_inferred": true @@ -544,6 +548,7 @@ "header": "Do you know what local authority the property is located in?", "hint_text": "", "type": "radio", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "0": { "value": "No" @@ -1151,6 +1156,7 @@ "header": "Postcode for the previous accommodation", "hint_text": "If the household has moved from settled accommodation immediately prior to being re-housed", "type": "text", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "width": 5 } } diff --git a/spec/models/form/sales/questions/age2_spec.rb b/spec/models/form/sales/questions/age2_spec.rb index e98f41f93..fc5dfa77a 100644 --- a/spec/models/form/sales/questions/age2_spec.rb +++ b/spec/models/form/sales/questions/age2_spec.rb @@ -59,4 +59,8 @@ RSpec.describe Form::Sales::Questions::Age2, type: :model do it "has the correct max" do expect(question.max).to eq(110) end + + it "has the correct step" do + expect(question.step).to be 1 + end end diff --git a/spec/models/form/sales/questions/person_age_spec.rb b/spec/models/form/sales/questions/person_age_spec.rb index 9f475f6b6..1358ec4d2 100644 --- a/spec/models/form/sales/questions/person_age_spec.rb +++ b/spec/models/form/sales/questions/person_age_spec.rb @@ -40,6 +40,10 @@ RSpec.describe Form::Sales::Questions::PersonAge, type: :model do expect(question.width).to eq(3) end + it "has the correct step" do + expect(question.step).to be 1 + end + context "with person 2" do let(:person_index) { 2 } let(:question_id) { "age2" } diff --git a/spec/models/form/sales/questions/uprn_confirmation_spec.rb b/spec/models/form/sales/questions/uprn_confirmation_spec.rb index 27f8ec125..d4917ee67 100644 --- a/spec/models/form/sales/questions/uprn_confirmation_spec.rb +++ b/spec/models/form/sales/questions/uprn_confirmation_spec.rb @@ -50,7 +50,7 @@ RSpec.describe Form::Sales::Questions::UprnConfirmation, type: :model do context "when address is present" do it "returns formatted value" do - log = create(:sales_log, address_line1: "1, Test Street", town_or_city: "Test Town", county: "Test County", postcode_full: "AA1 1AA", uprn: "1234", uprn_known: 1) + log = build(:sales_log, :outright_sale_setup_complete, address_line1: "1, Test Street", town_or_city: "Test Town", county: "Test County", postcode_full: "AA1 1AA", uprn: "1234", uprn_known: 1) expect(question.notification_banner(log)).to eq( { diff --git a/spec/models/form_spec.rb b/spec/models/form_spec.rb index f2a80efe3..5045d0280 100644 --- a/spec/models/form_spec.rb +++ b/spec/models/form_spec.rb @@ -204,32 +204,140 @@ RSpec.describe Form, type: :model do end end - describe "invalidated_page_questions" do - let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, needstype: 1) } - let(:expected_invalid) { %w[scheme_id retirement_value_check condition_effects cbl conditional_question_no_second_question net_income_value_check dependent_question offered layear declaration] } + describe "#reset_not_routed_questions_and_invalid_answers" do + around do |example| + Singleton.__init__(FormHandler) + Timecop.freeze(now) do + FormHandler.instance.use_real_forms! + example.run + end + FormHandler.instance.use_fake_forms! + end + + let(:now) { Time.zone.local(2023, 5, 5) } + + context "when there are multiple radio questions for attribute X" do + context "and attribute Y is changed such that a different question for X is routed to" do + let(:log) { FactoryBot.create(:lettings_log, :setup_completed, :sheltered_housing, startdate: now, renewal: 0, prevten:) } + + context "and the value of X remains valid" do + let(:prevten) { 36 } - context "when dependencies are not met" do - it "returns an array of question keys whose pages conditions are not met" do - expect(form.invalidated_page_questions(lettings_log).map(&:id).uniq).to eq(expected_invalid) + it "the value of this attribute is not cleared" do + log.renewal = 1 + log.form.reset_not_routed_questions_and_invalid_answers(log) + expect(log.prevten).to be 36 + end + end + + context "and the value of X is now invalid" do + let(:prevten) { 30 } + + it "the value of this attribute is cleared" do + log.renewal = 1 + log.form.reset_not_routed_questions_and_invalid_answers(log) + expect(log.prevten).to be nil + end + end end end - context "with two pages having the same question and only one has dependencies met" do - it "returns an array of question keys whose pages conditions are not met" do - lettings_log["preg_occ"] = "No" - expect(form.invalidated_page_questions(lettings_log).map(&:id).uniq).to eq(expected_invalid) + context "when there is one radio question for attribute X" do + context "and the start date or sale date is changed such that the collection year changes and there are different options" do + let(:log) { FactoryBot.create(:lettings_log, :setup_completed, :sheltered_housing, startdate: now, sheltered:) } + + context "and the value of X remains valid" do + let(:sheltered) { 2 } + + it "the value of this attribute is not cleared" do + log.update!(startdate: Time.zone.local(2023, 1, 1)) + expect(log.sheltered).to be 2 + end + end + + context "and the value of X is now invalid" do + let(:sheltered) { 5 } + + it "the value of this attribute is cleared" do + log.update!(startdate: Time.zone.local(2023, 1, 1)) + expect(log.sheltered).to be nil + end + end end end - context "when a question is marked as `derived` and `depends_on: false`" do - let(:lettings_log) { FactoryBot.build(:lettings_log, :in_progress, startdate: Time.utc(2022, 4, 2, 10, 36, 49)) } + context "when there is one free user input question for an attribute X" do + let(:log) { FactoryBot.create(:sales_log, :shared_ownership_setup_complete, staircase: 1, stairbought: 25) } - it "does not count it's questions as invalidated" do - expect(form.enabled_page_questions(lettings_log).map(&:id).uniq).to include("tshortfall_known") + context "and attribute Y is changed such that it is no longer routed to" do + it "the value of this attribute is cleared" do + expect(log.stairbought).to be 25 + log.staircase = 2 + log.form.reset_not_routed_questions_and_invalid_answers(log) + expect(log.stairbought).to be nil + end end + end + + context "when there are multiple free user input questions for attribute X" do + context "and attribute Y is changed such that a different question for X is routed to" do + let(:log) { FactoryBot.create(:sales_log, :saledate_today, :shared_ownership, :privacy_notice_seen, jointpur: 1, jointmore: 2, hholdcount: expected_hholdcount) } + let(:expected_hholdcount) { 2 } + + it "the value of this attribute is not cleared" do + log.jointpur = 2 + log.form.reset_not_routed_questions_and_invalid_answers(log) + expect(log.hholdcount).to eq expected_hholdcount + end + end + + context "and attribute Y is changed such that no questions for X are routed to" do + let(:log) { FactoryBot.create(:sales_log, :shared_ownership_setup_complete, value: initial_value) } + let(:initial_value) { 200_000.to_d } + + it "the value of this attribute is cleared" do + expect(log.value).to eq initial_value + log.ownershipsch = 2 + log.form.reset_not_routed_questions_and_invalid_answers(log) + expect(log.value).to be nil + end + end + end + + context "when a value is changed such that a checkbox question is no longer routed to" do + let(:log) { FactoryBot.create(:lettings_log, :setup_completed, startdate: now, reasonpref: 1, rp_homeless: 1, rp_medwel: 1, rp_hardship: 1) } + + it "all attributes relating to that checkbox question are cleared" do + expect(log.rp_homeless).to be 1 + log.reasonpref = 2 + log.form.reset_not_routed_questions_and_invalid_answers(log) + expect(log.rp_homeless).to be nil + expect(log.rp_medwel).to be nil + expect(log.rp_hardship).to be nil + end + end + + context "when an attribute is derived, but no questions for that attribute are routed to" do + let(:log) { FactoryBot.create(:sales_log, :outright_sale_setup_complete, value: 200_000) } + + it "the value of this attribute is not cleared" do + expect(log.deposit).to be nil + log.update!(mortgageused: 2) + expect(log.form.questions.any? { |q| q.id == "deposit" && q.page.routed_to?(log, nil) }).to be false + expect(log.deposit).not_to be nil + end + end + + context "when an attribute is related to a callback question with no set answer options, and no questions for that attribute are routed to" do + let(:location) { FactoryBot.create(:location) } + let(:log) { FactoryBot.create(:lettings_log, :startdate_today) } - it "does not route to the page" do - expect(form.invalidated_pages(lettings_log).map(&:id)).to include("outstanding_amount_known") + # Pages::PropertyPostcode and questions inside have been removed from form. do we not want to delete and migration delete_column? + it "the value of this attribute is not cleared" do + expect(log.form.questions.find { |q| q.id == "location_id" }.answer_options.keys).to be_empty + log.location_id = location.id + log.form.reset_not_routed_questions_and_invalid_answers(log) + expect(log.location_id).not_to be nil end end end diff --git a/spec/models/sales_log_spec.rb b/spec/models/sales_log_spec.rb index c008269ea..be03fa0fe 100644 --- a/spec/models/sales_log_spec.rb +++ b/spec/models/sales_log_spec.rb @@ -181,7 +181,7 @@ RSpec.describe SalesLog, type: :model do let(:sales_log) { create(:sales_log, :completed) } it "correctly derives and saves exday, exmonth and exyear" do - sales_log.update!(exdate: Time.gm(2022, 5, 4), saledate: Time.gm(2022, 7, 4), ownershipsch: 1, staircase: 2, resale: 2) + sales_log.update!(exdate: Time.gm(2022, 5, 4), saledate: Time.gm(2022, 7, 4), ownershipsch: 1, type: 18, staircase: 2, resale: 2, proplen: 0) record_from_db = ActiveRecord::Base.connection.execute("select exday, exmonth, exyear from sales_logs where id=#{sales_log.id}").to_a[0] expect(record_from_db["exday"]).to eq(4) expect(record_from_db["exmonth"]).to eq(5) @@ -571,8 +571,8 @@ RSpec.describe SalesLog, type: :model do end end - context "when service errors" do - let(:sales_log) { create(:sales_log, uprn_known: 1, uprn: "123456789", uprn_confirmed: 1) } + context "when the API returns an error" do + let(:sales_log) { build(:sales_log, :outright_sale_setup_complete, uprn_known: 1, uprn: "123456789", uprn_confirmed: 1) } let(:error_message) { "error" } it "adds error to sales log" do diff --git a/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb index a39f0969c..fd60c4a7b 100644 --- a/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb @@ -852,16 +852,6 @@ RSpec.describe BulkUpload::Lettings::Year2022::RowParser do end end - describe "#field_134" do - context "when an unpermitted value" do - let(:attributes) { { bulk_upload:, field_134: "3" } } - - it "has errors on the field" do - expect(parser.errors[:field_134]).to be_present - end - end - end - describe "#field_103" do context "when null" do let(:attributes) { setup_section_params.merge({ field_103: nil }) } @@ -874,14 +864,6 @@ RSpec.describe BulkUpload::Lettings::Year2022::RowParser do expect(parser.errors[:field_103]).to eql(["You must answer type of building"]) end end - - context "when unpermitted values" do - let(:attributes) { setup_section_params.merge({ field_103: "4" }) } - - it "returns an error" do - expect(parser.errors[:field_103]).to be_present - end - end end end diff --git a/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb index 37d8e24a5..3c472472e 100644 --- a/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb @@ -793,14 +793,6 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do end describe "#field_6" do # renewal - context "when an unpermitted value" do - let(:attributes) { { bulk_upload:, field_6: "3" } } - - it "has errors on the field" do - expect(parser.errors[:field_6]).to be_present - end - end - context "when blank" do let(:attributes) { { bulk_upload:, field_1: owning_org.old_visible_id, field_6: "" } } @@ -842,14 +834,6 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do expect(parser.errors[:field_30]).to eql(["You must answer type of building"]) end end - - context "when unpermitted values" do - let(:attributes) { setup_section_params.merge({ field_30: "4" }) } - - it "returns an error" do - expect(parser.errors[:field_30]).to be_present - end - end end describe "#field_52" do # age2