Browse Source

Merge branch 'main' into CLDC-687ImplementPropertyInformation

pull/95/head
magicmilo 3 years ago
parent
commit
fca826e7ff
  1. 2
      Gemfile
  2. 4
      Gemfile.lock
  3. 9
      app/constants/db_enums.rb
  4. 22
      app/controllers/bulk_upload_controller.rb
  5. 32
      app/helpers/check_answers_helper.rb
  6. 21
      app/helpers/tasklist_helper.rb
  7. 199
      app/models/bulk_upload.rb
  8. 5
      app/models/case_log.rb
  9. 30
      app/models/form.rb
  10. 6
      app/validations/tenancy_validations.rb
  11. 4
      app/views/case_logs/_tasklist.html.erb
  12. 10
      app/views/case_logs/bulk_upload.html.erb
  13. 2
      app/views/form/page.html.erb
  14. 42
      config/forms/2021_2022.json
  15. 15
      config/routes.rb
  16. 10
      db/migrate/20211116102527_change_datetime.rb
  17. 6
      db/schema.rb
  18. 2
      docs/api/DLUHC-CORE-Data.v1.json
  19. 10
      lib/tasks/form_definition.rake
  20. 9
      spec/factories/case_log.rb
  21. 5
      spec/fixtures/complete_case_log.json
  22. BIN
      spec/fixtures/files/2021_22_lettings_bulk_upload.xlsx
  23. BIN
      spec/fixtures/files/2021_22_lettings_bulk_upload_empty.xlsx
  24. 0
      spec/fixtures/files/random.txt
  25. 27
      spec/helpers/tasklist_helper_spec.rb
  26. 2
      spec/lib/tasks/form_definition_validator_spec.rb
  27. 18
      spec/models/case_log_spec.rb
  28. 60
      spec/requests/bulk_upload_controller_spec.rb

2
Gemfile

@ -29,6 +29,8 @@ gem "discard"
gem "activeadmin" gem "activeadmin"
# Admin charts # Admin charts
gem "chartkick" gem "chartkick"
# Spreadsheet parsing
gem "roo"
# Json Schema # Json Schema
gem "json-schema" gem "json-schema"
gem "uk_postcode" gem "uk_postcode"

4
Gemfile.lock

@ -294,6 +294,9 @@ GEM
actionpack (>= 5.0) actionpack (>= 5.0)
railties (>= 5.0) railties (>= 5.0)
rexml (3.2.5) rexml (3.2.5)
roo (2.8.3)
nokogiri (~> 1)
rubyzip (>= 1.3.0, < 3.0.0)
rubocop (1.21.0) rubocop (1.21.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.0.0.0) parser (>= 3.0.0.0)
@ -409,6 +412,7 @@ DEPENDENCIES
puma (~> 5.0) puma (~> 5.0)
rack-mini-profiler (~> 2.0) rack-mini-profiler (~> 2.0)
rails (~> 6.1.4) rails (~> 6.1.4)
roo
rspec-core! rspec-core!
rspec-expectations! rspec-expectations!
rspec-mocks! rspec-mocks!

9
app/constants/db_enums.rb

@ -166,11 +166,10 @@ module DbEnums
def self.tenancy def self.tenancy
{ {
"Fixed term – Secure" => 1, "Secure (including flexible)" => 1,
"Fixed term – Assured Shorthold Tenancy (AST)" => 4, "Assured" => 2,
"Lifetime – Secure" => 100, "Assured Shorthold" => 4,
"Lifetime – Assured" => 2, "Licence agreement (almshouses only)" => 5,
"License agreement" => 5,
"Other" => 3, "Other" => 3,
} }
end end

22
app/controllers/bulk_upload_controller.rb

@ -0,0 +1,22 @@
class BulkUploadController < ApplicationController
def show
@bulk_upload = BulkUpload.new(nil, nil)
render "case_logs/bulk_upload"
end
def bulk_upload
file = upload_params.tempfile
content_type = upload_params.content_type
@bulk_upload = BulkUpload.new(file, content_type)
@bulk_upload.process
if @bulk_upload.errors.present?
render "case_logs/bulk_upload", status: :unprocessable_entity
else
redirect_to(case_logs_path)
end
end
def upload_params
params.require("bulk_upload")["case_log_bulk_upload"]
end
end

32
app/helpers/check_answers_helper.rb

