Browse Source

Merge branch 'main' into CLDC-1765-remove-managed-by-sales-log-text

# Conflicts:
#	app/controllers/logs_controller.rb
#	app/models/sales_log.rb
#	db/schema.rb
pull/1169/head
natdeanlewissoftwire 4 years ago
parent
commit
109d1bf6f6
  1. 150
      Gemfile.lock
  2. 2
      app/components/check_answers_summary_list_card_component.rb
  3. 30
      app/controllers/form_controller.rb
  4. 9
      app/controllers/logs_controller.rb
  5. 7
      app/frontend/styles/_document-list.scss
  6. 2
      app/helpers/check_answers_helper.rb
  7. 11
      app/helpers/tab_nav_helper.rb
  8. 3
      app/models/bulk_upload.rb
  9. 2
      app/models/form.rb
  10. 2
      app/models/form/common/questions/created_by_id.rb
  11. 2
      app/models/form/common/questions/owning_organisation_id.rb
  12. 2
      app/models/form/lettings/questions/created_by_id.rb
  13. 10
      app/models/form/lettings/questions/managing_organisation.rb
  14. 11
      app/models/form/lettings/questions/stock_owner.rb
  15. 4
      app/models/form/page.rb
  16. 22
      app/models/form/question.rb
  17. 21
      app/models/form/sales/pages/extra_borrowing_value_check.rb
  18. 6
      app/models/form/sales/pages/handover_date.rb
  19. 13
      app/models/form/sales/pages/handover_date_check.rb
  20. 43
      app/models/form/sales/pages/retirement_value_check.rb
  21. 16
      app/models/form/sales/questions/age1.rb
  22. 6
      app/models/form/sales/questions/age2.rb
  23. 3
      app/models/form/sales/questions/buyer1_age_known.rb
  24. 4
      app/models/form/sales/questions/buyer1_ethnic_group.rb
  25. 1
      app/models/form/sales/questions/buyer1_income.rb
  26. 2
      app/models/form/sales/questions/buyer1_income_known.rb
  27. 6
      app/models/form/sales/questions/buyer1_working_situation.rb
  28. 1
      app/models/form/sales/questions/buyer2_income.rb
  29. 2
      app/models/form/sales/questions/buyer2_income_known.rb
  30. 8
      app/models/form/sales/questions/buyer2_relationship_to_buyer1.rb
  31. 6
      app/models/form/sales/questions/buyer2_working_situation.rb
  32. 4
      app/models/form/sales/questions/buyers_organisations.rb
  33. 1
      app/models/form/sales/questions/deposit_amount.rb
  34. 1
      app/models/form/sales/questions/deposit_discount.rb
  35. 23
      app/models/form/sales/questions/extra_borrowing_value_check.rb
  36. 6
      app/models/form/sales/questions/gender_identity2.rb
  37. 23
      app/models/form/sales/questions/handover_date_check.rb
  38. 1
      app/models/form/sales/questions/household_wheelchair_check.rb
  39. 1
      app/models/form/sales/questions/mortgage_amount.rb
  40. 1
      app/models/form/sales/questions/mortgage_length.rb
  41. 6
      app/models/form/sales/questions/nationality1.rb
  42. 2
      app/models/form/sales/questions/number_of_others_in_property.rb
  43. 6
      app/models/form/sales/questions/person_age.rb
  44. 6
      app/models/form/sales/questions/person_gender_identity.rb
  45. 6
      app/models/form/sales/questions/person_relationship_to_buyer_1.rb
  46. 6
      app/models/form/sales/questions/person_working_situation.rb
  47. 4
      app/models/form/sales/questions/postcode.rb
  48. 3
      app/models/form/sales/questions/previous_bedrooms.rb
  49. 4
      app/models/form/sales/questions/previous_postcode.rb
  50. 4
      app/models/form/sales/questions/prevloc.rb
  51. 4
      app/models/form/sales/questions/property_number_of_bedrooms.rb
  52. 24
      app/models/form/sales/questions/retirement_value_check.rb
  53. 4
      app/models/form/sales/subsections/discounted_ownership_scheme.rb
  54. 33
      app/models/form/sales/subsections/household_characteristics.rb
  55. 1
      app/models/form/sales/subsections/household_needs.rb
  56. 1
      app/models/form/sales/subsections/shared_ownership_scheme.rb
  57. 21
      app/models/lettings_log.rb
  58. 33
      app/models/log.rb
  59. 1
      app/models/organisation.rb
  60. 28
      app/models/sales_log.rb
  61. 19
      app/models/validations/household_validations.rb
  62. 8
      app/models/validations/sales/financial_validations.rb
  63. 114
      app/models/validations/sales/household_validations.rb
  64. 10
      app/models/validations/sales/property_validations.rb
  65. 41
      app/models/validations/sales/sale_information_validations.rb
  66. 16
      app/models/validations/sales/soft_validations.rb
  67. 23
      app/models/validations/shared_validations.rb
  68. 4
      app/models/validations/soft_validations.rb
  69. 58
      app/services/bulk_upload/lettings/csv_parser.rb
  70. 57
      app/services/bulk_upload/lettings/log_creator.rb
  71. 20
      app/services/bulk_upload/lettings/row_parser.rb
  72. 47
      app/services/bulk_upload/lettings/validator.rb
  73. 19
      app/services/bulk_upload/processor.rb
  74. 4
      app/services/csv/lettings_log_csv_service.rb
  75. 17
      app/views/form/guidance/_what_counts_as_income_sales.html.erb
  76. 2
      app/views/form/page.html.erb
  77. 18
      app/views/layouts/_collection_resources.html.erb
  78. 4
      app/views/locations/index.html.erb
  79. 2
      config/environments/development.rb
  80. 2
      config/environments/test.rb
  81. 50
      config/forms/2021_2022.json
  82. 50
      config/forms/2022_2023.json
  83. 4
      config/forms/schema/generic.json
  84. 48
      config/locales/en.yml
  85. 15450
      config/rent_range_data/2022.csv
  86. 6
      db/migrate/20230113154518_add_bulk_upload_to_logs.rb
  87. 7
      db/migrate/20230116124402_add_retirement_value_check_to_sales.rb
  88. 7
      db/migrate/20230116151942_add_ho_date_check.rb
  89. 7
      db/migrate/20230118170602_add_extra_borrowing_value_check_to_sales.rb
  90. 21
      db/schema.rb
  91. 10
      db/seeds.rb
  92. 4
      docs/form/builder.md
  93. 4
      docs/form/question.md
  94. 14
      spec/factories/sales_log.rb
  95. 239
      spec/features/lettings_log_spec.rb
  96. 10
      spec/features/schemes_spec.rb
  97. 2
      spec/fixtures/files/2022_23_lettings_bulk_upload.csv
  98. 10
      spec/fixtures/forms/2021_2022.json
  99. 26
      spec/helpers/tab_nav_helper_spec.rb
  100. 117
      spec/models/form/lettings/questions/managing_organisation_spec.rb
  101. Some files were not shown because too many files have changed in this diff Show More

150
Gemfile.lock

@ -13,67 +13,67 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.0.4) actioncable (7.0.4.1)
actionpack (= 7.0.4) actionpack (= 7.0.4.1)
activesupport (= 7.0.4) activesupport (= 7.0.4.1)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (7.0.4) actionmailbox (7.0.4.1)
actionpack (= 7.0.4) actionpack (= 7.0.4.1)
activejob (= 7.0.4) activejob (= 7.0.4.1)
activerecord (= 7.0.4) activerecord (= 7.0.4.1)
activestorage (= 7.0.4) activestorage (= 7.0.4.1)
activesupport (= 7.0.4) activesupport (= 7.0.4.1)
mail (>= 2.7.1) mail (>= 2.7.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
actionmailer (7.0.4) actionmailer (7.0.4.1)
actionpack (= 7.0.4) actionpack (= 7.0.4.1)
actionview (= 7.0.4) actionview (= 7.0.4.1)
activejob (= 7.0.4) activejob (= 7.0.4.1)
activesupport (= 7.0.4) activesupport (= 7.0.4.1)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (7.0.4) actionpack (7.0.4.1)
actionview (= 7.0.4) actionview (= 7.0.4.1)
activesupport (= 7.0.4) activesupport (= 7.0.4.1)
rack (~> 2.0, >= 2.2.0) rack (~> 2.0, >= 2.2.0)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (7.0.4) actiontext (7.0.4.1)
actionpack (= 7.0.4) actionpack (= 7.0.4.1)
activerecord (= 7.0.4) activerecord (= 7.0.4.1)
activestorage (= 7.0.4) activestorage (= 7.0.4.1)
activesupport (= 7.0.4) activesupport (= 7.0.4.1)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.0.4) actionview (7.0.4.1)
activesupport (= 7.0.4) activesupport (= 7.0.4.1)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (7.0.4) activejob (7.0.4.1)
activesupport (= 7.0.4) activesupport (= 7.0.4.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.0.4) activemodel (7.0.4.1)
activesupport (= 7.0.4) activesupport (= 7.0.4.1)
activerecord (7.0.4) activerecord (7.0.4.1)
activemodel (= 7.0.4) activemodel (= 7.0.4.1)
activesupport (= 7.0.4) activesupport (= 7.0.4.1)
activestorage (7.0.4) activestorage (7.0.4.1)
actionpack (= 7.0.4) actionpack (= 7.0.4.1)
activejob (= 7.0.4) activejob (= 7.0.4.1)
activerecord (= 7.0.4) activerecord (= 7.0.4.1)
activesupport (= 7.0.4) activesupport (= 7.0.4.1)
marcel (~> 1.0) marcel (~> 1.0)
mini_mime (>= 1.1.0) mini_mime (>= 1.1.0)
activesupport (7.0.4) activesupport (7.0.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
@ -136,6 +136,7 @@ GEM
crack (0.4.5) crack (0.4.5)
rexml rexml
crass (1.0.6) crass (1.0.6)
date (3.3.3)
devise_two_factor_authentication (3.0.0) devise_two_factor_authentication (3.0.0)
devise devise
encryptor encryptor
@ -143,7 +144,6 @@ GEM
randexp randexp
rotp (>= 4.0.0) rotp (>= 4.0.0)
diff-lcs (1.5.0) diff-lcs (1.5.0)
digest (3.1.0)
docile (1.4.0) docile (1.4.0)
dotenv (2.8.1) dotenv (2.8.1)
dotenv-rails (2.8.1) dotenv-rails (2.8.1)
@ -157,7 +157,7 @@ GEM
rainbow rainbow
rubocop rubocop
smart_properties smart_properties
erubi (1.11.0) erubi (1.12.0)
excon (0.92.5) excon (0.92.5)
factory_bot (6.2.1) factory_bot (6.2.1)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
@ -167,7 +167,7 @@ GEM
faker (2.23.0) faker (2.23.0)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
ffi (1.15.5) ffi (1.15.5)
globalid (1.0.0) globalid (1.0.1)
activesupport (>= 5.0) activesupport (>= 5.0)
govuk-components (3.2.1) govuk-components (3.2.1)
actionpack (>= 6.1) actionpack (>= 6.1)
@ -202,34 +202,32 @@ GEM
loofah (2.19.1) loofah (2.19.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
mail (2.7.1) mail (2.8.0.1)
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
marcel (1.0.2) marcel (1.0.2)
matrix (0.4.2) matrix (0.4.2)
method_source (1.0.0) method_source (1.0.0)
mini_mime (1.1.2) mini_mime (1.1.2)
minitest (5.16.3) minitest (5.17.0)
msgpack (1.5.6) msgpack (1.5.6)
net-imap (0.2.3) net-imap (0.3.4)
digest date
net-protocol net-protocol
strscan net-pop (0.1.2)
net-pop (0.1.1)
digest
net-protocol net-protocol
net-protocol (0.2.1)
timeout timeout
net-protocol (0.1.3) net-smtp (0.3.3)
timeout
net-smtp (0.3.1)
digest
net-protocol net-protocol
timeout
nio4r (2.5.8) nio4r (2.5.8)
nokogiri (1.13.10-arm64-darwin) nokogiri (1.14.0-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.13.10-x86_64-darwin) nokogiri (1.14.0-x86_64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.13.10-x86_64-linux) nokogiri (1.14.0-x86_64-linux)
racc (~> 1.4) racc (~> 1.4)
notifications-ruby-client (5.3.0) notifications-ruby-client (5.3.0)
jwt (>= 1.5, < 3) jwt (>= 1.5, < 3)
@ -269,36 +267,36 @@ GEM
public_suffix (5.0.0) public_suffix (5.0.0)
puma (5.6.5) puma (5.6.5)
nio4r (~> 2.0) nio4r (~> 2.0)
racc (1.6.1) racc (1.6.2)
rack (2.2.4) rack (2.2.6.2)
rack-attack (6.6.1) rack-attack (6.6.1)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rack-mini-profiler (2.3.4) rack-mini-profiler (2.3.4)
rack (>= 1.2.0) rack (>= 1.2.0)
rack-test (2.0.2) rack-test (2.0.2)
rack (>= 1.3) rack (>= 1.3)
rails (7.0.4) rails (7.0.4.1)
actioncable (= 7.0.4) actioncable (= 7.0.4.1)
actionmailbox (= 7.0.4) actionmailbox (= 7.0.4.1)
actionmailer (= 7.0.4) actionmailer (= 7.0.4.1)
actionpack (= 7.0.4) actionpack (= 7.0.4.1)
actiontext (= 7.0.4) actiontext (= 7.0.4.1)
actionview (= 7.0.4) actionview (= 7.0.4.1)
activejob (= 7.0.4) activejob (= 7.0.4.1)
activemodel (= 7.0.4) activemodel (= 7.0.4.1)
activerecord (= 7.0.4) activerecord (= 7.0.4.1)
activestorage (= 7.0.4) activestorage (= 7.0.4.1)
activesupport (= 7.0.4) activesupport (= 7.0.4.1)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.0.4) railties (= 7.0.4.1)
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.4.4) rails-html-sanitizer (1.4.4)
loofah (~> 2.19, >= 2.19.1) loofah (~> 2.19, >= 2.19.1)
railties (7.0.4) railties (7.0.4.1)
actionpack (= 7.0.4) actionpack (= 7.0.4.1)
activesupport (= 7.0.4) activesupport (= 7.0.4.1)
method_source method_source
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0) thor (~> 1.0)
@ -396,10 +394,9 @@ GEM
smart_properties (1.17.0) smart_properties (1.17.0)
stimulus-rails (1.1.0) stimulus-rails (1.1.0)
railties (>= 6.0.0) railties (>= 6.0.0)
strscan (3.0.4)
thor (1.2.1) thor (1.2.1)
timecop (0.9.5) timecop (0.9.5)
timeout (0.3.0) timeout (0.3.1)
tzinfo (2.0.5) tzinfo (2.0.5)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
uk_postcode (2.1.8) uk_postcode (2.1.8)
@ -425,10 +422,11 @@ GEM
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.6.0) zeitwerk (2.6.6)
PLATFORMS PLATFORMS
arm64-darwin-21 arm64-darwin-21
arm64-darwin-22
x86_64-darwin-19 x86_64-darwin-19
x86_64-darwin-20 x86_64-darwin-20
x86_64-darwin-21 x86_64-darwin-21

2
app/components/check_answers_summary_list_card_component.rb

@ -13,7 +13,7 @@ class CheckAnswersSummaryListCardComponent < ViewComponent::Base
end end
def get_answer_label(question) def get_answer_label(question)
question.answer_label(log).presence || "<span class=\"app-!-colour-muted\">You didn’t answer this question</span>".html_safe question.answer_label(log, user).presence || "<span class=\"app-!-colour-muted\">You didn’t answer this question</span>".html_safe
end end
def check_answers_card_title(question) def check_answers_card_title(question)

30
app/controllers/form_controller.rb

@ -6,7 +6,7 @@ class FormController < ApplicationController
def submit_form def submit_form
if @log if @log
@page = @log.form.get_page(params[@log.model_name.param_key][:page]) @page = form.get_page(params[@log.model_name.param_key][:page])
responses_for_page = responses_for_page(@page) responses_for_page = responses_for_page(@page)
mandatory_questions_with_no_response = mandatory_questions_with_no_response(responses_for_page) mandatory_questions_with_no_response = mandatory_questions_with_no_response(responses_for_page)
@ -30,7 +30,7 @@ class FormController < ApplicationController
def check_answers def check_answers
if @log if @log
current_url = request.env["PATH_INFO"] current_url = request.env["PATH_INFO"]
subsection = @log.form.get_subsection(current_url.split("/")[-2]) subsection = form.get_subsection(current_url.split("/")[-2])
render "form/check_answers", locals: { subsection:, current_user: } render "form/check_answers", locals: { subsection:, current_user: }
else else
render_not_found render_not_found
@ -49,8 +49,8 @@ class FormController < ApplicationController
if @log if @log
restore_error_field_values restore_error_field_values
page_id = request.path.split("/")[-1].underscore page_id = request.path.split("/")[-1].underscore
@page = @log.form.get_page(page_id) @page = form.get_page(page_id)
@subsection = @log.form.subsection_for_page(@page) @subsection = form.subsection_for_page(@page)
if @page.routed_to?(@log, current_user) if @page.routed_to?(@log, current_user)
render "form/page" render "form/page"
else else
@ -71,7 +71,7 @@ private
end end
if session["fields"] if session["fields"]
session["fields"].each do |field, value| session["fields"].each do |field, value|
if @log.form.get_question(field, @log)&.type != "date" && @log.respond_to?(field) if form.get_question(field, @log)&.type != "date" && @log.respond_to?(field)
@log[field] = value @log[field] = value
end end
end end
@ -129,20 +129,26 @@ private
def successful_redirect_path def successful_redirect_path
if is_referrer_check_answers? if is_referrer_check_answers?
page_ids = @log.form.subsection_for_page(@page).pages.map(&:id) page_ids = form.subsection_for_page(@page).pages.map(&:id)
page_index = page_ids.index(@page.id) page_index = page_ids.index(@page.id)
next_page = @log.form.next_page(@page, @log, current_user) next_page_id = form.next_page(@page, @log, current_user)
previous_page = @log.form.previous_page(page_ids, page_index, @log, current_user) next_page = form.get_page(next_page_id)
if next_page.to_s.include?("value_check") || next_page == previous_page previous_page = form.previous_page(page_ids, page_index, @log, current_user)
return send("#{@log.class.name.underscore}_#{next_page}_path", @log, { referrer: "check_answers" })
if next_page&.interruption_screen? || next_page_id == previous_page
return send("#{@log.class.name.underscore}_#{next_page_id}_path", @log, { referrer: "check_answers" })
else else
return send("#{@log.model_name.param_key}_#{@log.form.subsection_for_page(@page).id}_check_answers_path", @log) return send("#{@log.model_name.param_key}_#{form.subsection_for_page(@page).id}_check_answers_path", @log)
end end
end end
redirect_path = @log.form.next_page_redirect_path(@page, @log, current_user) redirect_path = form.next_page_redirect_path(@page, @log, current_user)
send(redirect_path, @log) send(redirect_path, @log)
end end
def form
@log&.form
end
def mandatory_questions_with_no_response(responses_for_page) def mandatory_questions_with_no_response(responses_for_page)
session["fields"] = {} session["fields"] = {}
calc_questions = @page.questions.map(&:result_field) calc_questions = @page.questions.map(&:result_field)

9
app/controllers/logs_controller.rb

@ -59,6 +59,15 @@ private
permitted permitted
end end
def org_params
owning_organisation_id = current_user.organisation.holds_own_stock? ? current_user.organisation.id : nil
{
"owning_organisation_id" => owning_organisation_id,
"managing_organisation_id" => current_user.organisation.id,
"created_by_id" => current_user.id,
}
end
def search_term def search_term
params["search"] params["search"]
end end

