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"
# Json Schema
gem "json-schema"
gem "uk_postcode"
# Authentication
gem "devise"
gem "turbo-rails", "~> 0.8"
gem "uk_postcode"
group :development, :test do
# 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",
}.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)
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
def get_subsections_count(form, case_log, status = :all)
subsections = form.all_subsections.keys
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
def get_first_page_or_check_answers(subsection, case_log, form, questions)
path = if is_started?(subsection, case_log, form, questions)
def get_first_page_or_check_answers(subsection, case_log, form)
path = if is_started?(subsection, case_log, form)
"case_log_#{subsection}_check_answers_path"
else
"case_log_#{form.first_page_for_subsection(subsection)}_path"
@ -46,15 +34,20 @@ module TasklistHelper
send(path, case_log)
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
def is_incomplete?(subsection, case_log, form, questions)
status = get_subsection_status(subsection, case_log, form, questions)
def is_incomplete?(subsection, case_log, form)
status = form.subsection_status(subsection, case_log)
%i[not_started in_progress].include?(status)
end
def is_started?(subsection, case_log, form, questions)
status = get_subsection_status(subsection, case_log, form, questions)
def is_started?(subsection, case_log, form)
status = form.subsection_status(subsection, case_log)
%i[in_progress completed].include?(status)
end
end

22
app/models/form.rb

@ -119,6 +119,28 @@ class Form
all_pages[page]["depends_on"]
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)
case question["type"]
when "numeric"

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

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

19
config/forms/2021_2022.json

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

118
spec/factories/case_log.rb

@ -8,11 +8,6 @@ FactoryBot.define do
previous_postcode { "SW2 6HI" }
age1 { "17" }
end
trait :completed do
status { 2 }
tenant_code { "BZ737" }
property_postcode { "NW1 7TY" }
end
trait :soft_validations_triggered do
status { 1 }
ecstat1 { "Full-time - 30 hours or more" }
@ -28,6 +23,119 @@ FactoryBot.define do
ecstat1 { 2 }
other_hhmemb { 0 }
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 }
updated_at { Time.zone.now }
end

11
spec/fixtures/forms/test_form.json vendored

@ -365,7 +365,7 @@
"pages": {
"rent": {
"questions": {
"rent_frequency": {
"period": {
"check_answer_label": "Rent Period",
"header": "Which period are rent and other charges due?",
"type": "radio",
@ -529,6 +529,15 @@
"subsections": {
"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": {
"declaration": {
"questions": {

52
spec/helpers/tasklist_helper_spec.rb

@ -3,56 +3,8 @@ require "rails_helper"
RSpec.describe TasklistHelper do
let(:empty_case_log) { FactoryBot.build(:case_log) }
let(:case_log) { FactoryBot.build(:case_log, :in_progress) }
let(:completed_case_log) { FactoryBot.build(:case_log, :completed) }
form_handler = FormHandler.instance
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
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
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
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

42
spec/models/form_spec.rb

@ -4,6 +4,8 @@ RSpec.describe Form, type: :model do
form_handler = FormHandler.instance
let(:form) { form_handler.get_form("test_form") }
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
let(:previous_page) { "person_1_age" }
@ -12,6 +14,46 @@ RSpec.describe Form, type: :model do
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
let(:subsection) { "household_characteristics" }
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!
expect(response.body).to match(/Check your email/)
end
end
context "when a password reset is requested the email" do

Loading…
Cancel
Save