@ -16,7 +16,7 @@ module CheckAnswersHelper
while page_name.to_s != "check_answers" && subsection_keys.include?(page_name) while page_name.to_s != "check_answers" && subsection_keys.include?(page_name)
questions = form.questions_for_page(page_name) questions = form.questions_for_page(page_name)
applicable_questions = filter_conditional_questions(questions, case_log) applicable_questions = form.filter_conditional_questions(questions, case_log)
total_questions = total_questions.merge(applicable_questions) total_questions = total_questions.merge(applicable_questions)
page_name = get_next_page_name(form, page_name, case_log) page_name = get_next_page_name(form, page_name, case_log)
@ -25,19 +25,6 @@ module CheckAnswersHelper
total_questions total_questions
end end
def filter_conditional_questions(questions, case_log)
applicable_questions = questions
questions.each do |k, question|
question.fetch("conditional_for", []).each do |conditional_question_key, condition|
if condition_not_met(case_log, k, question, condition)
applicable_questions = applicable_questions.reject { |z| z == conditional_question_key }
end
end
end
applicable_questions
end
def get_next_page_name(form, page_name, case_log) def get_next_page_name(form, page_name, case_log)
page = form.all_pages[page_name] page = form.all_pages[page_name]
if page.key?("conditional_route_to") if page.key?("conditional_route_to")
@ -50,23 +37,6 @@ module CheckAnswersHelper
form.next_page(page_name) form.next_page(page_name)
end end
def condition_not_met(case_log, question_key, question, condition)
case question["type"]
when "numeric"
operator = condition[/[<>=]+/].to_sym
operand = condition[/\d+/].to_i
case_log[question_key].blank? || !case_log[question_key].send(operator, operand)
when "text"
case_log[question_key].blank? || !condition.include?(case_log[question_key])
when "radio"
case_log[question_key].blank? || !condition.include?(case_log[question_key])
when "select"
case_log[question_key].blank? || !condition.include?(case_log[question_key])
else
raise "Not implemented yet"
end
end
def create_update_answer_link(case_log_answer, case_log_id, page) def create_update_answer_link(case_log_answer, case_log_id, page)
link_name = case_log_answer.blank? ? "Answer" : "Change" link_name = case_log_answer.blank? ? "Answer" : "Change"
link_to(link_name, "/case_logs/#{case_log_id}/#{page}", class: "govuk-link").html_safe link_to(link_name, "/case_logs/#{case_log_id}/#{page}", class: "govuk-link").html_safe

21
app/helpers/tasklist_helper.rb

@ -13,31 +13,32 @@ module TasklistHelper
in_progress: "govuk-tag--blue", in_progress: "govuk-tag--blue",
}.freeze }.freeze
def get_subsection_status(subsection_name, case_log, questions) def get_subsection_status(subsection_name, case_log, form, questions)
applicable_questions = form.filter_conditional_questions(questions, case_log).keys
if subsection_name == "declaration" if subsection_name == "declaration"
return case_log.completed? ? :not_started : :cannot_start_yet return case_log.completed? ? :not_started : :cannot_start_yet
end end
return :not_started if questions.all? { |question| case_log[question].blank? } return :not_started if applicable_questions.all? { |question| case_log[question].blank? }
return :completed if questions.all? { |question| case_log[question].present? } return :completed if applicable_questions.all? { |question| case_log[question].present? }
:in_progress :in_progress
end 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.questions_for_subsection(subsection).keys) } subsections.find { |subsection| is_incomplete?(subsection, case_log, form, form.questions_for_subsection(subsection)) }
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.questions_for_subsection(subsection).keys) == status } subsections.count { |subsection| get_subsection_status(subsection, case_log, form, form.questions_for_subsection(subsection)) == 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, questions)
path = if is_started?(subsection, case_log, questions) path = if is_started?(subsection, case_log, form, questions)
"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"
@ -47,13 +48,13 @@ module TasklistHelper
private private
def is_incomplete?(subsection, case_log, questions) def is_incomplete?(subsection, case_log, form, questions)
status = get_subsection_status(subsection, case_log, questions) status = get_subsection_status(subsection, case_log, form, questions)
%i[not_started in_progress].include?(status) %i[not_started in_progress].include?(status)
end end
def is_started?(subsection, case_log, questions) def is_started?(subsection, case_log, form, questions)
status = get_subsection_status(subsection, case_log, questions) status = get_subsection_status(subsection, case_log, form, questions)
%i[in_progress completed].include?(status) %i[in_progress completed].include?(status)
end end
end end

199
app/models/bulk_upload.rb