7
app/frontend/styles/_document-list.scss

@ -1,5 +1,10 @@
.app-document-list {
margin-top: govuk-spacing(3);
margin-bottom: govuk-spacing(6);
}
.app-document-list__item { .app-document-list__item {
margin-bottom: govuk-spacing(4); margin-bottom: govuk-spacing(3);
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;

2
app/helpers/check_answers_helper.rb

@ -43,6 +43,6 @@ private
end end
def get_answer_label(question, lettings_log) def get_answer_label(question, lettings_log)
question.answer_label(lettings_log).presence || "<span class=\"app-!-colour-muted\">You didn’t answer this question</span>".html_safe question.answer_label(lettings_log, current_user).presence || "<span class=\"app-!-colour-muted\">You didn’t answer this question</span>".html_safe
end end
end end

11
app/helpers/tab_nav_helper.rb

@ -8,24 +8,17 @@ module TabNavHelper
def location_cell_postcode(location, link) def location_cell_postcode(location, link)
link_text = location.postcode || "Add postcode" link_text = location.postcode || "Add postcode"
[govuk_link_to(link_text, link, method: :patch), "<span class=\"govuk-visually-hidden\">Location </span><span class=\"govuk-!-font-weight-regular app-!-colour-muted\">#{location.name}</span>"].join("\n") [govuk_link_to(link_text, link, method: :patch), "<span class=\"govuk-visually-hidden\">Location</span>"].join("\n")
end end
def scheme_cell(scheme) def scheme_cell(scheme)
link_text = scheme.service_name link_text = scheme.service_name
link = scheme.confirmed? ? scheme : scheme_check_answers_path(scheme) link = scheme.confirmed? ? scheme : scheme_check_answers_path(scheme)
[govuk_link_to(link_text, link), "<span class=\"govuk-visually-hidden\">Scheme </span><span class=\"govuk-!-font-weight-regular app-!-colour-muted\">#{scheme.primary_client_group}</span>"].join("\n") [govuk_link_to(link_text, link), "<span class=\"govuk-visually-hidden\">Scheme</span>"].join("\n")
end end
def org_cell(user) def org_cell(user)
role = "<span class=\"app-!-colour-muted\">#{user.role.to_s.humanize}</span>" role = "<span class=\"app-!-colour-muted\">#{user.role.to_s.humanize}</span>"
[user.organisation.name, role].join("\n") [user.organisation.name, role].join("\n")
end end
def tab_items(user)
[
{ name: t("Details"), url: details_organisation_path(user.organisation) },
{ name: t("Users"), url: users_organisation_path(user.organisation) },
]
end
end end

3
app/models/bulk_upload.rb

@ -2,7 +2,10 @@ class BulkUpload < ApplicationRecord
enum log_type: { lettings: "lettings", sales: "sales" } enum log_type: { lettings: "lettings", sales: "sales" }
belongs_to :user belongs_to :user
has_many :bulk_upload_errors has_many :bulk_upload_errors
has_many :lettings_logs
has_many :sales_logs
after_initialize :generate_identifier, unless: :identifier after_initialize :generate_identifier, unless: :identifier

2
app/models/form.rb

@ -61,7 +61,7 @@ class Form
page_ids = subsection_for_page(page).pages.map(&:id) page_ids = subsection_for_page(page).pages.map(&:id)
page_index = page_ids.index(page.id) page_index = page_ids.index(page.id)
page_id = if page.id.include?("value_check") && log[page.questions[0].id] == 1 && page.routed_to?(log, current_user) page_id = if page.interruption_screen? && log[page.questions[0].id] == 1 && page.routed_to?(log, current_user)
previous_page(page_ids, page_index, log, current_user) previous_page(page_ids, page_index, log, current_user)
else else
page_ids[page_index + 1] page_ids[page_index + 1]

2
app/models/form/common/questions/created_by_id.rb

@ -24,7 +24,7 @@ class Form::Common::Questions::CreatedById < ::Form::Question
answer_options.select { |k, _v| user_ids.include?(k) } answer_options.select { |k, _v| user_ids.include?(k) }
end end
def label_from_value(value) def label_from_value(value, _log = nil, _user = nil)
return unless value return unless value
answer_options[value] answer_options[value]

2
app/models/form/common/questions/owning_organisation_id.rb

@ -21,7 +21,7 @@ class Form::Common::Questions::OwningOrganisationId < ::Form::Question
answer_options answer_options
end end
def label_from_value(value) def label_from_value(value, _log = nil, _user = nil)
return unless value return unless value
answer_options[value] answer_options[value]

2
app/models/form/lettings/questions/created_by_id.rb

@ -27,7 +27,7 @@ class Form::Lettings::Questions::CreatedById < ::Form::Question
answer_options.select { |k, _v| user_ids.include?(k) } answer_options.select { |k, _v| user_ids.include?(k) }
end end
def label_from_value(value) def label_from_value(value, _log = nil, _user = nil)
return unless value return unless value
answer_options[value] answer_options[value]

10
app/models/form/lettings/questions/managing_organisation.rb

@ -12,10 +12,15 @@ class Form::Lettings::Questions::ManagingOrganisation < ::Form::Question
def answer_options def answer_options
opts = { "" => "Select an option" } opts = { "" => "Select an option" }
return opts unless ActiveRecord::Base.connected? return opts unless ActiveRecord::Base.connected?
return opts unless current_user return opts unless current_user
return opts unless log return opts unless log
if log.managing_organisation.present?
opts = opts.merge({ log.managing_organisation.id => log.managing_organisation.name })
end
if current_user.support? if current_user.support?
if log.owning_organisation.holds_own_stock? if log.owning_organisation.holds_own_stock?
opts[log.owning_organisation.id] = "#{log.owning_organisation.name} (Owning organisation)" opts[log.owning_organisation.id] = "#{log.owning_organisation.name} (Owning organisation)"
@ -34,7 +39,10 @@ class Form::Lettings::Questions::ManagingOrganisation < ::Form::Question
answer_options answer_options
end end
def label_from_value(value) def label_from_value(value, log = nil, user = nil)
@log = log
@current_user = user
return unless value return unless value
answer_options[value] answer_options[value]

11
app/models/form/lettings/questions/stock_owner.rb

@ -11,8 +11,14 @@ class Form::Lettings::Questions::StockOwner < ::Form::Question
def answer_options def answer_options
answer_opts = { "" => "Select an option" } answer_opts = { "" => "Select an option" }
return answer_opts unless ActiveRecord::Base.connected? return answer_opts unless ActiveRecord::Base.connected?
return answer_opts unless current_user return answer_opts unless current_user
return answer_opts unless log
if log.owning_organisation_id.present?
answer_opts = answer_opts.merge({ log.owning_organisation.id => log.owning_organisation.name })
end
if !current_user.support? && current_user.organisation.holds_own_stock? if !current_user.support? && current_user.organisation.holds_own_stock?
answer_opts[current_user.organisation.id] = "#{current_user.organisation.name} (Your organisation)" answer_opts[current_user.organisation.id] = "#{current_user.organisation.name} (Your organisation)"
@ -28,7 +34,10 @@ class Form::Lettings::Questions::StockOwner < ::Form::Question
answer_options answer_options
end end
def label_from_value(value) def label_from_value(value, log = nil, user = nil)
@log = log
@current_user = user
return unless value return unless value
answer_options[value] answer_options[value]

4
app/models/form/page.rb

@ -32,6 +32,10 @@ class Form::Page
end end
end end
def interruption_screen?
questions.all? { |question| question.type == "interruption_screen" }
end
private private
def conditional_question_ids def conditional_question_ids

22
app/models/form/question.rb

@ -46,17 +46,14 @@ class Form::Question
delegate :subsection, to: :page delegate :subsection, to: :page
delegate :form, to: :subsection delegate :form, to: :subsection
def answer_label(log) def answer_label(log, user = nil)
return checkbox_answer_label(log) if type == "checkbox" return checkbox_answer_label(log) if type == "checkbox"
return log[id]&.to_formatted_s(:govuk_date).to_s if type == "date" return log[id]&.to_formatted_s(:govuk_date).to_s if type == "date"
answer = label_from_value(log[id]) if log[id].present? answer = label_from_value(log[id], log, user) if log[id].present?
answer_label = [prefix, format_value(answer), suffix_label(log)].join("") if answer answer_label = [prefix, format_value(answer), suffix_label(log)].join("") if answer
inferred = inferred_check_answers_value["value"] if inferred_check_answers_value && has_inferred_check_answers_value?(log) inferred_answer_value(log) || answer_label
return inferred if inferred.present?
answer_label
end end
def get_inferred_answers(log) def get_inferred_answers(log)
@ -104,7 +101,7 @@ class Form::Question
def has_inferred_check_answers_value?(log) def has_inferred_check_answers_value?(log)
return true if selected_answer_option_is_derived?(log) return true if selected_answer_option_is_derived?(log)
return inferred_check_answers_value["condition"].values[0] == log[inferred_check_answers_value["condition"].keys[0]] if inferred_check_answers_value.present? return inferred_check_answers_value&.any? { |inferred_value| inferred_value["condition"].values.first == log[inferred_value["condition"].keys.first] } if inferred_check_answers_value.present?
false false
end end
@ -154,7 +151,7 @@ class Form::Question
end end
end end
def label_from_value(value) def label_from_value(value, _log = nil, _user = nil)
return unless value return unless value
label = case type label = case type
@ -271,7 +268,7 @@ private
end end
def has_inferred_display_value?(log) def has_inferred_display_value?(log)
inferred_check_answers_value.present? && log[inferred_check_answers_value["condition"].keys.first] == inferred_check_answers_value["condition"].values.first inferred_check_answers_value.present? && inferred_check_answers_value.any? { |inferred_value| log[inferred_value["condition"].keys.first] == inferred_value["condition"].values.first }
end end
def checkbox_answer_label(log) def checkbox_answer_label(log)
@ -309,6 +306,13 @@ private
inferred_answers.filter { |_key, value| value.all? { |condition_key, condition_value| log[condition_key] == condition_value } } inferred_answers.filter { |_key, value| value.all? { |condition_key, condition_value| log[condition_key] == condition_value } }
end end
def inferred_answer_value(log)
return unless inferred_check_answers_value
inferred_answer = inferred_check_answers_value.find { |inferred_value| inferred_value["condition"].values.first == log[inferred_value["condition"].keys.first] }
inferred_answer["value"] if inferred_answer.present?
end
RADIO_YES_VALUE = { RADIO_YES_VALUE = {
renewal: [1], renewal: [1],
postcode_known: [1], postcode_known: [1],

21
app/models/form/sales/pages/extra_borrowing_value_check.rb

@ -0,0 +1,21 @@
class Form::Sales::Pages::ExtraBorrowingValueCheck < Form::Page
def initialize(id, hsh, subsection)
super
@depends_on = [
{
"extra_borrowing_expected_but_not_reported?" => true,
},
]
@title_text = {
"translation" => "soft_validations.extra_borrowing.title",
}
@informative_text = {
}
end
def questions
@questions ||= [
Form::Sales::Questions::ExtraBorrowingValueCheck.new(nil, nil, self),
]
end
end

6
app/models/form/sales/pages/handover_date.rb

@ -2,9 +2,9 @@ class Form::Sales::Pages::HandoverDate < ::Form::Page
def initialize(id, hsh, subsection) def initialize(id, hsh, subsection)
super super
@id = "handover_date" @id = "handover_date"
@depends_on = [{ @depends_on = [
"ownershipsch" => 1, { "ownershipsch" => 1, "resale" => 2 },
}] ]
end end
def questions def questions

13
app/models/form/sales/pages/handover_date_check.rb

@ -0,0 +1,13 @@
class Form::Sales::Pages::HandoverDateCheck < ::Form::Page
def initialize(id, hsh, subsection)
super
@depends_on = [{ "hodate_3_years_or_more_saledate?" => true }]
@informative_text = {}
end
def questions
@questions ||= [
Form::Sales::Questions::HandoverDateCheck.new(nil, nil, self),
]
end
end

43
app/models/form/sales/pages/retirement_value_check.rb

@ -0,0 +1,43 @@
class Form::Sales::Pages::RetirementValueCheck < Form::Sales::Pages::Person
def initialize(id, hsh, subsection, person_index:)
super
@depends_on = [
{
"person_#{person_index}_retired_under_soft_min_age?" => true,
"jointpur" => joint_purchase? ? 1 : 2,
},
]
@person_index = person_index
@title_text = {
"translation" => "soft_validations.retirement.min.title",
"arguments" => [
{
"key" => "retirement_age_for_person_#{person_index}",
"label" => false,
"i18n_template" => "age",
},
],
}
@informative_text = {
"translation" => "soft_validations.retirement.min.hint_text",
"arguments" => [
{
"key" => "plural_gender_for_person_#{person_index}",
"label" => false,
"i18n_template" => "gender",
},
{
"key" => "retirement_age_for_person_#{person_index}",
"label" => false,
"i18n_template" => "age",
},
],
}
end
def questions
@questions ||= [
Form::Sales::Questions::RetirementValueCheck.new(nil, nil, self, person_index: @person_index),
]
end
end

16
app/models/form/sales/questions/age1.rb

@ -6,12 +6,18 @@ class Form::Sales::Questions::Age1 < ::Form::Question
@header = "Age" @header = "Age"
@type = "numeric" @type = "numeric"
@width = 2 @width = 2
@inferred_check_answers_value = { @inferred_check_answers_value = [
"condition" => { {
"age1_known" => 1, "condition" => { "age1_known" => 1 },
"value" => "Not known",
}, },
"value" => "Not known", {
} "condition" => { "age1_known" => 2 },
"value" => "Prefers not to say",
},
]
@check_answers_card_number = 1 @check_answers_card_number = 1
@min = 16
@max = 110
end end
end end

6
app/models/form/sales/questions/age2.rb

@ -6,10 +6,12 @@ class Form::Sales::Questions::Age2 < ::Form::Question
@header = "Age" @header = "Age"
@type = "numeric" @type = "numeric"
@width = 2 @width = 2
@inferred_check_answers_value = { @inferred_check_answers_value = [{
"condition" => { "age2_known" => 1 }, "condition" => { "age2_known" => 1 },
"value" => "Not known", "value" => "Not known",
} }]
@check_answers_card_number = 2 @check_answers_card_number = 2
@max = 110
@min = 0
end end
end end

3
app/models/form/sales/questions/buyer1_age_known.rb

@ -18,6 +18,9 @@ class Form::Sales::Questions::Buyer1AgeKnown < ::Form::Question
{ {
"age1_known" => 1, "age1_known" => 1,
}, },
{
"age1_known" => 2,
},
], ],
} }
@check_answers_card_number = 1 @check_answers_card_number = 1

4
app/models/form/sales/questions/buyer1_ethnic_group.rb

@ -7,12 +7,12 @@ class Form::Sales::Questions::Buyer1EthnicGroup < ::Form::Question
@type = "radio" @type = "radio"
@answer_options = ANSWER_OPTIONS @answer_options = ANSWER_OPTIONS
@hint_text = "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest." @hint_text = "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@inferred_check_answers_value = { @inferred_check_answers_value = [{
"condition" => { "condition" => {
"ethnic_group" => 17, "ethnic_group" => 17,
}, },
"value" => "Prefers not to say", "value" => "Prefers not to say",
} }]
@check_answers_card_number = 1 @check_answers_card_number = 1
end end

1
app/models/form/sales/questions/buyer1_income.rb

@ -4,6 +4,7 @@ class Form::Sales::Questions::Buyer1Income < ::Form::Question
@id = "income1" @id = "income1"
@check_answer_label = "Buyer 1’s gross annual income" @check_answer_label = "Buyer 1’s gross annual income"
@header = "Buyer 1’s gross annual income" @header = "Buyer 1’s gross annual income"
@hint_text = "Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments."
@type = "numeric" @type = "numeric"
@min = 0 @min = 0
@max = 999_999 @max = 999_999

2
app/models/form/sales/questions/buyer1_income_known.rb

@ -6,8 +6,6 @@ class Form::Sales::Questions::Buyer1IncomeKnown < ::Form::Question
@header = "Do you know buyer 1’s annual income?" @header = "Do you know buyer 1’s annual income?"
@type = "radio" @type = "radio"
@answer_options = ANSWER_OPTIONS @answer_options = ANSWER_OPTIONS
@guidance_position = GuidancePosition::BOTTOM
@guidance_partial = "what_counts_as_income_sales"
@conditional_for = { @conditional_for = {
"income1" => [0], "income1" => [0],
} }

6
app/models/form/sales/questions/buyer1_working_situation.rb

