From 37fbf16c1e3a1e025077dcb7e4bcca25219ab4b1 Mon Sep 17 00:00:00 2001 From: baarkerlounger <5101747+baarkerlounger@users.noreply.github.com> Date: Wed, 12 Jan 2022 12:26:59 +0000 Subject: [PATCH] CLDC-883: Clear dependent answers when the dependency value changes (#206) * Failing spec * Reset invalidated fields * Page routing can also depend on subsection --- app/models/bulk_upload.rb | 8 +- app/models/case_log.rb | 74 +++++------------- app/models/form.rb | 20 +++++ app/models/form/page.rb | 8 ++ app/models/form/question.rb | 2 +- spec/factories/case_log.rb | 4 +- spec/features/form/validations_spec.rb | 18 +++++ spec/fixtures/complete_case_log.json | 4 +- .../files/2021_22_lettings_bulk_upload.xlsx | Bin 25857 -> 25878 bytes spec/fixtures/forms/2021_2022.json | 2 +- spec/models/case_log_spec.rb | 22 +++--- spec/models/form/page_spec.rb | 12 +++ 12 files changed, 100 insertions(+), 74 deletions(-) diff --git a/app/models/bulk_upload.rb b/app/models/bulk_upload.rb index c7f211064..66d24bf8d 100644 --- a/app/models/bulk_upload.rb +++ b/app/models/bulk_upload.rb @@ -157,7 +157,7 @@ class BulkUpload mrcmonth: row[93], mrcyear: row[94], # supported_scheme: row[95], - startdate: row[96].to_s + row[97].to_s + row[98].to_s, + startdate: date_time(row[98], row[97], row[96]), # startdate_day: row[96], # startdate_month: row[97], # startdate_year: row[98], @@ -201,6 +201,12 @@ class BulkUpload } end + def date_time(year, month, day) + return unless year && month && day + + Time.zone.local("20#{year}", month.to_s, day.to_s) + end + def other_hhmemb(row) [13, 14, 15, 16, 17, 18, 19].count { |idx| row[idx].present? } end diff --git a/app/models/case_log.rb b/app/models/case_log.rb index 042759733..4b9d196b9 100644 --- a/app/models/case_log.rb +++ b/app/models/case_log.rb @@ -35,10 +35,11 @@ class CaseLog < ApplicationRecord default_scope -> { kept } validates_with CaseLogValidator - before_save :update_status! before_validation :process_postcode_changes!, if: :property_postcode_changed? + before_validation :reset_invalidated_dependent_fields! before_validation :reset_location_fields!, unless: :postcode_known? before_validation :set_derived_fields! + before_save :update_status! belongs_to :owning_organisation, class_name: "Organisation" belongs_to :managing_organisation, class_name: "Organisation" @@ -46,7 +47,6 @@ class CaseLog < ApplicationRecord scope :for_organisation, ->(org) { where(owning_organisation: org).or(where(managing_organisation: org)) } enum status: { "not_started" => 0, "in_progress" => 1, "completed" => 2 } - enum ethnic: ETHNIC enum national: NATIONAL, _suffix: true enum ecstat1: ECSTAT, _suffix: true @@ -135,10 +135,8 @@ class CaseLog < ApplicationRecord enum la_known: POLAR, _suffix: true enum net_income_known: NET_INCOME_KNOWN, _suffix: true - AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at renttype lettype is_la_inferred totchild totelder totadult incfreq tcharge].freeze - OPTIONAL_FIELDS = %w[postcode_known - la_known - first_time_property_let_as_social_housing].freeze + AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at].freeze + OPTIONAL_FIELDS = %w[postcode_known la_known first_time_property_let_as_social_housing].freeze def form FormHandler.instance.get_form(form_name) @@ -212,6 +210,16 @@ private end end + def reset_invalidated_dependent_fields! + form.invalidated_page_questions(self).each do |question| + public_send("#{question.id}=", nil) if respond_to?(question.id.to_s) + end + end + + def dynamically_not_required + (form.invalidated_questions(self) + form.readonly_questions).map(&:id).uniq + end + def set_derived_fields! if previous_postcode.present? self.ppostc1 = UKPostcode.parse(previous_postcode).outcode @@ -295,62 +303,16 @@ private end def all_fields_completed? - mandatory_fields.none? { |_key, val| val.nil? } + mandatory_fields.none? { |field| public_send(field).nil? if respond_to?(field) } end def all_fields_nil? init_fields = %w[owning_organisation_id managing_organisation_id] - fields = mandatory_fields.except(*init_fields) - fields.all? { |_key, val| val.nil? } + fields = mandatory_fields.difference(init_fields) + fields.none? { |field| public_send(field).present? if respond_to?(field) } end def mandatory_fields - required = attributes.except(*(AUTOGENERATED_FIELDS + OPTIONAL_FIELDS)) - - dynamically_not_required = [] - - if reason != "Other" - dynamically_not_required << "other_reason_for_leaving_last_settled_home" - end - - if earnings.to_i.zero? - dynamically_not_required << "incfreq" - end - - if sale_or_letting == "Letting" - dynamically_not_required << "sale_completion_date" - end - - if la.present? - dynamically_not_required << "why_dont_you_know_la" - end - - if tenancy == "Secure (including flexible)" - dynamically_not_required << "tenancylength" - end - - unless net_income_in_soft_max_range? || net_income_in_soft_min_range? - dynamically_not_required << "override_net_income_validation" - end - - unless tenancy == "Other" - dynamically_not_required << "tenancyother" - end - - dynamically_not_required << if net_income_known == "Tenant prefers not to say" - "earnings" - else - "incref" - end - - start_range = (other_hhmemb || 0) + 2 - (start_range..8).each do |n| - dynamically_not_required << "age#{n}" - dynamically_not_required << "sex#{n}" - dynamically_not_required << "relat#{n}" - dynamically_not_required << "ecstat#{n}" - end - - required.delete_if { |key, _value| dynamically_not_required.include?(key) } + form.questions.map(&:id).difference(OPTIONAL_FIELDS, dynamically_not_required) end end diff --git a/app/models/form.rb b/app/models/form.rb index 370a3cd41..4474eae8e 100644 --- a/app/models/form.rb +++ b/app/models/form.rb @@ -53,4 +53,24 @@ class Form c.map { |k, v| v.keys.map { |key| Hash(from: k, to: key, cond: v[key]) } } }.flatten end + + def invalidated_pages(case_log) + pages.reject { |p| p.routed_to?(case_log) } + end + + def invalidated_questions(case_log) + (invalidated_page_questions(case_log) + invalidated_conditional_questions(case_log)).uniq + end + + def invalidated_page_questions(case_log) + invalidated_pages(case_log).flat_map(&:questions) || [] + end + + def invalidated_conditional_questions(case_log) + questions.reject { |q| q.enabled?(case_log) } || [] + end + + def readonly_questions + questions.select(&:read_only?) + end end diff --git a/app/models/form/page.rb b/app/models/form/page.rb index 12b1065d6..c550114a2 100644 --- a/app/models/form/page.rb +++ b/app/models/form/page.rb @@ -22,6 +22,14 @@ class Form::Page end def routed_to?(case_log) + return true unless depends_on || subsection.depends_on + + subsection.enabled?(case_log) && depends_on_met(case_log) + end + +private + + def depends_on_met(case_log) return true unless depends_on depends_on.all? do |question, value| diff --git a/app/models/form/question.rb b/app/models/form/question.rb index 2bf2d4345..edbf96178 100644 --- a/app/models/form/question.rb +++ b/app/models/form/question.rb @@ -80,7 +80,7 @@ class Form::Question # Special case as No is a valid answer but doesn't let you progress and use the service return false if id == "gdpr_acceptance" && case_log[id] == "No" - case_log[id].present? + case_log[id].present? || !case_log.respond_to?(id.to_sym) end private diff --git a/spec/factories/case_log.rb b/spec/factories/case_log.rb index 8a133549b..c472d6f40 100644 --- a/spec/factories/case_log.rb +++ b/spec/factories/case_log.rb @@ -38,7 +38,7 @@ FactoryBot.define do tenant_code { "BZ737" } postcode { "NW1 7TY" } age1 { 35 } - sex1 { "F" } + sex1 { "Female" } ethnic { 2 } national { 4 } prevten { "Private sector tenancy" } @@ -54,7 +54,7 @@ FactoryBot.define do leftreg { "No - they left up to 5 years ago" } reservist { "No" } illness { "Yes" } - preg_occ { "No" } + preg_occ { "Yes" } accessibility_requirements { "No" } condition_effects { "dummy" } tenancy_code { "BZ757" } diff --git a/spec/features/form/validations_spec.rb b/spec/features/form/validations_spec.rb index 4aa72e261..0ba1eff37 100644 --- a/spec/features/form/validations_spec.rb +++ b/spec/features/form/validations_spec.rb @@ -102,6 +102,24 @@ RSpec.describe "validations" do end end + describe "Compound validations" do + context "when you select two compatible answers, that become incompatible if the first answer changes", js: true do + it "clears the second answer on change of the first" do + case_log.update!(other_hhmemb: 1, relat2: "Partner", age2: 32, sex2: "Female", ecstat2: "Not seeking work") + visit("/logs/#{id}/conditional-question") + choose("case-log-preg-occ-yes-field", allow_label_click: true) + click_button("Save and continue") + choose("case-log-cbl-yes-field", allow_label_click: true) + click_button("Save and continue") + page.go_back + click_link("Back") + choose("case-log-preg-occ-no-field", allow_label_click: true) + click_button("Save and continue") + expect(case_log.reload.cbl).to be_nil + end + 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) do diff --git a/spec/fixtures/complete_case_log.json b/spec/fixtures/complete_case_log.json index 3683832c9..1c8b61097 100644 --- a/spec/fixtures/complete_case_log.json +++ b/spec/fixtures/complete_case_log.json @@ -50,10 +50,10 @@ "accessibility_requirements": "No", "condition_effects": "dummy", "tenancy_code": "BZ757", - "startdate": "12/12/2020", + "startdate": "12/12/2021", "day": 12, "month": 12, - "year": 2020, + "year": 2021, "startertenancy": "No", "tenancylength": "5", "tenancy": "Secure (including flexible)", diff --git a/spec/fixtures/files/2021_22_lettings_bulk_upload.xlsx b/spec/fixtures/files/2021_22_lettings_bulk_upload.xlsx index c8e86cdbeda0df7fbead0c8192901341ee6e5085..ee6644dd49877cc283bfe62e0c57312e80d4c2c4 100644 GIT binary patch delta 12066 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 O