Browse Source

CLDC-883: Clear dependent answers when the dependency value changes (#206)

* Failing spec

* Reset invalidated fields

* Page routing can also depend on subsection
pull/217/head
baarkerlounger 3 years ago committed by GitHub
parent
commit
37fbf16c1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      app/models/bulk_upload.rb
  2. 74
      app/models/case_log.rb
  3. 20
      app/models/form.rb
  4. 8
      app/models/form/page.rb
  5. 2
      app/models/form/question.rb
  6. 4
      spec/factories/case_log.rb
  7. 18
      spec/features/form/validations_spec.rb
  8. 4
      spec/fixtures/complete_case_log.json
  9. BIN
      spec/fixtures/files/2021_22_lettings_bulk_upload.xlsx
  10. 2
      spec/fixtures/forms/2021_2022.json
  11. 22
      spec/models/case_log_spec.rb
  12. 12
      spec/models/form/page_spec.rb

8
app/models/bulk_upload.rb

@ -157,7 +157,7 @@ class BulkUpload
mrcmonth: row[93], mrcmonth: row[93],
mrcyear: row[94], mrcyear: row[94],
# supported_scheme: row[95], # 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_day: row[96],
# startdate_month: row[97], # startdate_month: row[97],
# startdate_year: row[98], # startdate_year: row[98],
@ -201,6 +201,12 @@ class BulkUpload
} }
end 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) def other_hhmemb(row)
[13, 14, 15, 16, 17, 18, 19].count { |idx| row[idx].present? } [13, 14, 15, 16, 17, 18, 19].count { |idx| row[idx].present? }
end end

74
app/models/case_log.rb

@ -35,10 +35,11 @@ class CaseLog < ApplicationRecord
default_scope -> { kept } default_scope -> { kept }
validates_with CaseLogValidator validates_with CaseLogValidator
before_save :update_status!
before_validation :process_postcode_changes!, if: :property_postcode_changed? 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 :reset_location_fields!, unless: :postcode_known?
before_validation :set_derived_fields! before_validation :set_derived_fields!
before_save :update_status!
belongs_to :owning_organisation, class_name: "Organisation" belongs_to :owning_organisation, class_name: "Organisation"
belongs_to :managing_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)) } scope :for_organisation, ->(org) { where(owning_organisation: org).or(where(managing_organisation: org)) }
enum status: { "not_started" => 0, "in_progress" => 1, "completed" => 2 } enum status: { "not_started" => 0, "in_progress" => 1, "completed" => 2 }
enum ethnic: ETHNIC enum ethnic: ETHNIC
enum national: NATIONAL, _suffix: true enum national: NATIONAL, _suffix: true
enum ecstat1: ECSTAT, _suffix: true enum ecstat1: ECSTAT, _suffix: true
@ -135,10 +135,8 @@ class CaseLog < ApplicationRecord
enum la_known: POLAR, _suffix: true enum la_known: POLAR, _suffix: true
enum net_income_known: NET_INCOME_KNOWN, _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 AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at].freeze
OPTIONAL_FIELDS = %w[postcode_known OPTIONAL_FIELDS = %w[postcode_known la_known first_time_property_let_as_social_housing].freeze
la_known
first_time_property_let_as_social_housing].freeze
def form def form
FormHandler.instance.get_form(form_name) FormHandler.instance.get_form(form_name)
@ -212,6 +210,16 @@ private
end end
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! def set_derived_fields!
if previous_postcode.present? if previous_postcode.present?
self.ppostc1 = UKPostcode.parse(previous_postcode).outcode self.ppostc1 = UKPostcode.parse(previous_postcode).outcode
@ -295,62 +303,16 @@ private
end end
def all_fields_completed? def all_fields_completed?
mandatory_fields.none? { |_key, val| val.nil? } mandatory_fields.none? { |field| public_send(field).nil? if respond_to?(field) }
end end
def all_fields_nil? def all_fields_nil?
init_fields = %w[owning_organisation_id managing_organisation_id] init_fields = %w[owning_organisation_id managing_organisation_id]
fields = mandatory_fields.except(*init_fields) fields = mandatory_fields.difference(init_fields)
fields.all? { |_key, val| val.nil? } fields.none? { |field| public_send(field).present? if respond_to?(field) }
end end
def mandatory_fields def mandatory_fields
required = attributes.except(*(AUTOGENERATED_FIELDS + OPTIONAL_FIELDS)) form.questions.map(&:id).difference(OPTIONAL_FIELDS, dynamically_not_required)
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) }
end end
end end