@ -8,6 +8,12 @@ class Form::Sales::Questions::Buyer1WorkingSituation < ::Form::Question
@answer_options = ANSWER_OPTIONS @answer_options = ANSWER_OPTIONS
@hint_text = "Buyer 1 is the person in the household who does the most paid work. If it's a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest." @hint_text = "Buyer 1 is the person in the household who does the most paid work. If it's a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@check_answers_card_number = 1 @check_answers_card_number = 1
@inferred_check_answers_value = [{
"condition" => {
"ecstat1" => 10,
},
"value" => "Prefers not to say",
}]
end end
ANSWER_OPTIONS = { ANSWER_OPTIONS = {

1
app/models/form/sales/questions/buyer2_income.rb

@ -5,6 +5,7 @@ class Form::Sales::Questions::Buyer2Income < ::Form::Question
@check_answer_label = "Buyer 2’s gross annual income" @check_answer_label = "Buyer 2’s gross annual income"
@header = "Buyer 2’s gross annual income" @header = "Buyer 2’s gross annual income"
@type = "numeric" @type = "numeric"
@hint_text = "Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments."
@min = 0 @min = 0
@step = 1 @step = 1
@width = 5 @width = 5

2
app/models/form/sales/questions/buyer2_income_known.rb

@ -6,8 +6,6 @@ class Form::Sales::Questions::Buyer2IncomeKnown < ::Form::Question
@header = "Do you know buyer 2’s annual income?" @header = "Do you know buyer 2’s annual income?"
@type = "radio" @type = "radio"
@answer_options = ANSWER_OPTIONS @answer_options = ANSWER_OPTIONS
@guidance_position = GuidancePosition::BOTTOM
@guidance_partial = "what_counts_as_income_sales"
@conditional_for = { @conditional_for = {
"income2" => [0], "income2" => [0],
} }

8
app/models/form/sales/questions/buyer2_relationship_to_buyer1.rb

@ -7,10 +7,16 @@ class Form::Sales::Questions::Buyer2RelationshipToBuyer1 < ::Form::Question
@type = "radio" @type = "radio"
@answer_options = ANSWER_OPTIONS @answer_options = ANSWER_OPTIONS
@check_answers_card_number = 2 @check_answers_card_number = 2
@inferred_check_answers_value = [{
"condition" => {
"relat2" => "R",
},
"value" => "Prefers not to say",
}]
end end
ANSWER_OPTIONS = { ANSWER_OPTIONS = {
"P" => { "value" => "Parent" }, "P" => { "value" => "Partner" },
"C" => { "value" => "Child", "hint" => "Must be eligible for child benefit, aged under 16 or under 20 if still in full-time education." }, "C" => { "value" => "Child", "hint" => "Must be eligible for child benefit, aged under 16 or under 20 if still in full-time education." },
"X" => { "value" => "Other" }, "X" => { "value" => "Other" },
"R" => { "value" => "Buyer prefers not to say" }, "R" => { "value" => "Buyer prefers not to say" },

6
app/models/form/sales/questions/buyer2_working_situation.rb

@ -7,6 +7,12 @@ class Form::Sales::Questions::Buyer2WorkingSituation < ::Form::Question
@type = "radio" @type = "radio"
@answer_options = ANSWER_OPTIONS @answer_options = ANSWER_OPTIONS
@check_answers_card_number = 2 @check_answers_card_number = 2
@inferred_check_answers_value = [{
"condition" => {
"ecstat2" => 10,
},
"value" => "Prefers not to say",
}]
end end
ANSWER_OPTIONS = { ANSWER_OPTIONS = {

4
app/models/form/sales/questions/buyers_organisations.rb

@ -15,4 +15,8 @@ class Form::Sales::Questions::BuyersOrganisations < ::Form::Question
"pregla" => { "value" => "Local Authority" }, "pregla" => { "value" => "Local Authority" },
"pregghb" => { "value" => "Help to Buy Agent" }, "pregghb" => { "value" => "Help to Buy Agent" },
}.freeze }.freeze
def unanswered_error_message
"At least one option must be selected of these four"
end
end end

1
app/models/form/sales/questions/deposit_amount.rb

@ -7,6 +7,7 @@ class Form::Sales::Questions::DepositAmount < ::Form::Question
@type = "numeric" @type = "numeric"
@min = 0 @min = 0
@width = 5 @width = 5
@max = 999_999
@prefix = "£" @prefix = "£"
@hint_text = "Enter the total cash sum paid by the buyer towards the property that was not funded by the mortgage" @hint_text = "Enter the total cash sum paid by the buyer towards the property that was not funded by the mortgage"
@derived = true @derived = true

1
app/models/form/sales/questions/deposit_discount.rb

@ -6,6 +6,7 @@ class Form::Sales::Questions::DepositDiscount < ::Form::Question
@header = "How much cash discount was given through Social HomeBuy?" @header = "How much cash discount was given through Social HomeBuy?"
@type = "numeric" @type = "numeric"
@min = 0 @min = 0
@max = 999_999
@width = 5 @width = 5
@prefix = "£" @prefix = "£"
@hint_text = "Enter the total cash discount given on the property being purchased through the Social HomeBuy scheme" @hint_text = "Enter the total cash discount given on the property being purchased through the Social HomeBuy scheme"

23
app/models/form/sales/questions/extra_borrowing_value_check.rb

@ -0,0 +1,23 @@
class Form::Sales::Questions::ExtraBorrowingValueCheck < ::Form::Question
def initialize(id, hsh, page)
super(id, hsh, page)
@id = "extrabor_value_check"
@check_answer_label = "Extra borrowing confirmation"
@type = "interruption_screen"
@answer_options = {
"0" => { "value" => "Yes" },
"1" => { "value" => "No" },
}
@hidden_in_check_answers = {
"depends_on" => [
{
"extrabor_value_check" => 0,
},
{
"extrabor_value_check" => 1,
},
],
}
@header = "Are you sure there is no extra borrowing?"
end
end

6
app/models/form/sales/questions/gender_identity2.rb

@ -7,6 +7,12 @@ class Form::Sales::Questions::GenderIdentity2 < ::Form::Question
@type = "radio" @type = "radio"
@answer_options = ANSWER_OPTIONS @answer_options = ANSWER_OPTIONS
@check_answers_card_number = 2 @check_answers_card_number = 2
@inferred_check_answers_value = [{
"condition" => {
"sex2" => "R",
},
"value" => "Prefers not to say",
}]
end end
ANSWER_OPTIONS = { ANSWER_OPTIONS = {

23
app/models/form/sales/questions/handover_date_check.rb

@ -0,0 +1,23 @@
class Form::Sales::Questions::HandoverDateCheck < ::Form::Question
def initialize(id, hsh, page)
super
@id = "hodate_check"
@check_answer_label = "Practical completion or handover date check"
@header = "Are you sure practical completion or handover date is more than 3 years before exchange date?"
@type = "interruption_screen"
@answer_options = {
"0" => { "value" => "Yes" },
"1" => { "value" => "No" },
}
@hidden_in_check_answers = {
"depends_on" => [
{
"hodate_check" => 0,
},
{
"hodate_check" => 1,
},
],
}
end
end

1
app/models/form/sales/questions/household_wheelchair_check.rb

@ -19,5 +19,6 @@ class Form::Sales::Questions::HouseholdWheelchairCheck < ::Form::Question
}, },
], ],
} }
@page = page
end end
end end

1
app/models/form/sales/questions/mortgage_amount.rb

@ -8,5 +8,6 @@ class Form::Sales::Questions::MortgageAmount < ::Form::Question
@min = 0 @min = 0
@width = 5 @width = 5
@prefix = "£" @prefix = "£"
@hint_text = "Enter the amount of mortgage agreed with the mortgage lender. Exclude any deposits or cash payments. Numeric in pounds. Rounded to the nearest pound."
end end
end end

1
app/models/form/sales/questions/mortgage_length.rb

@ -8,5 +8,6 @@ class Form::Sales::Questions::MortgageLength < ::Form::Question
@min = 0 @min = 0
@width = 5 @width = 5
@suffix = " years" @suffix = " years"
@hint_text = "You should round up to the nearest year. Value should not exceed 60 years."
end end
end end

6
app/models/form/sales/questions/nationality1.rb