@ -0,0 +1,199 @@
class BulkUpload
include ActiveModel::Model
include ActiveModel::Validations
include ActiveModel::Conversion
SPREADSHEET_CONTENT_TYPES = %w[
application/vnd.ms-excel
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
].freeze
FIRST_DATA_ROW = 7
def initialize(file, content_type)
@file = file
@content_type = content_type
end
def process
return unless valid_content_type?
xlsx = Roo::Spreadsheet.open(@file, extension: :xlsx)
sheet = xlsx.sheet(0)
last_row = sheet.last_row
if last_row < FIRST_DATA_ROW
errors.add(:case_log_bulk_upload, "No data found")
else
data_range = FIRST_DATA_ROW..last_row
data_range.map do |row_num|
case_log = CaseLog.create
map_row(sheet.row(row_num)).each do |attr_key, attr_val|
begin
case_log.update_attribute(attr_key, attr_val)
rescue ArgumentError
end
end
end
end
end
def valid_content_type?
if SPREADSHEET_CONTENT_TYPES.include?(@content_type)
true
else
errors.add(:case_log_bulk_upload, "Invalid file type")
false
end
end
def map_row(row)
{
lettype: row[1],
landlord: row[2],
# reg_num_la_core_code: row[3],
# managementgroup: row[4],
# schemecode: row[5],
# firstletting: row[6],
tenant_code: row[7],
startertenancy: row[8],
tenancy: row[9],
tenancyother: row[10],
# tenancyduration: row[11],
other_hhmemb: other_hhmemb(row),
hhmemb: other_hhmemb(row) + 1,
age1: row[12],
age2: row[13],
age3: row[14],
age4: row[15],
age5: row[16],
age6: row[17],
age7: row[18],
age8: row[19],
sex1: row[20],
sex2: row[21],
sex3: row[22],
sex4: row[23],
sex5: row[24],
sex6: row[25],
sex7: row[26],
sex8: row[27],
relat2: row[28],
relat3: row[29],
relat4: row[30],
relat5: row[31],
relat6: row[32],
relat7: row[33],
relat8: row[34],
ecstat1: row[35],
ecstat2: row[36],
ecstat3: row[37],
ecstat4: row[38],
ecstat5: row[39],
ecstat6: row[40],
ecstat7: row[41],
ecstat8: row[42],
ethnic: row[43],
national: row[44],
armed_forces: row[45],
reservist: row[46],
preg_occ: row[47],
hb: row[48],
benefits: row[49],
net_income_known: row[50].present? ? 1 : nil,
earnings: row[50],
# increfused: row[51],
reason: row[52],
other_reason_for_leaving_last_settled_home: row[53],
underoccupation_benefitcap: row[54],
housingneeds_a: row[55],
housingneeds_b: row[56],
housingneeds_c: row[57],
housingneeds_f: row[58],
housingneeds_g: row[59],
housingneeds_h: row[60],
prevten: row[61],
prevloc: row[62],
# ppostc1: row[63],
# ppostc2: row[64],
# prevpco_unknown: row[65],
layear: row[66],
lawaitlist: row[67],
homeless: row[68],
reasonpref: row[69],
rp_homeless: row[70],
rp_insan_unsat: row[71],
rp_medwel: row[72],
rp_hardship: row[73],
rp_dontknow: row[74],
cbl: row[75],
chr: row[76],
cap: row[77],
# referral_source: row[78],
period: row[79],
brent: row[80],
scharge: row[81],
pscharge: row[82],
supcharg: row[83],
tcharge: row[84],
# tcharge_care_homes: row[85],
# no_rent_or_charge: row[86],
hbrentshortfall: row[87],
tshortfall: row[88],
property_void_date: row[89].to_s + row[90].to_s + row[91].to_s,
# property_void_date_day: row[89],
# property_void_date_month: row[90],
# property_void_date_year: row[91],
majorrepairs: row[92].present? ? "1" : nil,
mrcdate: row[92].to_s + row[93].to_s + row[94].to_s,
mrcday: row[92],
mrcmonth: row[93],
mrcyear: row[94],
# supported_scheme: row[95],
startdate: row[96].to_s + row[97].to_s + row[98].to_s,
# startdate_day: row[96],
# startdate_month: row[97],
# startdate_year: row[98],
offered: row[99],
# property_reference: row[100],
beds: row[101],
unittype_gn: row[102],
property_building_type: row[103],
wchair: row[104],
property_relet: row[105],
rsnvac: row[106],
la: row[107],
# postcode: row[108],
# postcod2: row[109],
# row[110] removed
property_owner_organisation: row[111],
# username: row[112],
property_manager_organisation: row[113],
leftreg: row[114],
# uprn: row[115],
incfreq: row[116],
# sheltered_accom: row[117],
illness: row[118],
illness_type_1: row[119],
illness_type_2: row[120],
illness_type_3: row[121],
illness_type_4: row[122],
illness_type_8: row[123],
illness_type_5: row[124],
illness_type_6: row[125],
illness_type_7: row[126],
illness_type_9: row[127],
illness_type_10: row[128],
# london_affordable: row[129],
rent_type: row[130],
intermediate_rent_product_name: row[131],
# data_protection: row[132],
sale_or_letting: "letting",
gdpr_acceptance: 1,
gdpr_declined: 0,
}
end
def other_hhmemb(row)
[13, 14, 15, 16, 17, 18, 19].count { |idx| row[idx].present? }
end
end