20
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]) } } c.map { |k, v| v.keys.map { |key| Hash(from: k, to: key, cond: v[key]) } }
}.flatten }.flatten
end 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 end

8
app/models/form/page.rb

@ -22,6 +22,14 @@ class Form::Page
end end
def routed_to?(case_log) 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 return true unless depends_on
depends_on.all? do |question, value| depends_on.all? do |question, value|

2
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 # 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" 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 end
private private

4
spec/factories/case_log.rb

@ -38,7 +38,7 @@ FactoryBot.define do
tenant_code { "BZ737" } tenant_code { "BZ737" }
postcode { "NW1 7TY" } postcode { "NW1 7TY" }
age1 { 35 } age1 { 35 }
sex1 { "F" } sex1 { "Female" }
ethnic { 2 } ethnic { 2 }
national { 4 } national { 4 }
prevten { "Private sector tenancy" } prevten { "Private sector tenancy" }
@ -54,7 +54,7 @@ FactoryBot.define do
leftreg { "No - they left up to 5 years ago" } leftreg { "No - they left up to 5 years ago" }
reservist { "No" } reservist { "No" }
illness { "Yes" } illness { "Yes" }
preg_occ { "No" } preg_occ { "Yes" }
accessibility_requirements { "No" } accessibility_requirements { "No" }
condition_effects { "dummy" } condition_effects { "dummy" }
tenancy_code { "BZ757" } tenancy_code { "BZ757" }

18
spec/features/form/validations_spec.rb

@ -102,6 +102,24 @@ RSpec.describe "validations" do
end end
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 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 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 let(:case_log) do

4
spec/fixtures/complete_case_log.json vendored

@ -50,10 +50,10 @@
"accessibility_requirements": "No", "accessibility_requirements": "No",
"condition_effects": "dummy", "condition_effects": "dummy",
"tenancy_code": "BZ757", "tenancy_code": "BZ757",
"startdate": "12/12/2020", "startdate": "12/12/2021",
"day": 12, "day": 12,
"month": 12, "month": 12,
"year": 2020, "year": 2021,
"startertenancy": "No", "startertenancy": "No",
"tenancylength": "5", "tenancylength": "5",
"tenancy": "Secure (including flexible)", "tenancy": "Secure (including flexible)",

BIN
spec/fixtures/files/2021_22_lettings_bulk_upload.xlsx vendored

Binary file not shown.

2
spec/fixtures/forms/2021_2022.json vendored