@ -18,6 +18,12 @@ class Form::Sales::Questions::Nationality1 < ::Form::Question
], ],
} }
@check_answers_card_number = 1 @check_answers_card_number = 1
@inferred_check_answers_value = [{
"condition" => {
"national" => 13,
},
"value" => "Prefers not to say",
}]
end end
ANSWER_OPTIONS = { ANSWER_OPTIONS = {

2
app/models/form/sales/questions/number_of_others_in_property.rb

@ -3,7 +3,7 @@ class Form::Sales::Questions::NumberOfOthersInProperty < ::Form::Question
super super
@id = "hholdcount" @id = "hholdcount"
@check_answer_label = "Number of other people living in the property" @check_answer_label = "Number of other people living in the property"
@header = "Besides the buyers, how many other people live in the property?" @header = "Besides the buyer(s), how many other people live or will live in the property?"
@type = "numeric" @type = "numeric"
@hint_text = "You can provide details for a maximum of 4 other people." @hint_text = "You can provide details for a maximum of 4 other people."
@width = 2 @width = 2

6
app/models/form/sales/questions/person_age.rb

@ -5,10 +5,12 @@ class Form::Sales::Questions::PersonAge < Form::Sales::Questions::Person
@header = "Age" @header = "Age"
@type = "numeric" @type = "numeric"
@width = 3 @width = 3
@inferred_check_answers_value = { @inferred_check_answers_value = [{
"condition" => { field_for_person("age", "_known") => 1 }, "condition" => { field_for_person("age", "_known") => 1 },
"value" => "Not known", "value" => "Not known",
} }]
@check_answers_card_number = person_index @check_answers_card_number = person_index
@min = 0
@max = 110
end end
end end

6
app/models/form/sales/questions/person_gender_identity.rb

@ -6,6 +6,12 @@ class Form::Sales::Questions::PersonGenderIdentity < ::Form::Sales::Questions::P
@type = "radio" @type = "radio"
@answer_options = ANSWER_OPTIONS @answer_options = ANSWER_OPTIONS
@check_answers_card_number = person_index @check_answers_card_number = person_index
@inferred_check_answers_value = [{
"condition" => {
id => "R",
},
"value" => "Prefers not to say",
}]
end end
ANSWER_OPTIONS = { ANSWER_OPTIONS = {

6
app/models/form/sales/questions/person_relationship_to_buyer_1.rb

@ -6,6 +6,12 @@ class Form::Sales::Questions::PersonRelationshipToBuyer1 < ::Form::Sales::Questi
@type = "radio" @type = "radio"
@answer_options = ANSWER_OPTIONS @answer_options = ANSWER_OPTIONS
@check_answers_card_number = person_index @check_answers_card_number = person_index
@inferred_check_answers_value = [{
"condition" => {
id => "R",
},
"value" => "Prefers not to say",
}]
end end
ANSWER_OPTIONS = { ANSWER_OPTIONS = {

6
app/models/form/sales/questions/person_working_situation.rb

@ -6,6 +6,12 @@ class Form::Sales::Questions::PersonWorkingSituation < ::Form::Sales::Questions:
@type = "radio" @type = "radio"
@answer_options = ANSWER_OPTIONS @answer_options = ANSWER_OPTIONS
@check_answers_card_number = person_index @check_answers_card_number = person_index
@inferred_check_answers_value = [{
"condition" => {
id => 10,
},
"value" => "Prefers not to say",
}]
end end
ANSWER_OPTIONS = { ANSWER_OPTIONS = {

4
app/models/form/sales/questions/postcode.rb

@ -6,12 +6,12 @@ class Form::Sales::Questions::Postcode < ::Form::Question
@header = "Postcode" @header = "Postcode"
@type = "text" @type = "text"
@width = 5 @width = 5
@inferred_check_answers_value = { @inferred_check_answers_value = [{
"condition" => { "condition" => {
"pcodenk" => 1, "pcodenk" => 1,
}, },
"value" => "Not known", "value" => "Not known",
} }]
@inferred_answers = { @inferred_answers = {
"la" => { "la" => {
"is_la_inferred" => true, "is_la_inferred" => true,

3
app/models/form/sales/questions/previous_bedrooms.rb

@ -6,7 +6,8 @@ class Form::Sales::Questions::PreviousBedrooms < ::Form::Question
@header = "How many bedrooms did the property have?" @header = "How many bedrooms did the property have?"
@type = "numeric" @type = "numeric"
@width = 5 @width = 5
@min = 0 @min = 1
@max = 6
@hint_text = "For bedsits enter 1" @hint_text = "For bedsits enter 1"
end end
end end

4
app/models/form/sales/questions/previous_postcode.rb

@ -6,12 +6,12 @@ class Form::Sales::Questions::PreviousPostcode < ::Form::Question
@header = "Postcode" @header = "Postcode"
@type = "text" @type = "text"
@width = 5 @width = 5
@inferred_check_answers_value = { @inferred_check_answers_value = [{
"condition" => { "condition" => {
"ppcodenk" => 1, "ppcodenk" => 1,
}, },
"value" => "Not known", "value" => "Not known",
} }]
@inferred_answers = { @inferred_answers = {
"prevloc" => { "prevloc" => {
"is_previous_la_inferred" => true, "is_previous_la_inferred" => true,

4
app/models/form/sales/questions/prevloc.rb

@ -6,12 +6,12 @@ class Form::Sales::Questions::Prevloc < ::Form::Question
@header = "Select a local authority" @header = "Select a local authority"
@type = "select" @type = "select"
@answer_options = ANSWER_OPTIONS @answer_options = ANSWER_OPTIONS
@inferred_check_answers_value = { @inferred_check_answers_value = [{
"condition" => { "condition" => {
"previous_la_known" => 0, "previous_la_known" => 0,
}, },
"value" => "Not known", "value" => "Not known",
} }]
end end
ANSWER_OPTIONS = { ANSWER_OPTIONS = {

4
app/models/form/sales/questions/property_number_of_bedrooms.rb

@ -5,7 +5,9 @@ class Form::Sales::Questions::PropertyNumberOfBedrooms < ::Form::Question
@check_answer_label = "Number of bedrooms" @check_answer_label = "Number of bedrooms"
@header = "How many bedrooms does the property have?" @header = "How many bedrooms does the property have?"
@hint_text = "A bedsit has 1 bedroom" @hint_text = "A bedsit has 1 bedroom"
@type = "text" @type = "numeric"
@width = 10 @width = 10
@min = 1
@max = 9
end end
end end

24
app/models/form/sales/questions/retirement_value_check.rb

@ -0,0 +1,24 @@
class Form::Sales::Questions::RetirementValueCheck < ::Form::Question
def initialize(id, hsh, page, person_index:)
super(id, hsh, page)
@id = "retirement_value_check"
@check_answer_label = "Retirement confirmation"
@type = "interruption_screen"
@answer_options = {
"0" => { "value" => "Yes" },
"1" => { "value" => "No" },
}
@hidden_in_check_answers = {
"depends_on" => [
{
"retirement_value_check" => 0,
},
{
"retirement_value_check" => 1,
},
],
}
@check_answers_card_number = person_index
@header = "Are you sure this person is retired?"
end
end

4
app/models/form/sales/subsections/discounted_ownership_scheme.rb

@ -10,15 +10,19 @@ class Form::Sales::Subsections::DiscountedOwnershipScheme < ::Form::Subsection
@pages ||= [ @pages ||= [
Form::Sales::Pages::LivingBeforePurchase.new("living_before_purchase_discounted_ownership", nil, self), Form::Sales::Pages::LivingBeforePurchase.new("living_before_purchase_discounted_ownership", nil, self),
Form::Sales::Pages::AboutPriceRtb.new(nil, nil, self), Form::Sales::Pages::AboutPriceRtb.new(nil, nil, self),
Form::Sales::Pages::ExtraBorrowingValueCheck.new("extra_borrowing_price_value_check", nil, self),
Form::Sales::Pages::AboutPriceNotRtb.new(nil, nil, self), Form::Sales::Pages::AboutPriceNotRtb.new(nil, nil, self),
Form::Sales::Pages::PurchasePrice.new("purchase_price_discounted_ownership", nil, self), Form::Sales::Pages::PurchasePrice.new("purchase_price_discounted_ownership", nil, self),
Form::Sales::Pages::Mortgageused.new("mortgage_used_discounted_ownership", nil, self), Form::Sales::Pages::Mortgageused.new("mortgage_used_discounted_ownership", nil, self),
Form::Sales::Pages::MortgageAmount.new("mortgage_amount_discounted_ownership", nil, self), Form::Sales::Pages::MortgageAmount.new("mortgage_amount_discounted_ownership", nil, self),
Form::Sales::Pages::ExtraBorrowingValueCheck.new("extra_borrowing_mortgage_value_check", nil, self),
Form::Sales::Pages::MortgageLender.new("mortgage_lender_discounted_ownership", nil, self), Form::Sales::Pages::MortgageLender.new("mortgage_lender_discounted_ownership", nil, self),
Form::Sales::Pages::MortgageLenderOther.new("mortgage_lender_other_discounted_ownership", nil, self), Form::Sales::Pages::MortgageLenderOther.new("mortgage_lender_other_discounted_ownership", nil, self),
Form::Sales::Pages::MortgageLength.new("mortgage_length_discounted_ownership", nil, self), Form::Sales::Pages::MortgageLength.new("mortgage_length_discounted_ownership", nil, self),
Form::Sales::Pages::ExtraBorrowing.new("extra_borrowing_discounted_ownership", nil, self), Form::Sales::Pages::ExtraBorrowing.new("extra_borrowing_discounted_ownership", nil, self),
Form::Sales::Pages::ExtraBorrowingValueCheck.new("extra_borrowing_value_check", nil, self),
Form::Sales::Pages::AboutDepositWithoutDiscount.new("about_deposit_discounted_ownership", nil, self), Form::Sales::Pages::AboutDepositWithoutDiscount.new("about_deposit_discounted_ownership", nil, self),
Form::Sales::Pages::ExtraBorrowingValueCheck.new("extra_borrowing_deposit_value_check", nil, self),
Form::Sales::Pages::DepositValueCheck.new("discounted_ownership_deposit_value_check", nil, self), Form::Sales::Pages::DepositValueCheck.new("discounted_ownership_deposit_value_check", nil, self),
Form::Sales::Pages::LeaseholdCharges.new("leasehold_charges_discounted_ownership", nil, self), Form::Sales::Pages::LeaseholdCharges.new("leasehold_charges_discounted_ownership", nil, self),
] ]

33
app/models/form/sales/subsections/household_characteristics.rb

@ -11,7 +11,11 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Sales::Pages::BuyerInterview.new(nil, nil, self), Form::Sales::Pages::BuyerInterview.new(nil, nil, self),
Form::Sales::Pages::PrivacyNotice.new(nil, nil, self), Form::Sales::Pages::PrivacyNotice.new(nil, nil, self),
Form::Sales::Pages::Age1.new(nil, nil, self), Form::Sales::Pages::Age1.new(nil, nil, self),
Form::Sales::Pages::RetirementValueCheck.new("age_1_retirement_value_check", nil, self, person_index: 1),
Form::Sales::Pages::RetirementValueCheck.new("age_1_retirement_value_check_joint_purchase", nil, self, person_index: 1),
Form::Sales::Pages::GenderIdentity1.new(nil, nil, self), Form::Sales::Pages::GenderIdentity1.new(nil, nil, self),
Form::Sales::Pages::RetirementValueCheck.new("gender_1_retirement_value_check", nil, self, person_index: 1),
Form::Sales::Pages::RetirementValueCheck.new("gender_1_retirement_value_check_joint_purchase", nil, self, person_index: 1),
Form::Sales::Pages::Buyer1EthnicGroup.new(nil, nil, self), Form::Sales::Pages::Buyer1EthnicGroup.new(nil, nil, self),
Form::Sales::Pages::Buyer1EthnicBackgroundBlack.new(nil, nil, self), Form::Sales::Pages::Buyer1EthnicBackgroundBlack.new(nil, nil, self),
Form::Sales::Pages::Buyer1EthnicBackgroundAsian.new(nil, nil, self), Form::Sales::Pages::Buyer1EthnicBackgroundAsian.new(nil, nil, self),
@ -20,12 +24,17 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Sales::Pages::Buyer1EthnicBackgroundWhite.new(nil, nil, self), Form::Sales::Pages::Buyer1EthnicBackgroundWhite.new(nil, nil, self),
Form::Sales::Pages::Nationality1.new(nil, nil, self), Form::Sales::Pages::Nationality1.new(nil, nil, self),
Form::Sales::Pages::Buyer1WorkingSituation.new(nil, nil, self), Form::Sales::Pages::Buyer1WorkingSituation.new(nil, nil, self),
Form::Sales::Pages::RetirementValueCheck.new("working_situation_1_retirement_value_check", nil, self, person_index: 1),
Form::Sales::Pages::RetirementValueCheck.new("working_situation_1_retirement_value_check_joint_purchase", nil, self, person_index: 1),
Form::Sales::Pages::Buyer1IncomeValueCheck.new("working_situation_buyer_1_income_value_check", nil, self), Form::Sales::Pages::Buyer1IncomeValueCheck.new("working_situation_buyer_1_income_value_check", nil, self),
Form::Sales::Pages::Buyer1LiveInProperty.new(nil, nil, self), Form::Sales::Pages::Buyer1LiveInProperty.new(nil, nil, self),
Form::Sales::Pages::Buyer2RelationshipToBuyer1.new(nil, nil, self), Form::Sales::Pages::Buyer2RelationshipToBuyer1.new(nil, nil, self),
Form::Sales::Pages::Age2.new(nil, nil, self), Form::Sales::Pages::Age2.new(nil, nil, self),
Form::Sales::Pages::RetirementValueCheck.new("age_2_retirement_value_check_joint_purchase", nil, self, person_index: 2),
Form::Sales::Pages::GenderIdentity2.new(nil, nil, self), Form::Sales::Pages::GenderIdentity2.new(nil, nil, self),
Form::Sales::Pages::RetirementValueCheck.new("gender_2_retirement_value_check_joint_purchase", nil, self, person_index: 2),
Form::Sales::Pages::Buyer2WorkingSituation.new(nil, nil, self), Form::Sales::Pages::Buyer2WorkingSituation.new(nil, nil, self),
Form::Sales::Pages::RetirementValueCheck.new("working_situation_2_retirement_value_check_joint_purchase", nil, self, person_index: 2),
Form::Sales::Pages::Buyer2LiveInProperty.new(nil, nil, self), Form::Sales::Pages::Buyer2LiveInProperty.new(nil, nil, self),
Form::Sales::Pages::NumberOfOthersInProperty.new(nil, nil, self), Form::Sales::Pages::NumberOfOthersInProperty.new(nil, nil, self),
Form::Sales::Pages::PersonKnown.new("person_1_known", nil, self, person_index: 2), Form::Sales::Pages::PersonKnown.new("person_1_known", nil, self, person_index: 2),
@ -33,41 +42,65 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_1_relationship_to_buyer_1", nil, self, person_index: 2), Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_1_relationship_to_buyer_1", nil, self, person_index: 2),
Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_1_relationship_to_buyer_1_joint_purchase", nil, self, person_index: 3), Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_1_relationship_to_buyer_1_joint_purchase", nil, self, person_index: 3),
Form::Sales::Pages::PersonAge.new("person_1_age", nil, self, person_index: 2), Form::Sales::Pages::PersonAge.new("person_1_age", nil, self, person_index: 2),
Form::Sales::Pages::RetirementValueCheck.new("age_2_retirement_value_check", nil, self, person_index: 2),
Form::Sales::Pages::PersonAge.new("person_1_age_joint_purchase", nil, self, person_index: 3), Form::Sales::Pages::PersonAge.new("person_1_age_joint_purchase", nil, self, person_index: 3),
Form::Sales::Pages::RetirementValueCheck.new("age_3_retirement_value_check_joint_purchase", nil, self, person_index: 3),
Form::Sales::Pages::PersonGenderIdentity.new("person_1_gender_identity", nil, self, person_index: 2), Form::Sales::Pages::PersonGenderIdentity.new("person_1_gender_identity", nil, self, person_index: 2),
Form::Sales::Pages::RetirementValueCheck.new("gender_2_retirement_value_check", nil, self, person_index: 2),
Form::Sales::Pages::PersonGenderIdentity.new("person_1_gender_identity_joint_purchase", nil, self, person_index: 3), Form::Sales::Pages::PersonGenderIdentity.new("person_1_gender_identity_joint_purchase", nil, self, person_index: 3),
Form::Sales::Pages::RetirementValueCheck.new("gender_3_retirement_value_check_joint_purchase", nil, self, person_index: 3),
Form::Sales::Pages::PersonWorkingSituation.new("person_1_working_situation", nil, self, person_index: 2), Form::Sales::Pages::PersonWorkingSituation.new("person_1_working_situation", nil, self, person_index: 2),
Form::Sales::Pages::RetirementValueCheck.new("working_situation_2_retirement_value_check", nil, self, person_index: 2),
Form::Sales::Pages::PersonWorkingSituation.new("person_1_working_situation_joint_purchase", nil, self, person_index: 3), Form::Sales::Pages::PersonWorkingSituation.new("person_1_working_situation_joint_purchase", nil, self, person_index: 3),
Form::Sales::Pages::RetirementValueCheck.new("working_situation_3_retirement_value_check_joint_purchase", nil, self, person_index: 3),
Form::Sales::Pages::PersonKnown.new("person_2_known", nil, self, person_index: 3), Form::Sales::Pages::PersonKnown.new("person_2_known", nil, self, person_index: 3),
Form::Sales::Pages::PersonKnown.new("person_2_known_joint_purchase", nil, self, person_index: 4), Form::Sales::Pages::PersonKnown.new("person_2_known_joint_purchase", nil, self, person_index: 4),
Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_2_relationship_to_buyer_1", nil, self, person_index: 3), Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_2_relationship_to_buyer_1", nil, self, person_index: 3),
Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_2_relationship_to_buyer_1_joint_purchase", nil, self, person_index: 4), Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_2_relationship_to_buyer_1_joint_purchase", nil, self, person_index: 4),
Form::Sales::Pages::PersonAge.new("person_2_age", nil, self, person_index: 3), Form::Sales::Pages::PersonAge.new("person_2_age", nil, self, person_index: 3),
Form::Sales::Pages::RetirementValueCheck.new("age_3_retirement_value_check", nil, self, person_index: 3),
Form::Sales::Pages::PersonAge.new("person_2_age_joint_purchase", nil, self, person_index: 4), Form::Sales::Pages::PersonAge.new("person_2_age_joint_purchase", nil, self, person_index: 4),
Form::Sales::Pages::RetirementValueCheck.new("age_4_retirement_value_check_joint_purchase", nil, self, person_index: 4),
Form::Sales::Pages::PersonGenderIdentity.new("person_2_gender_identity", nil, self, person_index: 3), Form::Sales::Pages::PersonGenderIdentity.new("person_2_gender_identity", nil, self, person_index: 3),
Form::Sales::Pages::RetirementValueCheck.new("gender_3_retirement_value_check", nil, self, person_index: 3),
Form::Sales::Pages::PersonGenderIdentity.new("person_2_gender_identity_joint_purchase", nil, self, person_index: 4), Form::Sales::Pages::PersonGenderIdentity.new("person_2_gender_identity_joint_purchase", nil, self, person_index: 4),
Form::Sales::Pages::RetirementValueCheck.new("gender_4_retirement_value_check_joint_purchase", nil, self, person_index: 4),
Form::Sales::Pages::PersonWorkingSituation.new("person_2_working_situation", nil, self, person_index: 3), Form::Sales::Pages::PersonWorkingSituation.new("person_2_working_situation", nil, self, person_index: 3),
Form::Sales::Pages::RetirementValueCheck.new("working_situation_3_retirement_value_check", nil, self, person_index: 3),
Form::Sales::Pages::PersonWorkingSituation.new("person_2_working_situation_joint_purchase", nil, self, person_index: 4), Form::Sales::Pages::PersonWorkingSituation.new("person_2_working_situation_joint_purchase", nil, self, person_index: 4),
Form::Sales::Pages::RetirementValueCheck.new("working_situation_4_retirement_value_check_joint_purchase", nil, self, person_index: 4),
Form::Sales::Pages::PersonKnown.new("person_3_known", nil, self, person_index: 4), Form::Sales::Pages::PersonKnown.new("person_3_known", nil, self, person_index: 4),
Form::Sales::Pages::PersonKnown.new("person_3_known_joint_purchase", nil, self, person_index: 5), Form::Sales::Pages::PersonKnown.new("person_3_known_joint_purchase", nil, self, person_index: 5),
Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_3_relationship_to_buyer_1", nil, self, person_index: 4), Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_3_relationship_to_buyer_1", nil, self, person_index: 4),
Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_3_relationship_to_buyer_1_joint_purchase", nil, self, person_index: 5), Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_3_relationship_to_buyer_1_joint_purchase", nil, self, person_index: 5),
Form::Sales::Pages::PersonAge.new("person_3_age", nil, self, person_index: 4), Form::Sales::Pages::PersonAge.new("person_3_age", nil, self, person_index: 4),
Form::Sales::Pages::RetirementValueCheck.new("age_4_retirement_value_check", nil, self, person_index: 4),
Form::Sales::Pages::PersonAge.new("person_3_age_joint_purchase", nil, self, person_index: 5), Form::Sales::Pages::PersonAge.new("person_3_age_joint_purchase", nil, self, person_index: 5),
Form::Sales::Pages::RetirementValueCheck.new("age_5_retirement_value_check_joint_purchase", nil, self, person_index: 5),
Form::Sales::Pages::PersonGenderIdentity.new("person_3_gender_identity", nil, self, person_index: 4), Form::Sales::Pages::PersonGenderIdentity.new("person_3_gender_identity", nil, self, person_index: 4),
Form::Sales::Pages::RetirementValueCheck.new("gender_4_retirement_value_check", nil, self, person_index: 4),
Form::Sales::Pages::PersonGenderIdentity.new("person_3_gender_identity_joint_purchase", nil, self, person_index: 5), Form::Sales::Pages::PersonGenderIdentity.new("person_3_gender_identity_joint_purchase", nil, self, person_index: 5),
Form::Sales::Pages::RetirementValueCheck.new("gender_5_retirement_value_check_joint_purchase", nil, self, person_index: 5),
Form::Sales::Pages::PersonWorkingSituation.new("person_3_working_situation", nil, self, person_index: 4), Form::Sales::Pages::PersonWorkingSituation.new("person_3_working_situation", nil, self, person_index: 4),
Form::Sales::Pages::RetirementValueCheck.new("working_situation_4_retirement_value_check", nil, self, person_index: 4),
Form::Sales::Pages::PersonWorkingSituation.new("person_3_working_situation_joint_purchase", nil, self, person_index: 5), Form::Sales::Pages::PersonWorkingSituation.new("person_3_working_situation_joint_purchase", nil, self, person_index: 5),
Form::Sales::Pages::RetirementValueCheck.new("working_situation_5_retirement_value_check_joint_purchase", nil, self, person_index: 5),
Form::Sales::Pages::PersonKnown.new("person_4_known", nil, self, person_index: 5), Form::Sales::Pages::PersonKnown.new("person_4_known", nil, self, person_index: 5),
Form::Sales::Pages::PersonKnown.new("person_4_known_joint_purchase", nil, self, person_index: 6), Form::Sales::Pages::PersonKnown.new("person_4_known_joint_purchase", nil, self, person_index: 6),
Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_4_relationship_to_buyer_1", nil, self, person_index: 5), Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_4_relationship_to_buyer_1", nil, self, person_index: 5),
Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_4_relationship_to_buyer_1_joint_purchase", nil, self, person_index: 6), Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_4_relationship_to_buyer_1_joint_purchase", nil, self, person_index: 6),
Form::Sales::Pages::PersonAge.new("person_4_age", nil, self, person_index: 5), Form::Sales::Pages::PersonAge.new("person_4_age", nil, self, person_index: 5),
Form::Sales::Pages::RetirementValueCheck.new("age_5_retirement_value_check", nil, self, person_index: 5),
Form::Sales::Pages::PersonAge.new("person_4_age_joint_purchase", nil, self, person_index: 6), Form::Sales::Pages::PersonAge.new("person_4_age_joint_purchase", nil, self, person_index: 6),
Form::Sales::Pages::RetirementValueCheck.new("age_6_retirement_value_check_joint_purchase", nil, self, person_index: 6),
Form::Sales::Pages::PersonGenderIdentity.new("person_4_gender_identity", nil, self, person_index: 5), Form::Sales::Pages::PersonGenderIdentity.new("person_4_gender_identity", nil, self, person_index: 5),
Form::Sales::Pages::RetirementValueCheck.new("gender_5_retirement_value_check", nil, self, person_index: 5),
Form::Sales::Pages::PersonGenderIdentity.new("person_4_gender_identity_joint_purchase", nil, self, person_index: 6), Form::Sales::Pages::PersonGenderIdentity.new("person_4_gender_identity_joint_purchase", nil, self, person_index: 6),
Form::Sales::Pages::RetirementValueCheck.new("gender_6_retirement_value_check_joint_purchase", nil, self, person_index: 6),
Form::Sales::Pages::PersonWorkingSituation.new("person_4_working_situation", nil, self, person_index: 5), Form::Sales::Pages::PersonWorkingSituation.new("person_4_working_situation", nil, self, person_index: 5),
Form::Sales::Pages::RetirementValueCheck.new("working_situation_5_retirement_value_check", nil, self, person_index: 5),
Form::Sales::Pages::PersonWorkingSituation.new("person_4_working_situation_joint_purchase", nil, self, person_index: 6), Form::Sales::Pages::PersonWorkingSituation.new("person_4_working_situation_joint_purchase", nil, self, person_index: 6),
Form::Sales::Pages::RetirementValueCheck.new("working_situation_6_retirement_value_check_joint_purchase", nil, self, person_index: 6),
] ]
end end
end end

1
app/models/form/sales/subsections/household_needs.rb

@ -12,6 +12,7 @@ class Form::Sales::Subsections::HouseholdNeeds < ::Form::Subsection
Form::Sales::Pages::BuyerStillServing.new(nil, nil, self), Form::Sales::Pages::BuyerStillServing.new(nil, nil, self),
Form::Sales::Pages::ArmedForcesSpouse.new(nil, nil, self), Form::Sales::Pages::ArmedForcesSpouse.new(nil, nil, self),
Form::Sales::Pages::HouseholdDisability.new(nil, nil, self), Form::Sales::Pages::HouseholdDisability.new(nil, nil, self),
Form::Sales::Pages::HouseholdWheelchairCheck.new("disability_wheelchair_check", nil, self),
Form::Sales::Pages::HouseholdWheelchair.new(nil, nil, self), Form::Sales::Pages::HouseholdWheelchair.new(nil, nil, self),
Form::Sales::Pages::HouseholdWheelchairCheck.new("wheelchair_check", nil, self), Form::Sales::Pages::HouseholdWheelchairCheck.new("wheelchair_check", nil, self),
] ]

1
app/models/form/sales/subsections/shared_ownership_scheme.rb

@ -14,6 +14,7 @@ class Form::Sales::Subsections::SharedOwnershipScheme < ::Form::Subsection
Form::Sales::Pages::Resale.new(nil, nil, self), Form::Sales::Pages::Resale.new(nil, nil, self),
Form::Sales::Pages::ExchangeDate.new(nil, nil, self), Form::Sales::Pages::ExchangeDate.new(nil, nil, self),
Form::Sales::Pages::HandoverDate.new(nil, nil, self), Form::Sales::Pages::HandoverDate.new(nil, nil, self),
Form::Sales::Pages::HandoverDateCheck.new("handover_date_check", nil, self),
Form::Sales::Pages::LaNominations.new(nil, nil, self), Form::Sales::Pages::LaNominations.new(nil, nil, self),
Form::Sales::Pages::BuyerPrevious.new(nil, nil, self), Form::Sales::Pages::BuyerPrevious.new(nil, nil, self),
Form::Sales::Pages::PreviousBedrooms.new(nil, nil, self), Form::Sales::Pages::PreviousBedrooms.new(nil, nil, self),

21
app/models/lettings_log.rb

@ -458,16 +458,6 @@ class LettingsLog < Log
OPTIONAL_FIELDS + dynamically_not_required OPTIONAL_FIELDS + dynamically_not_required
end end
(1..8).each do |person_num|
define_method("retirement_age_for_person_#{person_num}") do
retirement_age_for_person(person_num)
end
define_method("plural_gender_for_person_#{person_num}") do
plural_gender_for_person(person_num)
end
end
def retirement_age_for_person(person_num) def retirement_age_for_person(person_num)
gender = public_send("sex#{person_num}".to_sym) gender = public_send("sex#{person_num}".to_sym)
return unless gender return unless gender
@ -475,17 +465,6 @@ class LettingsLog < Log
RETIREMENT_AGES[gender] RETIREMENT_AGES[gender]
end end
def plural_gender_for_person(person_num)
gender = public_send("sex#{person_num}".to_sym)
return unless gender
if %w[M X].include?(gender)
"male and non-binary people"
elsif gender == "F"
"females"
end
end
def age_known?(person_num) def age_known?(person_num)
return false unless person_num.is_a?(Integer) return false unless person_num.is_a?(Integer)

33
app/models/log.rb

@ -4,6 +4,8 @@ class Log < ApplicationRecord
belongs_to :owning_organisation, class_name: "Organisation", optional: true belongs_to :owning_organisation, class_name: "Organisation", optional: true
belongs_to :created_by, class_name: "User", optional: true belongs_to :created_by, class_name: "User", optional: true
belongs_to :updated_by, class_name: "User", optional: true belongs_to :updated_by, class_name: "User", optional: true
belongs_to :bulk_upload, optional: true
before_save :update_status! before_save :update_status!
STATUS = { "not_started" => 0, "in_progress" => 1, "completed" => 2 }.freeze STATUS = { "not_started" => 0, "in_progress" => 1, "completed" => 2 }.freeze
@ -44,8 +46,39 @@ class Log < ApplicationRecord
form.end_date > Time.zone.today form.end_date > Time.zone.today
end end
def blank_invalid_non_setup_fields!
setup_ids = form.setup_sections.flat_map(&:subsections).flat_map(&:questions).map(&:id)
errors.each do |error|
next if setup_ids.include?(error.attribute.to_s)
public_send("#{error.attribute}=", nil)
end
end
(1..8).each do |person_num|
define_method("retirement_age_for_person_#{person_num}") do
retirement_age_for_person(person_num)
end
define_method("plural_gender_for_person_#{person_num}") do
plural_gender_for_person(person_num)
end
end
private private
def plural_gender_for_person(person_num)
gender = public_send("sex#{person_num}".to_sym)
return unless gender
if %w[M X].include?(gender)
"male and non-binary people"
elsif gender == "F"
"females"
end
end
def update_status! def update_status!
self.status = if all_fields_completed? && errors.empty? self.status = if all_fields_completed? && errors.empty?
"completed" "completed"

1
app/models/organisation.rb

@ -6,7 +6,6 @@ class Organisation < ApplicationRecord
has_many :data_protection_confirmations has_many :data_protection_confirmations
has_many :organisation_rent_periods has_many :organisation_rent_periods
has_many :owned_schemes, class_name: "Scheme", foreign_key: "owning_organisation_id", dependent: :delete_all has_many :owned_schemes, class_name: "Scheme", foreign_key: "owning_organisation_id", dependent: :delete_all
has_many :managed_schemes, class_name: "Scheme", foreign_key: "managing_organisation_id"
has_many :parent_organisation_relationships, foreign_key: :child_organisation_id, class_name: "OrganisationRelationship" has_many :parent_organisation_relationships, foreign_key: :child_organisation_id, class_name: "OrganisationRelationship"
has_many :parent_organisations, through: :parent_organisation_relationships has_many :parent_organisations, through: :parent_organisation_relationships
has_many :child_organisation_relationships, foreign_key: :parent_organisation_id, class_name: "OrganisationRelationship" has_many :child_organisation_relationships, foreign_key: :parent_organisation_id, class_name: "OrganisationRelationship"

28
app/models/sales_log.rb

@ -1,7 +1,10 @@
class SalesLogValidator < ActiveModel::Validator class SalesLogValidator < ActiveModel::Validator
include Validations::Sales::HouseholdValidations include Validations::Sales::HouseholdValidations
include Validations::Sales::PropertyValidations
include Validations::SharedValidations include Validations::SharedValidations
include Validations::Sales::FinancialValidations include Validations::Sales::FinancialValidations
include Validations::Sales::SaleInformationValidations
include Validations::SharedValidations
include Validations::LocalAuthorityValidations include Validations::LocalAuthorityValidations
def validate(record) def validate(record)
@ -13,6 +16,7 @@ end
class SalesLog < Log class SalesLog < Log
include DerivedVariables::SalesLogVariables include DerivedVariables::SalesLogVariables
include Validations::Sales::SoftValidations include Validations::Sales::SoftValidations
include Validations::SoftValidations
self.inheritance_column = :_type_disabled self.inheritance_column = :_type_disabled
@ -31,6 +35,7 @@ class SalesLog < Log
scope :filter_by_organisation, ->(org, _user = nil) { where(owning_organisation: org) } scope :filter_by_organisation, ->(org, _user = nil) { where(owning_organisation: org) }
OPTIONAL_FIELDS = %w[purchid].freeze OPTIONAL_FIELDS = %w[purchid].freeze
RETIREMENT_AGES = { "M" => 65, "F" => 60, "X" => 65 }.freeze
def startdate def startdate
saledate saledate
@ -167,6 +172,10 @@ class SalesLog < Log
ownershipsch == 3 ownershipsch == 3
end end
def discounted_ownership_sale?
ownershipsch == 2
end
def mortgage_not_used? def mortgage_not_used?
mortgageused == 2 mortgageused == 2
end end
@ -183,4 +192,23 @@ class SalesLog < Log
update!(created_by: nil) update!(created_by: nil)
end end
def retirement_age_for_person(person_num)
gender = public_send("sex#{person_num}".to_sym)
return unless gender
RETIREMENT_AGES[gender]
end
def joint_purchase?
jointpur == 1
end
def not_joint_purchase?
jointpur == 2
end
def old_persons_shared_ownership?
type == 24
end
end end

19
app/models/validations/household_validations.rb

@ -47,7 +47,7 @@ module Validations::HouseholdValidations
validate_person_age_matches_relationship(record, n) validate_person_age_matches_relationship(record, n)
validate_person_age_and_relationship_matches_economic_status(record, n) validate_person_age_and_relationship_matches_economic_status(record, n)
end end
validate_partner_count(record) shared_validate_partner_count(record, 8)
end end
def validate_person_1_economic(record) def validate_person_1_economic(record)
@ -177,16 +177,9 @@ private
return unless age && economic_status && relationship return unless age && economic_status && relationship
if age >= 16 && age <= 19 && tenant_is_child?(relationship) && (!tenant_is_fulltime_student?(economic_status) && !tenant_economic_status_refused?(economic_status)) if age >= 16 && age <= 19 && tenant_is_child?(relationship) && (!tenant_is_fulltime_student?(economic_status) && !tenant_economic_status_refused?(economic_status))
record.errors.add "ecstat#{person_num}", I18n.t("validations.household.ecstat.student_16_19", person_num:) record.errors.add "ecstat#{person_num}", I18n.t("validations.household.ecstat.not_student_16_19", person_num:)
record.errors.add "age#{person_num}", I18n.t("validations.household.age.student_16_19", person_num:) record.errors.add "age#{person_num}", I18n.t("validations.household.age.not_student_16_19", person_num:)
record.errors.add "relat#{person_num}", I18n.t("validations.household.relat.student_16_19", person_num:) record.errors.add "relat#{person_num}", I18n.t("validations.household.relat.not_student_16_19", person_num:)
end
end
def validate_partner_count(record)
partner_count = (2..8).count { |n| tenant_is_partner?(record["relat#{n}"]) }
if partner_count > 1
record.errors.add :base, I18n.t("validations.household.relat.one_partner")
end end
end end
@ -202,10 +195,6 @@ private
economic_status == 10 economic_status == 10
end end
def tenant_is_partner?(relationship)
relationship == "P"
end
def tenant_is_child?(relationship) def tenant_is_child?(relationship)
relationship == "C" relationship == "C"
end end

8
app/models/validations/sales/financial_validations.rb

@ -11,4 +11,12 @@ module Validations::Sales::FinancialValidations
end end
end end
end end
def validate_cash_discount(record)
return unless record.cashdis
unless record.cashdis.between?(0, 999_999)
record.errors.add :cashdis, I18n.t("validations.financial.cash_discount_invalid")
end
end
end end

114
app/models/validations/sales/household_validations.rb

@ -1,4 +1,6 @@
module Validations::Sales::HouseholdValidations module Validations::Sales::HouseholdValidations
include Validations::SharedValidations
def validate_number_of_other_people_living_in_the_property(record) def validate_number_of_other_people_living_in_the_property(record)
return if record.hholdcount.blank? return if record.hholdcount.blank?
@ -6,4 +8,116 @@ module Validations::Sales::HouseholdValidations
record.errors.add :hholdcount, I18n.t("validations.numeric.valid", field: "Number of other people living in the property", min: 0, max: 4) record.errors.add :hholdcount, I18n.t("validations.numeric.valid", field: "Number of other people living in the property", min: 0, max: 4)
end end
end end
def validate_household_number_of_other_members(record)
(2..6).each do |n|
validate_person_age_matches_relationship(record, n)
validate_person_age_and_relationship_matches_economic_status(record, n)
validate_person_age_matches_economic_status(record, n)
end
shared_validate_partner_count(record, 6)
end
def validate_buyers_age_for_old_persons_shared_ownership(record)
if record.old_persons_shared_ownership?
if record.joint_purchase? && ages_unknown_or_under_64?(record, [1, 2])
record.errors.add :age1, I18n.t("validations.household.old_persons_shared_ownership")
record.errors.add :age2, I18n.t("validations.household.old_persons_shared_ownership")
record.errors.add :type, I18n.t("validations.household.old_persons_shared_ownership")
end
if record.not_joint_purchase? && ages_unknown_or_under_64?(record, [1])
record.errors.add :age1, I18n.t("validations.household.old_persons_shared_ownership")
record.errors.add :type, I18n.t("validations.household.old_persons_shared_ownership")
end
end
end
def validate_previous_postcode(record)
return unless record.postcode_full && record.ppostcode_full && record.discounted_ownership_sale?
unless record.postcode_full == record.ppostcode_full
record.errors.add :postcode_full, I18n.t("validations.household.postcode.discounted_ownership")
record.errors.add :ppostcode_full, I18n.t("validations.household.postcode.discounted_ownership")
end
end
private
def validate_person_age_matches_relationship(record, person_num)
age = record.public_send("age#{person_num}")
relationship = record.public_send("relat#{person_num}")
return unless age && relationship
if age < 16 && person_is_partner?(relationship)
record.errors.add "age#{person_num}", I18n.t("validations.household.age.partner_under_16")
record.errors.add "relat#{person_num}", I18n.t("validations.household.relat.partner_under_16")
elsif age >= 20 && person_is_child?(relationship)
record.errors.add "age#{person_num}", I18n.t("validations.household.age.child_over_20")
record.errors.add "relat#{person_num}", I18n.t("validations.household.relat.child_over_20")
end
end
def validate_person_age_and_relationship_matches_economic_status(record, person_num)
age = record.public_send("age#{person_num}")
economic_status = record.public_send("ecstat#{person_num}")
relationship = record.public_send("relat#{person_num}")
return unless age && economic_status && relationship
age_between_16_19 = age.between?(16, 19)
student = person_is_fulltime_student?(economic_status)
child = person_is_child?(relationship)
if age_between_16_19 && student && !child
record.errors.add "age#{person_num}", I18n.t("validations.household.age.student_16_19.cannot_be_16_19.student_not_child")
record.errors.add "ecstat#{person_num}", I18n.t("validations.household.ecstat.student_16_19.cannot_be_student.16_19_not_child")
record.errors.add "relat#{person_num}", I18n.t("validations.household.relat.student_16_19.must_be_child")
end
if age_between_16_19 && !student && child
record.errors.add "age#{person_num}", I18n.t("validations.household.age.student_16_19.cannot_be_16_19.child_not_student")
record.errors.add "ecstat#{person_num}", I18n.t("validations.household.ecstat.student_16_19.must_be_student")
record.errors.add "relat#{person_num}", I18n.t("validations.household.relat.student_16_19.cannot_be_child.16_19_not_student")
end
if !age_between_16_19 && student && child
record.errors.add "age#{person_num}", I18n.t("validations.household.age.student_16_19.must_be_16_19")
record.errors.add "ecstat#{person_num}", I18n.t("validations.household.ecstat.student_16_19.cannot_be_student.child_not_16_19")
record.errors.add "relat#{person_num}", I18n.t("validations.household.relat.student_16_19.cannot_be_child.student_not_16_19")
end
end
def validate_person_age_matches_economic_status(record, person_num)
age = record.public_send("age#{person_num}")
economic_status = record.public_send("ecstat#{person_num}")
return unless age && economic_status
if age < 16 && !tenant_is_economic_child?(economic_status)
record.errors.add "ecstat#{person_num}", I18n.t("validations.household.ecstat.child_under_16", person_num:)
record.errors.add "age#{person_num}", I18n.t("validations.household.age.child_under_16", person_num:)
end
if tenant_is_economic_child?(economic_status) && age > 16
record.errors.add "ecstat#{person_num}", I18n.t("validations.household.ecstat.child_over_16", person_num:)
record.errors.add "age#{person_num}", I18n.t("validations.household.age.child_over_16", person_num:)
end
end
def person_is_partner?(relationship)
relationship == "P"
end
def person_is_fulltime_student?(economic_status)
economic_status == 7
end
def person_is_child?(relationship)
relationship == "C"
end
def tenant_is_economic_child?(economic_status)
economic_status == 9
end
def ages_unknown_or_under_64?(record, person_indexes)
person_indexes.all? { |person_num| record["age#{person_num}"].present? && record["age#{person_num}"] < 64 || record["age#{person_num}_known"] == 1 }
end
end end

10
app/models/validations/sales/property_validations.rb

@ -0,0 +1,10 @@
module Validations::Sales::PropertyValidations
def validate_postcodes_match_if_discounted_ownership(record)
return unless record.ppostcode_full.present? && record.postcode_full.present?
if record.discounted_ownership_sale? && record.ppostcode_full != record.postcode_full
record.errors.add :postcode_full, I18n.t("validations.property.postcode.must_match_previous")
record.errors.add :ppostcode_full, I18n.t("validations.property.postcode.must_match_previous")
end
end
end

41
app/models/validations/sales/sale_information_validations.rb

@ -0,0 +1,41 @@
module Validations::Sales::SaleInformationValidations
def validate_practical_completion_date_before_saledate(record)
return if record.saledate.blank? || record.hodate.blank?
unless record.saledate > record.hodate
record.errors.add :hodate, "Practical completion or handover date must be before exchange date"
end
end
def validate_years_living_in_property_before_purchase(record)
return unless record.proplen && record.proplen.nonzero?
case record.type
when 18
record.errors.add :type, I18n.t("validations.sale_information.proplen.social_homebuy")
record.errors.add :proplen, I18n.t("validations.sale_information.proplen.social_homebuy")
when 28, 29
record.errors.add :type, I18n.t("validations.sale_information.proplen.rent_to_buy")
record.errors.add :proplen, I18n.t("validations.sale_information.proplen.rent_to_buy")
end
end
def validate_exchange_date(record)
return unless record.exdate && record.saledate
record.errors.add(:exdate, I18n.t("validations.sale_information.exdate.must_be_before_saledate")) if record.exdate > record.saledate
return if (record.saledate.to_date - record.exdate.to_date).to_i / 365 < 1
record.errors.add(:exdate, I18n.t("validations.sale_information.exdate.must_be_less_than_1_year_from_saledate"))
end
def validate_previous_property_unit_type(record)
return unless record.fromprop && record.frombeds
if record.frombeds != 1 && record.fromprop == 2
record.errors.add :frombeds, I18n.t("validations.sale_information.previous_property_beds.property_type_bedsit")
record.errors.add :fromprop, I18n.t("validations.sale_information.previous_property_type.property_type_bedsit")
end
end
end

16
app/models/validations/sales/soft_validations.rb

@ -22,9 +22,9 @@ module Validations::Sales::SoftValidations
end end
def wheelchair_when_not_disabled? def wheelchair_when_not_disabled?
return false unless disabled == 2 return unless disabled && wheel
wheel == 1 wheel == 1 && disabled == 2
end end
def savings_over_soft_max? def savings_over_soft_max?
@ -36,4 +36,16 @@ module Validations::Sales::SoftValidations
deposit > savings * 4 / 3 deposit > savings * 4 / 3
end end
def extra_borrowing_expected_but_not_reported?
return unless extrabor && mortgage && deposit && value && discount
extrabor != 1 && mortgage + deposit > value - value * discount / 100
end
def hodate_3_years_or_more_saledate?
return unless hodate && saledate
((saledate.to_date - hodate.to_date).to_i / 365) >= 3
end
end end

23
app/models/validations/shared_validations.rb

@ -1,4 +1,6 @@
module Validations::SharedValidations module Validations::SharedValidations
include ActionView::Helpers::NumberHelper
def validate_other_field(record, value_other = nil, main_field = nil, other_field = nil, main_label = nil, other_label = nil) def validate_other_field(record, value_other = nil, main_field = nil, other_field = nil, main_label = nil, other_label = nil)
return unless main_field || other_field return unless main_field || other_field
@ -19,17 +21,19 @@ module Validations::SharedValidations
next unless record[question.id] next unless record[question.id]
field = question.check_answer_label || question.id field = question.check_answer_label || question.id
min = [question.prefix, number_with_delimiter(question.min, delimiter: ","), question.suffix].join("")
max = [question.prefix, number_with_delimiter(question.max, delimiter: ","), question.suffix].join("")
begin begin
answer = Float(record.public_send("#{question.id}_before_type_cast")) answer = Float(record.public_send("#{question.id}_before_type_cast"))
rescue ArgumentError rescue ArgumentError
record.errors.add question.id.to_sym, I18n.t("validations.numeric.valid", field:, min: question.min, max: question.max) record.errors.add question.id.to_sym, I18n.t("validations.numeric.valid", field:, min:, max:)
end end
next unless answer next unless answer
if (question.min && question.min > answer) || (question.max && question.max < answer) if (question.min && question.min > answer) || (question.max && question.max < answer)
record.errors.add question.id.to_sym, I18n.t("validations.numeric.valid", field:, min: question.min, max: question.max) record.errors.add question.id.to_sym, I18n.t("validations.numeric.valid", field:, min:, max:)
end end
end end
end end
@ -89,4 +93,19 @@ module Validations::SharedValidations
record.errors.add(question_id, I18n.t("validations.invalid_option", question: question.check_answer_label&.downcase)) record.errors.add(question_id, I18n.t("validations.invalid_option", question: question.check_answer_label&.downcase))
end end
end end
def shared_validate_partner_count(record, max_people)
partner_numbers = (2..max_people).select { |n| person_is_partner?(record["relat#{n}"]) }
if partner_numbers.count > 1
partner_numbers.each do |n|
record.errors.add "relat#{n}", I18n.t("validations.household.relat.one_partner")
end
end
end
private
def person_is_partner?(relationship)
relationship == "P"
end
end end

4
app/models/validations/soft_validations.rb

@ -111,7 +111,7 @@ private
gender = public_send("sex#{person_num}") gender = public_send("sex#{person_num}")
return unless age && economic_status && gender return unless age && economic_status && gender
%w[M X].include?(gender) && tenant_is_retired?(economic_status) && age < 67 || %w[M X].include?(gender) && tenant_is_retired?(economic_status) && age < retirement_age_for_person(person_num) ||
gender == "F" && tenant_is_retired?(economic_status) && age < 60 gender == "F" && tenant_is_retired?(economic_status) && age < 60
end end
@ -122,7 +122,7 @@ private
tenant_retired_or_prefers_not_say = tenant_is_retired?(economic_status) || tenant_prefers_not_to_say?(economic_status) tenant_retired_or_prefers_not_say = tenant_is_retired?(economic_status) || tenant_prefers_not_to_say?(economic_status)
return unless age && economic_status && gender return unless age && economic_status && gender
%w[M X].include?(gender) && !tenant_retired_or_prefers_not_say && age > 67 || %w[M X].include?(gender) && !tenant_retired_or_prefers_not_say && age > retirement_age_for_person(person_num) ||
gender == "F" && !tenant_retired_or_prefers_not_say && age > 60 gender == "F" && !tenant_retired_or_prefers_not_say && age > 60
end end
end end

58
app/services/bulk_upload/lettings/csv_parser.rb

@ -0,0 +1,58 @@
require "csv"
class BulkUpload::Lettings::CsvParser
attr_reader :path
def initialize(path:)
@path = path
end
def row_offset
with_headers? ? 5 : 0
end
def col_offset
with_headers? ? 1 : 0
end
def cols
@cols ||= ("A".."EE").to_a
end
def row_parsers
@row_parsers ||= body_rows.map do |row|
stripped_row = row[col_offset..]
headers = ("field_1".."field_134").to_a
hash = Hash[headers.zip(stripped_row)]
BulkUpload::Lettings::RowParser.new(hash)
end
end
def body_rows
rows[row_offset..]
end
def rows
@rows ||= CSV.parse(normalised_string, row_sep:)
end
private
def with_headers?
rows[0][0]&.match?(/\D+/)
end
def row_sep
"\n"
end
def normalised_string
return @normalised_string if @normalised_string
@normalised_string = File.read(path, encoding: "bom|utf-8")
@normalised_string.gsub!("\r\n", "\n")
@normalised_string
end
end

57
app/services/bulk_upload/lettings/log_creator.rb

@ -0,0 +1,57 @@
class BulkUpload::Lettings::LogCreator
attr_reader :bulk_upload, :path
def initialize(bulk_upload:, path:)
@bulk_upload = bulk_upload
@path = path
end
def call
row_parsers.each do |row_parser|
row_parser.valid?
row_parser.log.blank_invalid_non_setup_fields!
row_parser.log.bulk_upload = bulk_upload
begin
row_parser.log.save!
rescue StandardError => e
Sentry.capture_exception(e)
end
end
end
private
def csv_parser
@csv_parser ||= BulkUpload::Lettings::CsvParser.new(path:)
end
def row_offset
csv_parser.row_offset
end
def col_offset
csv_parser.col_offset
end
def row_parsers
return @row_parsers if @row_parsers
@row_parsers = csv_parser.row_parsers
@row_parsers.each do |row_parser|
row_parser.bulk_upload = bulk_upload
end
@row_parsers
end
def body_rows
csv_parser.body_rows
end
def rows
csv_parser.rows
end
end

20
app/services/bulk_upload/lettings/row_parser.rb

@ -15,14 +15,14 @@ class BulkUpload::Lettings::RowParser
attribute :field_9, :integer attribute :field_9, :integer
attribute :field_10, :string attribute :field_10, :string
attribute :field_11, :integer attribute :field_11, :integer
attribute :field_12, :string attribute :field_12, :integer
attribute :field_13, :string attribute :field_13, :integer
attribute :field_14, :string attribute :field_14, :integer
attribute :field_15, :string attribute :field_15, :integer
attribute :field_16, :string attribute :field_16, :integer
attribute :field_17, :string attribute :field_17, :integer
attribute :field_18, :string attribute :field_18, :integer
attribute :field_19, :string attribute :field_19, :integer
attribute :field_20, :string attribute :field_20, :string
attribute :field_21, :string attribute :field_21, :string
attribute :field_22, :string attribute :field_22, :string
@ -205,7 +205,7 @@ private
next if log.optional_fields.include?(question.id) next if log.optional_fields.include?(question.id)
next if question.completed?(log) next if question.completed?(log)
fields.each { |field| errors.add(field, :blank) } fields.each { |field| errors.add(field, I18n.t("validations.not_answered", question: question.check_answer_label&.downcase)) }
end end
end end
@ -663,7 +663,7 @@ private
when 2 when 2
0 0
when nil when nil
field_116 == 14 ? 1 : 0 rsnvac == 14 ? 1 : 0
end end
end end

47
app/services/bulk_upload/lettings/validator.rb

@ -169,18 +169,26 @@ class BulkUpload::Lettings::Validator
end end
end end
def create_logs?
row_parsers.all?(&:valid?)
end
def self.question_for_field(field) def self.question_for_field(field)
QUESTIONS[field] QUESTIONS[field]
end end
private private
def csv_parser
@csv_parser ||= BulkUpload::Lettings::CsvParser.new(path:)
end
def row_offset def row_offset
5 csv_parser.row_offset
end end
def col_offset def col_offset
1 csv_parser.col_offset
end end
def field_number_for_attribute(attribute) def field_number_for_attribute(attribute)
@ -188,46 +196,27 @@ private
end end
def cols def cols
@cols ||= ("A".."EE").to_a csv_parser.cols
end end
def row_parsers def row_parsers
@row_parsers ||= body_rows.map do |row| return @row_parsers if @row_parsers
stripped_row = row[1..]
headers = ("field_1".."field_134").to_a
hash = Hash[headers.zip(stripped_row)]
hash[:bulk_upload] = bulk_upload
BulkUpload::Lettings::RowParser.new(hash) @row_parsers = csv_parser.row_parsers
end
end
# determine the row seperator from CSV
# Windows will use \r\n
def row_sep
contents = ""
File.open(path, "r") do |f| @row_parsers.each do |row_parser|
f.seek(9900) row_parser.bulk_upload = bulk_upload
contents = f.read
end end
rn_count = contents.scan("\r\n").count @row_parsers
n_count = contents.scan(/[^\r]\n/).count
if rn_count > n_count
"\r\n"
else
"\n"
end
end end
def rows def rows
@rows ||= CSV.read(path, row_sep:) csv_parser.rows
end end
def body_rows def body_rows
rows[row_offset..] csv_parser.body_rows
end end
def validate_file_not_empty def validate_file_not_empty

19
app/services/bulk_upload/processor.rb

@ -8,12 +8,31 @@ class BulkUpload::Processor
def call def call
download download
validator.call validator.call
create_logs if validator.create_logs?
ensure ensure
downloader.delete_local_file! downloader.delete_local_file!
end end
private private
def create_logs
log_creator_class.new(
bulk_upload:,
path: downloader.path,
).call
end
def log_creator_class
case bulk_upload.log_type
when "lettings"
BulkUpload::Lettings::LogCreator
when "sales"
BulkUpload::Sales::LogCreator
else
raise "Log creator not found for #{bulk_upload.log_type}"
end
end
def downloader def downloader
@downloader ||= BulkUpload::Downloader.new(bulk_upload:) @downloader ||= BulkUpload::Downloader.new(bulk_upload:)
end end

4
app/services/csv/lettings_log_csv_service.rb

@ -1,6 +1,6 @@
module Csv module Csv
class LettingsLogCsvService class LettingsLogCsvService
CSV_FIELDS_TO_OMIT = %w[hhmemb net_income_value_check first_time_property_let_as_social_housing renttype needstype postcode_known is_la_inferred totchild totelder totadult net_income_known is_carehome previous_la_known is_previous_la_inferred age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known letting_allocation_unknown details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 rent_type_detail wrent wscharge wpschrge wsupchrg wtcharge wtshortfall rent_value_check old_form_id old_id retirement_value_check tshortfall_known pregnancy_value_check hhtype new_old vacdays la prevloc unresolved updated_by_id].freeze CSV_FIELDS_TO_OMIT = %w[hhmemb net_income_value_check first_time_property_let_as_social_housing renttype needstype postcode_known is_la_inferred totchild totelder totadult net_income_known is_carehome previous_la_known is_previous_la_inferred age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known letting_allocation_unknown details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 rent_type_detail wrent wscharge wpschrge wsupchrg wtcharge wtshortfall rent_value_check old_form_id old_id retirement_value_check tshortfall_known pregnancy_value_check hhtype new_old vacdays la prevloc unresolved updated_by_id bulk_upload_id].freeze
def initialize(user) def initialize(user)
@user = user @user = user
@ -40,7 +40,7 @@ module Csv
def set_csv_attributes def set_csv_attributes
metadata_fields = %w[id status created_at updated_at created_by_name is_dpo owning_organisation_name managing_organisation_name collection_start_year] metadata_fields = %w[id status created_at updated_at created_by_name is_dpo owning_organisation_name managing_organisation_name collection_start_year]
metadata_id_fields = %w[managing_organisation_id owning_organisation_id created_by_id] metadata_id_fields = %w[managing_organisation_id owning_organisation_id created_by_id bulk_upload_id]
scheme_and_location_ids = %w[scheme_id location_id] scheme_and_location_ids = %w[scheme_id location_id]
scheme_attributes = %w[scheme_code scheme_service_name scheme_sensitive scheme_type scheme_registered_under_care_act scheme_owning_organisation_name scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at] scheme_attributes = %w[scheme_code scheme_service_name scheme_sensitive scheme_type scheme_registered_under_care_act scheme_owning_organisation_name scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at]
location_attributes = %w[location_code location_postcode location_name location_units location_type_of_unit location_mobility_type location_admin_district location_startdate] location_attributes = %w[location_code location_postcode location_name location_units location_type_of_unit location_mobility_type location_admin_district location_startdate]

17
app/views/form/guidance/_what_counts_as_income_sales.html.erb

@ -1,17 +0,0 @@
<%= govuk_details(summary_text: "What counts as income?") do %>
<p class="govuk-body">You should include any income from:</p>
<ul class="govuk-list govuk-list--bullet">
<li>employment</li>
<li>pensions</li>
<li>investments</li>
<li>Universal Credit</li>
</ul>
<p class="govuk-body">Don’t include:</p>
<ul class="govuk-list govuk-list--bullet">
<li>National Insurance (NI) contributions and tax</li>
<li>housing benefit</li>
<li>child benefit</li>
<li>council tax support</li>
</ul>
<% end %>

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

@ -44,7 +44,7 @@
<%= f.hidden_field :page, value: @page.id %> <%= f.hidden_field :page, value: @page.id %>
<div class="govuk-button-group"> <div class="govuk-button-group">
<% if !@page.id.include?("value_check") && if request.query_parameters["referrer"] != "check_answers" %> <% if !@page.interruption_screen? && if request.query_parameters["referrer"] != "check_answers" %>
<%= f.govuk_submit "Save and continue" %> <%= f.govuk_submit "Save and continue" %>
<%= govuk_link_to "Skip for now", send(@log.form.next_page_redirect_path(@page, @log, current_user), @log) %> <%= govuk_link_to "Skip for now", send(@log.form.next_page_redirect_path(@page, @log, current_user), @log) %>
<% else %> <% else %>

18
app/views/layouts/_collection_resources.html.erb

@ -1,16 +1,22 @@
<div class="app-card"> <div class="app-card">
<h2 class="govuk-heading-s">Collection resources</h2> <p class="govuk-body-l govuk-!-margin-bottom-3">Collection resources</p>
<p class="govuk-body govuk-!-margin-bottom-6">For lettings starting during 1 April 2023 - 31 March 2024 and sales completing during the same period, use the 23/24 forms.</p>
<h2 class="govuk-body-l govuk-!-margin-bottom-3">Lettings 23/24</h2>
<%= render DocumentListComponent.new(items: [
{
name: "Lettings log for tenants (2023/24)",
href: "https://core.communities.gov.uk/public/download/guides-and-manuals/2023-24%20Lettings%20paper%20form.pdf?download-format=pdf",
metadata: "PDF, 281 KB, 8 pages",
},
]) %>
<h2 class="govuk-body-l govuk-!-margin-bottom-3">Lettings 22/23</h2>
<%= render DocumentListComponent.new(items: [ <%= render DocumentListComponent.new(items: [
{ {
name: "Lettings log for tenants (2022/23)", name: "Lettings log for tenants (2022/23)",
href: "https://core.communities.gov.uk/public/download/guides-and-manuals/2022-23%20Lettings%20paper%20form.pdf?download-format=pdf", href: "https://core.communities.gov.uk/public/download/guides-and-manuals/2022-23%20Lettings%20paper%20form.pdf?download-format=pdf",
metadata: "PDF, 654 KB, 4 pages", metadata: "PDF, 654 KB, 4 pages",
}, },
{
name: "Lettings log for tenants (2021/22)",
href: "https://core.communities.gov.uk/public/download/guides-and-manuals/2021_22%20Lettings%20Log.pdf?download-format=pdf",
metadata: "PDF, 302 KB, 3 pages",
},
]) %> ]) %>
</div> </div>

4
app/views/locations/index.html.erb

@ -39,6 +39,9 @@
<% row.cell(header: true, text: "Postcode", html_attributes: { <% row.cell(header: true, text: "Postcode", html_attributes: {
scope: "col", scope: "col",
}) %> }) %>
<% row.cell(header: true, text: "Name", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Location code", html_attributes: { <% row.cell(header: true, text: "Location code", html_attributes: {
scope: "col", scope: "col",
}) %> }) %>
@ -55,6 +58,7 @@
else else
location.postcode.present? ? scheme_location_check_answers_path(@scheme, location, route: "locations") : scheme_location_postcode_path(@scheme, location) location.postcode.present? ? scheme_location_check_answers_path(@scheme, location, route: "locations") : scheme_location_postcode_path(@scheme, location)
end), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %> end), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %>
<% row.cell(text: location.name) %>
<% row.cell(text: location.id) %> <% row.cell(text: location.id) %>
<% row.cell(text: status_tag(location.status)) %> <% row.cell(text: status_tag(location.status)) %>
<% end %> <% end %>

2
config/environments/development.rb

@ -73,7 +73,7 @@ Rails.application.configure do
config.active_record.verbose_query_logs = true config.active_record.verbose_query_logs = true
# Raises error for missing translations. # Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true config.i18n.raise_on_missing_translations = true
# Annotate rendered view with file names. # Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true # config.action_view.annotate_rendered_view_with_filenames = true

2
config/environments/test.rb

@ -57,7 +57,7 @@ Rails.application.configure do
config.active_support.disallowed_deprecation_warnings = [] config.active_support.disallowed_deprecation_warnings = []
# Raises error for missing translations. # Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true config.i18n.raise_on_missing_translations = true
# Annotate rendered view with file names. # Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true # config.action_view.annotate_rendered_view_with_filenames = true

50
config/forms/2021_2022.json

@ -59,12 +59,12 @@
"is_la_inferred": true "is_la_inferred": true
} }
}, },
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"postcode_known": 0 "postcode_known": 0
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -1298,12 +1298,12 @@
"max": 120, "max": 120,
"step": 1, "step": 1,
"width": 2, "width": 2,
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"age1_known": 1 "age1_known": 1
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -2077,12 +2077,12 @@
"max": 120, "max": 120,
"step": 1, "step": 1,
"width": 2, "width": 2,
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"age2_known": 1 "age2_known": 1
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -2612,12 +2612,12 @@
"max": 120, "max": 120,
"step": 1, "step": 1,
"width": 2, "width": 2,
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"age3_known": 1 "age3_known": 1
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -3144,12 +3144,12 @@
"max": 120, "max": 120,
"step": 1, "step": 1,
"width": 2, "width": 2,
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"age4_known": 1 "age4_known": 1
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -3673,12 +3673,12 @@
"max": 120, "max": 120,
"step": 1, "step": 1,
"width": 2, "width": 2,
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"age5_known": 1 "age5_known": 1
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -4199,12 +4199,12 @@
"max": 120, "max": 120,
"step": 1, "step": 1,
"width": 2, "width": 2,
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"age6_known": 1 "age6_known": 1
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -4722,12 +4722,12 @@
"max": 120, "max": 120,
"step": 1, "step": 1,
"width": 2, "width": 2,
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"age7_known": 1 "age7_known": 1
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -5242,12 +5242,12 @@
"max": 120, "max": 120,
"step": 1, "step": 1,
"width": 2, "width": 2,
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"age8_known": 1 "age8_known": 1
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -6166,10 +6166,10 @@
"1": { "1": {
"value": "Permanently decanted from another property owned by this landlord" "value": "Permanently decanted from another property owned by this landlord"
}, },
"45": { "46": {
"value": "Discharged from long-stay hospital or similar institution" "value": "Discharged from long-stay hospital or similar institution"
}, },
"44": { "45": {
"value": "Discharged from prison" "value": "Discharged from prison"
}, },
"2": { "2": {
@ -6181,7 +6181,7 @@
"9": { "9": {
"value": "Asked to leave by family or friends" "value": "Asked to leave by family or friends"
}, },
"46": { "44": {
"value": "Death of household member in last settled accommodation" "value": "Death of household member in last settled accommodation"
}, },
"8": { "8": {
@ -6517,12 +6517,12 @@
"is_previous_la_inferred": true "is_previous_la_inferred": true
} }
}, },
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"ppcodenk": 0 "ppcodenk": 0
}, },
"value": "Not known" "value": "Not known"
} }]
} }
} }
}, },
@ -6950,12 +6950,12 @@
"W92000004": "Wales", "W92000004": "Wales",
"9300000XX": "Outside UK" "9300000XX": "Outside UK"
}, },
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"previous_la_known": 0 "previous_la_known": 0
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [

50
config/forms/2022_2023.json

@ -59,12 +59,12 @@
"is_la_inferred": true "is_la_inferred": true
} }
}, },
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"postcode_known": 0 "postcode_known": 0
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -1333,12 +1333,12 @@
"max": 120, "max": 120,
"step": 1, "step": 1,
"width": 2, "width": 2,
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"age1_known": 1 "age1_known": 1
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -2076,12 +2076,12 @@
"max": 120, "max": 120,
"step": 1, "step": 1,
"width": 2, "width": 2,
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"age2_known": 1 "age2_known": 1
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -2611,12 +2611,12 @@
"max": 120, "max": 120,
"step": 1, "step": 1,
"width": 2, "width": 2,
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"age3_known": 1 "age3_known": 1
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -3143,12 +3143,12 @@
"max": 120, "max": 120,
"step": 1, "step": 1,
"width": 2, "width": 2,
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"age4_known": 1 "age4_known": 1
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -3672,12 +3672,12 @@
"max": 120, "max": 120,
"step": 1, "step": 1,
"width": 2, "width": 2,
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"age5_known": 1 "age5_known": 1
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -4198,12 +4198,12 @@
"max": 120, "max": 120,
"step": 1, "step": 1,
"width": 2, "width": 2,
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"age6_known": 1 "age6_known": 1
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -4721,12 +4721,12 @@
"max": 120, "max": 120,
"step": 1, "step": 1,
"width": 2, "width": 2,
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"age7_known": 1 "age7_known": 1
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -5241,12 +5241,12 @@
"max": 120, "max": 120,
"step": 1, "step": 1,
"width": 2, "width": 2,
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"age8_known": 1 "age8_known": 1
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [
@ -6165,10 +6165,10 @@
"1": { "1": {
"value": "Permanently decanted from another property owned by this landlord" "value": "Permanently decanted from another property owned by this landlord"
}, },
"45": { "46": {
"value": "Discharged from long-stay hospital or similar institution" "value": "Discharged from long-stay hospital or similar institution"
}, },
"44": { "45": {
"value": "Discharged from prison" "value": "Discharged from prison"
}, },
"2": { "2": {
@ -6180,7 +6180,7 @@
"9": { "9": {
"value": "Asked to leave by family or friends" "value": "Asked to leave by family or friends"
}, },
"46": { "44": {
"value": "Death of household member in last settled accommodation" "value": "Death of household member in last settled accommodation"
}, },
"8": { "8": {
@ -6482,12 +6482,12 @@
"is_previous_la_inferred": true "is_previous_la_inferred": true
} }
}, },
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"ppcodenk": 0 "ppcodenk": 0
}, },
"value": "Not known" "value": "Not known"
} }]
} }
} }
}, },
@ -6915,12 +6915,12 @@
"W92000004": "Wales", "W92000004": "Wales",
"9300000XX": "Outside UK" "9300000XX": "Outside UK"
}, },
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"previous_la_known": 0 "previous_la_known": 0
}, },
"value": "Not known" "value": "Not known"
} }]
} }
}, },
"depends_on": [ "depends_on": [

4
config/forms/schema/generic.json

@ -115,7 +115,7 @@
"description": "fields that get inferred based on the value of the current field", "description": "fields that get inferred based on the value of the current field",
"type": "object" "type": "object"
}, },
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"description": "value that gets displayed in the check answers for this field if the given condition is met", "description": "value that gets displayed in the check answers for this field if the given condition is met",
"type": "object", "type": "object",
"properties": { "properties": {
@ -128,7 +128,7 @@
"type": "object" "type": "object"
} }
} }
} }]
}, },
"minProperties": 1 "minProperties": 1
} }