5
app/models/case_log.rb

@ -39,7 +39,6 @@ class CaseLog < ApplicationRecord
include SoftValidations include SoftValidations
include DbEnums include DbEnums
default_scope -> { kept } default_scope -> { kept }
scope :not_completed, -> { where.not(status: "completed") }
validates_with CaseLogValidator validates_with CaseLogValidator
before_save :update_status! before_save :update_status!
@ -129,6 +128,8 @@ class CaseLog < ApplicationRecord
end end
def weekly_net_income def weekly_net_income
return unless earnings && incfreq
case incfreq case incfreq
when "Weekly" when "Weekly"
earnings earnings
@ -230,7 +231,7 @@ private
dynamically_not_required << "incfreq" dynamically_not_required << "incfreq"
end end
if tenancy == "Fixed term – Secure" if tenancy == "Secure (including flexible)"
dynamically_not_required << "tenancylength" dynamically_not_required << "tenancylength"
end end

30
app/models/form.rb

@ -97,6 +97,36 @@ class Form
}.reduce(:merge) }.reduce(:merge)
end end
def filter_conditional_questions(questions, case_log)
applicable_questions = questions
questions.each do |k, question|
question.fetch("conditional_for", []).each do |conditional_question_key, condition|
if condition_not_met(case_log, k, question, condition)
applicable_questions = applicable_questions.reject { |z| z == conditional_question_key }
end
end
end
applicable_questions
end
def condition_not_met(case_log, question_key, question, condition)
case question["type"]
when "numeric"
operator = condition[/[<>=]+/].to_sym
operand = condition[/\d+/].to_i
case_log[question_key].blank? || !case_log[question_key].send(operator, operand)
when "text"
case_log[question_key].blank? || !condition.include?(case_log[question_key])
when "radio"
case_log[question_key].blank? || !condition.include?(case_log[question_key])
when "select"
case_log[question_key].blank? || !condition.include?(case_log[question_key])
else
raise "Not implemented yet"
end
end
def get_answer_label(case_log, question_title) def get_answer_label(case_log, question_title)
question = all_questions[question_title] question = all_questions[question_title]
if question["type"] == "checkbox" if question["type"] == "checkbox"

6
app/validations/tenancy_validations.rb

@ -4,12 +4,12 @@ module TenancyValidations
def validate_fixed_term_tenancy(record) def validate_fixed_term_tenancy(record)
is_present = record.tenancylength.present? is_present = record.tenancylength.present?
is_in_range = record.tenancylength.to_i.between?(2, 99) is_in_range = record.tenancylength.to_i.between?(2, 99)
is_secure = record.tenancy == "Fixed term – Secure" is_secure = record.tenancy == "Secure (including flexible)"
is_ast = record.tenancy == "Fixed term – Assured Shorthold Tenancy (AST)" is_ast = record.tenancy == "Assured Shorthold"
conditions = [ conditions = [
{ condition: !(is_secure || is_ast) && is_present, error: "You must only answer the fixed term tenancy length question if the tenancy type is fixed term" }, { condition: !(is_secure || is_ast) && is_present, error: "You must only answer the fixed term tenancy length question if the tenancy type is fixed term" },
{ condition: is_ast && !is_in_range, error: "Fixed term – Assured Shorthold Tenancy (AST) should be between 2 and 99 years" }, { condition: is_ast && !is_in_range, error: "Fixed term – Assured Shorthold Tenancy (AST) should be between 2 and 99 years" },
{ condition: is_secure && (!is_in_range && is_present), error: "Fixed term – Secure should be between 2 and 99 years or not specified" }, { condition: is_secure && (!is_in_range && is_present), error: "Secure (including flexible) should be between 2 and 99 years or not specified" },
] ]
conditions.each { |condition| condition[:condition] ? (record.errors.add :tenancylength, condition[:error]) : nil } conditions.each { |condition| condition[:condition] ? (record.errors.add :tenancylength, condition[:error]) : nil }

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

@ -9,10 +9,10 @@
<ul class="app-task-list__items"> <ul class="app-task-list__items">
<% 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).keys %> <% 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) %> <% 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" %> <%= link_to subsection_value["label"], next_page_path, class: "task-name govuk-link" %>
<% subsection_status = get_subsection_status(subsection_key, @case_log, questions_for_subsection) %> <% 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>

10
app/views/case_logs/bulk_upload.html.erb

@ -0,0 +1,10 @@
<div class="govuk-form-group">
<%= form_for @bulk_upload, url: bulk_upload_case_logs_path, method: "post", builder: GOVUKDesignSystemFormBuilder::FormBuilder do |f| %>
<%= f.govuk_error_summary %>
<%= f.govuk_file_field :case_log_bulk_upload,
label: { text: "Bulk Upload", size: "l" },
hint: { text: "Upload a spreadsheet using the template" }
%>
<%= f.govuk_submit "Upload" %>
<% end %>
</div>

