Browse Source

Add section dependencies (#103)

* Lock sections until about this log is completed

* Add dependent sections

* Specs

* Fix tests
pull/105/head
Daniel Baark 3 years ago committed by GitHub
parent
commit
f333ed7a35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      Gemfile
  2. 33
      app/helpers/tasklist_helper.rb
  3. 22
      app/models/form.rb
  4. 5
      app/views/case_logs/_tasklist.html.erb
  5. 19
      config/forms/2021_2022.json
  6. 118
      spec/factories/case_log.rb
  7. 11
      spec/fixtures/forms/test_form.json
  8. 52
      spec/helpers/tasklist_helper_spec.rb
  9. 42
      spec/models/form_spec.rb
  10. 2
      spec/requests/users/passwords_controller_spec.rb

2
Gemfile

@ -33,10 +33,10 @@ gem "chartkick"
gem "roo" gem "roo"
# Json Schema # Json Schema
gem "json-schema" gem "json-schema"
gem "uk_postcode"
# Authentication # Authentication
gem "devise" gem "devise"
gem "turbo-rails", "~> 0.8" gem "turbo-rails", "~> 0.8"
gem "uk_postcode"
group :development, :test do group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console # Call 'byebug' anywhere in the code to stop execution and get a debugger console

33
app/helpers/tasklist_helper.rb

@ -13,32 +13,20 @@ module TasklistHelper
in_progress: "govuk-tag--blue", in_progress: "govuk-tag--blue",
}.freeze }.freeze
def get_subsection_status(subsection_name, case_log, form, questions)
applicable_questions = form.filter_conditional_questions(questions, case_log).keys
if subsection_name == "declaration"
return case_log.completed? ? :not_started : :cannot_start_yet
end
return :not_started if applicable_questions.all? { |question| case_log[question].blank? }
return :completed if applicable_questions.all? { |question| case_log[question].present? }
:in_progress
end
def get_next_incomplete_section(form, case_log) def get_next_incomplete_section(form, case_log)
subsections = form.all_subsections.keys subsections = form.all_subsections.keys
subsections.find { |subsection| is_incomplete?(subsection, case_log, form, form.questions_for_subsection(subsection)) } subsections.find { |subsection| is_incomplete?(subsection, case_log, form) }
end end
def get_subsections_count(form, case_log, status = :all) def get_subsections_count(form, case_log, status = :all)
subsections = form.all_subsections.keys subsections = form.all_subsections.keys
return subsections.count if status == :all return subsections.count if status == :all
subsections.count { |subsection| get_subsection_status(subsection, case_log, form, form.questions_for_subsection(subsection)) == status } subsections.count { |subsection| form.subsection_status(subsection, case_log) == status }
end end
def get_first_page_or_check_answers(subsection, case_log, form, questions) def get_first_page_or_check_answers(subsection, case_log, form)
path = if is_started?(subsection, case_log, form, questions) path = if is_started?(subsection, case_log, form)
"case_log_#{subsection}_check_answers_path" "case_log_#{subsection}_check_answers_path"
else else
"case_log_#{form.first_page_for_subsection(subsection)}_path" "case_log_#{form.first_page_for_subsection(subsection)}_path"
@ -46,15 +34,20 @@ module TasklistHelper
send(path, case_log) send(path, case_log)
end end
def subsection_link(subsection_key, subsection_value, status, form, case_log)
next_page_path = status != :cannot_start_yet ? get_first_page_or_check_answers(subsection_key, case_log, form) : "#"
link_to(subsection_value["label"], next_page_path, class: "task-name govuk-link")
end
private private
def is_incomplete?(subsection, case_log, form, questions) def is_incomplete?(subsection, case_log, form)
status = get_subsection_status(subsection, case_log, form, questions) status = form.subsection_status(subsection, case_log)
%i[not_started in_progress].include?(status) %i[not_started in_progress].include?(status)
end end
def is_started?(subsection, case_log, form, questions) def is_started?(subsection, case_log, form)
status = get_subsection_status(subsection, case_log, form, questions) status = form.subsection_status(subsection, case_log)
%i[in_progress completed].include?(status) %i[in_progress completed].include?(status)
end end
end end

22
app/models/form.rb