48
config/locales/en.yml

@ -195,6 +195,8 @@ en:
beds: beds:
non_positive: "Number of bedrooms has to be greater than 0" non_positive: "Number of bedrooms has to be greater than 0"
over_max: "Number of bedrooms cannot be more than 12" over_max: "Number of bedrooms cannot be more than 12"
postcode:
must_match_previous: "Buyer's last accommodation and discounted ownership postcodes must match"
financial: financial:
tshortfall: tshortfall:
@ -272,6 +274,7 @@ en:
carehome: carehome:
out_of_range: "Household rent and other charges must be between %{min_chcharge} and %{max_chcharge} if paying %{period}" out_of_range: "Household rent and other charges must be between %{min_chcharge} and %{max_chcharge} if paying %{period}"
not_provided: "Enter how much rent and other charges the household pays %{period}" not_provided: "Enter how much rent and other charges the household pays %{period}"
cash_discount_invalid: "Cash discount must be £0 - £999,999"
household: household:
reasonpref: reasonpref:
not_homeless: "Answer cannot be ‘homeless or about to lose their home’ as the tenant was not homeless immediately prior to this letting" not_homeless: "Answer cannot be ‘homeless or about to lose their home’ as the tenant was not homeless immediately prior to this letting"
@ -295,20 +298,39 @@ en:
child_under_16_relat: "Answer cannot be under 16 as person %{person_num} is not a child of the lead tenant" child_under_16_relat: "Answer cannot be under 16 as person %{person_num} is not a child of the lead tenant"
child_under_16: "Answer cannot be under 16 as person’s %{person_num} working situation is not ‘child under 16’" child_under_16: "Answer cannot be under 16 as person’s %{person_num} working situation is not ‘child under 16’"
child_over_16: "Answer cannot be over 16 as person’s %{person_num} working situation is ‘child under 16‘" child_over_16: "Answer cannot be over 16 as person’s %{person_num} working situation is ‘child under 16‘"
student_16_19: "Answer cannot be between 16 and 19 as person %{person_num} is a child of the lead tenant but is not a full-time student" child_over_20: "Answer cannot be 20 or over as the relationship is ‘child’"
not_student_16_19: "Answer cannot be between 16 and 19 as person %{person_num} is a child of the lead tenant but is not a full-time student"
student_16_19:
cannot_be_16_19:
student_not_child: "Person cannot be aged 16-19 if they are a student but don't have relationship ‘child’"
child_not_student: "Person cannot be aged 16-19 if they have relationship ‘child’ but are not a student"
must_be_16_19: "Person must be aged 16-19 if they are a student and have relationship ‘child’"
partner_under_16: "Cannot be under 16 if the relationship is partner"
lead: lead:
over_20: "The lead tenant must be under 20 as you told us their housing situation immediately before this letting was a children’s home or foster care" over_20: "The lead tenant must be under 20 as you told us their housing situation immediately before this letting was a children’s home or foster care"
ecstat: ecstat:
retired_over_70: "Person %{person_num} must be retired if over 70" retired_over_70: "Person %{person_num} must be retired if over 70"
child_under_16: "Person’s %{person_num} working situation must be ’child under 16‘ as you told us they’re under 16" child_under_16: "Person’s %{person_num} working situation must be ’child under 16‘ as you told us they’re under 16"
child_over_16: "Answer cannot be ‘child under 16’ as you told us the person %{person_num} is older than 16" child_over_16: "Answer cannot be ‘child under 16’ as you told us the person %{person_num} is older than 16"
student_16_19: "Person’s %{person_num} working situation must be full-time student or prefers not to say as you told us they’re between 16 and 19." not_student_16_19: "Person’s %{person_num} working situation must be full-time student or prefers not to say as you told us they’re between 16 and 19."
student_16_19:
cannot_be_student:
child_not_16_19: "Person cannot be a student if they are not aged 16-19 but have relationship ‘child’"
16_19_not_child: "Person cannot be a student if they are aged 16-19 but don‘t have relationship ‘child’"
must_be_student: "Person must be a student if they are aged 16-19 and have relationship ‘child’"
retired_male: "Answer cannot be ‘retired’ as the male tenant is under 65" retired_male: "Answer cannot be ‘retired’ as the male tenant is under 65"
retired_female: "Answer cannot be ‘retired’ as the female tenant is under 60" retired_female: "Answer cannot be ‘retired’ as the female tenant is under 60"
relat: relat:
child_under_16: "Person’s %{person_num}’s relationship to tenant 1 must be ‘child’ as you told us they’re under 16" partner_under_16: "Answer cannot be ‘partner’ if the person's age is under 16"
child_under_16: "Person’s relationship to tenant 1 must be ‘child’ as you told us they’re under 16"
child_over_20: "Answer cannot be ‘child’ if the person's age is 20 or over"
one_partner: "Number of partners cannot be greater than 1" one_partner: "Number of partners cannot be greater than 1"
student_16_19: "Answer cannot be ‘child’ as you told us the person %{person_num} is between 16 and 19 and is not a full-time student" not_student_16_19: "Answer cannot be ‘child’ as you told us the person %{person_num} is between 16 and 19 and is not a full-time student"
student_16_19:
cannot_be_child:
student_not_16_19: "Answer cannot be ‘child’ if the person is a student but not aged 16-19"
16_19_not_student: "Answer cannot be ‘child’ if the person is aged 16-19 but not a student"
must_be_child: "Answer must be ‘child’ if the person is aged 16-19 and a student"
housingneeds_a: housingneeds_a:
one_or_two_choices: "You can only select one option or ‘other disabled access needs’ plus ‘wheelchair-accessible housing’, ‘wheelchair access to essential rooms’ or ‘level access housing’" one_or_two_choices: "You can only select one option or ‘other disabled access needs’ plus ‘wheelchair-accessible housing’, ‘wheelchair access to essential rooms’ or ‘level access housing’"
prevten: prevten:
@ -346,6 +368,9 @@ en:
not_internal_transfer: "Answer cannot be ‘permanently decanted from another property owned by this landlord’ as you told us the source of referral for this tenancy was not an internal transfer" not_internal_transfer: "Answer cannot be ‘permanently decanted from another property owned by this landlord’ as you told us the source of referral for this tenancy was not an internal transfer"
condition_effects: condition_effects:
no_choices: "You cannot answer this question as you told us nobody in the household has a physical or mental health condition (or other illness) expected to last 12 months or more" no_choices: "You cannot answer this question as you told us nobody in the household has a physical or mental health condition (or other illness) expected to last 12 months or more"
old_persons_shared_ownership: "Are you sure? At least one buyer should be aged over 64 for Older persons‘ shared ownership scheme"
postcode:
discounted_ownership: "Last settled accommodation and discounted ownership property postcodes must match"
tenancy: tenancy:
length: length:
@ -387,6 +412,19 @@ en:
before_deactivation: "This location was deactivated on %{date}. The reactivation date must be on or after deactivation date" before_deactivation: "This location was deactivated on %{date}. The reactivation date must be on or after deactivation date"
deactivation: deactivation:
during_deactivated_period: "The location is already deactivated during this date, please enter a different date" during_deactivated_period: "The location is already deactivated during this date, please enter a different date"
sale_information:
proplen:
social_homebuy: "Social HomeBuy buyers should not have lived here before"
rent_to_buy: "Rent to Buy buyers should not have lived here before"
exdate:
must_be_before_saledate:
Contract exchange date must be less than 1 year before completion date
must_be_less_than_1_year_from_saledate:
Contract exchange date must be less than 1 year before completion date
previous_property_beds:
property_type_bedsit: "Bedsit bedroom maximum 1"
previous_property_type:
property_type_bedsit: "A bedsit can not have more than 1 bedroom"
soft_validations: soft_validations:
net_income: net_income:
@ -410,6 +448,8 @@ en:
max: max:
title: "You told us this person is %{age} or over and not retired" title: "You told us this person is %{age} or over and not retired"
hint_text: "The minimum expected retirement age for %{gender} in England is %{age}." hint_text: "The minimum expected retirement age for %{gender} in England is %{age}."
extra_borrowing:
title: "The mortgage and deposit are higher than the purchase minus the discount"
pregnancy: pregnancy:
title: "You told us somebody in the household is pregnant" title: "You told us somebody in the household is pregnant"
no_females: "You also told us there are no female tenants living at the property." no_females: "You also told us there are no female tenants living at the property."