2
app/views/form/page.html.erb

@ -10,7 +10,7 @@
<%= page_info["header"] %> <%= page_info["header"] %>
</h1> </h1>
<% end %> <% end %>
<%= form_with model: @case_log, method: "submit_form", builder: GOVUKDesignSystemFormBuilder::FormBuilder do |f| %> <%= form_with model: @case_log, url: form_case_log_path(@case_log), method: "post", builder: GOVUKDesignSystemFormBuilder::FormBuilder do |f| %>
<%= f.govuk_error_summary %> <%= f.govuk_error_summary %>
<% page_info["questions"].map do |question_key, question| %> <% page_info["questions"].map do |question_key, question| %>
<div id=<%= question_key + "_div " %><%= display_question_key_div(page_info, question_key) %> > <div id=<%= question_key + "_div " %><%= display_question_key_div(page_info, question_key) %> >

42
config/forms/2021_2022.json

@ -80,9 +80,8 @@
} }
}, },
"conditional_route_to": { "conditional_route_to": {
"tenant_same_property_renewal": { "sale_or_letting": "Letting" } "sale_completion_date": { "sale_or_letting": "Sale" }
}, }
"default_next_page" : "check_answers"
}, },
"tenant_same_property_renewal": { "tenant_same_property_renewal": {
"header": "About this log", "header": "About this log",
@ -100,7 +99,7 @@
} }
} }
}, },
"tenancy_start_date": { "startdate": {
"header": "About this log", "header": "About this log",
"description": "", "description": "",
"questions": { "questions": {
@ -112,8 +111,8 @@
} }
} }
}, },
"rent_type": { "about_this_letting": {
"header": "About this log", "header": "Tell us about this letting",
"description": "", "description": "",
"questions": { "questions": {
"rent_type": { "rent_type": {
@ -150,12 +149,25 @@
} }
} }
}, },
"tenant_code": {
"header": "",
"description": "",
"questions": {
"tenant_code": {
"check_answer_label": "Tenant code",
"header": "What is the tenant code?",
"hint_text": "",
"type": "text"
}
},
"default_next_page": "check_answers"
},
"sale_completion_date": { "sale_completion_date": {
"header": "About this log", "header": "Sale Completion Date",
"description": "", "description": "",
"questions": { "questions": {
"sale_completion_date": { "sale_completion_date": {
"check_answer_label": "What is the sale completion date?", "check_answer_label": "Sale completion date",
"header": "What is the sale completion date?", "header": "What is the sale completion date?",
"hint_text": "For example, 27 3 2007", "hint_text": "For example, 27 3 2007",
"type": "date" "type": "date"
@ -172,7 +184,8 @@
"hint_text": "", "hint_text": "",
"type": "text" "type": "text"
} }
} },
"default_next_page": "check_answers"
} }
} }
} }
@ -1068,12 +1081,11 @@
"hint_text": "", "hint_text": "",
"type": "radio", "type": "radio",
"answer_options": { "answer_options": {
"0": "Fixed term – Secure", "0": "Secure (including flexible)",
"1": "Fixed term – Assured Shorthold Tenancy (AST)", "1": "Assured",
"2": "Lifetime – Secure", "2": "Assured Shorthold",
"3": "Lifetime – Assured", "3": "Licence agreement (almshouses only)",
"4": "License agreement", "4": "Other"
"5": "Other"
}, },
"conditional_for": { "conditional_for": {
"other_tenancy_type": ["Other"] "other_tenancy_type": ["Other"]

15
config/routes.rb

@ -4,15 +4,24 @@ Rails.application.routes.draw do
root to: "test#index" root to: "test#index"
get "about", to: "about#index" get "about", to: "about#index"
post "/case_logs/:id", to: "case_logs#submit_form"
form_handler = FormHandler.instance form_handler = FormHandler.instance
form = form_handler.get_form("2021_2022") form = form_handler.get_form("2021_2022")
resources :case_logs do resources :case_logs do
collection do
post "/bulk_upload", to: "bulk_upload#bulk_upload"
get "/bulk_upload", to: "bulk_upload#show"
end
member do
post "/form", to: "case_logs#submit_form"
end
form.all_pages.keys.map do |page| form.all_pages.keys.map do |page|
get page.to_s, to: "case_logs##{page}" get page.to_s, to: "case_logs##{page}"
get "#{page}/soft_validations", to: "soft_validations#show" get "#{page}/soft_validations", to: "soft_validations#show" if form.soft_validations_for_page(page)
end end
form.all_subsections.keys.map do |subsection| form.all_subsections.keys.map do |subsection|
get "#{subsection}/check_answers", to: "case_logs#check_answers" get "#{subsection}/check_answers", to: "case_logs#check_answers"
end end

10
db/migrate/20211116102527_change_datetime.rb

@ -0,0 +1,10 @@
class ChangeDatetime < ActiveRecord::Migration[6.1]
def change
change_table :case_logs, bulk: true do |t|
t.remove :sale_completion_date
t.column :sale_completion_date, :datetime
t.remove :startdate
t.column :startdate, :datetime
end
end
end

6
db/schema.rb

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_11_12_105348) do ActiveRecord::Schema.define(version: 2021_11_16_102527) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -66,7 +66,6 @@ ActiveRecord::Schema.define(version: 2021_11_12_105348) do
t.string "accessibility_requirements" t.string "accessibility_requirements"
t.string "condition_effects" t.string "condition_effects"
t.string "tenancy_code" t.string "tenancy_code"
t.string "startdate"
t.integer "startertenancy" t.integer "startertenancy"
t.integer "tenancylength" t.integer "tenancylength"
t.integer "tenancy" t.integer "tenancy"
@ -131,7 +130,6 @@ ActiveRecord::Schema.define(version: 2021_11_12_105348) do
t.string "rent_type" t.string "rent_type"
t.string "intermediate_rent_product_name" t.string "intermediate_rent_product_name"
t.string "needs_type" t.string "needs_type"
t.string "sale_completion_date"
t.string "purchaser_code" t.string "purchaser_code"
t.integer "override_net_income_validation" t.integer "override_net_income_validation"
t.string "net_income_known" t.string "net_income_known"
@ -154,6 +152,8 @@ ActiveRecord::Schema.define(version: 2021_11_12_105348) do
t.integer "mrcyear" t.integer "mrcyear"
t.integer "other_hhmemb" t.integer "other_hhmemb"
t.integer "incref" t.integer "incref"
t.datetime "sale_completion_date"
t.datetime "startdate"
t.index ["discarded_at"], name: "index_case_logs_on_discarded_at" t.index ["discarded_at"], name: "index_case_logs_on_discarded_at"
end end

2
docs/api/DLUHC-CORE-Data.v1.json

@ -306,7 +306,7 @@
"startdate": "12/03/2019", "startdate": "12/03/2019",
"startertenancy": "No", "startertenancy": "No",
"tenancylength": "No", "tenancylength": "No",
"tenancy": "Fixed term – Secure", "tenancy": "Secure (including flexible)",
"lettype": "Affordable Rent - General Needs", "lettype": "Affordable Rent - General Needs",
"landlord": "This landlord", "landlord": "This landlord",
"la": "Barnet", "la": "Barnet",

10
lib/tasks/form_definition.rake

@ -32,12 +32,12 @@ namespace :form_definition do
schema = JSON.parse(file.read) schema = JSON.parse(file.read)
meta_schema = JSON::Validator.validator_for_name("draft4").metaschema meta_schema = JSON::Validator.validator_for_name("draft4").metaschema
puts path puts path unless Rails.env.test?
if JSON::Validator.validate(meta_schema, schema) if JSON::Validator.validate(meta_schema, schema)
puts "Schema Definition is Valid" puts "Schema Definition is Valid" unless Rails.env.test?
else else
puts "Schema Definition in #{path} is not valid against draft4 json schema." puts "Schema Definition in #{path} is not valid against draft4 json schema." unless Rails.env.test?
next next
end end
@ -45,8 +45,8 @@ namespace :form_definition do
file = File.open(path) file = File.open(path)
form_definition = JSON.parse(file.read) form_definition = JSON.parse(file.read)
puts path puts path unless Rails.env.test?
puts JSON::Validator.fully_validate(schema, form_definition, strict: true) puts JSON::Validator.fully_validate(schema, form_definition, strict: true) unless Rails.env.test?
begin begin
JSON::Validator.validate!(schema, form_definition) JSON::Validator.validate!(schema, form_definition)

9
spec/factories/case_log.rb

@ -19,6 +19,15 @@ FactoryBot.define do
earnings { 750 } earnings { 750 }
incfreq { "Weekly" } incfreq { "Weekly" }
end end
trait :conditional_section_complete do
tenant_code { "TH356" }
age1 { 34 }
sex1 { "M" }
ethnic { 2 }
national { 4 }
ecstat1 { 2 }
other_hhmemb { 0 }
end
created_at { Time.zone.now } created_at { Time.zone.now }
updated_at { Time.zone.now } updated_at { Time.zone.now }
end end

5
spec/fixtures/complete_case_log.json vendored

@ -52,7 +52,7 @@
"startdate": "12/03/2019", "startdate": "12/03/2019",
"startertenancy": "No", "startertenancy": "No",
"tenancylength": "5", "tenancylength": "5",
"tenancy": "Fixed term – Secure", "tenancy": "Secure (including flexible)",
"lettype": "Affordable Rent - General Needs", "lettype": "Affordable Rent - General Needs",
"landlord": "This landlord", "landlord": "This landlord",
"la": "Barnet", "la": "Barnet",
@ -129,10 +129,9 @@
"rent_type": "", "rent_type": "",
"intermediate_rent_product_name": "", "intermediate_rent_product_name": "",
"needs_type": "", "needs_type": "",
"sale_completion_date": "", "sale_completion_date": "01/01/2020",
"purchaser_code": "", "purchaser_code": "",
"propcode": "123", "propcode": "123",
"majorrepairs": "Yes",
"postcode": "a1", "postcode": "a1",
"postcod2": "w3", "postcod2": "w3",
"ppostc1": "w3", "ppostc1": "w3",

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

Binary file not shown.

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

Binary file not shown.

0
spec/fixtures/files/random.txt vendored

27
spec/helpers/tasklist_helper_spec.rb

@ -6,26 +6,27 @@ RSpec.describe TasklistHelper do
let(:completed_case_log) { FactoryBot.build(:case_log, :completed) } 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 describe "get subsection status" do
let(:section) { "income_and_benefits" } let(:section) { "income_and_benefits" }
let(:income_and_benefits_questions) { form.questions_for_subsection("income_and_benefits").keys } let(:income_and_benefits_questions) { form.questions_for_subsection("income_and_benefits") }
let(:declaration_questions) { form.questions_for_subsection("declaration").keys } let(:declaration_questions) { form.questions_for_subsection("declaration") }
let(:local_authority_questions) { form.questions_for_subsection("local_authority").keys } 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 it "returns not started if none of the questions in the subsection are answered" do
status = get_subsection_status("income_and_benefits", case_log, income_and_benefits_questions) status = get_subsection_status("income_and_benefits", case_log, form, income_and_benefits_questions)
expect(status).to eq(:not_started) expect(status).to eq(:not_started)
end end
it "returns cannot start yet if the subsection is declaration" do it "returns cannot start yet if the subsection is declaration" do
status = get_subsection_status("declaration", case_log, declaration_questions) status = get_subsection_status("declaration", case_log, form, declaration_questions)
expect(status).to eq(:cannot_start_yet) expect(status).to eq(:cannot_start_yet)
end end
it "returns in progress if some of the questions have been answered" do it "returns in progress if some of the questions have been answered" do
case_log["previous_postcode"] = "P0 5TT" case_log["previous_postcode"] = "P0 5TT"
status = get_subsection_status("local_authority", case_log, local_authority_questions) status = get_subsection_status("local_authority", case_log, form, local_authority_questions)
expect(status).to eq(:in_progress) expect(status).to eq(:in_progress)
end end
@ -35,14 +36,22 @@ RSpec.describe TasklistHelper do
case_log["benefits"] = "All" case_log["benefits"] = "All"
case_log["hb"] = "Do not know" case_log["hb"] = "Do not know"
status = get_subsection_status("income_and_benefits", case_log, income_and_benefits_questions) status = get_subsection_status("income_and_benefits", case_log, form, income_and_benefits_questions)
expect(status).to eq(:completed) expect(status).to eq(:completed)
end end
it "returns not started if the subsection is declaration and all the questions are completed" do it "returns not started if the subsection is declaration and all the questions are completed" do
status = get_subsection_status("declaration", completed_case_log, declaration_questions) status = get_subsection_status("declaration", completed_case_log, form, declaration_questions)
expect(status).to eq(:not_started) expect(status).to eq(:not_started)
end 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 end
describe "get next incomplete section" do describe "get next incomplete section" do
@ -79,8 +88,6 @@ RSpec.describe TasklistHelper do
end end
describe "get_first_page_or_check_answers" do describe "get_first_page_or_check_answers" do
let(:household_characteristics_questions) { form.questions_for_subsection("household_characteristics").keys }
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, household_characteristics_questions)).to match(/check_answers/)
end end