@ -119,6 +119,28 @@ class Form
all_pages[page]["depends_on"] all_pages[page]["depends_on"]
end end
def subsection_dependencies_met?(subsection_name, case_log)
conditions = all_subsections[subsection_name]["depends_on"]
return true unless conditions
conditions.all? do |subsection, status|
subsection_status(subsection, case_log) == status.to_sym
end
end
def subsection_status(subsection_name, case_log)
unless subsection_dependencies_met?(subsection_name, case_log)
return :cannot_start_yet
end
questions = questions_for_subsection(subsection_name)
applicable_questions = filter_conditional_questions(questions, case_log).keys
return :not_started if applicable_questions.all? { |question| case_log[question].blank? }
return :completed if applicable_questions.all? { |question| case_log[question].present? }
:in_progress
end
def condition_not_met(case_log, question_key, question, condition) def condition_not_met(case_log, question_key, question, condition)
case question["type"] case question["type"]
when "numeric" when "numeric"

5
app/views/case_logs/_tasklist.html.erb

@ -10,9 +10,8 @@
<% section_value["subsections"].map do |subsection_key, subsection_value| %> <% section_value["subsections"].map do |subsection_key, subsection_value| %>
<li class="app-task-list__item" id=<%= subsection_key %>> <li class="app-task-list__item" id=<%= subsection_key %>>
<% questions_for_subsection = @form.questions_for_subsection(subsection_key) %> <% questions_for_subsection = @form.questions_for_subsection(subsection_key) %>
<% next_page_path = get_first_page_or_check_answers(subsection_key, @case_log, @form, questions_for_subsection) %> <% subsection_status = @form.subsection_status(subsection_key, @case_log) %>
<%= link_to subsection_value["label"], next_page_path, class: "task-name govuk-link" %> <%= subsection_link(subsection_key, subsection_value, subsection_status, @form, @case_log) %>
<% subsection_status = get_subsection_status(subsection_key, @case_log, @form, questions_for_subsection) %>
<strong class="govuk-tag app-task-list__tag <%= TasklistHelper::STYLES[subsection_status] %>"> <strong class="govuk-tag app-task-list__tag <%= TasklistHelper::STYLES[subsection_status] %>">
<%= TasklistHelper::STATUSES[subsection_status] %> <%= TasklistHelper::STATUSES[subsection_status] %>
</strong> </strong>

19
config/forms/2021_2022.json