15450
config/rent_range_data/2022.csv

File diff suppressed because it is too large Load Diff

6
db/migrate/20230113154518_add_bulk_upload_to_logs.rb

@ -0,0 +1,6 @@
class AddBulkUploadToLogs < ActiveRecord::Migration[7.0]
def change
add_reference :lettings_logs, :bulk_upload
add_reference :sales_logs, :bulk_upload
end
end

7
db/migrate/20230116124402_add_retirement_value_check_to_sales.rb

@ -0,0 +1,7 @@
class AddRetirementValueCheckToSales < ActiveRecord::Migration[7.0]
def change
change_table :sales_logs, bulk: true do |t|
t.column :retirement_value_check, :integer
end
end
end

7
db/migrate/20230116151942_add_ho_date_check.rb

@ -0,0 +1,7 @@
class AddHoDateCheck < ActiveRecord::Migration[7.0]
def change
change_table :sales_logs, bulk: true do |t|
t.column :hodate_check, :integer
end
end
end

7
db/migrate/20230118170602_add_extra_borrowing_value_check_to_sales.rb

@ -0,0 +1,7 @@
class AddExtraBorrowingValueCheckToSales < ActiveRecord::Migration[7.0]
def change
change_table :sales_logs, bulk: true do |t|
t.column :extrabor_value_check, :integer
end
end
end