2
spec/lib/tasks/form_definition_validator_spec.rb

@ -27,7 +27,7 @@ describe "rake form_definition:validate", type: :task do
end end
it "runs the validate task for the given form definition" do it "runs the validate task for the given form definition" do
expect(JSON::Validator).to receive(:validate!).at_least(2).times expect(JSON::Validator).to receive(:validate!).at_least(1).time
task.invoke("config/forms/2021_2022.json") task.invoke("config/forms/2021_2022.json")
end end
end end

18
spec/models/case_log_spec.rb

@ -182,39 +182,39 @@ RSpec.describe Form, type: :model do
it "Must be completed and between 2 and 99 if type of tenancy is Assured shorthold" do it "Must be completed and between 2 and 99 if type of tenancy is Assured shorthold" do
expect { expect {
CaseLog.create!(tenancy: "Fixed term – Assured Shorthold Tenancy (AST)", CaseLog.create!(tenancy: "Assured Shorthold",
tenancylength: 1) tenancylength: 1)
}.to raise_error(ActiveRecord::RecordInvalid) }.to raise_error(ActiveRecord::RecordInvalid)
expect { expect {
CaseLog.create!(tenancy: "Fixed term – Assured Shorthold Tenancy (AST)", CaseLog.create!(tenancy: "Assured Shorthold",
tenancylength: nil) tenancylength: nil)
}.to raise_error(ActiveRecord::RecordInvalid) }.to raise_error(ActiveRecord::RecordInvalid)
expect { expect {
CaseLog.create!(tenancy: "Fixed term – Assured Shorthold Tenancy (AST)", CaseLog.create!(tenancy: "Assured Shorthold",
tenancylength: 2) tenancylength: 2)
}.not_to raise_error }.not_to raise_error
end end
it "Must be empty or between 2 and 99 if type of tenancy is Secure" do it "Must be empty or between 2 and 99 if type of tenancy is Secure" do
expect { expect {
CaseLog.create!(tenancy: "Fixed term – Secure", CaseLog.create!(tenancy: "Secure (including flexible)",
tenancylength: 1) tenancylength: 1)
}.to raise_error(ActiveRecord::RecordInvalid) }.to raise_error(ActiveRecord::RecordInvalid)
expect { expect {
CaseLog.create!(tenancy: "Fixed term – Secure", CaseLog.create!(tenancy: "Secure (including flexible)",
tenancylength: 100) tenancylength: 100)
}.to raise_error(ActiveRecord::RecordInvalid) }.to raise_error(ActiveRecord::RecordInvalid)
expect { expect {
CaseLog.create!(tenancy: "Fixed term – Secure", CaseLog.create!(tenancy: "Secure (including flexible)",
tenancylength: nil) tenancylength: nil)
}.not_to raise_error }.not_to raise_error
expect { expect {
CaseLog.create!(tenancy: "Fixed term – Secure", CaseLog.create!(tenancy: "Secure (including flexible)",
tenancylength: 2) tenancylength: 2)
}.not_to raise_error }.not_to raise_error
end end
@ -294,12 +294,12 @@ RSpec.describe Form, type: :model do
it "must not be provided if tenancy type is not other" do it "must not be provided if tenancy type is not other" do
expect { expect {
CaseLog.create!(tenancy: "Fixed term – Secure", CaseLog.create!(tenancy: "Secure (including flexible)",
tenancyother: "the other reason provided") tenancyother: "the other reason provided")
}.to raise_error(ActiveRecord::RecordInvalid) }.to raise_error(ActiveRecord::RecordInvalid)
expect { expect {
CaseLog.create!(tenancy: "Fixed term – Secure", CaseLog.create!(tenancy: "Secure (including flexible)",
tenancyother: nil) tenancyother: nil)
}.not_to raise_error }.not_to raise_error
end end