@ -194,6 +194,7 @@
"subsections": { "subsections": {
"household_characteristics": { "household_characteristics": {
"label": "Household characteristics", "label": "Household characteristics",
"depends_on": {"about_this_log": "completed"},
"pages": { "pages": {
"person_1_age": { "person_1_age": {
"header": "", "header": "",
@ -740,6 +741,7 @@
}, },
"household_situation": { "household_situation": {
"label": "Household situation", "label": "Household situation",
"depends_on": {"about_this_log": "completed"},
"pages": { "pages": {
"previous_housing_situation": { "previous_housing_situation": {
"header": "", "header": "",
@ -865,6 +867,7 @@
}, },
"household_needs": { "household_needs": {
"label": "Household needs", "label": "Household needs",
"depends_on": {"about_this_log": "completed"},
"pages": { "pages": {
"armed_forces": { "armed_forces": {
"header": "Experience of the UK Armed Forces", "header": "Experience of the UK Armed Forces",
@ -1002,6 +1005,7 @@
"subsections": { "subsections": {
"tenancy_information": { "tenancy_information": {
"label": "Tenancy information", "label": "Tenancy information",
"depends_on": {"about_this_log": "completed"},
"pages": { "pages": {
"starter_tenancy": { "starter_tenancy": {
"header": "", "header": "",
@ -1101,6 +1105,7 @@
}, },
"property_information": { "property_information": {
"label": "Property information", "label": "Property information",
"depends_on": { "about_this_log": "completed" },
"pages": { "pages": {
"property_location": { "property_location": {
"header": "", "header": "",
@ -1599,6 +1604,7 @@
"subsections": { "subsections": {
"income_and_benefits": { "income_and_benefits": {
"label": "Income and benefits", "label": "Income and benefits",
"depends_on": {"about_this_log": "completed"},
"pages": { "pages": {
"net_income": { "net_income": {
"header": "", "header": "",
@ -1692,6 +1698,7 @@
}, },
"rent": { "rent": {
"label": "Rent", "label": "Rent",
"depends_on": {"about_this_log": "completed"},
"pages": { "pages": {
"rent": { "rent": {
"header": "", "header": "",
@ -1817,6 +1824,7 @@
"subsections": { "subsections": {
"local_authority": { "local_authority": {
"label": "Local authority", "label": "Local authority",
"depends_on": {"about_this_log": "completed"},
"pages": { "pages": {
"time_lived_in_la": { "time_lived_in_la": {
"header": "", "header": "",
@ -2280,6 +2288,17 @@
"subsections": { "subsections": {
"declaration": { "declaration": {
"label": "Declaration", "label": "Declaration",
"depends_on": {
"about_this_log": "completed",
"household_characteristics": "completed",
"household_situation": "completed",
"household_needs": "completed",
"tenancy_information": "completed",
"property_information": "completed",
"income_and_benefits": "completed",
"rent": "completed",
"local_authority": "completed"
},
"pages": { "pages": {
"declaration": { "declaration": {
"header": "", "header": "",

118
spec/factories/case_log.rb

@ -8,11 +8,6 @@ FactoryBot.define do
previous_postcode { "SW2 6HI" } previous_postcode { "SW2 6HI" }
age1 { "17" } age1 { "17" }
end end
trait :completed do
status { 2 }
tenant_code { "BZ737" }
property_postcode { "NW1 7TY" }
end
trait :soft_validations_triggered do trait :soft_validations_triggered do
status { 1 } status { 1 }
ecstat1 { "Full-time - 30 hours or more" } ecstat1 { "Full-time - 30 hours or more" }
@ -28,6 +23,119 @@ FactoryBot.define do
ecstat1 { 2 } ecstat1 { 2 }
other_hhmemb { 0 } other_hhmemb { 0 }
end end
trait :completed do
status { 2 }
tenant_code { "BZ737" }
postcode { "NW1 7TY" }
age1 { 35 }
sex1 { "F" }
ethnic { 2 }
national { 4 }
prevten { "Private sector tenancy" }
ecstat1 { 2 }
other_hhmemb { 1 }
hhmemb { 2 }
relat2 { "Partner" }
age2 { 32 }
sex2 { "Male" }
ecstat2 { "Not seeking work" }
homeless { "Yes - other homelessness" }
underoccupation_benefitcap { "No" }
leftreg { "No - they left up to 5 years ago" }
reservist { "No" }
illness { "Yes" }
preg_occ { "No" }
accessibility_requirements { "No" }
condition_effects { "dummy" }
tenancy_code { "BZ757" }
startertenancy { "No" }
tenancylength { 5 }
tenancy { "Secure (including flexible)" }
lettype { "Affordable Rent - General Needs" }
landlord { "This landlord" }
previous_postcode { "SE2 6RT" }
rsnvac { "Relet - tenant abandoned property" }
unittype_gn { "House" }
property_building_type { "dummy" }
beds { 3 }
property_void_date { "03/11/2019" }
offered { 2 }
wchair { "Yes" }
earnings { 60 }
incfreq { "Weekly" }
benefits { "Some" }
period { "Fortnightly" }
brent { 200 }
scharge { 50 }
pscharge { 40 }
supcharg { 35 }
tcharge { 325 }
layear { "1 to 2 years" }
lawaitlist { "Less than 1 year" }
property_postcode { "NW1 5TY" }
reasonpref { "Yes" }
reasonable_preference_reason { "dummy" }
cbl { "Yes" }
chr { "Yes" }
cap { "No" }
other_reason_for_leaving_last_settled_home { nil }
housingneeds_a { "Yes" }
housingneeds_b { "No" }
housingneeds_c { "No" }
housingneeds_f { "No" }
housingneeds_g { "No" }
housingneeds_h { "No" }
accessibility_requirements_prefer_not_to_say { 0 }
illness_type_1 { "No" }
illness_type_2 { "Yes" }
illness_type_3 { "No" }
illness_type_4 { "No" }
illness_type_8 { "No" }
illness_type_5 { "No" }
illness_type_6 { "No" }
illness_type_7 { "No" }
illness_type_9 { "No" }
illness_type_10 { "No" }
rp_homeless { "Yes" }
rp_insan_unsat { "No" }
rp_medwel { "No" }
rp_hardship { "No" }
rp_dontknow { "No" }
discarded_at { nil }
tenancyother { nil }
override_net_income_validation { nil }
net_income_known { "Yes" }
gdpr_acceptance { "Yes" }
gdpr_declined { "No" }
property_owner_organisation { "Test" }
property_manager_organisation { "Test" }
sale_or_letting { "Letting" }
tenant_same_property_renewal { 1 }
rent_type { 1 }
intermediate_rent_product_name { 2 }
needs_type { 1 }
purchaser_code { 798_794 }
reason { "Permanently decanted from another property owned by this landlord" }
propcode { "123" }
majorrepairs { "Yes" }
la { "Barnet" }
prevloc { "Ashford" }
hb { 1 }
hbrentshortfall { "Yes" }
tshortfall { 12 }
postcod2 { "w3" }
ppostc1 { "w3" }
ppostc2 { "w3" }
property_relet { "No" }
mrcdate { Time.zone.now }
mrcday { 5 }
mrcmonth { 5 }
mrcyear { 2020 }
incref { 554_355 }
sale_completion_date { nil }
startdate { nil }
armedforces { 1 }
end
created_at { Time.zone.now } created_at { Time.zone.now }
updated_at { Time.zone.now } updated_at { Time.zone.now }
end end

11
spec/fixtures/forms/test_form.json vendored

@ -365,7 +365,7 @@
"pages": { "pages": {
"rent": { "rent": {
"questions": { "questions": {
"rent_frequency": { "period": {
"check_answer_label": "Rent Period", "check_answer_label": "Rent Period",
"header": "Which period are rent and other charges due?", "header": "Which period are rent and other charges due?",
"type": "radio", "type": "radio",
@ -529,6 +529,15 @@
"subsections": { "subsections": {
"declaration": { "declaration": {
"label": "Declaration", "label": "Declaration",
"depends_on": {
"household_characteristics": "completed",
"household_needs": "completed",
"tenancy_information": "completed",
"property_information": "completed",
"income_and_benefits": "completed",
"rent": "completed",
"local_authority": "completed"
},
"pages": { "pages": {
"declaration": { "declaration": {
"questions": { "questions": {

52
spec/helpers/tasklist_helper_spec.rb

@ -3,56 +3,8 @@ require "rails_helper"
RSpec.describe TasklistHelper do RSpec.describe TasklistHelper do
let(:empty_case_log) { FactoryBot.build(:case_log) } let(:empty_case_log) { FactoryBot.build(:case_log) }
let(:case_log) { FactoryBot.build(:case_log, :in_progress) } let(:case_log) { FactoryBot.build(:case_log, :in_progress) }
let(:completed_case_log) { FactoryBot.build(:case_log, :completed) }
form_handler = FormHandler.instance form_handler = FormHandler.instance
let(:form) { form_handler.get_form("test_form") } let(:form) { form_handler.get_form("test_form") }
let(:household_characteristics_questions) { form.questions_for_subsection("household_characteristics") }
describe "get subsection status" do
let(:section) { "income_and_benefits" }
let(:income_and_benefits_questions) { form.questions_for_subsection("income_and_benefits") }
let(:declaration_questions) { form.questions_for_subsection("declaration") }
let(:local_authority_questions) { form.questions_for_subsection("local_authority") }
it "returns not started if none of the questions in the subsection are answered" do
status = get_subsection_status("income_and_benefits", case_log, form, income_and_benefits_questions)
expect(status).to eq(:not_started)
end
it "returns cannot start yet if the subsection is declaration" do
status = get_subsection_status("declaration", case_log, form, declaration_questions)
expect(status).to eq(:cannot_start_yet)
end
it "returns in progress if some of the questions have been answered" do
case_log["previous_postcode"] = "P0 5TT"
status = get_subsection_status("local_authority", case_log, form, local_authority_questions)
expect(status).to eq(:in_progress)
end
it "returns completed if all the questions in the subsection have been answered" do
case_log["earnings"] = "value"
case_log["incfreq"] = "Weekly"
case_log["benefits"] = "All"
case_log["hb"] = "Do not know"
status = get_subsection_status("income_and_benefits", case_log, form, income_and_benefits_questions)
expect(status).to eq(:completed)
end
it "returns not started if the subsection is declaration and all the questions are completed" do
status = get_subsection_status("declaration", completed_case_log, form, declaration_questions)
expect(status).to eq(:not_started)
end
let(:conditional_section_complete_case_log) { FactoryBot.build(:case_log, :conditional_section_complete) }
it "sets the correct status for sections with conditional questions" do
status = get_subsection_status(
"household_characteristics", conditional_section_complete_case_log, form, household_characteristics_questions
)
expect(status).to eq(:completed)
end
end
describe "get next incomplete section" do describe "get next incomplete section" do
it "returns the first subsection name if it is not completed" do it "returns the first subsection name if it is not completed" do
@ -89,11 +41,11 @@ RSpec.describe TasklistHelper do
describe "get_first_page_or_check_answers" do describe "get_first_page_or_check_answers" do
it "returns the check answers page path if the section has been started already" do it "returns the check answers page path if the section has been started already" do
expect(get_first_page_or_check_answers("household_characteristics", case_log, form, household_characteristics_questions)).to match(/check_answers/) expect(get_first_page_or_check_answers("household_characteristics", case_log, form)).to match(/check_answers/)
end end
it "returns the first question page path for the section if it has not been started yet" do it "returns the first question page path for the section if it has not been started yet" do
expect(get_first_page_or_check_answers("household_characteristics", empty_case_log, form, household_characteristics_questions)).to match(/tenant_code/) expect(get_first_page_or_check_answers("household_characteristics", empty_case_log, form)).to match(/tenant_code/)
end end
end end
end end

42
spec/models/form_spec.rb

@ -4,6 +4,8 @@ RSpec.describe Form, type: :model do
form_handler = FormHandler.instance form_handler = FormHandler.instance
let(:form) { form_handler.get_form("test_form") } let(:form) { form_handler.get_form("test_form") }
let(:case_log) { FactoryBot.build(:case_log, :in_progress) } let(:case_log) { FactoryBot.build(:case_log, :in_progress) }
let(:completed_case_log) { FactoryBot.build(:case_log, :completed) }
let(:conditional_section_complete_case_log) { FactoryBot.build(:case_log, :conditional_section_complete) }
describe ".next_page" do describe ".next_page" do
let(:previous_page) { "person_1_age" } let(:previous_page) { "person_1_age" }
@ -12,6 +14,46 @@ RSpec.describe Form, type: :model do
end end
end end
describe "get subsection status" do
let(:section) { "income_and_benefits" }
it "returns not started if none of the questions in the subsection are answered" do
status = form.subsection_status("income_and_benefits", case_log)
expect(status).to eq(:not_started)
end
it "returns cannot start yet if the subsection is declaration" do
status = form.subsection_status("declaration", case_log)
expect(status).to eq(:cannot_start_yet)
end
it "returns in progress if some of the questions have been answered" do
case_log["previous_postcode"] = "P0 5TT"
status = form.subsection_status("local_authority", case_log)
expect(status).to eq(:in_progress)
end
it "returns completed if all the questions in the subsection have been answered" do
case_log["earnings"] = "value"
case_log["incfreq"] = "Weekly"
case_log["benefits"] = "All"
case_log["hb"] = "Do not know"
status = form.subsection_status("income_and_benefits", case_log)
expect(status).to eq(:completed)
end
it "returns not started if the subsection is declaration and all the questions are completed" do
status = form.subsection_status("declaration", completed_case_log)
expect(status).to eq(:not_started)
end
it "sets the correct status for sections with conditional questions" do
status = form.subsection_status("household_characteristics", conditional_section_complete_case_log)
expect(status).to eq(:completed)
end
end
describe ".first_page_for_subsection" do describe ".first_page_for_subsection" do
let(:subsection) { "household_characteristics" } let(:subsection) { "household_characteristics" }
it "returns the first page given a subsection" do it "returns the first page given a subsection" do

2
spec/requests/users/passwords_controller_spec.rb

@ -29,8 +29,6 @@ RSpec.describe Users::PasswordsController, type: :request do
follow_redirect! follow_redirect!
expect(response.body).to match(/Check your email/) expect(response.body).to match(/Check your email/)
end end
end end
context "when a password reset is requested the email" do context "when a password reset is requested the email" do

Loading…
Cancel
Save