21
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[7.0].define(version: 2023_01_13_125117) do ActiveRecord::Schema[7.0].define(version: 2023_01_18_170602) 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"
@ -265,6 +265,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_13_125117) do
t.integer "housingneeds_other" t.integer "housingneeds_other"
t.boolean "unresolved" t.boolean "unresolved"
t.bigint "updated_by_id" t.bigint "updated_by_id"
t.bigint "bulk_upload_id"
t.index ["bulk_upload_id"], name: "index_lettings_logs_on_bulk_upload_id"
t.index ["created_by_id"], name: "index_lettings_logs_on_created_by_id" t.index ["created_by_id"], name: "index_lettings_logs_on_created_by_id"
t.index ["location_id"], name: "index_lettings_logs_on_location_id" t.index ["location_id"], name: "index_lettings_logs_on_location_id"
t.index ["managing_organisation_id"], name: "index_lettings_logs_on_managing_organisation_id" t.index ["managing_organisation_id"], name: "index_lettings_logs_on_managing_organisation_id"
@ -365,6 +367,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_13_125117) do
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.bigint "owning_organisation_id" t.bigint "owning_organisation_id"
t.bigint "managing_organisation_id"
t.bigint "created_by_id" t.bigint "created_by_id"
t.string "purchid" t.string "purchid"
t.integer "type" t.integer "type"
@ -487,17 +490,23 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_13_125117) do
t.integer "mortgagelender" t.integer "mortgagelender"
t.string "mortgagelenderother" t.string "mortgagelenderother"
t.integer "mortlen" t.integer "mortlen"
t.integer "extrabor"
t.integer "hhmemb"
t.integer "totadult"
t.integer "totchild"
t.integer "hhtype"
t.string "pcode1" t.string "pcode1"
t.string "pcode2" t.string "pcode2"
t.integer "pcodenk" t.integer "pcodenk"
t.string "postcode_full" t.string "postcode_full"
t.boolean "is_la_inferred" t.boolean "is_la_inferred"
t.integer "extrabor"
t.integer "hhmemb"
t.integer "totadult"
t.integer "totchild"
t.integer "hhtype"
t.integer "hodate_check"
t.bigint "bulk_upload_id"
t.integer "retirement_value_check"
t.integer "extrabor_value_check"
t.index ["bulk_upload_id"], name: "index_sales_logs_on_bulk_upload_id"
t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id" t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id"
t.index ["managing_organisation_id"], name: "index_sales_logs_on_managing_organisation_id"
t.index ["owning_organisation_id"], name: "index_sales_logs_on_owning_organisation_id" t.index ["owning_organisation_id"], name: "index_sales_logs_on_owning_organisation_id"
t.index ["updated_by_id"], name: "index_sales_logs_on_updated_by_id" t.index ["updated_by_id"], name: "index_sales_logs_on_updated_by_id"
end end

10
db/seeds.rb

@ -110,8 +110,8 @@ unless Rails.env.test?
) )
User.find_or_create_by!( User.find_or_create_by!(
name: "Provider Owns Stock", name: "Provider No Stock",
email: "provider.nostock1@example.com", email: "provider.nostock@example.com",
organisation: standalone_no_stock, organisation: standalone_no_stock,
role: "data_provider", role: "data_provider",
) do |user| ) do |user|
@ -120,8 +120,8 @@ unless Rails.env.test?
end end
User.find_or_create_by!( User.find_or_create_by!(
name: "Coordinator Owns Stock", name: "Coordinator No Stock",
email: "coordinator.nostock1@example.com", email: "coordinator.nostock@example.com",
organisation: standalone_no_stock, organisation: standalone_no_stock,
role: "data_coordinator", role: "data_coordinator",
) do |user| ) do |user|
@ -160,7 +160,7 @@ unless Rails.env.test?
saledate: Date.new(1, 1, 1), saledate: Date.new(1, 1, 1),
purchid: "1", purchid: "1",
ownershipsch: 2, ownershipsch: 2,
type: 8, type: 9,
jointpur: 1, jointpur: 1,
jointmore: 1, jointmore: 1,
) )

4
docs/form/builder.md

@ -82,10 +82,10 @@ The JSON should follow the structure:
"[snake_case_question_to_enable_2_name_string]": ["condition-that-enables"] "[snake_case_question_to_enable_2_name_string]": ["condition-that-enables"]
}, },
"inferred_answers": { "field_that_gets_inferred_from_current_field": { "is_that_field_inferred": true } }, "inferred_answers": { "field_that_gets_inferred_from_current_field": { "is_that_field_inferred": true } },
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "field_name_for_inferred_check_answers_condition": "field_value_for_inferred_check_answers_condition" }, "condition": { "field_name_for_inferred_check_answers_condition": "field_value_for_inferred_check_answers_condition" },
"value": "Inferred value that gets displayed if condition is met" "value": "Inferred value that gets displayed if condition is met"
} }]
} }
}, },
"depends_on": [{ "question_key": "answer_value_required_for_this_page_to_be_shown" }] "depends_on": [{ "question_key": "answer_value_required_for_this_page_to_be_shown" }]

4
docs/form/question.md

@ -74,12 +74,12 @@ The answer the data inputter provides to some questions allows us to infer the v
"is_la_inferred": true "is_la_inferred": true
} }
}, },
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"postcode_known": 0 "postcode_known": 0
}, },
"value": "Not known" "value": "Not known"
} }]
} }
``` ```

14
spec/factories/sales_log.rb

@ -40,10 +40,10 @@ FactoryBot.define do
wheel { 1 } wheel { 1 }
details_known_1 { 1 } details_known_1 { 1 }
age3_known { 0 } age3_known { 0 }
age3 { 40 } age3 { 14 }
details_known_2 { 1 } details_known_2 { 1 }
age4_known { 0 } age4_known { 0 }
age4 { 40 } age4 { 18 }
details_known_3 { 1 } details_known_3 { 1 }
age5_known { 0 } age5_known { 0 }
age5 { 40 } age5 { 40 }
@ -65,7 +65,7 @@ FactoryBot.define do
sex5 { "X" } sex5 { "X" }
sex6 { "X" } sex6 { "X" }
mortgage { 20_000 } mortgage { 20_000 }
ecstat3 { 10 } ecstat3 { 9 }
ecstat4 { 3 } ecstat4 { 3 }
ecstat5 { 2 } ecstat5 { 2 }
ecstat6 { 1 } ecstat6 { 1 }
@ -84,10 +84,10 @@ FactoryBot.define do
ppcodenk { 1 } ppcodenk { 1 }
prevten { 1 } prevten { 1 }
previous_la_known { 0 } previous_la_known { 0 }
relat3 { "P" } relat3 { "C" }
relat4 { "P" } relat4 { "X" }
relat5 { "P" } relat5 { "R" }
relat6 { "P" } relat6 { "R" }
hb { 4 } hb { 4 }
mortgageused { 1 } mortgageused { 1 }
wchair { 1 } wchair { 1 }

239
spec/features/lettings_log_spec.rb

@ -58,7 +58,8 @@ RSpec.describe "Lettings Log Features" do
end end
context "when the signed is user is a Support user" do context "when the signed is user is a Support user" do
let(:support_user) { create(:user, :support, last_sign_in_at: Time.zone.now) } let(:organisation) { create(:organisation, name: "User org") }
let(:support_user) { create(:user, :support, last_sign_in_at: Time.zone.now, organisation:) }
let(:devise_notify_mailer) { DeviseNotifyMailer.new } let(:devise_notify_mailer) { DeviseNotifyMailer.new }
let(:notify_client) { instance_double(Notifications::Client) } let(:notify_client) { instance_double(Notifications::Client) }
let(:mfa_template_id) { User::MFA_TEMPLATE_ID } let(:mfa_template_id) { User::MFA_TEMPLATE_ID }
@ -78,7 +79,7 @@ RSpec.describe "Lettings Log Features" do
end end
context "when completing the setup lettings log section", :aggregate_failure do context "when completing the setup lettings log section", :aggregate_failure do
it "includes the organisation and created by questions" do it "includes the owning organisation and created by questions" do
visit("/lettings-logs") visit("/lettings-logs")
click_button("Create a new lettings log") click_button("Create a new lettings log")
click_link("Set up this lettings log") click_link("Set up this lettings log")
@ -88,14 +89,116 @@ RSpec.describe "Lettings Log Features" do
click_button("Save and continue") click_button("Save and continue")
log_id = page.current_path.scan(/\d/).join log_id = page.current_path.scan(/\d/).join
visit("lettings-logs/#{log_id}/setup/check-answers") visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Stock owner #{support_user.organisation.name}") expect(page).to have_content("Stock owner User org")
expect(page).to have_content("You have answered 2 of 8 questions") expect(page).to have_content("You have answered 2 of 8 questions")
end end
end end
context "when the owning organisation question isn't answered" do
it "doesn't show the managing agent question" do
visit("/lettings-logs")
click_button("Create a new lettings log")
click_link("Set up this lettings log")
log_id = page.current_path.scan(/\d/).join
click_link("Skip for now")
expect(page).not_to have_current_path("/lettings-logs/#{log_id}/managing-organisation")
end
end
context "when the owning organisation question is answered" do
context "and the owning organisation doesn't hold stock" do
let(:managing_org) { create(:organisation, name: "Managing org") }
let!(:org_rel) { create(:organisation_relationship, parent_organisation: support_user.organisation, child_organisation: managing_org) }
before do
support_user.organisation.update!(holds_own_stock: false)
end
it "shows the managing organisation question" do
visit("/lettings-logs")
click_button("Create a new lettings log")
click_link("Set up this lettings log")
log_id = page.current_path.scan(/\d/).join
select(support_user.organisation.name, from: "lettings-log-owning-organisation-id-field")
click_button("Save and continue")
expect(page).to have_current_path("/lettings-logs/#{log_id}/managing-organisation")
select(managing_org.name, from: "lettings-log-managing-organisation-id-field")
click_button("Save and continue")
visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Managing agent Managing org")
expect(support_user.organisation.managing_agents).to eq([org_rel.child_organisation])
end
end
context "and the owning organisation does hold stock" do
before do
support_user.organisation.update!(holds_own_stock: true)
end
context "and the owning organisation has no managing agents" do
it "doesn't show the managing organisation question" do
visit("/lettings-logs")
click_button("Create a new lettings log")
click_link("Set up this lettings log")
log_id = page.current_path.scan(/\d/).join
select(support_user.organisation.name, from: "lettings-log-owning-organisation-id-field")
click_button("Save and continue")
expect(page).not_to have_current_path("/lettings-logs/#{log_id}/managing-organisation")
visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).not_to have_content("Managing agent ")
end
end
context "and the owning organisation has 1 or more managing agents" do
let(:managing_org1) { create(:organisation, name: "Managing org 1") }
let!(:org_rel1) { create(:organisation_relationship, parent_organisation: support_user.organisation, child_organisation: managing_org1) }
it "does show the managing organisation question" do
visit("/lettings-logs")
click_button("Create a new lettings log")
click_link("Set up this lettings log")
log_id = page.current_path.scan(/\d/).join
select(support_user.organisation.name, from: "lettings-log-owning-organisation-id-field")
click_button("Save and continue")
expect(page).to have_current_path("/lettings-logs/#{log_id}/managing-organisation")
select(managing_org1.name, from: "lettings-log-managing-organisation-id-field")
click_button("Save and continue")
visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Managing agent Managing org 1")
end
context "and the owning organisation has 2 or more managing agents" do
let(:managing_org2) { create(:organisation, name: "Managing org 2") }
let!(:org_rel2) { create(:organisation_relationship, parent_organisation: support_user.organisation, child_organisation: managing_org2) }
context "and the organisation relationship for the selected managing agent is deleted" do
it "doesn't change the CYA page text to be 'You didn't answer this question'" do
visit("/lettings-logs")
click_button("Create a new lettings log")
click_link("Set up this lettings log")
log_id = page.current_path.scan(/\d/).join
select(support_user.organisation.name, from: "lettings-log-owning-organisation-id-field")
click_button("Save and continue")
expect(page).to have_current_path("/lettings-logs/#{log_id}/managing-organisation")
select(managing_org1.name, from: "lettings-log-managing-organisation-id-field")
click_button("Save and continue")
visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Managing agent Managing org 1")
org_rel1.destroy!
visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Managing agent Managing org 1")
expect(support_user.organisation.managing_agents).to eq([org_rel2.child_organisation])
end
end
end
end
end
end
end end
context "when the signed is user is not a Support user" do context "when the signed is user is not a Support user" do
let(:user) { create(:user) } let(:organisation) { create(:organisation, name: "User org") }
let(:user) { create(:user, :data_coordinator, name: "User name", organisation:) }
let(:devise_notify_mailer) { DeviseNotifyMailer.new } let(:devise_notify_mailer) { DeviseNotifyMailer.new }
let(:notify_client) { instance_double(Notifications::Client) } let(:notify_client) { instance_double(Notifications::Client) }
@ -110,15 +213,125 @@ RSpec.describe "Lettings Log Features" do
end end
context "when completing the setup log section" do context "when completing the setup log section" do
it "does not include the organisation and created by questions" do context "and there is at most 1 potential stock owner" do
visit("/lettings-logs") it "does not include the owning organisation and created by questions" do
click_button("Create a new lettings log") visit("/lettings-logs")
click_link("Set up this lettings log") click_button("Create a new lettings log")
log_id = page.current_path.scan(/\d/).join click_link("Set up this lettings log")
expect(page).to have_current_path("/lettings-logs/#{log_id}/needs-type") log_id = page.current_path.scan(/\d/).join
visit("lettings-logs/#{log_id}/setup/check-answers") expect(page).to have_current_path("/lettings-logs/#{log_id}/needs-type")
expect(page).not_to have_content("Owning organisation #{user.organisation.name}") visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).not_to have_content("Log owner #{user.name}") expect(page).not_to have_content("Stock owner ")
expect(page).not_to have_content("Log owner ")
end
end
context "and there are 2 or more potential stock owners" do
let(:owning_org1) { create(:organisation, name: "Owning org 1") }
let!(:org_rel1) { create(:organisation_relationship, child_organisation: user.organisation, parent_organisation: owning_org1) }
it "does include the owning organisation question" do
visit("/lettings-logs")
click_button("Create a new lettings log")
click_link("Set up this lettings log")
log_id = page.current_path.scan(/\d/).join
expect(page).to have_current_path("/lettings-logs/#{log_id}/stock-owner")
visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Stock owner User org")
end
context "and there are 3 or more potential stock owners" do
let(:owning_org2) { create(:organisation, name: "Owning org 2") }
let!(:org_rel2) { create(:organisation_relationship, child_organisation: user.organisation, parent_organisation: owning_org2) }
context "and the organisation relationship for the selected stock owner is deleted" do
it "doesn't change the CYA page text to be 'You didn't answer this question'" do
visit("/lettings-logs")
click_button("Create a new lettings log")
click_link("Set up this lettings log")
log_id = page.current_path.scan(/\d/).join
expect(page).to have_current_path("/lettings-logs/#{log_id}/stock-owner")
select(owning_org1.name, from: "lettings-log-owning-organisation-id-field")
click_button("Save and continue")
visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Stock owner Owning org 1")
org_rel1.destroy!
visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Stock owner Owning org 1")
expect(user.organisation.stock_owners).to eq([org_rel2.parent_organisation])
end
end
end
end
context "when the current user's organisation doesn't hold stock" do
let(:owning_org1) { create(:organisation, name: "Owning org 1") }
let(:owning_org2) { create(:organisation, name: "Owning org 2") }
let!(:org_rel1) { create(:organisation_relationship, child_organisation: user.organisation, parent_organisation: owning_org1) }
let!(:org_rel2) { create(:organisation_relationship, child_organisation: user.organisation, parent_organisation: owning_org2) }
it "shows the managing organisation question" do
user.organisation.update!(holds_own_stock: false)
visit("/lettings-logs")
click_button("Create a new lettings log")
click_link("Set up this lettings log")
log_id = page.current_path.scan(/\d/).join
expect(page).to have_current_path("/lettings-logs/#{log_id}/stock-owner")
select(owning_org1.name, from: "lettings-log-owning-organisation-id-field")
click_button("Save and continue")
expect(page).to have_current_path("/lettings-logs/#{log_id}/managing-organisation")
select(user.organisation.name, from: "lettings-log-managing-organisation-id-field")
click_button("Save and continue")
visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Managing agent User org")
expect(user.organisation.stock_owners).to eq([org_rel1.parent_organisation, org_rel2.parent_organisation])
end
end
context "when the current user's organisation does hold stock" do
let!(:owning_org) { create(:organisation, name: "Owning org") }
let!(:org_rel1) { create(:organisation_relationship, child_organisation: user.organisation, parent_organisation: owning_org) }
before do
user.organisation.update!(holds_own_stock: true)
end
context "and the user's organisation has no managing agents" do
it "doesn't show the managing organisation question" do
visit("/lettings-logs")
click_button("Create a new lettings log")
click_link("Set up this lettings log")
log_id = page.current_path.scan(/\d/).join
expect(page).to have_current_path("/lettings-logs/#{log_id}/stock-owner")
select(owning_org.name, from: "lettings-log-owning-organisation-id-field")
click_button("Save and continue")
expect(page).not_to have_current_path("/lettings-logs/#{log_id}/managing-organisation")
visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).not_to have_content("Managing agent ")
expect(user.organisation.stock_owners).to eq([org_rel1.parent_organisation])
end
end
context "and the user's organisation has 1 or more managing agents" do
let(:managing_org) { create(:organisation, name: "Managing org") }
let!(:org_rel2) { create(:organisation_relationship, parent_organisation: user.organisation, child_organisation: managing_org) }
it "does show the managing organisation question" do
visit("/lettings-logs")
click_button("Create a new lettings log")
click_link("Set up this lettings log")
log_id = page.current_path.scan(/\d/).join
expect(page).to have_current_path("/lettings-logs/#{log_id}/stock-owner")
select(user.organisation.name, from: "lettings-log-owning-organisation-id-field")
click_button("Save and continue")
expect(page).to have_current_path("/lettings-logs/#{log_id}/managing-organisation")
select(managing_org.name, from: "lettings-log-managing-organisation-id-field")
click_button("Save and continue")
visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Managing agent Managing org")
expect(user.organisation.managing_agents).to eq([org_rel2.child_organisation])
end
end
end end
end end