60
spec/requests/bulk_upload_controller_spec.rb

@ -0,0 +1,60 @@
require "rails_helper"
RSpec.describe BulkUploadController, type: :request do
let(:url) { "/case_logs/bulk_upload" }
describe "GET #show" do
before do
get url, params: {}
end
it "returns a success response" do
expect(response).to be_successful
end
it "returns a page with a file upload form" do
expect(response.body).to match(/<input id="bulk-upload-case-log-bulk-upload-field" class="govuk-file-upload"/)
expect(response.body).to match(/<button type="submit" formnovalidate="formnovalidate" class="govuk-button"/)
end
end
describe "POST #bulk upload" do
subject { post url, params: { bulk_upload: { case_log_bulk_upload: @file } } }
context "given a valid file based on the upload template" do
before do
@file = fixture_file_upload("2021_22_lettings_bulk_upload.xlsx", "application/vnd.ms-excel")
end
it "creates case logs for each row in the template" do
expect { subject }.to change(CaseLog, :count).by(9)
end
it "redirects to the case log index page" do
expect(subject).to redirect_to(case_logs_path)
end
end
context "given an invalid file type" do
before do
@file = fixture_file_upload("random.txt", "text/plain")
subject
end
it "displays an error message" do
expect(response.body).to match(/Invalid file type/)
end
end
context "given an empty file" do
before do
@file = fixture_file_upload("2021_22_lettings_bulk_upload_empty.xlsx", "application/vnd.ms-excel")
subject
end
it "displays an error message" do
expect(response.body).to match(/No data found/)
end
end
end
end
Loading…
Cancel
Save