@ -596,7 +596,7 @@
"header": "", "header": "",
"description": "", "description": "",
"questions": { "questions": {
"why_dont_you_know_la": { "reason": {
"check_answer_label": "Reason for not knowing local authority", "check_answer_label": "Reason for not knowing local authority",
"header": "Give a reason why you don’t know the postcode or local authority", "header": "Give a reason why you don’t know the postcode or local authority",
"hint_text": "", "hint_text": "",

22
spec/models/case_log_spec.rb

@ -722,8 +722,8 @@ RSpec.describe Form, type: :model do
it "cannot be later than the tenancy start date" do it "cannot be later than the tenancy start date" do
expect { expect {
CaseLog.create!( CaseLog.create!(
mrcdate: Date.new(2020, 10, 10), mrcdate: Date.new(2021, 10, 10),
startdate: Date.new(2020, 10, 9), startdate: Date.new(2021, 10, 9),
owning_organisation: owning_organisation, owning_organisation: owning_organisation,
managing_organisation: managing_organisation, managing_organisation: managing_organisation,
) )
@ -731,8 +731,8 @@ RSpec.describe Form, type: :model do
expect { expect {
CaseLog.create!( CaseLog.create!(
mrcdate: Date.new(2020, 10, 9), mrcdate: Date.new(2021, 10, 9),
startdate: Date.new(2020, 10, 10), startdate: Date.new(2021, 10, 10),
owning_organisation: owning_organisation, owning_organisation: owning_organisation,
managing_organisation: managing_organisation, managing_organisation: managing_organisation,
) )
@ -771,7 +771,7 @@ RSpec.describe Form, type: :model do
it "must have less than two years between the tenancy start date and major repairs date" do it "must have less than two years between the tenancy start date and major repairs date" do
expect { expect {
CaseLog.create!( CaseLog.create!(
startdate: Date.new(2020, 10, 10), startdate: Date.new(2021, 10, 10),
mrcdate: Date.new(2017, 10, 10), mrcdate: Date.new(2017, 10, 10),
owning_organisation: owning_organisation, owning_organisation: owning_organisation,
managing_organisation: managing_organisation, managing_organisation: managing_organisation,
@ -784,7 +784,7 @@ RSpec.describe Form, type: :model do
it "must have less than 10 years between the tenancy start date and void" do it "must have less than 10 years between the tenancy start date and void" do
expect { expect {
CaseLog.create!( CaseLog.create!(
startdate: Date.new(2020, 10, 10), startdate: Date.new(2021, 10, 10),
property_void_date: Date.new(2009, 10, 10), property_void_date: Date.new(2009, 10, 10),
owning_organisation: owning_organisation, owning_organisation: owning_organisation,
managing_organisation: managing_organisation, managing_organisation: managing_organisation,
@ -793,7 +793,7 @@ RSpec.describe Form, type: :model do
expect { expect {
CaseLog.create!( CaseLog.create!(
startdate: Date.new(2020, 10, 10), startdate: Date.new(2021, 10, 10),
property_void_date: Date.new(2015, 10, 10), property_void_date: Date.new(2015, 10, 10),
owning_organisation: owning_organisation, owning_organisation: owning_organisation,
managing_organisation: managing_organisation, managing_organisation: managing_organisation,
@ -804,8 +804,8 @@ RSpec.describe Form, type: :model do
it "must be before the tenancy start date" do it "must be before the tenancy start date" do
expect { expect {
CaseLog.create!( CaseLog.create!(
startdate: Date.new(2020, 10, 10), startdate: Date.new(2021, 10, 10),
property_void_date: Date.new(2021, 10, 10), property_void_date: Date.new(2021, 10, 11),
owning_organisation: owning_organisation, owning_organisation: owning_organisation,
managing_organisation: managing_organisation, managing_organisation: managing_organisation,
) )
@ -813,7 +813,7 @@ RSpec.describe Form, type: :model do
expect { expect {
CaseLog.create!( CaseLog.create!(
startdate: Date.new(2020, 10, 10), startdate: Date.new(2021, 10, 10),
property_void_date: Date.new(2019, 10, 10), property_void_date: Date.new(2019, 10, 10),
owning_organisation: owning_organisation, owning_organisation: owning_organisation,
managing_organisation: managing_organisation, managing_organisation: managing_organisation,
@ -824,7 +824,7 @@ RSpec.describe Form, type: :model do
it "must be before major repairs date if major repairs date provided" do it "must be before major repairs date if major repairs date provided" do
expect { expect {
CaseLog.create!( CaseLog.create!(
startdate: Date.new(2020, 10, 10), startdate: Date.new(2021, 10, 10),
mrcdate: Date.new(2019, 10, 10), mrcdate: Date.new(2019, 10, 10),
property_void_date: Date.new(2019, 11, 11), property_void_date: Date.new(2019, 11, 11),
owning_organisation: owning_organisation, owning_organisation: owning_organisation,

12
spec/models/form/page_spec.rb

@ -63,5 +63,17 @@ RSpec.describe Form::Page, type: :model do
expect(subject.routed_to?(case_log)).to be true expect(subject.routed_to?(case_log)).to be true
end end
end end
context "when the page's subsection has routing conditions" do
let(:section_id) { "submission" }
let(:subsection_id) { "declaration" }
let(:page_id) { "declaration" }
let(:completed_case_log) { FactoryBot.build(:case_log, :completed, incfreq: "Weekly") }
it "evaluates the sections dependencies" do
expect(subject.routed_to?(case_log)).to be false
expect(subject.routed_to?(completed_case_log)).to be true
end
end
end end
end end

Loading…
Cancel
Save