10
spec/features/schemes_spec.rb

@ -101,9 +101,9 @@ RSpec.describe "Schemes scheme Features" do
it "shows a list of schemes" do it "shows a list of schemes" do
schemes.each do |scheme| schemes.each do |scheme|
expect(page).to have_content(scheme.id)
expect(page).to have_link(scheme.service_name) expect(page).to have_link(scheme.service_name)
expect(page).to have_content(scheme.primary_client_group) expect(page).to have_content(scheme.id_to_display)
expect(page).to have_content(scheme.locations&.count)
end end
end end
@ -175,7 +175,8 @@ RSpec.describe "Schemes scheme Features" do
click_on("Back") click_on("Back")
schemes.each do |scheme| schemes.each do |scheme|
expect(page).to have_link(scheme.service_name) expect(page).to have_link(scheme.service_name)
expect(page).to have_content(scheme.primary_client_group) expect(page).to have_content(scheme.id_to_display)
expect(page).to have_content(scheme.locations&.count)
end end
end end
end end
@ -671,7 +672,8 @@ RSpec.describe "Schemes scheme Features" do
it "shows list of links to schemes" do it "shows list of links to schemes" do
schemes.each do |scheme| schemes.each do |scheme|
expect(page).to have_link(scheme.service_name) expect(page).to have_link(scheme.service_name)
expect(page).to have_content(scheme.primary_client_group) expect(page).to have_content(scheme.id_to_display)
expect(page).to have_content(scheme.locations&.count)
end end
end end

2
spec/fixtures/files/2022_23_lettings_bulk_upload.csv vendored

@ -69,4 +69,6 @@ if 87 = 1, then a value must be entered",No,,,Yes,,,,No,,,"If the property is be
or 106 = 15 - 17",No,"Only if 1 = 2, 4, 6, 8, 10 or 12",,,,No,,No,"Yes, if 45 = 2, 3 or 6",,"Yes, if 50 = 1","Only if 1 = 1, 3, 5, 7, 9 or 11",No,Yes,,,,,,,,,,Only if 1 = 1 - 4 or 9 - 12.,Only if 1 = 1 - 8.,Only if 130 is not 3,No,No,Yes, or 106 = 15 - 17",No,"Only if 1 = 2, 4, 6, 8, 10 or 12",,,,No,,No,"Yes, if 45 = 2, 3 or 6",,"Yes, if 50 = 1","Only if 1 = 1, 3, 5, 7, 9 or 11",No,Yes,,,,,,,,,,Only if 1 = 1 - 4 or 9 - 12.,Only if 1 = 1 - 8.,Only if 130 is not 3,No,No,Yes,
Bulk upload format and duplicate check,All lettings,Question removed from 22/23 onwards,,Supported housing only,,Question Removed from 2020/21,,,,,,Duplicate check field,,,,,,,,Duplicate check field,,,,,,,,,,,,,,,Duplicate check field,,,,,,,,,,,,,,,,,,,Question removed from 22/23 onwards,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Duplicate check field,,,,,,,,,,,Question removed from 22/23 onwards,Duplicate check fields,,,,Duplicate check field,General Needs lettings only,,,All lettings,General Needs lettings only,All lettings,General Needs lettings only,,,Question removed from 2020/21,Duplicate check field, “Username does not exist”. ,,,Question removed from 21/22 onwards,,Supported Housing lettings only.,,,,,,,,,,,,Affordable Rent Lettings only,Intermediate Rent Lettings only,,All lettings,All lettings,, Bulk upload format and duplicate check,All lettings,Question removed from 22/23 onwards,,Supported housing only,,Question Removed from 2020/21,,,,,,Duplicate check field,,,,,,,,Duplicate check field,,,,,,,,,,,,,,,Duplicate check field,,,,,,,,,,,,,,,,,,,Question removed from 22/23 onwards,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Duplicate check field,,,,,,,,,,,Question removed from 22/23 onwards,Duplicate check fields,,,,Duplicate check field,General Needs lettings only,,,All lettings,General Needs lettings only,All lettings,General Needs lettings only,,,Question removed from 2020/21,Duplicate check field, “Username does not exist”. ,,,Question removed from 21/22 onwards,,Supported Housing lettings only.,,,,,,,,,,,,Affordable Rent Lettings only,Intermediate Rent Lettings only,,All lettings,All lettings,,
Bulk upload field number,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134, Bulk upload field number,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,
,1,,,,,,123,1,4,,2,55,54,,,,,,,F,,,,,,,,,,,,,,,1,,,,,,,,17,13,2,,2,3,4,,2,7,,,,,,,,,3,,,,2,1,2,1,3,,,,,,,,,16,4,1000,100,100,100,1300,,,,,,,,,,,,13,1,23,,,4,1,1,2,,,,EC1N,2TD,,3,,3,,,,,2,,,,,,,,,,,,,,1,2,2
,1,,,,,,123,1,2,,6,55,54,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ,1,,,,,,123,1,2,,6,55,54,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,1,,,,,,123,1,2,,,55,54,,,,,,,"A",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,13,1,23,,,,,,,,,,,,,123,,123,,,,,,,,,,,,,,,,,,,,,2,

Can't render this file because it has a wrong number of fields in line 72.

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

@ -208,7 +208,7 @@
"check_answer_label": "Retirement age soft validation", "check_answer_label": "Retirement age soft validation",
"hidden_in_check_answers": true, "hidden_in_check_answers": true,
"header": "Are you sure this person is retired?", "header": "Are you sure this person is retired?",
"type": "radio", "type": "interruption_screen",
"answer_options": { "answer_options": {
"0": { "0": {
"value": "Yes" "value": "Yes"
@ -342,12 +342,12 @@
1 1
] ]
}, },
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"armedforces": 3 "armedforces": 3
}, },
"value": "Prefers not to say" "value": "Prefers not to say"
} }]
}, },
"leftreg": { "leftreg": {
"header": "Are they still serving?", "header": "Are they still serving?",
@ -526,12 +526,12 @@
"is_la_inferred": true "is_la_inferred": true
} }
}, },
"inferred_check_answers_value": { "inferred_check_answers_value": [{
"condition": { "condition": {
"postcode_known": 0 "postcode_known": 0
}, },
"value": "Not known" "value": "Not known"
} }]
} }
} }
}, },

26
spec/helpers/tab_nav_helper_spec.rb

@ -23,37 +23,15 @@ RSpec.describe TabNavHelper do
describe "#location_cell" do describe "#location_cell" do
it "returns the location link to the postcode with optional name" do it "returns the location link to the postcode with optional name" do
link = "/schemes/#{location.scheme.id}/locations/#{location.id}/edit" link = "/schemes/#{location.scheme.id}/locations/#{location.id}/edit"
expected_html = "<a class=\"govuk-link\" rel=\"nofollow\" data-method=\"patch\" href=\"/schemes/#{scheme.id}/locations/#{location.id}/edit\">#{location.postcode}</a>\n<span class=\"govuk-visually-hidden\">Location </span><span class=\"govuk-!-font-weight-regular app-!-colour-muted\">#{location.name}</span>" expected_html = "<a class=\"govuk-link\" rel=\"nofollow\" data-method=\"patch\" href=\"/schemes/#{scheme.id}/locations/#{location.id}/edit\">#{location.postcode}</a>\n<span class=\"govuk-visually-hidden\">Location</span>"
expect(location_cell_postcode(location, link)).to match(expected_html) expect(location_cell_postcode(location, link)).to match(expected_html)
end end
end end
describe "#scheme_cell" do describe "#scheme_cell" do
it "returns the scheme link service name and primary user group separated by a newline character" do it "returns the scheme link service name and primary user group separated by a newline character" do
expected_html = "<a class=\"govuk-link\" href=\"/schemes/#{scheme.id}\">#{scheme.service_name}</a>\n<span class=\"govuk-visually-hidden\">Scheme </span><span class=\"govuk-!-font-weight-regular app-!-colour-muted\">#{scheme.primary_client_group}</span>" expected_html = "<a class=\"govuk-link\" href=\"/schemes/#{scheme.id}\">#{scheme.service_name}</a>\n<span class=\"govuk-visually-hidden\">Scheme</span>"
expect(scheme_cell(scheme)).to match(expected_html) expect(scheme_cell(scheme)).to match(expected_html)
end end
end end
describe "#tab_items" do
context "when user is a data_coordinator" do
let(:user) { FactoryBot.build(:user, :data_coordinator, organisation:) }
it "returns details and user tabs" do
result = tab_items(user).map { |i| i[:name] }
expect(result.count).to eq(2)
expect(result.first).to match("Details")
expect(result.second).to match("Users")
end
end
context "when user is a data_provider" do
it "returns details and user tabs" do
result = tab_items(user).map { |i| i[:name] }
expect(result.count).to eq(2)
expect(result.first).to match("Details")
expect(result.second).to match("Users")
end
end
end
end end

117
spec/models/form/lettings/questions/managing_organisation_spec.rb

@ -52,86 +52,97 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do
end end
end end
context "when user not support and owns own stock" do context "when user is not support" do
let(:user) { create(:user, :data_coordinator, organisation: create(:organisation, holds_own_stock: true)) } let(:user_org) { create(:organisation, name: "User org") }
let(:user) { create(:user, :data_coordinator, organisation: user_org) }
let(:log) { create(:lettings_log) } let(:managing_org1) { create(:organisation, name: "Managing org 1") }
let!(:org_rel1) { create(:organisation_relationship, parent_organisation: user.organisation) } let(:managing_org2) { create(:organisation, name: "Managing org 2") }
let!(:org_rel2) { create(:organisation_relationship, parent_organisation: user.organisation) } let(:managing_org3) { create(:organisation, name: "Managing org 3") }
let(:options) do let(:log) { create(:lettings_log, managing_organisation: managing_org1) }
{ let!(:org_rel1) { create(:organisation_relationship, parent_organisation: user.organisation, child_organisation: managing_org2) }
"" => "Select an option", let!(:org_rel2) { create(:organisation_relationship, parent_organisation: user.organisation, child_organisation: managing_org3) }
user.organisation.id => "#{user.organisation.name} (Your organisation)",
org_rel1.child_organisation.id => org_rel1.child_organisation.name,
org_rel2.child_organisation.id => org_rel2.child_organisation.name,
}
end
it "shows managing agents with own org at the top" do
expect(question.displayed_answer_options(log, user)).to eq(options)
end
end
context "when user not support and does not own stock" do
let(:user) { create(:user, :data_coordinator, organisation: create(:organisation, holds_own_stock: false)) }
let(:log) { create(:lettings_log) }
let!(:org_rel1) { create(:organisation_relationship, parent_organisation: user.organisation) }
let!(:org_rel2) { create(:organisation_relationship, parent_organisation: user.organisation) }
let(:options) do let(:options) do
{ {
"" => "Select an option", "" => "Select an option",
user.organisation.id => "#{user.organisation.name} (Your organisation)", log.managing_organisation.id => "Managing org 1",
org_rel1.child_organisation.id => org_rel1.child_organisation.name, user.organisation.id => "User org (Your organisation)",
org_rel2.child_organisation.id => org_rel2.child_organisation.name, org_rel1.child_organisation.id => "Managing org 2",
org_rel2.child_organisation.id => "Managing org 3",
} }
end end
it "shows managing agents with own org at the top" do it "shows current managing agent at top, followed by user's org (with hint), followed by the managing agents of the user's org" do
expect(question.displayed_answer_options(log, user)).to eq(options) expect(question.displayed_answer_options(log, user)).to eq(options)
end end
end end
context "when support user and org does not own own stock" do context "when user is support" do
let(:user) { create(:user, :support) } let(:user) { create(:user, :support) }
let(:log_owning_org) { create(:organisation, holds_own_stock: false) } let(:log_owning_org) { create(:organisation, name: "Owning org") }
let(:log) { create(:lettings_log, owning_organisation: log_owning_org) }
let!(:org_rel1) { create(:organisation_relationship, parent_organisation: log_owning_org) } let(:managing_org1) { create(:organisation, name: "Managing org 1") }
let!(:org_rel2) { create(:organisation_relationship, parent_organisation: log_owning_org) } let(:managing_org2) { create(:organisation, name: "Managing org 2") }
let(:managing_org3) { create(:organisation, name: "Managing org 3") }
let(:options) do
{ let(:log) { create(:lettings_log, owning_organisation: log_owning_org, managing_organisation: managing_org1, created_by: nil) }
"" => "Select an option", let!(:org_rel1) { create(:organisation_relationship, parent_organisation: log_owning_org, child_organisation: managing_org2) }
org_rel1.child_organisation.id => org_rel1.child_organisation.name, let!(:org_rel2) { create(:organisation_relationship, parent_organisation: log_owning_org, child_organisation: managing_org3) }
org_rel2.child_organisation.id => org_rel2.child_organisation.name,
} context "when org owns stock" do
let(:options) do
{
"" => "Select an option",
log.managing_organisation.id => "Managing org 1",
log_owning_org.id => "Owning org (Owning organisation)",
org_rel1.child_organisation.id => "Managing org 2",
org_rel2.child_organisation.id => "Managing org 3",
}
end
it "shows current managing agent at top, followed by the current owning organisation (with hint), followed by the managing agents of the current owning organisation" do
log_owning_org.update!(holds_own_stock: true)
expect(question.displayed_answer_options(log, user)).to eq(options)
end
end end
it "shows owning org managing agents with hint" do context "when org does not own stock" do
expect(question.displayed_answer_options(log, user)).to eq(options) let(:options) do
{
"" => "Select an option",
log.managing_organisation.id => "Managing org 1",
org_rel1.child_organisation.id => "Managing org 2",
org_rel2.child_organisation.id => "Managing org 3",
}
end
it "shows current managing agent at top, followed by the managing agents of the current owning organisation" do
log_owning_org.update!(holds_own_stock: false)
expect(question.displayed_answer_options(log, user)).to eq(options)
end
end end
end end
context "when support user and org does own stock" do context "when the owning-managing organisation relationship is deleted" do
let(:user) { create(:user, :support) } let(:user) { create(:user, :support) }
let(:log_owning_org) { create(:organisation, holds_own_stock: true) }
let(:log) { create(:lettings_log, owning_organisation: log_owning_org) } let(:owning_org) { create(:organisation, name: "Owning org", holds_own_stock: true) }
let!(:org_rel1) { create(:organisation_relationship, parent_organisation: log_owning_org) } let(:managing_org) { create(:organisation, name: "Managing org", holds_own_stock: false) }
let!(:org_rel2) { create(:organisation_relationship, parent_organisation: log_owning_org) } let(:org_rel) { create(:organisation_relationship, parent_organisation: owning_org, child_organisation: managing_org) }
let(:log) { create(:lettings_log, owning_organisation: owning_org, managing_organisation: managing_org, created_by: nil) }
let(:options) do let(:options) do
{ {
"" => "Select an option", "" => "Select an option",
log_owning_org.id => "#{log_owning_org.name} (Owning organisation)", owning_org.id => "Owning org (Owning organisation)",
org_rel1.child_organisation.id => org_rel1.child_organisation.name, managing_org.id => "Managing org",
org_rel2.child_organisation.id => org_rel2.child_organisation.name,
} }
end end
it "shows owning org managing agents it "doesn't remove the managing org from the list of allowed managing orgs" do
" do org_rel.destroy!
expect(question.displayed_answer_options(log, user)).to eq(options) expect(question.displayed_answer_options(log, user)).to eq(options)
end end
end end

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save