Browse Source

Merge branch 'main' into CLDC-868-purchase-price-validations

pull/1285/head
natdeanlewissoftwire 4 years ago
parent
commit
cdfd91842a
  1. 3
      .github/workflows/review_pipeline.yml
  2. 5
      Gemfile
  3. 276
      Gemfile.lock
  4. 4
      app/controllers/lettings_logs_controller.rb
  5. 4
      app/controllers/organisations_controller.rb
  6. 4
      app/controllers/sales_logs_controller.rb
  7. 36
      app/helpers/collection_time_helper.rb
  8. 8
      app/helpers/filters_helper.rb
  9. 33
      app/helpers/interruption_screen_helper.rb
  10. 25
      app/helpers/tasklist_helper.rb
  11. 28
      app/mailers/bulk_upload_mailer.rb
  12. 12
      app/models/bulk_upload.rb
  13. 4
      app/models/derived_variables/lettings_log_variables.rb
  14. 6
      app/models/form.rb
  15. 10
      app/models/form/lettings/questions/location_id.rb
  16. 2
      app/models/form/lettings/questions/working_situation1.rb
  17. 6
      app/models/form/sales/pages/about_price_value_check.rb
  18. 9
      app/models/form/sales/pages/about_staircase.rb
  19. 15
      app/models/form/sales/pages/buyer1_income_value_check.rb
  20. 15
      app/models/form/sales/pages/buyer2_ethnic_background_arab.rb
  21. 15
      app/models/form/sales/pages/buyer2_ethnic_background_asian.rb
  22. 15
      app/models/form/sales/pages/buyer2_ethnic_background_black.rb
  23. 15
      app/models/form/sales/pages/buyer2_ethnic_background_mixed.rb
  24. 15
      app/models/form/sales/pages/buyer2_ethnic_background_white.rb
  25. 22
      app/models/form/sales/pages/buyer2_ethnic_group.rb
  26. 35
      app/models/form/sales/pages/buyer2_income_value_check.rb
  27. 2
      app/models/form/sales/questions/buyer1_income_value_check.rb
  28. 16
      app/models/form/sales/questions/buyer2_ethnic_background_arab.rb
  29. 19
      app/models/form/sales/questions/buyer2_ethnic_background_asian.rb
  30. 17
      app/models/form/sales/questions/buyer2_ethnic_background_black.rb
  31. 18
      app/models/form/sales/questions/buyer2_ethnic_background_mixed.rb
  32. 18
      app/models/form/sales/questions/buyer2_ethnic_background_white.rb
  33. 27
      app/models/form/sales/questions/buyer2_ethnic_group.rb
  34. 1
      app/models/form/sales/questions/buyer2_income.rb
  35. 25
      app/models/form/sales/questions/buyer2_income_value_check.rb
  36. 10
      app/models/form/sales/questions/previous_postcode_known.rb
  37. 16
      app/models/form/sales/questions/staircase_sale.rb
  38. 17
      app/models/form/sales/subsections/household_characteristics.rb
  39. 1
      app/models/form/sales/subsections/income_benefits_and_savings.rb
  40. 8
      app/models/form_handler.rb
  41. 12
      app/models/lettings_log.rb
  42. 6
      app/models/location.rb
  43. 8
      app/models/log.rb
  44. 27
      app/models/organisation.rb
  45. 30
      app/models/sales_log.rb
  46. 10
      app/models/scheme.rb
  47. 63
      app/models/validations/sales/financial_validations.rb
  48. 9
      app/models/validations/sales/property_validations.rb
  49. 38
      app/models/validations/sales/setup_validations.rb
  50. 12
      app/models/validations/sales/soft_validations.rb
  51. 2
      app/services/bulk_upload/lettings/log_creator.rb
  52. 194
      app/services/bulk_upload/lettings/row_parser.rb
  53. 1
      app/services/bulk_upload/lettings/validator.rb
  54. 5
      app/services/bulk_upload/processor.rb
  55. 9
      app/services/exports/lettings_log_export_constants.rb
  56. 11
      app/services/exports/lettings_log_export_service.rb
  57. 45
      app/views/content/privacy_notice.md
  58. 40
      app/views/form/review.html.erb
  59. 3
      app/views/logs/_log_filters.erb
  60. 2
      config/credentials.yml.enc
  61. 2
      config/forms/2022_2023.json
  62. 17
      config/initializers/feature_toggle.rb
  63. 31
      config/locales/en.yml
  64. 4
      config/routes.rb
  65. 1185
      config/sale_range_data/2023.csv
  66. 5
      db/migrate/20230105103134_add_income2_value_check.rb
  67. 5
      db/migrate/20230210122037_add_staircasesale_to_sales_logs.rb
  68. 8
      db/migrate/20230210143120_add_ethnic_fields_for_buyer2.rb
  69. 6
      db/schema.rb
  70. 63
      docs/Gemfile.lock
  71. 18
      lib/tasks/data_export.rake
  72. 5
      spec/factories/location.rb
  73. 8
      spec/factories/organisation.rb
  74. 4
      spec/factories/scheme.rb
  75. 2
      spec/fixtures/exports/general_needs_log.csv
  76. 2
      spec/fixtures/exports/general_needs_log.xml
  77. 2
      spec/fixtures/exports/supported_housing_logs.xml
  78. 46
      spec/helpers/collection_time_helper_spec.rb
  79. 26
      spec/helpers/filters_helper_spec.rb
  80. 53
      spec/helpers/interruption_screen_helper_spec.rb
  81. 147
      spec/helpers/tasklist_helper_spec.rb
  82. 54
      spec/mailers/bulk_upload_mailer_spec.rb
  83. 6
      spec/models/form/lettings/pages/location_spec.rb
  84. 18
      spec/models/form/lettings/questions/location_id_spec.rb
  85. 20
      spec/models/form/sales/pages/about_staircase_spec.rb
  86. 33
      spec/models/form/sales/pages/buyer2_ethnic_background_arab_spec.rb
  87. 33
      spec/models/form/sales/pages/buyer2_ethnic_background_asian_spec.rb
  88. 33
      spec/models/form/sales/pages/buyer2_ethnic_background_black_spec.rb
  89. 33
      spec/models/form/sales/pages/buyer2_ethnic_background_mixed_spec.rb
  90. 33
      spec/models/form/sales/pages/buyer2_ethnic_background_white_spec.rb
  91. 42
      spec/models/form/sales/pages/buyer2_ethnic_group_spec.rb
  92. 2
      spec/models/form/sales/questions/buyer1_income_value_check_spec.rb
  93. 48
      spec/models/form/sales/questions/buyer2_ethnic_background_arab_spec.rb
  94. 51
      spec/models/form/sales/questions/buyer2_ethnic_background_asian_spec.rb
  95. 49
      spec/models/form/sales/questions/buyer2_ethnic_background_black_spec.rb
  96. 50
      spec/models/form/sales/questions/buyer2_ethnic_background_mixed_spec.rb
  97. 50
      spec/models/form/sales/questions/buyer2_ethnic_background_white_spec.rb
  98. 62
      spec/models/form/sales/questions/buyer2_ethnic_group_spec.rb
  99. 9
      spec/models/form/sales/questions/previous_postcode_known_spec.rb
  100. 49
      spec/models/form/sales/questions/staircase_sale_spec.rb
  101. Some files were not shown because too many files have changed in this diff Show More

3
.github/workflows/review_pipeline.yml

@ -72,6 +72,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: staging environment: staging
needs: [postgres, redis] needs: [postgres, redis]
permissions:
issues: write
pull-requests: write
steps: steps:
- name: Checkout code - name: Checkout code

5
Gemfile

@ -20,7 +20,7 @@ gem "bootsnap", ">= 1.4.4", require: false
# GOV UK frontend components # GOV UK frontend components
gem "govuk-components" gem "govuk-components"
# GOV UK component form builder DSL # GOV UK component form builder DSL
gem "govuk_design_system_formbuilder" gem "govuk_design_system_formbuilder", "3.1.2"
# Convert Markdown into GOV.UK frontend-styled HTML # Convert Markdown into GOV.UK frontend-styled HTML
gem "govuk_markdown" gem "govuk_markdown"
# GOV UK Notify # GOV UK Notify
@ -68,6 +68,7 @@ group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console # Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem "byebug", platforms: %i[mri mingw x64_mingw] gem "byebug", platforms: %i[mri mingw x64_mingw]
gem "dotenv-rails" gem "dotenv-rails"
gem "factory_bot_rails"
gem "pry-byebug" gem "pry-byebug"
gem "parallel_tests" gem "parallel_tests"
@ -90,7 +91,7 @@ end
group :test do group :test do
gem "capybara", require: false gem "capybara", require: false
gem "capybara-lockstep" gem "capybara-lockstep"
gem "factory_bot_rails" gem "capybara-screenshot"
gem "faker" gem "faker"
gem "rspec-rails", require: false gem "rspec-rails", require: false
gem "selenium-webdriver", require: false gem "selenium-webdriver", require: false

276
Gemfile.lock

@ -13,67 +13,67 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.0.4.1) actioncable (7.0.4.2)
actionpack (= 7.0.4.1) actionpack (= 7.0.4.2)
activesupport (= 7.0.4.1) activesupport (= 7.0.4.2)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (7.0.4.1) actionmailbox (7.0.4.2)
actionpack (= 7.0.4.1) actionpack (= 7.0.4.2)
activejob (= 7.0.4.1) activejob (= 7.0.4.2)
activerecord (= 7.0.4.1) activerecord (= 7.0.4.2)
activestorage (= 7.0.4.1) activestorage (= 7.0.4.2)
activesupport (= 7.0.4.1) activesupport (= 7.0.4.2)
mail (>= 2.7.1) mail (>= 2.7.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
actionmailer (7.0.4.1) actionmailer (7.0.4.2)
actionpack (= 7.0.4.1) actionpack (= 7.0.4.2)
actionview (= 7.0.4.1) actionview (= 7.0.4.2)
activejob (= 7.0.4.1) activejob (= 7.0.4.2)
activesupport (= 7.0.4.1) activesupport (= 7.0.4.2)
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.1) actionpack (7.0.4.2)
actionview (= 7.0.4.1) actionview (= 7.0.4.2)
activesupport (= 7.0.4.1) activesupport (= 7.0.4.2)
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.1) actiontext (7.0.4.2)
actionpack (= 7.0.4.1) actionpack (= 7.0.4.2)
activerecord (= 7.0.4.1) activerecord (= 7.0.4.2)
activestorage (= 7.0.4.1) activestorage (= 7.0.4.2)
activesupport (= 7.0.4.1) activesupport (= 7.0.4.2)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.0.4.1) actionview (7.0.4.2)
activesupport (= 7.0.4.1) activesupport (= 7.0.4.2)
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.1) activejob (7.0.4.2)
activesupport (= 7.0.4.1) activesupport (= 7.0.4.2)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.0.4.1) activemodel (7.0.4.2)
activesupport (= 7.0.4.1) activesupport (= 7.0.4.2)
activerecord (7.0.4.1) activerecord (7.0.4.2)
activemodel (= 7.0.4.1) activemodel (= 7.0.4.2)
activesupport (= 7.0.4.1) activesupport (= 7.0.4.2)
activestorage (7.0.4.1) activestorage (7.0.4.2)
actionpack (= 7.0.4.1) actionpack (= 7.0.4.2)
activejob (= 7.0.4.1) activejob (= 7.0.4.2)
activerecord (= 7.0.4.1) activerecord (= 7.0.4.2)
activesupport (= 7.0.4.1) activesupport (= 7.0.4.2)
marcel (~> 1.0) marcel (~> 1.0)
mini_mime (>= 1.1.0) mini_mime (>= 1.1.0)
activesupport (7.0.4.1) activesupport (7.0.4.2)
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)
@ -84,20 +84,20 @@ GEM
auto_strip_attributes (2.6.0) auto_strip_attributes (2.6.0)
activerecord (>= 4.0) activerecord (>= 4.0)
aws-eventstream (1.2.0) aws-eventstream (1.2.0)
aws-partitions (1.635.0) aws-partitions (1.714.0)
aws-sdk-core (3.153.0) aws-sdk-core (3.170.0)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0) aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1) jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.58.0) aws-sdk-kms (1.62.0)
aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-core (~> 3, >= 3.165.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.114.0) aws-sdk-s3 (1.119.1)
aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-core (~> 3, >= 3.165.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4) aws-sigv4 (~> 1.4)
aws-sigv4 (1.5.1) aws-sigv4 (1.5.2)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
bcrypt (3.1.18) bcrypt (3.1.18)
better_html (2.0.1) better_html (2.0.1)
@ -108,14 +108,14 @@ GEM
parser (>= 2.4) parser (>= 2.4)
smart_properties smart_properties
bindex (0.8.1) bindex (0.8.1)
bootsnap (1.13.0) bootsnap (1.16.0)
msgpack (~> 1.2) msgpack (~> 1.2)
builder (3.2.4) builder (3.2.4)
bundler-audit (0.9.1) bundler-audit (0.9.1)
bundler (>= 1.2.0, < 3) bundler (>= 1.2.0, < 3)
thor (~> 1.0) thor (~> 1.0)
byebug (11.1.3) byebug (11.1.3)
capybara (3.37.1) capybara (3.38.0)
addressable addressable
matrix matrix
mini_mime (>= 0.1.3) mini_mime (>= 0.1.3)
@ -124,14 +124,17 @@ GEM
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0) regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2) xpath (~> 3.2)
capybara-lockstep (1.2.1) capybara-lockstep (1.3.0)
activesupport (>= 3.2) activesupport (>= 3.2)
capybara (>= 2.0) capybara (>= 2.0)
ruby2_keywords ruby2_keywords
selenium-webdriver (>= 3) selenium-webdriver (>= 3)
capybara-screenshot (1.0.26)
capybara (>= 1.0, < 4)
launchy
childprocess (4.1.0) childprocess (4.1.0)
coderay (1.1.3) coderay (1.1.3)
concurrent-ruby (1.1.10) concurrent-ruby (1.2.0)
connection_pool (2.3.0) connection_pool (2.3.0)
crack (0.4.5) crack (0.4.5)
rexml rexml
@ -150,7 +153,7 @@ GEM
dotenv (= 2.8.1) dotenv (= 2.8.1)
railties (>= 3.2) railties (>= 3.2)
encryptor (3.0.0) encryptor (3.0.0)
erb_lint (0.2.0) erb_lint (0.3.1)
activesupport activesupport
better_html (>= 2.0.1) better_html (>= 2.0.1)
parser (>= 2.7.1.4) parser (>= 2.7.1.4)
@ -160,19 +163,19 @@ GEM
erubi (1.12.0) erubi (1.12.0)
et-orbi (1.2.7) et-orbi (1.2.7)
tzinfo tzinfo
excon (0.92.5) excon (0.99.0)
factory_bot (6.2.1) factory_bot (6.2.1)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
factory_bot_rails (6.2.0) factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0) factory_bot (~> 6.2.0)
railties (>= 5.0.0) railties (>= 5.0.0)
faker (2.23.0) faker (3.1.1)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
ffi (1.15.5) ffi (1.15.5)
fugit (1.8.1) fugit (1.8.1)
et-orbi (~> 1, >= 1.2.7) et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4) raabro (~> 1.4)
globalid (1.0.1) globalid (1.1.0)
activesupport (>= 5.0) activesupport (>= 5.0)
govuk-components (3.2.1) govuk-components (3.2.1)
actionpack (>= 6.1) actionpack (>= 6.1)
@ -186,7 +189,7 @@ GEM
activemodel (>= 6.1) activemodel (>= 6.1)
activesupport (>= 6.1) activesupport (>= 6.1)
html-attributes-utils (~> 0.9, >= 0.9.2) html-attributes-utils (~> 0.9, >= 0.9.2)
govuk_markdown (1.0.0) govuk_markdown (2.0.0)
activesupport activesupport
redcarpet redcarpet
hashdiff (1.0.1) hashdiff (1.0.1)
@ -195,19 +198,21 @@ GEM
i18n (1.12.0) i18n (1.12.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
iniparse (1.5.0) iniparse (1.5.0)
jmespath (1.6.1) jmespath (1.6.2)
jsbundling-rails (1.0.3) jsbundling-rails (1.1.1)
railties (>= 6.0.0) railties (>= 6.0.0)
json-schema (3.0.0) json-schema (3.0.0)
addressable (>= 2.8) addressable (>= 2.8)
jwt (2.5.0) jwt (2.7.0)
listen (3.7.1) launchy (2.5.2)
addressable (~> 2.8)
listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
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.8.0.1) mail (2.8.1)
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
net-imap net-imap
net-pop net-pop
@ -217,7 +222,7 @@ GEM
method_source (1.0.0) method_source (1.0.0)
mini_mime (1.1.2) mini_mime (1.1.2)
minitest (5.17.0) minitest (5.17.0)
msgpack (1.5.6) msgpack (1.6.0)
net-imap (0.3.4) net-imap (0.3.4)
date date
net-protocol net-protocol
@ -228,33 +233,33 @@ GEM
net-smtp (0.3.3) net-smtp (0.3.3)
net-protocol net-protocol
nio4r (2.5.8) nio4r (2.5.8)
nokogiri (1.14.0-arm64-darwin) nokogiri (1.14.2-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.14.0-x86_64-darwin) nokogiri (1.14.2-x86_64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.14.0-x86_64-linux) nokogiri (1.14.2-x86_64-linux)
racc (~> 1.4) racc (~> 1.4)
notifications-ruby-client (5.3.0) notifications-ruby-client (5.4.0)
jwt (>= 1.5, < 3) jwt (>= 1.5, < 3)
orm_adapter (0.5.0) orm_adapter (0.5.0)
overcommit (0.59.1) overcommit (0.60.0)
childprocess (>= 0.6.3, < 5) childprocess (>= 0.6.3, < 5)
iniparse (~> 1.4) iniparse (~> 1.4)
rexml (~> 3.2) rexml (~> 3.2)
pagy (5.10.1) pagy (5.10.1)
activesupport activesupport
paper_trail (13.0.0) paper_trail (14.0.0)
activerecord (>= 5.2) activerecord (>= 6.0)
request_store (~> 1.1) request_store (~> 1.4)
paper_trail-globalid (0.2.0) paper_trail-globalid (0.2.0)
globalid globalid
paper_trail (>= 3.0.0) paper_trail (>= 3.0.0)
parallel (1.22.1) parallel (1.22.1)
parallel_tests (4.0.0) parallel_tests (4.2.0)
parallel parallel
parser (3.1.2.1) parser (3.2.1.0)
ast (~> 2.4.1) ast (~> 2.4.1)
pg (1.4.3) pg (1.4.5)
possessive (1.0.1) possessive (1.0.1)
postcodes_io (0.4.0) postcodes_io (0.4.0)
excon (~> 0.39) excon (~> 0.39)
@ -263,13 +268,13 @@ GEM
activesupport (>= 7.0.0) activesupport (>= 7.0.0)
rack rack
railties (>= 7.0.0) railties (>= 7.0.0)
pry (0.14.1) pry (0.14.2)
coderay (~> 1.1) coderay (~> 1.1)
method_source (~> 1.0) method_source (~> 1.0)
pry-byebug (3.10.1) pry-byebug (3.10.1)
byebug (~> 11.0) byebug (~> 11.0)
pry (>= 0.13, < 0.15) pry (>= 0.13, < 0.15)
public_suffix (5.0.0) public_suffix (5.0.1)
puma (5.6.5) puma (5.6.5)
nio4r (~> 2.0) nio4r (~> 2.0)
raabro (1.4.0) raabro (1.4.0)
@ -281,28 +286,28 @@ GEM
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.1) rails (7.0.4.2)
actioncable (= 7.0.4.1) actioncable (= 7.0.4.2)
actionmailbox (= 7.0.4.1) actionmailbox (= 7.0.4.2)
actionmailer (= 7.0.4.1) actionmailer (= 7.0.4.2)
actionpack (= 7.0.4.1) actionpack (= 7.0.4.2)
actiontext (= 7.0.4.1) actiontext (= 7.0.4.2)
actionview (= 7.0.4.1) actionview (= 7.0.4.2)
activejob (= 7.0.4.1) activejob (= 7.0.4.2)
activemodel (= 7.0.4.1) activemodel (= 7.0.4.2)
activerecord (= 7.0.4.1) activerecord (= 7.0.4.2)
activestorage (= 7.0.4.1) activestorage (= 7.0.4.2)
activesupport (= 7.0.4.1) activesupport (= 7.0.4.2)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.0.4.1) railties (= 7.0.4.2)
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.5.0)
loofah (~> 2.19, >= 2.19.1) loofah (~> 2.19, >= 2.19.1)
railties (7.0.4.1) railties (7.0.4.2)
actionpack (= 7.0.4.1) actionpack (= 7.0.4.2)
activesupport (= 7.0.4.1) activesupport (= 7.0.4.2)
method_source method_source
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0) thor (~> 1.0)
@ -313,36 +318,38 @@ GEM
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
redcarpet (3.5.1) redcarpet (3.6.0)
redis (4.8.0) redis (4.8.1)
regexp_parser (2.5.0) redis-client (0.12.2)
connection_pool
regexp_parser (2.7.0)
request_store (1.5.1) request_store (1.5.1)
rack (>= 1.4) rack (>= 1.4)
responders (3.0.1) responders (3.1.0)
actionpack (>= 5.0) actionpack (>= 5.2)
railties (>= 5.0) railties (>= 5.2)
rexml (3.2.5) rexml (3.2.5)
roo (2.9.0) roo (2.10.0)
nokogiri (~> 1) nokogiri (~> 1)
rubyzip (>= 1.3.0, < 3.0.0) rubyzip (>= 1.3.0, < 3.0.0)
rotp (6.2.0) rotp (6.2.2)
rspec-core (3.11.0) rspec-core (3.12.1)
rspec-support (~> 3.11.0) rspec-support (~> 3.12.0)
rspec-expectations (3.11.1) rspec-expectations (3.12.2)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0) rspec-support (~> 3.12.0)
rspec-mocks (3.11.1) rspec-mocks (3.12.3)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0) rspec-support (~> 3.12.0)
rspec-rails (5.1.2) rspec-rails (6.0.1)
actionpack (>= 5.2) actionpack (>= 6.1)
activesupport (>= 5.2) activesupport (>= 6.1)
railties (>= 5.2) railties (>= 6.1)
rspec-core (~> 3.10) rspec-core (~> 3.11)
rspec-expectations (~> 3.10) rspec-expectations (~> 3.11)
rspec-mocks (~> 3.10) rspec-mocks (~> 3.11)
rspec-support (~> 3.10) rspec-support (~> 3.11)
rspec-support (3.11.1) rspec-support (3.12.0)
rubocop (1.25.0) rubocop (1.25.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.1.0.0) parser (>= 3.1.0.0)
@ -360,7 +367,7 @@ GEM
rubocop-rails (= 2.13.2) rubocop-rails (= 2.13.2)
rubocop-rake (= 0.6.0) rubocop-rake (= 0.6.0)
rubocop-rspec (= 2.7.0) rubocop-rspec (= 2.7.0)
rubocop-performance (1.15.0) rubocop-performance (1.16.0)
rubocop (>= 1.7.0, < 2.0) rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0) rubocop-ast (>= 0.4.0)
rubocop-rails (2.13.2) rubocop-rails (2.13.2)
@ -374,39 +381,39 @@ GEM
ruby-progressbar (1.11.0) ruby-progressbar (1.11.0)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
rubyzip (2.3.2) rubyzip (2.3.2)
selenium-webdriver (4.4.0) selenium-webdriver (4.8.1)
childprocess (>= 0.5, < 5.0)
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0) rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0) websocket (~> 1.0)
sentry-rails (5.4.2) sentry-rails (5.8.0)
railties (>= 5.0) railties (>= 5.0)
sentry-ruby (~> 5.4.2) sentry-ruby (~> 5.8.0)
sentry-ruby (5.4.2) sentry-ruby (5.8.0)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (6.5.5) sidekiq (7.0.5)
connection_pool (>= 2.2.2) concurrent-ruby (< 2)
rack (~> 2.0) connection_pool (>= 2.3.0)
redis (>= 4.5.0) rack (>= 2.2.4)
sidekiq-cron (1.8.0) redis-client (>= 0.11.0)
fugit (~> 1) sidekiq-cron (1.9.1)
fugit (~> 1.8)
sidekiq (>= 4.2.1) sidekiq (>= 4.2.1)
simplecov (0.21.2) simplecov (0.22.0)
docile (~> 1.1) docile (~> 1.1)
simplecov-html (~> 0.11) simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1) simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3) simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4) simplecov_json_formatter (0.1.4)
smart_properties (1.17.0) smart_properties (1.17.0)
stimulus-rails (1.1.0) stimulus-rails (1.2.1)
railties (>= 6.0.0) railties (>= 6.0.0)
thor (1.2.1) thor (1.2.1)
timecop (0.9.5) timecop (0.9.6)
timeout (0.3.1) timeout (0.3.2)
tzinfo (2.0.5) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
uk_postcode (2.1.8) uk_postcode (2.1.8)
unicode-display_width (2.3.0) unicode-display_width (2.4.2)
view_component (2.69.0) view_component (2.69.0)
activesupport (>= 5.0.0, < 8.0) activesupport (>= 5.0.0, < 8.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
@ -428,7 +435,7 @@ 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.6) zeitwerk (2.6.7)
PLATFORMS PLATFORMS
arm64-darwin-21 arm64-darwin-21
@ -447,6 +454,7 @@ DEPENDENCIES
byebug byebug
capybara capybara
capybara-lockstep capybara-lockstep
capybara-screenshot
devise! devise!
devise_two_factor_authentication devise_two_factor_authentication
dotenv-rails dotenv-rails
@ -454,7 +462,7 @@ DEPENDENCIES
factory_bot_rails factory_bot_rails
faker faker
govuk-components govuk-components
govuk_design_system_formbuilder govuk_design_system_formbuilder (= 3.1.2)
govuk_markdown govuk_markdown
jsbundling-rails jsbundling-rails
json-schema json-schema

4
app/controllers/lettings_logs_controller.rb

@ -1,7 +1,7 @@
class LettingsLogsController < LogsController class LettingsLogsController < LogsController
before_action :find_resource, except: %i[create index edit] before_action :find_resource, except: %i[create index edit]
before_action :session_filters, if: :current_user before_action :session_filters, if: :current_user, only: %i[index email_csv download_csv]
before_action :set_session_filters, if: :current_user before_action :set_session_filters, if: :current_user, only: %i[index email_csv download_csv]
before_action :extract_bulk_upload_from_session_filters, only: [:index] before_action :extract_bulk_upload_from_session_filters, only: [:index]
before_action :redirect_if_bulk_upload_resolved, only: [:index] before_action :redirect_if_bulk_upload_resolved, only: [:index]

4
app/controllers/organisations_controller.rb

@ -6,8 +6,8 @@ class OrganisationsController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
before_action :find_resource, except: %i[index new create] before_action :find_resource, except: %i[index new create]
before_action :authenticate_scope!, except: [:index] before_action :authenticate_scope!, except: [:index]
before_action -> { session_filters(specific_org: true) }, if: -> { current_user.support? || current_user.organisation.has_managing_agents? } before_action -> { session_filters(specific_org: true) }, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_csv download_csv]
before_action :set_session_filters, if: -> { current_user.support? || current_user.organisation.has_managing_agents? } before_action :set_session_filters, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_csv download_csv]
def index def index
redirect_to organisation_path(current_user.organisation) unless current_user.support? redirect_to organisation_path(current_user.organisation) unless current_user.support?

4
app/controllers/sales_logs_controller.rb

@ -1,6 +1,6 @@
class SalesLogsController < LogsController class SalesLogsController < LogsController
before_action :session_filters, if: :current_user before_action :session_filters, if: :current_user, only: %i[index email_csv download_csv]
before_action :set_session_filters, if: :current_user before_action :set_session_filters, if: :current_user, only: %i[index email_csv download_csv]
def create def create
super { SalesLog.new(log_params) } super { SalesLog.new(log_params) }

36
app/helpers/collection_time_helper.rb

@ -1,16 +1,42 @@
module CollectionTimeHelper module CollectionTimeHelper
def collection_start_year(date)
window_end_date = Time.zone.local(date.year, 4, 1)
date < window_end_date ? date.year - 1 : date.year
end
def current_collection_start_year def current_collection_start_year
today = Time.zone.now collection_start_year(Time.zone.now)
window_end_date = Time.zone.local(today.year, 4, 1)
today < window_end_date ? today.year - 1 : today.year
end end
def collection_start_date(date) def collection_start_date(date)
window_end_date = Time.zone.local(date.year, 4, 1) Time.zone.local(collection_start_year(date), 4, 1)
date < window_end_date ? Time.zone.local(date.year - 1, 4, 1) : Time.zone.local(date.year, 4, 1)
end end
def current_collection_start_date def current_collection_start_date
Time.zone.local(current_collection_start_year, 4, 1) Time.zone.local(current_collection_start_year, 4, 1)
end end
def collection_end_date(date)
Time.zone.local(collection_start_year(date) + 1, 3, 31)
end
def current_collection_end_date
Time.zone.local(current_collection_start_year + 1, 3, 31)
end
def previous_collection_end_date
current_collection_end_date - 1.year
end
def next_collection_start_year
current_collection_start_year + 1
end
def previous_collection_start_year
current_collection_start_year - 1
end
def previous_collection_start_date
current_collection_start_date - 1.year
end
end end

8
app/helpers/filters_helper.rb

@ -27,4 +27,12 @@ module FiltersHelper
organisation_options = user.support? ? Organisation.all : [user.organisation] + user.organisation.managing_agents organisation_options = user.support? ? Organisation.all : [user.organisation] + user.organisation.managing_agents
[OpenStruct.new(id: "", name: "Select an option")] + organisation_options.map { |org| OpenStruct.new(id: org.id, name: org.name) } [OpenStruct.new(id: "", name: "Select an option")] + organisation_options.map { |org| OpenStruct.new(id: org.id, name: org.name) }
end end
def collection_year_options
if FeatureToggle.collection_2023_2024_year_enabled?
{ "2023": "2023/24", "2022": "2022/23", "2021": "2021/22" }
else
{ "2022": "2022/23", "2021": "2021/22" }
end
end
end end

33
app/helpers/interruption_screen_helper.rb

@ -1,17 +1,10 @@
module InterruptionScreenHelper module InterruptionScreenHelper
def display_informative_text(informative_text, lettings_log) def display_informative_text(informative_text, log)
return "" unless informative_text["arguments"] return "" unless informative_text["arguments"]
translation_params = {} translation_params = {}
informative_text["arguments"].each do |argument| informative_text["arguments"].each do |argument|
value = if argument["label"] value = get_value_from_argument(log, argument)
pre_casing_value = lettings_log.form.get_question(argument["key"], lettings_log).answer_label(lettings_log)
pre_casing_value.downcase
elsif argument["currency"]
number_to_currency(lettings_log.public_send(argument["key"]), delimiter: ",", format: "%n", unit: "£")
else
lettings_log.public_send(argument["key"])
end
translation_params[argument["i18n_template"].to_sym] = value translation_params[argument["i18n_template"].to_sym] = value
end end
@ -24,21 +17,27 @@ module InterruptionScreenHelper
end end
end end
def display_title_text(title_text, lettings_log) def display_title_text(title_text, log)
return "" if title_text.nil? return "" if title_text.nil?
translation_params = {} translation_params = {}
arguments = title_text["arguments"] || {} arguments = title_text["arguments"] || {}
arguments.each do |argument| arguments.each do |argument|
value = if argument["label"] value = get_value_from_argument(log, argument)
lettings_log.form.get_question(argument["key"], lettings_log).answer_label(lettings_log).downcase
elsif argument["currency"]
number_to_currency(lettings_log.public_send(argument["key"]), delimiter: ",", format: "%n", unit: "£")
else
lettings_log.public_send(argument["key"])
end
translation_params[argument["i18n_template"].to_sym] = value translation_params[argument["i18n_template"].to_sym] = value
end end
I18n.t(title_text["translation"], **translation_params).to_s I18n.t(title_text["translation"], **translation_params).to_s
end end
private
def get_value_from_argument(log, argument)
if argument["label"]
log.form.get_question(argument["key"], log).answer_label(log).downcase
elsif argument["arguments_for_key"]
log.public_send(argument["key"], argument["arguments_for_key"])
else
log.public_send(argument["key"])
end
end
end end

25
app/helpers/tasklist_helper.rb

@ -11,15 +11,6 @@ module TasklistHelper
log.form.subsections.count { |subsection| subsection.status(log) == status && subsection.applicable_questions(log).count.positive? } log.form.subsections.count { |subsection| subsection.status(log) == status && subsection.applicable_questions(log).count.positive? }
end end
def next_page_or_check_answers(subsection, log, current_user)
path = if subsection.is_started?(log)
"#{log.class.name.underscore}_#{subsection.id}_check_answers_path"
else
"#{log.class.name.underscore}_#{next_question_page(subsection, log, current_user)}_path"
end
send(path, log)
end
def next_question_page(subsection, log, current_user) def next_question_page(subsection, log, current_user)
if subsection.pages.first.routed_to?(log, current_user) if subsection.pages.first.routed_to?(log, current_user)
subsection.pages.first.id subsection.pages.first.id
@ -39,9 +30,23 @@ module TasklistHelper
def review_log_text(log) def review_log_text(log)
if log.collection_period_open? if log.collection_period_open?
"You can #{govuk_link_to 'review and make changes to this log', review_lettings_log_path(log)} until #{log.form.end_date.to_formatted_s(:govuk_date)}.".html_safe link = log.sales? ? review_sales_log_path(id: log, sales_log: true) : review_lettings_log_path(log)
"You can #{govuk_link_to 'review and make changes to this log', link} until #{log.form.end_date.to_formatted_s(:govuk_date)}.".html_safe
else else
"This log is from the #{log.form.start_date.year}/#{log.form.start_date.year + 1} collection window, which is now closed." "This log is from the #{log.form.start_date.year}/#{log.form.start_date.year + 1} collection window, which is now closed."
end end
end end
private
def next_page_or_check_answers(subsection, log, current_user)
path = if subsection.is_started?(log)
"#{log.class.name.underscore}_#{subsection.id}_check_answers_path"
else
"#{log.class.name.underscore}_#{next_question_page(subsection, log, current_user)}_path"
end
send(path, log)
end
end end

28
app/mailers/bulk_upload_mailer.rb

@ -33,17 +33,29 @@ class BulkUploadMailer < NotifyMailer
) )
end end
def send_bulk_upload_failed_csv_errors_mail(user, bulk_upload) def columns_with_errors(bulk_upload:)
array = bulk_upload.columns_with_errors
if array.size > 3
"#{array.take(3).join(', ')} and more"
else
array.join(", ")
end
end
def send_correct_and_upload_again_mail(bulk_upload:)
error_description = "We noticed that you have a lot of similar errors in column #{columns_with_errors(bulk_upload:)}. Please correct your data export and upload again."
send_email( send_email(
user.email, bulk_upload.user.email,
BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID, BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID,
{ {
filename: "[#{bulk_upload} filename]", filename: bulk_upload.filename,
upload_timestamp: "[#{bulk_upload} upload_timestamp]", upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
year_combo: "[#{bulk_upload} year_combo]", year_combo: bulk_upload.year_combo,
lettings_or_sales: "[#{bulk_upload} lettings_or_sales]", lettings_or_sales: bulk_upload.log_type,
error_description: "[#{bulk_upload} error_description]", error_description:,
summary_report_link: "[#{bulk_upload} summary_report_link]", summary_report_link: summary_bulk_upload_lettings_result_url(bulk_upload),
}, },
) )
end end

12
app/models/bulk_upload.rb

@ -30,10 +30,22 @@ class BulkUpload < ApplicationRecord
end end
end end
def columns_with_errors
bulk_upload_errors
.select(:col)
.distinct(:col)
.pluck(:col)
.sort_by { |col| col.rjust(2, "0") }
end
def general_needs? def general_needs?
needstype == 1 needstype == 1
end end
def supported_housing?
needstype == 2
end
private private
def generate_identifier def generate_identifier

4
app/models/derived_variables/lettings_log_variables.rb

@ -45,6 +45,9 @@ module DerivedVariables::LettingsLogVariables
self.underoccupation_benefitcap = 2 if collection_start_year == 2021 self.underoccupation_benefitcap = 2 if collection_start_year == 2021
self.referral = 1 self.referral = 1
self.waityear = 2 self.waityear = 2
self.offered = 0
self.voiddate = startdate
self.first_time_property_let_as_social_housing = 0
if is_general_needs? if is_general_needs?
# fixed term # fixed term
self.prevten = 32 if managing_organisation&.provider_type == "PRP" self.prevten = 32 if managing_organisation&.provider_type == "PRP"
@ -60,7 +63,6 @@ module DerivedVariables::LettingsLogVariables
if is_supported_housing? && location if is_supported_housing? && location
self.wchair = location.mobility_type_before_type_cast == "W" ? 1 : 2 self.wchair = location.mobility_type_before_type_cast == "W" ? 1 : 2
end end
self.voiddate = startdate if is_renewal?
self.vacdays = property_vacant_days self.vacdays = property_vacant_days
set_housingneeds_fields if housingneeds? set_housingneeds_fields if housingneeds?

6
app/models/form.rb

@ -6,7 +6,11 @@ class Form
def initialize(form_path, start_year = "", sections_in_form = [], type = "lettings") def initialize(form_path, start_year = "", sections_in_form = [], type = "lettings")
if sales_or_start_year_after_2022?(type, start_year) if sales_or_start_year_after_2022?(type, start_year)
@start_date = Time.zone.local(start_year, 4, 1) @start_date = Time.zone.local(start_year, 4, 1)
@end_date = Time.zone.local(start_year + 1, 7, 1) @end_date = if start_year && start_year.to_i > 2022
Time.zone.local(start_year + 1, 7, 9)
else
Time.zone.local(start_year + 1, 7, 7)
end
@setup_sections = type == "sales" ? [Form::Sales::Sections::Setup.new(nil, nil, self)] : [Form::Lettings::Sections::Setup.new(nil, nil, self)] @setup_sections = type == "sales" ? [Form::Sales::Sections::Setup.new(nil, nil, self)] : [Form::Lettings::Sections::Setup.new(nil, nil, self)]
@form_sections = sections_in_form.map { |sec| sec.new(nil, nil, self) } @form_sections = sections_in_form.map { |sec| sec.new(nil, nil, self) }
@type = type @type = type

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

@ -2,7 +2,7 @@ class Form::Lettings::Questions::LocationId < ::Form::Question
def initialize(_id, hsh, page) def initialize(_id, hsh, page)
super("location_id", hsh, page) super("location_id", hsh, page)
@check_answer_label = "Location" @check_answer_label = "Location"
@header = "Which location is this log for?" @header = header_text
@type = "radio" @type = "radio"
@answer_options = answer_options @answer_options = answer_options
@inferred_answers = { @inferred_answers = {
@ -47,4 +47,12 @@ private
def selected_answer_option_is_derived?(_lettings_log) def selected_answer_option_is_derived?(_lettings_log)
false false
end end
def header_text
if form.start_date && form.start_date.year >= 2023
"Which location is this letting for?"
else
"Which location is this log for?"
end
end
end end

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

@ -11,8 +11,8 @@ class Form::Lettings::Questions::WorkingSituation1 < ::Form::Question
end end
ANSWER_OPTIONS = { ANSWER_OPTIONS = {
"2" => { "value" => "Part-time – Less than 30 hours" },
"1" => { "value" => "Full-time – 30 hours or more" }, "1" => { "value" => "Full-time – 30 hours or more" },
"2" => { "value" => "Part-time – Less than 30 hours" },
"7" => { "value" => "Full-time student" }, "7" => { "value" => "Full-time student" },
"3" => { "value" => "In government training into work, such as New Deal" }, "3" => { "value" => "In government training into work, such as New Deal" },
"4" => { "value" => "Jobseeker" }, "4" => { "value" => "Jobseeker" },

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

@ -20,14 +20,12 @@ class Form::Sales::Pages::AboutPriceValueCheck < ::Form::Page
"translation" => "soft_validations.purchase_price.hint_text", "translation" => "soft_validations.purchase_price.hint_text",
"arguments" => [ "arguments" => [
{ {
"key" => "purchase_price_soft_min_or_soft_max", "key" => "field_formatted_as_currency",
"label" => false, "arguments_for_key" => "purchase_price_soft_min_or_soft_max",
"i18n_template" => "soft_min_or_soft_max", "i18n_template" => "soft_min_or_soft_max",
"currency" => true,
}, },
{ {
"key" => "purchase_price_min_or_max_text", "key" => "purchase_price_min_or_max_text",
"label" => false,
"i18n_template" => "min_or_max", "i18n_template" => "min_or_max",
}, },
], ],

9
app/models/form/sales/pages/about_staircase.rb

@ -12,6 +12,13 @@ class Form::Sales::Pages::AboutStaircase < ::Form::Page
@questions ||= [ @questions ||= [
Form::Sales::Questions::StaircaseBought.new(nil, nil, self), Form::Sales::Questions::StaircaseBought.new(nil, nil, self),
Form::Sales::Questions::StaircaseOwned.new(nil, nil, self), Form::Sales::Questions::StaircaseOwned.new(nil, nil, self),
] staircase_sale_question,
].compact
end
def staircase_sale_question
if form.start_date.year >= 2023
Form::Sales::Questions::StaircaseSale.new(nil, nil, self)
end
end end
end end

15
app/models/form/sales/pages/buyer1_income_value_check.rb

@ -6,6 +6,21 @@ class Form::Sales::Pages::Buyer1IncomeValueCheck < ::Form::Page
"income1_under_soft_min?" => true, "income1_under_soft_min?" => true,
}, },
] ]
@title_text = {
"translation" => "soft_validations.income.under_soft_min_for_economic_status",
"arguments" => [
{
"key" => "field_formatted_as_currency",
"arguments_for_key" => "income1",
"i18n_template" => "income",
},
{
"key" => "income_soft_min_for_ecstat",
"arguments_for_key" => "ecstat1",
"i18n_template" => "minimum",
},
],
}
@informative_text = {} @informative_text = {}
end end

15
app/models/form/sales/pages/buyer2_ethnic_background_arab.rb

@ -0,0 +1,15 @@
class Form::Sales::Pages::Buyer2EthnicBackgroundArab < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_ethnic_background_arab"
@depends_on = [{
"ethnic_group2" => 4,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::Buyer2EthnicBackgroundArab.new(nil, nil, self),
]
end
end

15
app/models/form/sales/pages/buyer2_ethnic_background_asian.rb

@ -0,0 +1,15 @@
class Form::Sales::Pages::Buyer2EthnicBackgroundAsian < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_ethnic_background_asian"
@depends_on = [{
"ethnic_group2" => 2,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::Buyer2EthnicBackgroundAsian.new(nil, nil, self),
]
end
end

15
app/models/form/sales/pages/buyer2_ethnic_background_black.rb

@ -0,0 +1,15 @@
class Form::Sales::Pages::Buyer2EthnicBackgroundBlack < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_ethnic_background_black"
@depends_on = [{
"ethnic_group2" => 3,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::Buyer2EthnicBackgroundBlack.new(nil, nil, self),
]
end
end

15
app/models/form/sales/pages/buyer2_ethnic_background_mixed.rb

@ -0,0 +1,15 @@
class Form::Sales::Pages::Buyer2EthnicBackgroundMixed < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_ethnic_background_mixed"
@depends_on = [{
"ethnic_group2" => 1,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::Buyer2EthnicBackgroundMixed.new(nil, nil, self),
]
end
end

15
app/models/form/sales/pages/buyer2_ethnic_background_white.rb

@ -0,0 +1,15 @@
class Form::Sales::Pages::Buyer2EthnicBackgroundWhite < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_ethnic_background_white"
@depends_on = [{
"ethnic_group2" => 0,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::Buyer2EthnicBackgroundWhite.new(nil, nil, self),
]
end
end

22
app/models/form/sales/pages/buyer2_ethnic_group.rb

@ -0,0 +1,22 @@
class Form::Sales::Pages::Buyer2EthnicGroup < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_ethnic_group"
@depends_on = [
{
"jointpur" => 1,
"privacynotice" => 1,
},
{
"jointpur" => 1,
"noint" => 1,
},
]
end
def questions
@questions ||= [
Form::Sales::Questions::Buyer2EthnicGroup.new(nil, nil, self),
]
end
end

35
app/models/form/sales/pages/buyer2_income_value_check.rb

@ -0,0 +1,35 @@
class Form::Sales::Pages::Buyer2IncomeValueCheck < ::Form::Page
def initialize(id, hsh, subsection)
super
@header = ""
@description = ""
@subsection = subsection
@depends_on = [
{
"income2_under_soft_min?" => true,
},
]
@title_text = {
"translation" => "soft_validations.income.under_soft_min_for_economic_status",
"arguments" => [
{
"key" => "field_formatted_as_currency",
"arguments_for_key" => "income2",
"i18n_template" => "income",
},
{
"key" => "income_soft_min_for_ecstat",
"arguments_for_key" => "ecstat2",
"i18n_template" => "minimum",
},
],
}
@informative_text = {}
end
def questions
@questions ||= [
Form::Sales::Questions::Buyer2IncomeValueCheck.new(nil, nil, self),
]
end
end

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

@ -3,7 +3,7 @@ class Form::Sales::Questions::Buyer1IncomeValueCheck < ::Form::Question
super super
@id = "income1_value_check" @id = "income1_value_check"
@check_answer_label = "Income confirmation" @check_answer_label = "Income confirmation"
@header = "Are you sure this income is correct?" @header = "Are you sure this is correct?"
@type = "interruption_screen" @type = "interruption_screen"
@answer_options = { @answer_options = {
"0" => { "value" => "Yes" }, "0" => { "value" => "Yes" },

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

@ -0,0 +1,16 @@
class Form::Sales::Questions::Buyer2EthnicBackgroundArab < ::Form::Question
def initialize(id, hsh, page)
super
@id = "ethnicbuy2"
@check_answer_label = "Buyer 2’s ethnic background"
@header = "Which of the following best describes the buyer 2’s Arab background?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@check_answers_card_number = 2
end
ANSWER_OPTIONS = {
"19" => { "value" => "Arab" },
"16" => { "value" => "Other ethnic group" },
}.freeze
end

19
app/models/form/sales/questions/buyer2_ethnic_background_asian.rb

@ -0,0 +1,19 @@
class Form::Sales::Questions::Buyer2EthnicBackgroundAsian < ::Form::Question
def initialize(id, hsh, page)
super
@id = "ethnicbuy2"
@check_answer_label = "Buyer 2’s ethnic background"
@header = "Which of the following best describes the buyer 2’s Asian or Asian British background?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@check_answers_card_number = 2
end
ANSWER_OPTIONS = {
"10" => { "value" => "Bangladeshi" },
"15" => { "value" => "Chinese" },
"8" => { "value" => "Indian" },
"9" => { "value" => "Pakistani" },
"11" => { "value" => "Any other Asian or Asian British background" },
}.freeze
end

17
app/models/form/sales/questions/buyer2_ethnic_background_black.rb

@ -0,0 +1,17 @@
class Form::Sales::Questions::Buyer2EthnicBackgroundBlack < ::Form::Question
def initialize(id, hsh, page)
super
@id = "ethnicbuy2"
@check_answer_label = "Buyer 2’s ethnic background"
@header = "Which of the following best describes the buyer 2’s Black, African, Caribbean or Black British background?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@check_answers_card_number = 2
end
ANSWER_OPTIONS = {
"13" => { "value" => "African" },
"12" => { "value" => "Caribbean" },
"14" => { "value" => "Any other Black, African or Caribbean background" },
}.freeze
end

18
app/models/form/sales/questions/buyer2_ethnic_background_mixed.rb

@ -0,0 +1,18 @@
class Form::Sales::Questions::Buyer2EthnicBackgroundMixed < ::Form::Question
def initialize(id, hsh, page)
super
@id = "ethnicbuy2"
@check_answer_label = "Buyer 2’s ethnic background"
@header = "Which of the following best describes the buyer 2’s Mixed or Multiple ethnic groups background?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@check_answers_card_number = 2
end
ANSWER_OPTIONS = {
"4" => { "value" => "White and Black Caribbean" },
"5" => { "value" => "White and Black African" },
"6" => { "value" => "White and Asian" },
"7" => { "value" => "Any other Mixed or Multiple ethnic background" },
}.freeze
end

18
app/models/form/sales/questions/buyer2_ethnic_background_white.rb

@ -0,0 +1,18 @@
class Form::Sales::Questions::Buyer2EthnicBackgroundWhite < ::Form::Question
def initialize(id, hsh, page)
super
@id = "ethnicbuy2"
@check_answer_label = "Buyer 2’s ethnic background"
@header = "Which of the following best describes the buyer 2’s White background?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@check_answers_card_number = 2
end
ANSWER_OPTIONS = {
"1" => { "value" => "English, Welsh, Northern Irish, Scottish or British" },
"2" => { "value" => "Irish" },
"18" => { "value" => "Gypsy or Irish Traveller" },
"3" => { "value" => "Any other White background" },
}.freeze
end

27
app/models/form/sales/questions/buyer2_ethnic_group.rb

@ -0,0 +1,27 @@
class Form::Sales::Questions::Buyer2EthnicGroup < ::Form::Question
def initialize(id, hsh, page)
super
@id = "ethnic_group2"
@check_answer_label = "Buyer 2’s ethnic group"
@header = "What is buyer 2’s ethnic group?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@inferred_check_answers_value = [{
"condition" => {
"ethnic_group2" => 17,
},
"value" => "Prefers not to say",
}]
@check_answers_card_number = 2
end
ANSWER_OPTIONS = {
"0" => { "value" => "White" },
"1" => { "value" => "Mixed or Multiple ethnic groups" },
"2" => { "value" => "Asian or Asian British" },
"3" => { "value" => "Black, African, Caribbean or Black British" },
"4" => { "value" => "Arab or other ethnic group" },
"divider" => { "value" => true },
"17" => { "value" => "Buyer 1 prefers not to say" },
}.freeze
end

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

@ -7,6 +7,7 @@ class Form::Sales::Questions::Buyer2Income < ::Form::Question
@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." @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
@max = 999_999
@step = 1 @step = 1
@width = 5 @width = 5
@prefix = "£" @prefix = "£"

25
app/models/form/sales/questions/buyer2_income_value_check.rb

@ -0,0 +1,25 @@
class Form::Sales::Questions::Buyer2IncomeValueCheck < ::Form::Question
def initialize(id, hsh, page)
super
@id = "income2_value_check"
@check_answer_label = "Income confirmation"
@header = "Are you sure this is correct?"
@type = "interruption_screen"
@answer_options = {
"0" => { "value" => "Yes" },
"1" => { "value" => "No" },
}
@hidden_in_check_answers = {
"depends_on" => [
{
"income2_value_check" => 0,
},
{
"income2_value_check" => 1,
},
],
}
@check_answers_card_number = 2
@page = page
end
end

10
app/models/form/sales/questions/previous_postcode_known.rb

@ -10,6 +10,16 @@ class Form::Sales::Questions::PreviousPostcodeKnown < ::Form::Question
"ppostcode_full" => [0], "ppostcode_full" => [0],
} }
@hint_text = "This is also known as the household’s 'last settled home'" @hint_text = "This is also known as the household’s 'last settled home'"
@hidden_in_check_answers = {
"depends_on" => [
{
"ppcodenk" => 0,
},
{
"ppcodenk" => 1,
},
],
}
end end
ANSWER_OPTIONS = { ANSWER_OPTIONS = {

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

@ -0,0 +1,16 @@
class Form::Sales::Questions::StaircaseSale < ::Form::Question
def initialize(id, hsh, page)
super
@id = "staircasesale"
@check_answer_label = "Part of a back-to-back staircasing transaction"
@header = "Is this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
}.freeze
end

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

@ -32,8 +32,10 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Sales::Pages::RetirementValueCheck.new("age_2_buyer_retirement_value_check", nil, self, person_index: 2), Form::Sales::Pages::RetirementValueCheck.new("age_2_buyer_retirement_value_check", 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_buyer_retirement_value_check", nil, self, person_index: 2), Form::Sales::Pages::RetirementValueCheck.new("gender_2_buyer_retirement_value_check", nil, self, person_index: 2),
ethnic_pages_for_buyer_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_buyer_retirement_value_check", nil, self, person_index: 2), Form::Sales::Pages::RetirementValueCheck.new("working_situation_2_retirement_value_check_joint_purchase", nil, self, person_index: 2),
Form::Sales::Pages::Buyer2IncomeValueCheck.new("working_situation_buyer_2_income_value_check", nil, self),
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_2_known", nil, self, person_index: 2), Form::Sales::Pages::PersonKnown.new("person_2_known", nil, self, person_index: 2),
@ -68,6 +70,17 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Sales::Pages::RetirementValueCheck.new("gender_5_retirement_value_check", nil, self, person_index: 5), Form::Sales::Pages::RetirementValueCheck.new("gender_5_retirement_value_check", nil, self, person_index: 5),
Form::Sales::Pages::PersonWorkingSituation.new("person_5_working_situation", nil, self, person_index: 5), Form::Sales::Pages::PersonWorkingSituation.new("person_5_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::RetirementValueCheck.new("working_situation_5_retirement_value_check", nil, self, person_index: 5),
] ].flatten.compact
end
def ethnic_pages_for_buyer_2
if form.start_date.year >= 2023
[Form::Sales::Pages::Buyer2EthnicGroup.new(nil, nil, self),
Form::Sales::Pages::Buyer2EthnicBackgroundBlack.new(nil, nil, self),
Form::Sales::Pages::Buyer2EthnicBackgroundAsian.new(nil, nil, self),
Form::Sales::Pages::Buyer2EthnicBackgroundArab.new(nil, nil, self),
Form::Sales::Pages::Buyer2EthnicBackgroundMixed.new(nil, nil, self),
Form::Sales::Pages::Buyer2EthnicBackgroundWhite.new(nil, nil, self)]
end
end end
end end

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

@ -15,6 +15,7 @@ class Form::Sales::Subsections::IncomeBenefitsAndSavings < ::Form::Subsection
Form::Sales::Pages::MortgageValueCheck.new("buyer_1_mortgage_value_check", nil, self, 1), Form::Sales::Pages::MortgageValueCheck.new("buyer_1_mortgage_value_check", nil, self, 1),
Form::Sales::Pages::Buyer2Income.new(nil, nil, self), Form::Sales::Pages::Buyer2Income.new(nil, nil, self),
Form::Sales::Pages::MortgageValueCheck.new("buyer_2_income_mortgage_value_check", nil, self, 2), Form::Sales::Pages::MortgageValueCheck.new("buyer_2_income_mortgage_value_check", nil, self, 2),
Form::Sales::Pages::Buyer2IncomeValueCheck.new("buyer_2_income_value_check", nil, self),
Form::Sales::Pages::Buyer2Mortgage.new(nil, nil, self), Form::Sales::Pages::Buyer2Mortgage.new(nil, nil, self),
Form::Sales::Pages::MortgageValueCheck.new("buyer_2_mortgage_value_check", nil, self, 2), Form::Sales::Pages::MortgageValueCheck.new("buyer_2_mortgage_value_check", nil, self, 2),
Form::Sales::Pages::HousingBenefits.new(nil, nil, self), Form::Sales::Pages::HousingBenefits.new(nil, nil, self),

8
app/models/form_handler.rb

@ -35,8 +35,8 @@ class FormHandler
def sales_forms def sales_forms
{ {
"current_sales" => Form.new(nil, current_collection_start_year, SALES_SECTIONS, "sales"), "current_sales" => Form.new(nil, current_collection_start_year, SALES_SECTIONS, "sales"),
"previous_sales" => Form.new(nil, current_collection_start_year - 1, SALES_SECTIONS, "sales"), "previous_sales" => Form.new(nil, previous_collection_start_year, SALES_SECTIONS, "sales"),
"next_sales" => Form.new(nil, current_collection_start_year + 1, SALES_SECTIONS, "sales"), "next_sales" => Form.new(nil, next_collection_start_year, SALES_SECTIONS, "sales"),
} }
end end
@ -52,10 +52,10 @@ class FormHandler
end end
if forms["previous_lettings"].blank? && current_collection_start_year >= 2022 if forms["previous_lettings"].blank? && current_collection_start_year >= 2022
forms["previous_lettings"] = Form.new(nil, current_collection_start_year - 1, LETTINGS_SECTIONS, "lettings") forms["previous_lettings"] = Form.new(nil, previous_collection_start_year, LETTINGS_SECTIONS, "lettings")
end end
forms["current_lettings"] = Form.new(nil, current_collection_start_year, LETTINGS_SECTIONS, "lettings") if forms["current_lettings"].blank? forms["current_lettings"] = Form.new(nil, current_collection_start_year, LETTINGS_SECTIONS, "lettings") if forms["current_lettings"].blank?
forms["next_lettings"] = Form.new(nil, current_collection_start_year + 1, LETTINGS_SECTIONS, "lettings") if forms["next_lettings"].blank? forms["next_lettings"] = Form.new(nil, next_collection_start_year, LETTINGS_SECTIONS, "lettings") if forms["next_lettings"].blank?
forms forms
end end

12
app/models/lettings_log.rb

@ -70,6 +70,14 @@ class LettingsLog < Log
collection_start_year collection_start_year
end end
def lettings?
true
end
def sales?
false
end
def form_name def form_name
return unless startdate return unless startdate
@ -481,10 +489,6 @@ class LettingsLog < Log
location.type_of_unit_before_type_cast if location location.type_of_unit_before_type_cast if location
end end
def lettings?
true
end
def rent_type_detail def rent_type_detail
form.get_question("rent_type", self)&.label_from_value(rent_type) form.get_question("rent_type", self)&.label_from_value(rent_type)
end end

6
app/models/location.rb

@ -366,6 +366,12 @@ class Location < ApplicationRecord
enum type_of_unit: TYPE_OF_UNIT enum type_of_unit: TYPE_OF_UNIT
def self.find_by_id_on_mulitple_fields(id)
return if id.nil?
where(id:).or(where(old_visible_id: id)).first
end
def postcode=(postcode) def postcode=(postcode)
if postcode if postcode
super UKPostcode.parse(postcode).to_s super UKPostcode.parse(postcode).to_s

8
app/models/log.rb

@ -43,6 +43,10 @@ class Log < ApplicationRecord
false false
end end
def sales?
false
end
def ethnic_refused? def ethnic_refused?
ethnic_group == 17 ethnic_group == 17
end end
@ -143,4 +147,8 @@ private
self[is_inferred_key] = false self[is_inferred_key] = false
self[postcode_key] = nil self[postcode_key] = nil
end end
def format_as_currency(num_string)
ActionController::Base.helpers.number_to_currency(num_string, unit: "£")
end
end end

27
app/models/organisation.rb

@ -17,8 +17,21 @@ class Organisation < ApplicationRecord
has_many :managing_agent_relationships, foreign_key: :parent_organisation_id, class_name: "OrganisationRelationship" has_many :managing_agent_relationships, foreign_key: :parent_organisation_id, class_name: "OrganisationRelationship"
has_many :managing_agents, through: :managing_agent_relationships, source: :child_organisation has_many :managing_agents, through: :managing_agent_relationships, source: :child_organisation
def affiliated_stock_owners
ids = []
if holds_own_stock? && persisted?
ids << id
end
ids.concat(stock_owners.pluck(:id))
Organisation.where(id: ids)
end
scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") } scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") }
scope :search_by, ->(param) { search_by_name(param) } scope :search_by, ->(param) { search_by_name(param) }
has_paper_trail has_paper_trail
auto_strip_attributes :name auto_strip_attributes :name
@ -35,6 +48,20 @@ class Organisation < ApplicationRecord
validates :name, presence: { message: I18n.t("validations.organisation.name_missing") } validates :name, presence: { message: I18n.t("validations.organisation.name_missing") }
validates :provider_type, presence: { message: I18n.t("validations.organisation.provider_type_missing") } validates :provider_type, presence: { message: I18n.t("validations.organisation.provider_type_missing") }
def self.find_by_id_on_mulitple_fields(id)
return if id.nil?
if id.start_with?("ORG")
where(id: id[3..]).first
else
where(old_visible_id: id).first
end
end
def can_be_managed_by?(organisation:)
organisation == self || managing_agents.include?(organisation)
end
def lettings_logs def lettings_logs
LettingsLog.filter_by_organisation(self) LettingsLog.filter_by_organisation(self)
end end

30
app/models/sales_log.rb

@ -38,6 +38,14 @@ class SalesLog < Log
OPTIONAL_FIELDS = %w[saledate_check purchid monthly_charges_value_check old_persons_shared_ownership_value_check].freeze OPTIONAL_FIELDS = %w[saledate_check purchid monthly_charges_value_check old_persons_shared_ownership_value_check].freeze
RETIREMENT_AGES = { "M" => 65, "F" => 60, "X" => 65 }.freeze RETIREMENT_AGES = { "M" => 65, "F" => 60, "X" => 65 }.freeze
def lettings?
false
end
def sales?
true
end
def startdate def startdate
saledate saledate
end end
@ -116,6 +124,10 @@ class SalesLog < Log
la && LONDON_BOROUGHS.include?(la) la && LONDON_BOROUGHS.include?(la)
end end
def property_not_in_london?
!london_property?
end
def income1_used_for_mortgage? def income1_used_for_mortgage?
inc1mort == 1 inc1mort == 1
end end
@ -223,6 +235,10 @@ class SalesLog < Log
type == 24 type == 24
end end
def is_bedsit?
proptype == 2
end
def shared_ownership_scheme? def shared_ownership_scheme?
ownershipsch == 1 ownershipsch == 1
end end
@ -244,4 +260,18 @@ class SalesLog < Log
def purchase_price_soft_max def purchase_price_soft_max
LaSaleRange.find_by(start_year: collection_start_year, la:, bedrooms: beds).soft_max LaSaleRange.find_by(start_year: collection_start_year, la:, bedrooms: beds).soft_max
end end
def income_soft_min_for_ecstat(ecstat_field)
economic_status_code = public_send(ecstat_field)
return unless ALLOWED_INCOME_RANGES_SALES
soft_min = ALLOWED_INCOME_RANGES_SALES[economic_status_code]&.soft_min
format_as_currency(soft_min)
end
def field_formatted_as_currency(field_name)
field_value = public_send(field_name)
format_as_currency(field_value)
end
end end

10
app/models/scheme.rb

@ -110,6 +110,16 @@ class Scheme < ApplicationRecord
enum arrangement_type: ARRANGEMENT_TYPE, _suffix: true enum arrangement_type: ARRANGEMENT_TYPE, _suffix: true
def self.find_by_id_on_mulitple_fields(id)
return if id.nil?
if id.start_with?("S")
where(id: id[1..]).first
else
where(old_visible_id: id).first
end
end
def id_to_display def id_to_display
"S#{id}" "S#{id}"
end end

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

@ -3,20 +3,36 @@ module Validations::Sales::FinancialValidations
# or 'validate_' to run on submit as well # or 'validate_' to run on submit as well
def validate_income1(record) def validate_income1(record)
if record.ecstat1 && record.income1 && record.la && record.ownershipsch == 1 return unless record.income1 && record.la && record.shared_ownership_scheme?
if record.london_property?
record.errors.add :income1, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 relevant_fields = %i[income1 ownershipsch la postcode_full]
record.errors.add :ecstat1, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 if record.london_property? && record.income1 > 90_000
record.errors.add :ownershipsch, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.over_hard_max_for_london") }
record.errors.add :la, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 elsif record.property_not_in_london? && record.income1 > 80_000
record.errors.add :postcode_full, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.over_hard_max_for_outside_london") }
elsif record.income1 > 80_000 end
record.errors.add :income1, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) end
record.errors.add :ecstat1, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000)
record.errors.add :ownershipsch, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) def validate_income2(record)
record.errors.add :la, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) if record.income1 > 80_000 return unless record.income2 && record.la && record.shared_ownership_scheme?
record.errors.add :postcode_full, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) if record.income1 > 80_000
end relevant_fields = %i[income2 ownershipsch la postcode_full]
if record.london_property? && record.income2 > 90_000
relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.over_hard_max_for_london") }
elsif record.property_not_in_london? && record.income2 > 80_000
relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.over_hard_max_for_outside_london") }
end
end
def validate_combined_income(record)
return unless record.income1 && record.income2 && record.la && record.shared_ownership_scheme?
combined_income = record.income1 + record.income2
relevant_fields = %i[income1 income2 ownershipsch la postcode_full]
if record.london_property? && combined_income > 90_000
relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.combined_over_hard_max_for_london") }
elsif record.property_not_in_london? && combined_income > 80_000
relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.combined_over_hard_max_for_outside_london") }
end end
end end
@ -36,6 +52,15 @@ module Validations::Sales::FinancialValidations
end end
end end
def validate_child_income(record)
return unless record.income2 && record.ecstat2
if record.income2.positive? && is_economic_status_child?(record.ecstat2)
record.errors.add :ecstat2, I18n.t("validations.financial.income.child_has_income")
record.errors.add :income2, I18n.t("validations.financial.income.child_has_income")
end
end
def validate_percentage_owned_not_too_much_if_older_person(record) def validate_percentage_owned_not_too_much_if_older_person(record)
return unless record.old_persons_shared_ownership? && record.stairowned return unless record.old_persons_shared_ownership? && record.stairowned
@ -44,4 +69,14 @@ module Validations::Sales::FinancialValidations
record.errors.add :type, I18n.t("validations.financial.staircasing.older_person_percentage_owned_maximum_75") record.errors.add :type, I18n.t("validations.financial.staircasing.older_person_percentage_owned_maximum_75")
end end
end end
private
def is_relationship_child?(relationship)
relationship == "C"
end
def is_economic_status_child?(economic_status)
economic_status == 9
end
end end

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

@ -7,4 +7,13 @@ module Validations::Sales::PropertyValidations
record.errors.add :ppostcode_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 end
def validate_bedsit_number_of_beds(record)
return unless record.proptype.present? && record.beds.present?
if record.is_bedsit? && record.beds > 1
record.errors.add :proptype, I18n.t("validations.property.proptype.bedsits_have_max_one_bedroom")
record.errors.add :beds, I18n.t("validations.property.beds.bedsits_have_max_one_bedroom")
end
end
end end

38
app/models/validations/sales/setup_validations.rb

@ -1,11 +1,45 @@
module Validations::Sales::SetupValidations module Validations::Sales::SetupValidations
include Validations::SharedValidations include Validations::SharedValidations
include CollectionTimeHelper
def validate_saledate(record) def validate_saledate(record)
return unless record.saledate && date_valid?("saledate", record) return unless record.saledate && date_valid?("saledate", record)
unless record.saledate.between?(Time.zone.local(2022, 4, 1), Time.zone.local(2023, 3, 31)) || !FeatureToggle.saledate_collection_window_validation_enabled? unless record.saledate.between?(active_collection_start_date, current_collection_end_date) || !FeatureToggle.saledate_collection_window_validation_enabled?
record.errors.add :saledate, I18n.t("validations.setup.saledate.financial_year") record.errors.add :saledate, validation_error_message
end
end
private
def active_collection_start_date
if FormHandler.instance.sales_in_crossover_period?
previous_collection_start_date
else
current_collection_start_date
end
end
def validation_error_message
current_end_year_long = current_collection_end_date.strftime("#{current_collection_end_date.day.ordinalize} %B %Y")
if FormHandler.instance.sales_in_crossover_period?
I18n.t(
"validations.setup.saledate.previous_and_current_financial_year",
previous_start_year_short: previous_collection_start_date.strftime("%y"),
previous_end_year_short: previous_collection_end_date.strftime("%y"),
previous_start_year_long: previous_collection_start_date.strftime("#{previous_collection_start_date.day.ordinalize} %B %Y"),
current_end_year_short: current_collection_end_date.strftime("%y"),
current_end_year_long:,
)
else
I18n.t(
"validations.setup.saledate.current_financial_year",
current_start_year_short: current_collection_start_date.strftime("%y"),
current_end_year_short: current_collection_end_date.strftime("%y"),
current_start_year_long: current_collection_start_date.strftime("#{current_collection_start_date.day.ordinalize} %B %Y"),
current_end_year_long:,
)
end end
end end
end end

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

@ -1,5 +1,5 @@
module Validations::Sales::SoftValidations module Validations::Sales::SoftValidations
ALLOWED_INCOME_RANGES = { ALLOWED_INCOME_RANGES_SALES = {
1 => OpenStruct.new(soft_min: 5000), 1 => OpenStruct.new(soft_min: 5000),
2 => OpenStruct.new(soft_min: 1500), 2 => OpenStruct.new(soft_min: 1500),
3 => OpenStruct.new(soft_min: 1000), 3 => OpenStruct.new(soft_min: 1000),
@ -8,9 +8,15 @@ module Validations::Sales::SoftValidations
}.freeze }.freeze
def income1_under_soft_min? def income1_under_soft_min?
return false unless ecstat1 && income1 && ALLOWED_INCOME_RANGES[ecstat1] return false unless ecstat1 && income1 && ALLOWED_INCOME_RANGES_SALES[ecstat1]
income1 < ALLOWED_INCOME_RANGES[ecstat1][:soft_min] income1 < ALLOWED_INCOME_RANGES_SALES[ecstat1][:soft_min]
end
def income2_under_soft_min?
return false unless ecstat2 && income2 && ALLOWED_INCOME_RANGES_SALES[ecstat2]
income2 < ALLOWED_INCOME_RANGES_SALES[ecstat2][:soft_min]
end end
def staircase_bought_above_fifty? def staircase_bought_above_fifty?

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

@ -10,6 +10,8 @@ class BulkUpload::Lettings::LogCreator
row_parsers.each do |row_parser| row_parsers.each do |row_parser|
row_parser.valid? row_parser.valid?
next if row_parser.blank_row?
row_parser.log.blank_invalid_non_setup_fields! row_parser.log.blank_invalid_non_setup_fields!
row_parser.log.bulk_upload = bulk_upload row_parser.log.bulk_upload = bulk_upload

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

@ -3,11 +3,12 @@ class BulkUpload::Lettings::RowParser
include ActiveModel::Attributes include ActiveModel::Attributes
attribute :bulk_upload attribute :bulk_upload
attribute :block_log_creation, :boolean, default: -> { false }
attribute :field_1, :integer attribute :field_1, :integer
attribute :field_2 attribute :field_2
attribute :field_3 attribute :field_3
attribute :field_4, :integer attribute :field_4, :string
attribute :field_5, :integer attribute :field_5, :integer
attribute :field_6 attribute :field_6
attribute :field_7, :string attribute :field_7, :string
@ -114,9 +115,9 @@ class BulkUpload::Lettings::RowParser
attribute :field_108, :string attribute :field_108, :string
attribute :field_109, :string attribute :field_109, :string
attribute :field_110 attribute :field_110
attribute :field_111, :integer attribute :field_111, :string
attribute :field_112, :string attribute :field_112, :string
attribute :field_113, :integer attribute :field_113, :string
attribute :field_114, :integer attribute :field_114, :integer
attribute :field_115 attribute :field_115
attribute :field_116, :integer attribute :field_116, :integer
@ -142,17 +143,38 @@ class BulkUpload::Lettings::RowParser
validates :field_1, presence: { message: I18n.t("validations.not_answered", question: "letting type") }, validates :field_1, presence: { message: I18n.t("validations.not_answered", question: "letting type") },
inclusion: { in: (1..12).to_a, message: I18n.t("validations.invalid_option", question: "letting type") } inclusion: { in: (1..12).to_a, message: I18n.t("validations.invalid_option", question: "letting type") }
validates :field_4, presence: { if: proc { [2, 4, 6, 8, 10, 12].include?(field_1) } } validates :field_4, presence: { if: proc { [2, 4, 6, 8, 10, 12].include?(field_1) } }
validates :field_98, format: { with: /\A\d{2}\z/, message: I18n.t("validations.setup.startdate.year_not_two_digits") }
validate :validate_data_types validate :validate_data_types
validate :validate_nulls validate :validate_nulls
validate :validate_relevant_collection_window validate :validate_relevant_collection_window
validate :validate_la_with_local_housing_referral validate :validate_la_with_local_housing_referral
validate :validate_cannot_be_la_referral_if_general_needs validate :validate_cannot_be_la_referral_if_general_needs_and_la
validate :leaving_reason_for_renewal validate :validate_leaving_reason_for_renewal
validate :validate_lettings_type_matches_bulk_upload
validate :validate_only_one_housing_needs_type
validate :validate_no_disabled_needs_conjunction
validate :validate_dont_know_disabled_needs_conjunction
validate :validate_no_and_dont_know_disabled_needs_conjunction
validate :validate_owning_org_permitted
validate :validate_owning_org_owns_stock
validate :validate_owning_org_exists
validate :validate_managing_org_related
validate :validate_managing_org_exists
validate :validate_scheme_related
validate :validate_scheme_exists
validate :validate_location_related
validate :validate_location_exists
def valid? def valid?
errors.clear errors.clear
return true if blank_row?
super super
log.valid? log.valid?
@ -165,14 +187,139 @@ class BulkUpload::Lettings::RowParser
errors.blank? errors.blank?
end end
def blank_row?
attribute_set.to_hash.reject { |k, _| %w[bulk_upload block_log_creation].include?(k) }.values.compact.empty?
end
def log def log
@log ||= LettingsLog.new(attributes_for_log) @log ||= LettingsLog.new(attributes_for_log)
end end
def block_log_creation!
self.block_log_creation = true
end
def block_log_creation?
block_log_creation
end
private private
def validate_cannot_be_la_referral_if_general_needs def validate_location_related
if field_78 == 4 && bulk_upload.general_needs? return if scheme.blank? || location.blank?
unless location.scheme == scheme
block_log_creation!
errors.add(:field_5, "Scheme code must relate to a location that is owned by owning organisation or managing organisation")
end
end
def location
return if scheme.nil?
@location ||= scheme.locations.find_by_id_on_mulitple_fields(field_5)
end
def validate_location_exists
if scheme && field_5.present? && location.nil?
errors.add(:field_5, "Location could be found with provided scheme code")
end
end
def validate_scheme_related
return unless field_4.present? && scheme.present?
owned_by_owning_org = owning_organisation && scheme.owning_organisation == owning_organisation
owned_by_managing_org = managing_organisation && scheme.owning_organisation == managing_organisation
unless owned_by_owning_org || owned_by_managing_org
block_log_creation!
errors.add(:field_4, "This management group code does not belong to your organisation, or any of your stock owners / managing agents")
end
end
def validate_scheme_exists
if field_4.present? && scheme.nil?
errors.add(:field_4, "The management group code is not correct")
end
end
def validate_managing_org_related
if owning_organisation && managing_organisation && !owning_organisation.can_be_managed_by?(organisation: managing_organisation)
block_log_creation!
errors.add(:field_113, "This managing organisation does not have a relationship with the owning organisation")
end
end
def validate_managing_org_exists
if managing_organisation.nil?
errors.delete(:field_113)
errors.add(:field_113, "The managing organisation code is incorrect")
end
end
def validate_owning_org_owns_stock
if owning_organisation && !owning_organisation.holds_own_stock?
block_log_creation!
errors.delete(:field_111)
errors.add(:field_111, "The owning organisation code provided is for an organisation that does not own stock")
end
end
def validate_owning_org_exists
if owning_organisation.nil?
errors.delete(:field_111)
errors.add(:field_111, "The owning organisation code is incorrect")
end
end
def validate_owning_org_permitted
if owning_organisation && !bulk_upload.user.organisation.affiliated_stock_owners.include?(owning_organisation)
block_log_creation!
errors.delete(:field_111)
errors.add(:field_111, "You do not have permission to add logs for this owning organisation")
end
end
def validate_no_and_dont_know_disabled_needs_conjunction
if field_59 == 1 && field_60 == 1
errors.add(:field_59, I18n.t("validations.household.housingneeds.no_and_dont_know_disabled_needs_conjunction"))
errors.add(:field_60, I18n.t("validations.household.housingneeds.no_and_dont_know_disabled_needs_conjunction"))
end
end
def validate_dont_know_disabled_needs_conjunction
if field_60 == 1 && [field_55, field_56, field_57, field_58].compact.count.positive?
errors.add(:field_60, I18n.t("validations.household.housingneeds.dont_know_disabled_needs_conjunction"))
end
end
def validate_no_disabled_needs_conjunction
if field_59 == 1 && [field_55, field_56, field_57, field_58].compact.count.positive?
errors.add(:field_59, I18n.t("validations.household.housingneeds.no_disabled_needs_conjunction"))
end
end
def validate_only_one_housing_needs_type
if [field_55, field_56, field_57].compact.count.positive?
errors.add(:field_55, I18n.t("validations.household.housingneeds_type.only_one_option_permitted"))
errors.add(:field_56, I18n.t("validations.household.housingneeds_type.only_one_option_permitted"))
errors.add(:field_57, I18n.t("validations.household.housingneeds_type.only_one_option_permitted"))
end
end
def validate_lettings_type_matches_bulk_upload
if [1, 3, 5, 7, 9, 11].include?(field_1) && !bulk_upload.general_needs?
errors.add(:field_1, I18n.t("validations.setup.lettype.supported_housing_mismatch"))
end
if [2, 4, 6, 8, 10, 12].include?(field_1) && !bulk_upload.supported_housing?
errors.add(:field_1, I18n.t("validations.setup.lettype.general_needs_mismatch"))
end
end
def validate_cannot_be_la_referral_if_general_needs_and_la
if field_78 == 4 && bulk_upload.general_needs? && owning_organisation && owning_organisation.la?
errors.add :field_78, I18n.t("validations.household.referral.la_general_needs.prp_referred_by_la") errors.add :field_78, I18n.t("validations.household.referral.la_general_needs.prp_referred_by_la")
end end
end end
@ -183,7 +330,7 @@ private
end end
end end
def leaving_reason_for_renewal def validate_leaving_reason_for_renewal
if field_134 == 1 && ![40, 42].include?(field_52) if field_134 == 1 && ![40, 42].include?(field_52)
errors.add(:field_52, I18n.t("validations.household.reason.renewal_reason_needed")) errors.add(:field_52, I18n.t("validations.household.reason.renewal_reason_needed"))
end end
@ -399,6 +546,8 @@ private
def startdate def startdate
Date.new(field_98 + 2000, field_97, field_96) if field_98.present? && field_97.present? && field_96.present? Date.new(field_98 + 2000, field_97, field_96) if field_98.present? && field_97.present? && field_96.present?
rescue Date::Error
Date.new
end end
def renttype def renttype
@ -435,15 +584,19 @@ private
end end
def owning_organisation def owning_organisation
Organisation.find_by(old_visible_id: field_111) Organisation.find_by_id_on_mulitple_fields(field_111)
end end
def owning_organisation_id def owning_organisation_id
owning_organisation&.id owning_organisation&.id
end end
def managing_organisation
Organisation.find_by_id_on_mulitple_fields(field_113)
end
def managing_organisation_id def managing_organisation_id
Organisation.find_by(old_visible_id: field_113)&.id managing_organisation&.id
end end
def attributes_for_log def attributes_for_log
@ -458,6 +611,7 @@ private
attributes["managing_organisation_id"] = managing_organisation_id attributes["managing_organisation_id"] = managing_organisation_id
attributes["renewal"] = renewal attributes["renewal"] = renewal
attributes["scheme"] = scheme attributes["scheme"] = scheme
attributes["location"] = location
attributes["created_by"] = bulk_upload.user attributes["created_by"] = bulk_upload.user
attributes["needstype"] = bulk_upload.needstype attributes["needstype"] = bulk_upload.needstype
attributes["rent_type"] = rent_type attributes["rent_type"] = rent_type
@ -535,6 +689,8 @@ private
attributes["preg_occ"] = field_47 attributes["preg_occ"] = field_47
attributes["housingneeds"] = housingneeds attributes["housingneeds"] = housingneeds
attributes["housingneeds_type"] = housingneeds_type
attributes["housingneeds_other"] = housingneeds_other
attributes["illness"] = field_118 attributes["illness"] = field_118
@ -791,7 +947,7 @@ private
def housingneeds def housingneeds
if field_59 == 1 if field_59 == 1
1 2
elsif field_60 == 1 elsif field_60 == 1
3 3
else else
@ -799,6 +955,20 @@ private
end end
end end
def housingneeds_type
if field_55 == 1
0
elsif field_56 == 1
1
elsif field_57 == 1
2
end
end
def housingneeds_other
return 1 if field_58 == 1
end
def ethnic_group_from_ethnic def ethnic_group_from_ethnic
return nil if field_43.blank? return nil if field_43.blank?
@ -819,6 +989,6 @@ private
end end
def scheme def scheme
@scheme ||= Scheme.find_by(old_visible_id: field_4) @scheme ||= Scheme.find_by_id_on_mulitple_fields(field_4)
end end
end end

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

@ -176,6 +176,7 @@ class BulkUpload::Lettings::Validator
def create_logs? def create_logs?
return false if any_setup_sections_incomplete? return false if any_setup_sections_incomplete?
return false if over_column_error_threshold? return false if over_column_error_threshold?
return false if row_parsers.any?(&:block_log_creation?)
row_parsers.all? { |row_parser| row_parser.log.valid? } row_parsers.all? { |row_parser| row_parser.log.valid? }
end end

5
app/services/bulk_upload/processor.rb

@ -13,6 +13,7 @@ class BulkUpload::Processor
validator.call validator.call
create_logs if validator.create_logs? create_logs if validator.create_logs?
send_correct_and_upload_again_mail unless validator.create_logs?
send_fix_errors_mail if created_logs_but_incompleted? send_fix_errors_mail if created_logs_but_incompleted?
send_success_mail if created_logs_and_all_completed? send_success_mail if created_logs_and_all_completed?
@ -25,6 +26,10 @@ class BulkUpload::Processor
private private
def send_correct_and_upload_again_mail
BulkUploadMailer.send_correct_and_upload_again_mail(bulk_upload:).deliver_later
end
def send_fix_errors_mail def send_fix_errors_mail
BulkUploadMailer.send_bulk_upload_with_errors_mail(bulk_upload:).deliver_later BulkUploadMailer.send_bulk_upload_with_errors_mail(bulk_upload:).deliver_later
end end

9
app/services/exports/lettings_log_export_constants.rb

@ -7,13 +7,6 @@ module Exports::LettingsLogExportConstants
csv: 2, csv: 2,
}.freeze }.freeze
QUARTERS = {
0 => "jan_mar",
1 => "apr_jun",
2 => "jul_sep",
3 => "oct_dec",
}.freeze
EXPORT_FIELDS = Set[ EXPORT_FIELDS = Set[
"armedforces", "armedforces",
"beds", "beds",
@ -30,7 +23,7 @@ module Exports::LettingsLogExportConstants
"confidential", "confidential",
"earnings", "earnings",
"ethnic", "ethnic",
"form", "formid",
"has_benefits", "has_benefits",
"hb", "hb",
"hbrentshortfall", "hbrentshortfall",

11
app/services/exports/lettings_log_export_service.rb

@ -1,6 +1,7 @@
module Exports module Exports
class LettingsLogExportService class LettingsLogExportService
include Exports::LettingsLogExportConstants include Exports::LettingsLogExportConstants
include CollectionTimeHelper
def initialize(storage_service, logger = Rails.logger) def initialize(storage_service, logger = Rails.logger)
@storage_service = storage_service @storage_service = storage_service
@ -66,11 +67,11 @@ module Exports
return unless lettings_log.startdate return unless lettings_log.startdate
collection_start = lettings_log.collection_start_year collection_start = lettings_log.collection_start_year
month = lettings_log.startdate.month start_month = collection_start_date(lettings_log.startdate).strftime("%b")
quarter = QUARTERS[(month - 1) / 3] end_month = collection_end_date(lettings_log.startdate).strftime("%b")
base_number_str = "f#{base_number.to_s.rjust(4, '0')}" base_number_str = "f#{base_number.to_s.rjust(4, '0')}"
increment_str = "inc#{increment.to_s.rjust(4, '0')}" increment_str = "inc#{increment.to_s.rjust(4, '0')}"
"core_#{collection_start}_#{collection_start + 1}_#{quarter}_#{base_number_str}_#{increment_str}" "core_#{collection_start}_#{collection_start + 1}_#{start_month}_#{end_month}_#{base_number_str}_#{increment_str}".downcase
end end
def write_export_archive(export, lettings_logs) def write_export_archive(export, lettings_logs)
@ -152,7 +153,7 @@ module Exports
def apply_cds_transformation(lettings_log, export_mode) def apply_cds_transformation(lettings_log, export_mode)
attribute_hash = lettings_log.attributes_before_type_cast attribute_hash = lettings_log.attributes_before_type_cast
attribute_hash["form"] = attribute_hash["old_form_id"] || (attribute_hash["id"] + LOG_ID_OFFSET) attribute_hash["formid"] = attribute_hash["old_form_id"] || (attribute_hash["id"] + LOG_ID_OFFSET)
# We can't have a variable number of columns in CSV # We can't have a variable number of columns in CSV
unless export_mode == EXPORT_MODE[:csv] unless export_mode == EXPORT_MODE[:csv]
@ -214,7 +215,7 @@ module Exports
attribute_hash["reghome"] = scheme.registered_under_care_act_before_type_cast attribute_hash["reghome"] = scheme.registered_under_care_act_before_type_cast
attribute_hash["schtype"] = scheme.scheme_type_before_type_cast attribute_hash["schtype"] = scheme.scheme_type_before_type_cast
attribute_hash["support"] = scheme.support_type_before_type_cast attribute_hash["support"] = scheme.support_type_before_type_cast
attribute_hash["units_scheme"] = scheme.locations.map(&:units).sum attribute_hash["units_scheme"] = scheme.locations.map(&:units).compact.sum
end end
def add_location_fields!(location, attribute_hash) def add_location_fields!(location, attribute_hash)

45
app/views/content/privacy_notice.md

@ -1,46 +1,47 @@
## How are we using your information? ## How do we use your information?
If your household has entered a new social housing tenancy, social housing providers will share your personal information with the Department for Levelling Up, Housing & Communities (DLUHC) for research and statistical purposes. If your household enters a new social housing tenancy or purchases a social housing property, social housing providers will share your personal information with the Department for Levelling Up, Housing & Communities (DLUHC) for research and statistical purposes only.
## How is this information provided? ## How do we get this information?
The information is provided via ‘<%= t('service_name') %>’, a service funded and managed by DLUHC. It collects information on the tenants or buyers, tenancy or sale, and the dwelling itself. Some of this information is personal and sensitive, so DLUHC is responsible for ensuring that all data is processed in line with data protection legislation. The information is provided via ‘<%= t('service_name') %>’, a service funded and managed by DLUHC. It collects information on the tenants or residents, tenancy or sale, and the dwelling itself. Some of this data is personal and sensitive, so DLUHC is responsible for ensuring it’s processed in line with data protection legislation.
## Why are we sharing this information?
Information collected using this service is shared with other government departments and agencies. Data is shared with the Greater London Authority and the Regulator of Social Housing. Data providers can also access data for their organisations via the online service. Data is only shared for research and statistical purposes. ## Why do we share this information?
Information collected via CORE is shared with other government departments and agencies. It’s shared with the Greater London Authority and the Regulator of Social Housing. Data providers can also access data for their organisations via CORE. Data is only shared for research and statistical purposes.
## How does this affect you? ## How does this affect you?
It will not affect your benefits, services or any treatments you receive. The information shared is anonymous and handled in accordance with the law. We are collecting and sharing your information to help us better understand the social housing market and inform social housing policy. Information sharing will not affect your benefits, services or any treatments you receive. It’s anonymous and handled in accordance with the law. We collect and share your information to help us better understand the social housing market and inform social housing policy.
## If you want to know more ## To find out more…
Social housing lettings and sales data is collected on behalf of DLUHC for research and statistical purposes only. Data providers do not require the consent of tenants to provide the information, but tenants have the right to know how and for what purpose data is being collected, held and used. Social housing lettings and sales data is collected on DLUHC’s behalf. Data providers do not require the tenant or buyer’s consent to provide this information, but tenants and buyers have the right to know how and for what purpose data is being collected, held and used.
The processing must have a lawful basis. In this case the processing is necessary for the performance of a task carried out in the public interest to meet a function of the Crown, a Minister of the Crown, or a government department. Data processing must have a lawful basis. In this case it’s necessary for a task carried out in the public interest meeting a function of the Crown, a Minister of the Crown, or government department.
You have the right to object and you have the right to obtain confirmation that your data is being processed, and to access your personal data. You also have the right to have any incorrect personal data corrected. You have the right to object, and obtain confirmation that your data is being processed, as well as access your personal data, and have any incorrect personal data corrected.
The information collected via this service relates to your tenancy, the dwelling you are living in or buying, and your household. Some of the information may have been provided by you as a tenant when signing the new tenancy or buying your property. Other information has been gathered from the housing management systems of social housing providers. Information collected via CORE relates to your tenancy, the dwelling you are living in or buying, and your household. Some information may have been provided by you (as a tenant or buyer) when signing the new tenancy or buying your property. Other information has been gathered from the housing management systems of social housing providers.
Data collected will be held for as long as necessary for research and statistical purposes. When no longer needed, data will be deleted in a safe manner. We are aware that some of the data collected is particularly sensitive. For example: Collected data will be held for as long as necessary for research and statistical purposes. When no longer needed, data will be deleted in a safe manner. We’re aware some collected data is particularly sensitive. For example:
* ethnic group * ethnic group
* if previous tenure is a hospital or prison or approved probation hostel support * if previous tenure is a hospital, prison or approved probation hostel support
* if household left last settled home because discharged from prison, a long stay hospital or other institution * if household left last settled home because discharged from prison, a long stay hospital or other institution
* if source of referral is probation or prison, youth offending team, community mental health team or health service * if referral source is probation or prison, youth offending or community mental health team, or health service
All the information collected via this service is treated in accordance with data protection requirements and guidelines. DLUHC publishes data annually, in aggregate form, as part of a report and complementary tables.
Data is published by DLUHC in aggregate form on an annual basis as part of a report and complementary tables. * For annual lettings data, visit: [www.gov.uk/government/collections/rents-lettings-and-tenancies](www.gov.uk/government/collections/rents-lettings-and-tenancies)
You can visit <www.gov.uk/government/collections/rents-lettings-and-tenancies> to access the annual publications on lettings. Or visit <gov.uk/government/collections/social-housing-sales-including-right-to-buy-and-transfers> to view the publications on sales. * For annual sales data, visit: [www.gov.uk/government/collections/social-housing-sales-including-right-to-buy-and-transfers](www.gov.uk/government/collections/social-housing-sales-including-right-to-buy-and-transfers)
The detail level data is anonymised and protected to minimise the risk of identification and held with the UK Data Archive for research purposes. Detail-level data is anonymised and protected, minimising identification risk. It's held with the UK Data Archive.
## Making a complaint ## Complaints
If you are unhappy with any aspect of this privacy notice, or how your personal information is being processed, contact the Department Data Protection Officer at: <dataprotection@communities.gsi.gov.uk> If you’re unhappy with any privacy notice aspect, or how we process your information, contact the Department Data Protection Officer: <dataprotection@communities.gsi.gov.uk>
If you are still not happy, you have the right to lodge a complaint with the Information Commissioner’s Office (ICO) at [ico.org.uk/concern](https://ico.org.uk/concern). You also have the right to complain to the Information Commissioner’s Office (ICO): [www.ico.org.uk/concern](www.ico.org.uk/concern)

40
app/views/form/review.html.erb

@ -1,9 +1,18 @@
<% content_for :title, "Review lettings log" %> <% if @log.sales? %>
<% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: { <% content_for :title, "Review sales log" %>
"Logs" => "/logs", <% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: {
"Log #{@log.id}" => "/lettings-logs/#{@log.id}", "Logs" => "/logs",
"Review lettings log" => "", "Log #{@log.id}" => "/sales-logs/#{@log.id}",
}) %> "Review sales log" => "",
}) %>
<% else %>
<% content_for :title, "Review lettings log" %>
<% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: {
"Logs" => "/logs",
"Log #{@log.id}" => "/lettings-logs/#{@log.id}",
"Review lettings log" => "",
}) %>
<% end %>
<div class="govuk-grid-row"> <div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop"> <div class="govuk-grid-column-two-thirds-from-desktop">
@ -16,17 +25,16 @@
<% @log.form.sections.map do |section| %> <% @log.form.sections.map do |section| %>
<h2 class="govuk-heading-m"><%= section.label %></h2> <h2 class="govuk-heading-m"><%= section.label %></h2>
<% section.subsections.map do |subsection| %> <% section.subsections.map do |subsection| %>
<div class="x-govuk-summary-card govuk-!-margin-bottom-6"> <% if total_applicable_questions(subsection, @log, current_user).any? %>
<div class="x-govuk-summary-card__header"> <div class="x-govuk-summary-card govuk-!-margin-bottom-6">
<h3 class="x-govuk-summary-card__title"><%= subsection.label %></h3> <div class="x-govuk-summary-card__header">
<h3 class="x-govuk-summary-card__title"><%= subsection.label %></h3>
</div>
<div class="x-govuk-summary-card__body">
<%= render partial: "form/check_answers_summary_list", locals: { subsection: } %>
</div>
</div> </div>
<div class="x-govuk-summary-card__body"> <% end %>
<%= render partial: "form/check_answers_summary_list", locals: {
subsection:,
lettings_log: @log,
} %>
</div>
</div>
<% end %> <% end %>
<% end %> <% end %>
</div> </div>

3
app/views/logs/_log_filters.erb

@ -6,7 +6,6 @@
<div class="app-filter__content"> <div class="app-filter__content">
<%= form_with html: { method: :get } do |f| %> <%= form_with html: { method: :get } do |f| %>
<% years = { "2021": "2021/22", "2022": "2022/23" } %>
<% all_or_yours = { "all": { label: "All" }, "yours": { label: "Yours" } } %> <% all_or_yours = { "all": { label: "All" }, "yours": { label: "Yours" } } %>
<% if bulk_upload_options(@bulk_upload).present? %> <% if bulk_upload_options(@bulk_upload).present? %>
@ -23,7 +22,7 @@
<%= render partial: "filters/checkbox_filter", <%= render partial: "filters/checkbox_filter",
locals: { locals: {
f: f, f: f,
options: years, options: collection_year_options,
label: "Collection year", label: "Collection year",
category: "years", category: "years",
} %> } %>

2
config/credentials.yml.enc

@ -1 +1 @@
PWC9A1AmalyFv4A63MG9jCm2YtwE1Eyu1BJKodtbmq2nW7FD9svZZ3V4yC0JO9J8sLfCS6UrxQC4TOKKYWP5iYpOr5TKPi9MqNCVueLsoErN+nMAPhCJVpl8+eJ4BOvNYGlmle4bcwbAvHLq6IoANTBjktFh5/tgJS+IHBWK1FwPzK1eAhgQ1fE1jluWslee4iYesmh+ufHIMZAkoWuGJVky4i4uN5nKnwQfaPN5MUxBlvGiDH+s+yex4pcIaJ6hxYWAQRZXJRQVpZcf/agwU0Tk/S/fuDMm8zVeHCQkkmsCYzH5czB8b8IWYwyslqFBZNCix7YnbgwgYk8MUh7wPBuF8CFoPKVyteqic9HgUp9KY8kkt/RcWJ4zpv+Vwz3cre+iZ3S1bxFcSxXqO0MGRug2H9iwhAnBQDLl3vXLNNRYEL5LhNv0Z9Cy7at1fnYe1FcvF+3DR9kG/RAYzR8S2eEDdzBl797+DG81yhfkjP3/gfWxD+J+Mx0F4SDEOaGK5c/MqNTdiRRbnhzuRaQFMg6itoJbZybe+EOQScNft1QLqC4QwPd4Qevhj/A=--QffpsNB1u+1Lk/tD--spkCqBIGHl8g6HGwd89yCg== EZNV2LiNWzf52erbQ41Dz3Bh+2f3Uih8liEyhXp5XzHCLzAbmN6/IJqr7b9cTZiCiroFo4n/dFoG3yYrospp3frKsDXxF1K2/MTCJWjpgnn7wc+HiPQWG0W3HRtQCNkyyrHes0YKcYyDWIP6kztYv1I/Me3p0pGEx6t3CpSTg1v46eRnOlDWiUz3rVxPauwq9IYZ75gmnThqvg/Z8wcYsWLx0arago0SXtRPASCNj4uO/lbqTcAfyIXOTSiOlcAIjoPFRSQY7UqY0o2p8jRR/1L16SmGDsk8ijm+UygNmMexa3Khy5WcKctpQICakHs4NRjHNflqgXpXKL9dVBmNc9d7h+gbhbGJQ53Y0d+a35UbhPRMiv4SRH98FwB+WEsLCDdGSHvdmM6ArfOLljTrqrsmSRf0JfUrvzyVYmMCxjv4xgJwUS/TD5lQD1yPwkp2ss00kQJqzNmB7qwFhA8a3e2iNzV8qtAV/Nj+tMlr99Hb7vZZs98/38G2p5RAsE/5Xl9taKhc/ACnVc/bwJND4JWaBB7duCa08xVB8nkjlt5cCwMurzAcy1ZT+e8JepR+g6s8fpScMEWVJXE0hd8=--rZ41rY9TMXmiBUJw--QiLRVNVXZzTW446s7cec1g==

2
config/forms/2022_2023.json

@ -1,7 +1,7 @@
{ {
"form_type": "lettings", "form_type": "lettings",
"start_date": "2022-04-01T00:00:00.000+01:00", "start_date": "2022-04-01T00:00:00.000+01:00",
"end_date": "2023-07-01T00:00:00.000+01:00", "end_date": "2023-07-09T00:00:00.000+01:00",
"unresolved_log_redirect_page_id": "tenancy_start_date", "unresolved_log_redirect_page_id": "tenancy_start_date",
"sections": { "sections": {
"tenancy_and_property": { "tenancy_and_property": {

17
config/initializers/feature_toggle.rb

@ -1,14 +1,15 @@
class FeatureToggle class FeatureToggle
def self.startdate_two_week_validation_enabled? # Disable check on preview apps to allow for testing of future forms
Rails.env.production? || Rails.env.test? def self.saledate_collection_window_validation_enabled?
Rails.env.production? || Rails.env.test? || Rails.env.staging?
end end
def self.startdate_collection_window_validation_enabled? def self.startdate_two_week_validation_enabled?
Rails.env.production? || Rails.env.test? Rails.env.production? || Rails.env.test? || Rails.env.staging?
end end
def self.saledate_collection_window_validation_enabled? def self.startdate_collection_window_validation_enabled?
Rails.env.production? || Rails.env.test? Rails.env.production? || Rails.env.test? || Rails.env.staging?
end end
def self.sales_log_enabled? def self.sales_log_enabled?
@ -42,4 +43,8 @@ class FeatureToggle
def self.validate_valid_radio_options? def self.validate_valid_radio_options?
!(Rails.env.production? || Rails.env.staging?) !(Rails.env.production? || Rails.env.staging?)
end end
def self.collection_2023_2024_year_enabled?
!Rails.env.production?
end
end end

31
config/locales/en.yml

@ -150,12 +150,17 @@ en:
intermediate_rent_product_name: intermediate_rent_product_name:
blank: "Enter name of other intermediate rent product" blank: "Enter name of other intermediate rent product"
saledate: saledate:
financial_year: "Date must be from 22/23 financial year, which is between 1st April 2022 and 31st March 2023" current_financial_year:
Enter a date within the %{current_start_year_short}/%{current_end_year_short} financial year, which is between %{current_start_year_long} and %{current_end_year_long}
previous_and_current_financial_year:
"Enter a date within the %{previous_start_year_short}/%{previous_end_year_short} or %{previous_end_year_short}/%{current_end_year_short} financial years, which is between %{previous_start_year_long} and %{current_end_year_long}"
startdate: startdate:
later_than_14_days_after: "The tenancy start date must not be later than 14 days from today’s date" later_than_14_days_after: "The tenancy start date must not be later than 14 days from today’s date"
before_scheme_end_date: "The tenancy start date must be before the end date for this supported housing scheme" before_scheme_end_date: "The tenancy start date must be before the end date for this supported housing scheme"
after_void_date: "Enter a tenancy start date that is after the void date" after_void_date: "Enter a tenancy start date that is after the void date"
after_major_repair_date: "Enter a tenancy start date that is after the major repair date" after_major_repair_date: "Enter a tenancy start date that is after the major repair date"
year_not_two_digits: Tenancy start year must be 2 digits
location: location:
deactivated: "The location %{postcode} was deactivated on %{date} and was not available on the day you entered." deactivated: "The location %{postcode} was deactivated on %{date} and was not available on the day you entered."
reactivating_soon: "The location %{postcode} is not available until %{date}. Select another location or edit the tenancy start date" reactivating_soon: "The location %{postcode} is not available until %{date}. Select another location or edit the tenancy start date"
@ -170,6 +175,9 @@ en:
invalid: "Please select owning organisation or managing organisation that you belong to" invalid: "Please select owning organisation or managing organisation that you belong to"
created_by: created_by:
invalid: "Please select owning organisation or managing organisation that you belong to" invalid: "Please select owning organisation or managing organisation that you belong to"
lettype:
general_needs_mismatch: Lettings type must be a general needs type because you selected general needs when uploading the file
supported_housing_mismatch: Lettings type must be a supported housing type because you selected supported housing when uploading the file
property: property:
mrcdate: mrcdate:
@ -199,6 +207,9 @@ 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"
bedsits_have_max_one_bedroom: "Number of bedrooms must be 1 if the property is a bedsit"
proptype:
bedsits_have_max_one_bedroom: "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms"
postcode: postcode:
must_match_previous: "Buyer's last accommodation and discounted ownership postcodes must match" must_match_previous: "Buyer's last accommodation and discounted ownership postcodes must match"
@ -216,8 +227,12 @@ en:
under_hard_min: "Net income cannot be less than £%{hard_min} per week given the tenant’s working situation" under_hard_min: "Net income cannot be less than £%{hard_min} per week given the tenant’s working situation"
freq_missing: "Select how often the household receives income" freq_missing: "Select how often the household receives income"
earnings_missing: "Enter how much income the household has in total" earnings_missing: "Enter how much income the household has in total"
income1: income:
over_hard_max: "Income must be lower than £%{hard_max}" over_hard_max_for_london: "Income must not exceed £90,000 for properties within London local authorities"
over_hard_max_for_outside_london: "Income must not exceed £80,000 for properties outside London local authorities"
combined_over_hard_max_for_london: "Combined income must not exceed £90,000 for properties within London local authorities"
combined_over_hard_max_for_outside_london: "Combined income must not exceed £80,000 for properties outside London local authorities"
child_has_income: "Child's income must be £0"
negative_currency: "Enter an amount above 0" negative_currency: "Enter an amount above 0"
rent: rent:
less_than_shortfall: "Enter an amount that is more than the shortfall in basic rent" less_than_shortfall: "Enter an amount that is more than the shortfall in basic rent"
@ -341,6 +356,12 @@ en:
must_be_child: "Answer must be ‘child’ if the person is aged 16-19 and 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’"
housingneeds_type:
only_one_option_permitted: "Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected"
housingneeds:
no_disabled_needs_conjunction: "No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs"
dont_know_disabled_needs_conjunction: "Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs"
no_and_dont_know_disabled_needs_conjunction: "No disabled access needs and don’t know disabled access needs cannot be selected together"
prevten: prevten:
non_temp_accommodation: "Answer cannot be non-temporary accommodation as this is a re-let to a tenant who occupied the same property as temporary accommodation" non_temp_accommodation: "Answer cannot be non-temporary accommodation as this is a re-let to a tenant who occupied the same property as temporary accommodation"
over_20_foster_care: "Answer cannot be a children’s home or foster care as the lead tenant is 20 or older" over_20_foster_care: "Answer cannot be a children’s home or foster care as the lead tenant is 20 or older"
@ -454,6 +475,8 @@ en:
message: "Net income is lower than expected based on the lead tenant’s working situation. Are you sure this is correct?" message: "Net income is lower than expected based on the lead tenant’s working situation. Are you sure this is correct?"
in_soft_max_range: in_soft_max_range:
message: "Net income is higher than expected based on the lead tenant’s working situation. Are you sure this is correct?" message: "Net income is higher than expected based on the lead tenant’s working situation. Are you sure this is correct?"
income:
under_soft_min_for_economic_status: "You said income was %{income}, which is below this working situation's minimum (%{minimum})"
rent: rent:
outside_range_title: "You told us the rent is %{brent}" outside_range_title: "You told us the rent is %{brent}"
min_hint_text: "The minimum rent expected for this type of property in this local authority is £%{soft_min_for_period}." min_hint_text: "The minimum rent expected for this type of property in this local authority is £%{soft_min_for_period}."
@ -546,4 +569,4 @@ en:
one_argument: "This is based on the tenant’s work situation: %{ecstat1}" one_argument: "This is based on the tenant’s work situation: %{ecstat1}"
title_text: title_text:
no_argument: "Some test text" no_argument: "Some test text"
one_argument: "You said this: %{ecstat1}" one_argument: "You said this: %{argument}"

4
config/routes.rb

@ -171,6 +171,10 @@ Rails.application.routes.draw do
resources :bulk_upload_sales_results, path: "bulk-upload-results", only: [:show] resources :bulk_upload_sales_results, path: "bulk-upload-results", only: [:show]
end end
member do
get "review", to: "form#review"
end
FormHandler.instance.sales_forms.each do |_key, form| FormHandler.instance.sales_forms.each do |_key, form|
form.pages.map do |page| form.pages.map do |page|
get page.id.to_s.dasherize, to: "form#show_page" get page.id.to_s.dasherize, to: "form#show_page"

1185
config/sale_range_data/2023.csv

File diff suppressed because it is too large Load Diff

5
db/migrate/20230105103134_add_income2_value_check.rb

@ -0,0 +1,5 @@
class AddIncome2ValueCheck < ActiveRecord::Migration[7.0]
def change
add_column :sales_logs, :income2_value_check, :integer
end
end

5
db/migrate/20230210122037_add_staircasesale_to_sales_logs.rb

@ -0,0 +1,5 @@
class AddStaircasesaleToSalesLogs < ActiveRecord::Migration[7.0]
def change
add_column :sales_logs, :staircasesale, :integer
end
end

8
db/migrate/20230210143120_add_ethnic_fields_for_buyer2.rb

@ -0,0 +1,8 @@
class AddEthnicFieldsForBuyer2 < ActiveRecord::Migration[7.0]
def change
change_table :sales_logs, bulk: true do |t|
t.column :ethnic_group2, :integer
t.column :ethnicbuy2, :integer
end
end
end

6
db/schema.rb

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_02_03_174815) do ActiveRecord::Schema[7.0].define(version: 2023_02_10_143120) 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"
@ -520,11 +520,15 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_03_174815) do
t.integer "value_value_check" t.integer "value_value_check"
t.integer "old_persons_shared_ownership_value_check" t.integer "old_persons_shared_ownership_value_check"
t.integer "staircase_bought_value_check" t.integer "staircase_bought_value_check"
t.integer "income2_value_check"
t.integer "monthly_charges_value_check" t.integer "monthly_charges_value_check"
t.integer "details_known_5" t.integer "details_known_5"
t.integer "details_known_6" t.integer "details_known_6"
t.integer "saledate_check" t.integer "saledate_check"
t.integer "prevshared" t.integer "prevshared"
t.integer "staircasesale"
t.integer "ethnic_group2"
t.integer "ethnicbuy2"
t.index ["bulk_upload_id"], name: "index_sales_logs_on_bulk_upload_id" 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 ["owning_organisation_id"], name: "index_sales_logs_on_owning_organisation_id" t.index ["owning_organisation_id"], name: "index_sales_logs_on_owning_organisation_id"

63
docs/Gemfile.lock

@ -1,12 +1,11 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
activesupport (6.0.6) activesupport (7.0.4.2)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 1.6, < 2)
minitest (~> 5.1) minitest (>= 5.1)
tzinfo (~> 1.1) tzinfo (~> 2.0)
zeitwerk (~> 2.2, >= 2.2.2)
addressable (2.8.1) addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0) public_suffix (>= 2.0.2, < 6.0)
coffee-script (2.4.1) coffee-script (2.4.1)
@ -14,30 +13,30 @@ GEM
execjs execjs
coffee-script-source (1.11.1) coffee-script-source (1.11.1)
colorator (1.1.0) colorator (1.1.0)
commonmarker (0.23.6) commonmarker (0.23.8)
concurrent-ruby (1.1.10) concurrent-ruby (1.2.0)
dnsruby (1.61.9) dnsruby (1.61.9)
simpleidn (~> 0.1) simpleidn (~> 0.1)
em-websocket (0.5.3) em-websocket (0.5.3)
eventmachine (>= 0.12.9) eventmachine (>= 0.12.9)
http_parser.rb (~> 0) http_parser.rb (~> 0)
ethon (0.15.0) ethon (0.16.0)
ffi (>= 1.15.0) ffi (>= 1.15.0)
eventmachine (1.2.7) eventmachine (1.2.7)
execjs (2.8.1) execjs (2.8.1)
faraday (2.5.2) faraday (2.7.4)
faraday-net_http (>= 2.0, < 3.1) faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4) ruby2_keywords (>= 0.0.4)
faraday-net_http (3.0.0) faraday-net_http (3.0.2)
ffi (1.15.5) ffi (1.15.5)
forwardable-extended (2.6.0) forwardable-extended (2.6.0)
gemoji (3.0.1) gemoji (3.0.1)
github-pages (227) github-pages (228)
github-pages-health-check (= 1.17.9) github-pages-health-check (= 1.17.9)
jekyll (= 3.9.2) jekyll (= 3.9.3)
jekyll-avatar (= 0.7.0) jekyll-avatar (= 0.7.0)
jekyll-coffeescript (= 1.1.1) jekyll-coffeescript (= 1.1.1)
jekyll-commonmark-ghpages (= 0.2.0) jekyll-commonmark-ghpages (= 0.4.0)
jekyll-default-layout (= 0.1.4) jekyll-default-layout (= 0.1.4)
jekyll-feed (= 0.15.1) jekyll-feed (= 0.15.1)
jekyll-gist (= 1.5.0) jekyll-gist (= 1.5.0)
@ -71,7 +70,7 @@ GEM
jemoji (= 0.12.0) jemoji (= 0.12.0)
kramdown (= 2.3.2) kramdown (= 2.3.2)
kramdown-parser-gfm (= 1.1.0) kramdown-parser-gfm (= 1.1.0)
liquid (= 4.0.3) liquid (= 4.0.4)
mercenary (~> 0.3) mercenary (~> 0.3)
minima (= 2.5.1) minima (= 2.5.1)
nokogiri (>= 1.13.6, < 2.0) nokogiri (>= 1.13.6, < 2.0)
@ -83,17 +82,17 @@ GEM
octokit (~> 4.0) octokit (~> 4.0)
public_suffix (>= 3.0, < 5.0) public_suffix (>= 3.0, < 5.0)
typhoeus (~> 1.3) typhoeus (~> 1.3)
html-pipeline (2.14.2) html-pipeline (2.14.3)
activesupport (>= 2) activesupport (>= 2)
nokogiri (>= 1.4) nokogiri (>= 1.4)
http_parser.rb (0.8.0) http_parser.rb (0.8.0)
i18n (0.9.5) i18n (1.12.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
jekyll (3.9.2) jekyll (3.9.3)
addressable (~> 2.4) addressable (~> 2.4)
colorator (~> 1.0) colorator (~> 1.0)
em-websocket (~> 0.5) em-websocket (~> 0.5)
i18n (~> 0.7) i18n (>= 0.7, < 2)
jekyll-sass-converter (~> 1.0) jekyll-sass-converter (~> 1.0)
jekyll-watch (~> 2.0) jekyll-watch (~> 2.0)
kramdown (>= 1.17, < 3) kramdown (>= 1.17, < 3)
@ -109,11 +108,11 @@ GEM
coffee-script-source (~> 1.11.1) coffee-script-source (~> 1.11.1)
jekyll-commonmark (1.4.0) jekyll-commonmark (1.4.0)
commonmarker (~> 0.22) commonmarker (~> 0.22)
jekyll-commonmark-ghpages (0.2.0) jekyll-commonmark-ghpages (0.4.0)
commonmarker (~> 0.23.4) commonmarker (~> 0.23.7)
jekyll (~> 3.9.0) jekyll (~> 3.9.0)
jekyll-commonmark (~> 1.4.0) jekyll-commonmark (~> 1.4.0)
rouge (>= 2.0, < 4.0) rouge (>= 2.0, < 5.0)
jekyll-default-layout (0.1.4) jekyll-default-layout (0.1.4)
jekyll (~> 3.0) jekyll (~> 3.0)
jekyll-feed (0.15.1) jekyll-feed (0.15.1)
@ -201,8 +200,8 @@ GEM
rexml rexml
kramdown-parser-gfm (1.1.0) kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0) kramdown (~> 2.0)
liquid (4.0.3) liquid (4.0.4)
listen (3.7.1) listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.3.6) mercenary (0.3.6)
@ -210,12 +209,12 @@ GEM
jekyll (>= 3.5, < 5.0) jekyll (>= 3.5, < 5.0)
jekyll-feed (~> 0.9) jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1) jekyll-seo-tag (~> 2.1)
minitest (5.16.3) minitest (5.17.0)
nokogiri (1.13.10-arm64-darwin) nokogiri (1.14.2-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.13.10-x86_64-darwin) nokogiri (1.14.2-x86_64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.13.10-x86_64-linux) nokogiri (1.14.2-x86_64-linux)
racc (~> 1.4) racc (~> 1.4)
octokit (4.25.1) octokit (4.25.1)
faraday (>= 1, < 3) faraday (>= 1, < 3)
@ -223,7 +222,7 @@ GEM
pathutil (0.16.2) pathutil (0.16.2)
forwardable-extended (~> 2.6) forwardable-extended (~> 2.6)
public_suffix (4.0.7) public_suffix (4.0.7)
racc (1.6.1) racc (1.6.2)
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
@ -244,17 +243,15 @@ GEM
unf (~> 0.1.4) unf (~> 0.1.4)
terminal-table (1.8.0) terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1) unicode-display_width (~> 1.1, >= 1.1.1)
thread_safe (0.3.6)
typhoeus (1.4.0) typhoeus (1.4.0)
ethon (>= 0.9.0) ethon (>= 0.9.0)
tzinfo (1.2.10) tzinfo (2.0.6)
thread_safe (~> 0.1) concurrent-ruby (~> 1.0)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.8.2) unf_ext (0.0.8.2)
unicode-display_width (1.8.0) unicode-display_width (1.8.0)
webrick (1.7.0) webrick (1.8.1)
zeitwerk (2.6.0)
PLATFORMS PLATFORMS
arm64-darwin-21 arm64-darwin-21

18
lib/tasks/data_export.rake

@ -11,21 +11,3 @@ namespace :core do
DataExportXmlJob.perform_later(full_update:) DataExportXmlJob.perform_later(full_update:)
end end
end end
namespace :illness_type_0 do
desc "Export log data where illness_type_0 == 1"
task export: :environment do |_task|
logs = LettingsLog.where(illness_type_0: 1, status: "completed").includes(created_by: :organisation)
puts "log_id,created_by_id,organisation_id,organisation_name,startdate"
logs.each do |log|
puts [
log.id,
log.created_by_id,
log.created_by.organisation.id,
log.created_by.organisation.name,
log.startdate&.strftime("%d/%m/%Y"),
].join(",")
end
end
end

5
spec/factories/location.rb

@ -10,6 +10,7 @@ FactoryBot.define do
startdate { Time.zone.local(2022, 4, 1) } startdate { Time.zone.local(2022, 4, 1) }
confirmed { true } confirmed { true }
scheme scheme
trait :export do trait :export do
postcode { "SW1A 2AA" } postcode { "SW1A 2AA" }
name { "Downing Street" } name { "Downing Street" }
@ -19,5 +20,9 @@ FactoryBot.define do
scheme { FactoryBot.create(:scheme, :export) } scheme { FactoryBot.create(:scheme, :export) }
old_visible_id { "111" } old_visible_id { "111" }
end end
trait :with_old_visible_id do
old_visible_id { rand(9_999_999).to_s }
end
end end
end end

8
spec/factories/organisation.rb

@ -13,6 +13,14 @@ FactoryBot.define do
trait :with_old_visible_id do trait :with_old_visible_id do
old_visible_id { rand(9_999_999).to_s } old_visible_id { rand(9_999_999).to_s }
end end
trait :prp do
provider_type { "PRP" }
end
trait :does_not_own_stock do
holds_own_stock { false }
end
end end
factory :organisation_rent_period do factory :organisation_rent_period do

4
spec/factories/scheme.rb

@ -21,5 +21,9 @@ FactoryBot.define do
primary_client_group { "G" } primary_client_group { "G" }
secondary_client_group { "M" } secondary_client_group { "M" }
end end
trait :with_old_visible_id do
old_visible_id { rand(9_999_999) }
end
end end
end end

2
spec/fixtures/exports/general_needs_log.csv vendored

@ -1,2 +1,2 @@
status,tenancycode,age1,sex1,ethnic,national,prevten,ecstat1,hhmemb,age2,sex2,ecstat2,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,leftreg,reservist,illness,preg_occ,startertenancy,tenancylength,tenancy,ppostcode_full,rsnvac,unittype_gn,beds,offered,wchair,earnings,incfreq,benefits,period,layear,waityear,postcode_full,reasonpref,cbl,chr,cap,reasonother,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_f,housingneeds_g,housingneeds_h,illness_type_1,illness_type_2,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,irproduct_other,reason,propcode,la,prevloc,hb,hbrentshortfall,mrcdate,incref,startdate,armedforces,unitletas,builtype,voiddate,renttype,needstype,lettype,totchild,totelder,totadult,nocharge,referral,brent,scharge,pscharge,supcharg,tcharge,tshortfall,chcharge,ppcodenk,has_benefits,renewal,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat2,relat3,relat4,relat5,relat6,relat7,relat8,lar,irproduct,joint,sheltered,hhtype,new_old,vacdays,form,owningorgid,owningorgname,hcnum,maningorgid,maningorgname,manhcnum,createddate,uploaddate status,tenancycode,age1,sex1,ethnic,national,prevten,ecstat1,hhmemb,age2,sex2,ecstat2,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,leftreg,reservist,illness,preg_occ,startertenancy,tenancylength,tenancy,ppostcode_full,rsnvac,unittype_gn,beds,offered,wchair,earnings,incfreq,benefits,period,layear,waityear,postcode_full,reasonpref,cbl,chr,cap,reasonother,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_f,housingneeds_g,housingneeds_h,illness_type_1,illness_type_2,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,irproduct_other,reason,propcode,la,prevloc,hb,hbrentshortfall,mrcdate,incref,startdate,armedforces,unitletas,builtype,voiddate,renttype,needstype,lettype,totchild,totelder,totadult,nocharge,referral,brent,scharge,pscharge,supcharg,tcharge,tshortfall,chcharge,ppcodenk,has_benefits,renewal,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat2,relat3,relat4,relat5,relat6,relat7,relat8,lar,irproduct,joint,sheltered,hhtype,new_old,vacdays,formid,owningorgid,owningorgname,hcnum,maningorgid,maningorgname,manhcnum,createddate,uploaddate
2,BZ737,35,F,2,4,6,0,2,32,M,6,,,,,,,,,,,,,,,,,,,1,4,1,1,1,2,1,5,1,SE2 6RT,6,7,3,2,1,68,1,1,2,2,1,NW1 5TY,1,2,1,2,,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,,,4,123,E09000003,E07000105,6,1,2020-05-05T10:36:49+01:00,0,2022-02-02T10:36:49+00:00,1,2,1,2019-11-03T00:00:00+00:00,2,1,7,0,0,2,0,2,200.0,50.0,40.0,35.0,325.0,12.0,,1,1,0,100.0,25.0,20.0,17.5,162.5,6.0,0,1,,2,P,,,,,,,,,,,4,2,638,{id},{owning_org_id},DLUHC,1234,{managing_org_id},DLUHC,1234,2022-02-08T16:52:15+00:00,2022-02-08T16:52:15+00:00 2,BZ737,35,F,2,4,6,0,2,32,M,6,,,,,,,,,,,,,,,,,,,1,4,1,1,1,2,1,5,1,SE2 6RT,6,7,3,2,1,68,1,1,2,2,1,NW1 5TY,1,2,1,2,,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,,,4,123,E09000003,E07000105,6,1,2020-05-05T10:36:49+01:00,0,2022-02-02T10:36:49+00:00,1,2,1,2019-11-03T00:00:00+00:00,2,1,7,0,0,2,0,2,200.0,50.0,40.0,35.0,325.0,12.0,,1,1,0,100.0,25.0,20.0,17.5,162.5,6.0,0,1,,2,P,,,,,,,,,,,4,2,638,{id},{owning_org_id},DLUHC,1234,{managing_org_id},DLUHC,1234,2022-02-08T16:52:15+00:00,2022-02-08T16:52:15+00:00

1 status tenancycode age1 sex1 ethnic national prevten ecstat1 hhmemb age2 sex2 ecstat2 age3 sex3 ecstat3 age4 sex4 ecstat4 age5 sex5 ecstat5 age6 sex6 ecstat6 age7 sex7 ecstat7 age8 sex8 ecstat8 homeless underoccupation_benefitcap leftreg reservist illness preg_occ startertenancy tenancylength tenancy ppostcode_full rsnvac unittype_gn beds offered wchair earnings incfreq benefits period layear waityear postcode_full reasonpref cbl chr cap reasonother housingneeds_a housingneeds_b housingneeds_c housingneeds_f housingneeds_g housingneeds_h illness_type_1 illness_type_2 illness_type_3 illness_type_4 illness_type_8 illness_type_5 illness_type_6 illness_type_7 illness_type_9 illness_type_10 rp_homeless rp_insan_unsat rp_medwel rp_hardship rp_dontknow tenancyother irproduct_other reason propcode la prevloc hb hbrentshortfall mrcdate incref startdate armedforces unitletas builtype voiddate renttype needstype lettype totchild totelder totadult nocharge referral brent scharge pscharge supcharg tcharge tshortfall chcharge ppcodenk has_benefits renewal wrent wscharge wpschrge wsupchrg wtcharge wtshortfall refused housingneeds wchchrg newprop relat2 relat3 relat4 relat5 relat6 relat7 relat8 lar irproduct joint sheltered hhtype new_old vacdays form formid owningorgid owningorgname hcnum maningorgid maningorgname manhcnum createddate uploaddate
2 2 BZ737 35 F 2 4 6 0 2 32 M 6 1 4 1 1 1 2 1 5 1 SE2 6RT 6 7 3 2 1 68 1 1 2 2 1 NW1 5TY 1 2 1 2 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 4 123 E09000003 E07000105 6 1 2020-05-05T10:36:49+01:00 0 2022-02-02T10:36:49+00:00 1 2 1 2019-11-03T00:00:00+00:00 2 1 7 0 0 2 0 2 200.0 50.0 40.0 35.0 325.0 12.0 1 1 0 100.0 25.0 20.0 17.5 162.5 6.0 0 1 2 P 4 2 638 {id} {id} {owning_org_id} DLUHC 1234 {managing_org_id} DLUHC 1234 2022-02-08T16:52:15+00:00 2022-02-08T16:52:15+00:00

2
spec/fixtures/exports/general_needs_log.xml vendored

@ -135,7 +135,7 @@
<hhtype>4</hhtype> <hhtype>4</hhtype>
<new_old>2</new_old> <new_old>2</new_old>
<vacdays>638</vacdays> <vacdays>638</vacdays>
<form>{id}</form> <formid>{id}</formid>
<owningorgid>{owning_org_id}</owningorgid> <owningorgid>{owning_org_id}</owningorgid>
<owningorgname>DLUHC</owningorgname> <owningorgname>DLUHC</owningorgname>
<hcnum>1234</hcnum> <hcnum>1234</hcnum>

2
spec/fixtures/exports/supported_housing_logs.xml vendored

@ -134,7 +134,7 @@
<hhtype>4</hhtype> <hhtype>4</hhtype>
<new_old>2</new_old> <new_old>2</new_old>
<vacdays>638</vacdays> <vacdays>638</vacdays>
<form>{id}</form> <formid>{id}</formid>
<owningorgid>{owning_org_id}</owningorgid> <owningorgid>{owning_org_id}</owningorgid>
<owningorgname>DLUHC</owningorgname> <owningorgname>DLUHC</owningorgname>
<hcnum>1234</hcnum> <hcnum>1234</hcnum>

46
spec/helpers/collection_time_helper_spec.rb

@ -21,6 +21,10 @@ RSpec.describe CollectionTimeHelper do
it "returns the correct current start date" do it "returns the correct current start date" do
expect(current_collection_start_date).to eq(Time.zone.local(2022, 4, 1)) expect(current_collection_start_date).to eq(Time.zone.local(2022, 4, 1))
end end
it "returns the correct current end date" do
expect(current_collection_end_date).to eq(Time.zone.local(2023, 3, 31))
end
end end
context "with the date before 1st of April" do context "with the date before 1st of April" do
@ -29,6 +33,48 @@ RSpec.describe CollectionTimeHelper do
it "returns the previous year as the current start year" do it "returns the previous year as the current start year" do
expect(current_collection_start_year).to eq(2021) expect(current_collection_start_year).to eq(2021)
end end
it "returns the correct current start date" do
expect(current_collection_start_date).to eq(Time.zone.local(2021, 4, 1))
end
it "returns the correct current end date" do
expect(current_collection_end_date).to eq(Time.zone.local(2022, 3, 31))
end
end
end
describe "Any collection year" do
context "when the date is after 1st of April" do
let(:now) { Time.utc(2022, 8, 3) }
it "returns the same year as the current start year" do
expect(collection_start_year(now)).to eq(2022)
end
it "returns the correct current start date" do
expect(collection_start_date(now)).to eq(Time.zone.local(2022, 4, 1))
end
it "returns the correct current end date" do
expect(collection_end_date(now)).to eq(Time.zone.local(2023, 3, 31))
end
end
context "with the date before 1st of April" do
let(:now) { Time.utc(2022, 2, 3) }
it "returns the previous year as the current start year" do
expect(collection_start_year(now)).to eq(2021)
end
it "returns the correct current start date" do
expect(collection_start_date(now)).to eq(Time.zone.local(2021, 4, 1))
end
it "returns the correct current end date" do
expect(collection_end_date(now)).to eq(Time.zone.local(2022, 3, 31))
end
end end
end end
end end

26
spec/helpers/filters_helper_spec.rb

@ -116,4 +116,30 @@ RSpec.describe FiltersHelper do
end end
end end
end end
describe "#collection_year_options" do
context "when not production" do
it "includes 2023/2024 option" do
expect(collection_year_options).to eq(
{
"2021": "2021/22", "2022": "2022/23", "2023": "2023/24"
},
)
end
end
context "when production" do
before do
allow(Rails.env).to receive(:production?).and_return(true)
end
it "includes 2023/2024 option" do
expect(collection_year_options).to eq(
{
"2021": "2021/22", "2022": "2022/23"
},
)
end
end
end
end end

53
spec/helpers/interruption_screen_helper_spec.rb

@ -13,6 +13,7 @@ RSpec.describe InterruptionScreenHelper do
earnings: 750, earnings: 750,
incfreq: 1, incfreq: 1,
created_by: user, created_by: user,
sex1: "F",
) )
end end
@ -94,6 +95,54 @@ RSpec.describe InterruptionScreenHelper do
.to eq("") .to eq("")
end end
end end
context "when an argument is given not for a label" do
translation = "test.title_text.one_argument"
it "returns the correct text" do
informative_text_hash = {
"translation" => translation,
"arguments" => [
{
"key" => "earnings",
"i18n_template" => "argument",
},
],
}
expect(display_informative_text(informative_text_hash, lettings_log)).to eq(I18n.t(translation, argument: lettings_log.earnings))
end
end
context "when and argument is given with a key and arguments for the key" do
it "makes the correct method call" do
informative_text_hash = {
"arguments" => [
{
"key" => "retirement_age_for_person",
"arguments_for_key" => 1,
"i18n_template" => "argument",
},
],
}
allow(lettings_log).to receive(:retirement_age_for_person)
display_informative_text(informative_text_hash, lettings_log)
expect(lettings_log).to have_received(:retirement_age_for_person).with(1)
end
it "returns the correct text" do
translation = "test.title_text.one_argument"
informative_text_hash = {
"translation" => translation,
"arguments" => [
{
"key" => "retirement_age_for_person",
"arguments_for_key" => 1,
"i18n_template" => "argument",
},
],
}
expect(display_informative_text(informative_text_hash, lettings_log)).to eq(I18n.t(translation, argument: lettings_log.retirement_age_for_person(1)))
end
end
end end
describe "display_title_text" do describe "display_title_text" do
@ -113,12 +162,12 @@ RSpec.describe InterruptionScreenHelper do
{ {
"key" => "ecstat1", "key" => "ecstat1",
"label" => true, "label" => true,
"i18n_template" => "ecstat1", "i18n_template" => "argument",
}, },
], ],
} }
expect(display_title_text(title_text, lettings_log)) expect(display_title_text(title_text, lettings_log))
.to eq(I18n.t("test.title_text.one_argument", ecstat1: lettings_log.form.get_question("ecstat1", lettings_log).answer_label(lettings_log).downcase)) .to eq(I18n.t("test.title_text.one_argument", argument: lettings_log.form.get_question("ecstat1", lettings_log).answer_label(lettings_log).downcase))
end end
end end

147
spec/helpers/tasklist_helper_spec.rb

@ -1,100 +1,55 @@
require "rails_helper" require "rails_helper"
RSpec.describe TasklistHelper do RSpec.describe TasklistHelper do
describe "with lettings" do let(:now) { Time.utc(2022, 6, 1) }
let(:empty_lettings_log) { FactoryBot.create(:lettings_log) }
let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, needstype: 1) }
let(:fake_2021_2022_form) { Form.new("spec/fixtures/forms/2021_2022.json") }
context "with 2021 2022 form" do around do |example|
before do Timecop.freeze(now) do
allow(FormHandler.instance).to receive(:current_lettings_form).and_return(fake_2021_2022_form) Singleton.__init__(FormHandler)
end example.run
end
Timecop.return
Singleton.__init__(FormHandler)
end
describe "get next incomplete section" do describe "with lettings" do
it "returns the first subsection name if it is not completed" do let(:empty_lettings_log) { create(:lettings_log) }
expect(get_next_incomplete_section(lettings_log).id).to eq("household_characteristics") let(:lettings_log) { build(:lettings_log, :in_progress, needstype: 1) }
end
it "returns the first subsection name if it is partially completed" do describe "get next incomplete section" do
lettings_log["tenancycode"] = 123 it "returns the first subsection name if it is not completed" do
expect(get_next_incomplete_section(lettings_log).id).to eq("household_characteristics") expect(get_next_incomplete_section(lettings_log).id).to eq("household_characteristics")
end
end end
describe "get sections count" do it "returns the first subsection name if it is partially completed" do
it "returns the total of sections if no status is given" do lettings_log["tenancycode"] = 123
expect(get_subsections_count(empty_lettings_log)).to eq(8) expect(get_next_incomplete_section(lettings_log).id).to eq("household_characteristics")
end
it "returns 0 sections for completed sections if no sections are completed" do
expect(get_subsections_count(empty_lettings_log, :completed)).to eq(0)
end
it "returns the number of not started sections" do
expect(get_subsections_count(empty_lettings_log, :not_started)).to eq(8)
end
it "returns the number of sections in progress" do
expect(get_subsections_count(lettings_log, :in_progress)).to eq(3)
end
it "returns 0 for invalid state" do
expect(get_subsections_count(lettings_log, :fake)).to eq(0)
end
end end
end
describe "get_next_page_or_check_answers" do describe "get sections count" do
let(:subsection) { lettings_log.form.get_subsection("household_characteristics") } it "returns the total of sections if no status is given" do
let(:user) { FactoryBot.build(:user) } expect(get_subsections_count(empty_lettings_log)).to eq(1)
it "returns the check answers page path if the section has been started already" do
expect(next_page_or_check_answers(subsection, lettings_log, user)).to match(/check-answers/)
end
it "returns the first question page path for the section if it has not been started yet" do
expect(next_page_or_check_answers(subsection, empty_lettings_log, user)).to match(/tenant-code-test/)
end
it "when first question being not routed to returns the next routed question link" do
empty_lettings_log.housingneeds_a = "No"
expect(next_page_or_check_answers(subsection, empty_lettings_log, user)).to match(/person-1-gender/)
end
end end
describe "subsection link" do it "returns 0 sections for completed sections if no sections are completed" do
let(:subsection) { lettings_log.form.get_subsection("household_characteristics") } expect(get_subsections_count(empty_lettings_log, :completed)).to eq(0)
let(:user) { FactoryBot.build(:user) } end
context "with a subsection that's enabled" do
it "returns the subsection link url" do
expect(subsection_link(subsection, lettings_log, user)).to match(/household-characteristics/)
end
end
context "with a subsection that cannot be started yet" do it "returns the number of not started sections" do
before do expect(get_subsections_count(empty_lettings_log, :not_started)).to eq(1)
allow(subsection).to receive(:status).with(lettings_log).and_return(:cannot_start_yet) end
end
it "returns the label instead of a link" do it "returns the number of sections in progress" do
expect(subsection_link(subsection, lettings_log, user)).to match(subsection.label) expect(get_subsections_count(lettings_log, :in_progress)).to eq(2)
end
end
end end
end
end
describe "#review_log_text" do it "returns 0 for invalid state" do
around do |example| expect(get_subsections_count(lettings_log, :fake)).to eq(0)
Timecop.freeze(now) do
Singleton.__init__(FormHandler)
example.run
end end
Singleton.__init__(FormHandler)
end end
context "with lettings log" do describe "review_log_text" do
context "when collection_period_open? == true" do context "when collection_period_open? == true" do
context "with 2023 deadline" do context "with 2023 deadline" do
let(:now) { Time.utc(2022, 6, 1) } let(:now) { Time.utc(2022, 6, 1) }
@ -113,7 +68,7 @@ RSpec.describe TasklistHelper do
it "returns relevant text" do it "returns relevant text" do
expect(review_log_text(lettings_log)).to eq( expect(review_log_text(lettings_log)).to eq(
"You can #{govuk_link_to 'review and make changes to this log', review_lettings_log_path(lettings_log)} until 1 July 2024.".html_safe, "You can #{govuk_link_to 'review and make changes to this log', review_lettings_log_path(lettings_log)} until 9 July 2024.".html_safe,
) )
end end
end end
@ -129,6 +84,30 @@ RSpec.describe TasklistHelper do
end end
end end
describe "subsection link" do
let(:lettings_log) { create(:lettings_log, :completed) }
let(:subsection) { lettings_log.form.get_subsection("household_characteristics") }
let(:user) { build(:user) }
context "with a subsection that's enabled" do
it "returns the subsection link url" do
expect(subsection_link(subsection, lettings_log, user)).to match(/household-characteristics/)
end
end
context "with a subsection that cannot be started yet" do
before do
allow(subsection).to receive(:status).with(lettings_log).and_return(:cannot_start_yet)
end
it "returns the label instead of a link" do
expect(subsection_link(subsection, lettings_log, user)).to match(subsection.label)
end
end
end
end
describe "#review_log_text" do
context "with sales log" do context "with sales log" do
context "when collection_period_open? == true" do context "when collection_period_open? == true" do
let(:now) { Time.utc(2022, 6, 1) } let(:now) { Time.utc(2022, 6, 1) }
@ -136,17 +115,19 @@ RSpec.describe TasklistHelper do
it "returns relevant text" do it "returns relevant text" do
expect(review_log_text(sales_log)).to eq( expect(review_log_text(sales_log)).to eq(
"You can #{govuk_link_to 'review and make changes to this log', review_lettings_log_path(sales_log)} until 1 July 2023.".html_safe, "You can #{govuk_link_to 'review and make changes to this log', review_sales_log_path(id: sales_log, sales_log: true)} until 7 July 2023.".html_safe,
) )
end end
end end
context "when collection_period_open? == false" do context "when collection_period_open? == false" do
let(:now) { Time.utc(2023, 7, 8) } let(:now) { Time.utc(2022, 6, 1) }
let(:sales_log) { create(:sales_log, :completed, saledate: Time.utc(2023, 2, 8)) } let!(:sales_log) { create(:sales_log, :completed) }
it "returns relevant text" do it "returns relevant text" do
expect(review_log_text(sales_log)).to eq("This log is from the 2022/2023 collection window, which is now closed.") Timecop.freeze(now + 1.year) do
expect(review_log_text(sales_log)).to eq("This log is from the 2021/2022 collection window, which is now closed.")
end
end end
end end
end end

54
spec/mailers/bulk_upload_mailer_spec.rb

@ -5,7 +5,7 @@ RSpec.describe BulkUploadMailer do
let(:notify_client) { instance_double(Notifications::Client) } let(:notify_client) { instance_double(Notifications::Client) }
let(:user) { create(:user, email: "user@example.com") } let(:user) { create(:user, email: "user@example.com") }
let(:bulk_upload) { build(:bulk_upload, :lettings, user:) } let(:bulk_upload) { create(:bulk_upload, :lettings, user:) }
before do before do
allow(Notifications::Client).to receive(:new).and_return(notify_client) allow(Notifications::Client).to receive(:new).and_return(notify_client)
@ -75,4 +75,56 @@ RSpec.describe BulkUploadMailer do
end end
end end
end end
describe "#send_correct_and_upload_again_mail" do
context "when 2 columns with errors" do
before do
create(:bulk_upload_error, bulk_upload:, col: "A")
create(:bulk_upload_error, bulk_upload:, col: "B")
end
it "sends correctly formed email with A, B" do
expect(notify_client).to receive(:send_email).with(
email_address: user.email,
template_id: described_class::BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID,
personalisation: {
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
year_combo: bulk_upload.year_combo,
lettings_or_sales: bulk_upload.log_type,
error_description: "We noticed that you have a lot of similar errors in column A, B. Please correct your data export and upload again.",
summary_report_link: "http://localhost:3000/lettings-logs/bulk-upload-results/#{bulk_upload.id}/summary",
},
)
mailer.send_correct_and_upload_again_mail(bulk_upload:)
end
end
context "when 4 columns with errors" do
before do
create(:bulk_upload_error, bulk_upload:, col: "A")
create(:bulk_upload_error, bulk_upload:, col: "B")
create(:bulk_upload_error, bulk_upload:, col: "C")
create(:bulk_upload_error, bulk_upload:, col: "D")
end
it "sends correctly formed email with A, B, C and more" do
expect(notify_client).to receive(:send_email).with(
email_address: user.email,
template_id: described_class::BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID,
personalisation: {
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
year_combo: bulk_upload.year_combo,
lettings_or_sales: bulk_upload.log_type,
error_description: "We noticed that you have a lot of similar errors in column A, B, C and more. Please correct your data export and upload again.",
summary_report_link: "http://localhost:3000/lettings-logs/bulk-upload-results/#{bulk_upload.id}/summary",
},
)
mailer.send_correct_and_upload_again_mail(bulk_upload:)
end
end
end
end end

6
spec/models/form/lettings/pages/location_spec.rb

@ -6,6 +6,12 @@ RSpec.describe Form::Lettings::Pages::Location, type: :model do
let(:page_id) { nil } let(:page_id) { nil }
let(:page_definition) { nil } let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) } let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form) }
before do
allow(form).to receive(:start_date).and_return(Time.zone.local(2022, 4, 1))
allow(subsection).to receive(:form).and_return(form)
end
it "has correct subsection" do it "has correct subsection" do
expect(page.subsection).to eq(subsection) expect(page.subsection).to eq(subsection)

18
spec/models/form/lettings/questions/location_id_spec.rb

@ -6,6 +6,14 @@ RSpec.describe Form::Lettings::Questions::LocationId, type: :model do
let(:question_id) { nil } let(:question_id) { nil }
let(:question_definition) { nil } let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) } let(:page) { instance_double(Form::Page) }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form) }
before do
allow(form).to receive(:start_date).and_return(Time.zone.local(2022, 4, 1))
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(form)
end
it "has correct page" do it "has correct page" do
expect(question.page).to eq(page) expect(question.page).to eq(page)
@ -103,4 +111,14 @@ RSpec.describe Form::Lettings::Questions::LocationId, type: :model do
end end
end end
end end
context "with collection year on or after 2023" do
before do
allow(form).to receive(:start_date).and_return(Time.zone.local(2023, 4, 1))
end
it "has the correct header" do
expect(question.header).to eq("Which location is this letting for?")
end
end
end end

20
spec/models/form/sales/pages/about_staircase_spec.rb

@ -11,8 +11,24 @@ RSpec.describe Form::Sales::Pages::AboutStaircase, type: :model do
expect(page.subsection).to eq(subsection) expect(page.subsection).to eq(subsection)
end end
it "has correct questions" do describe "questions" do
expect(page.questions.map(&:id)).to eq(%w[stairbought stairowned]) let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date:)) }
context "when 2022" do
let(:start_date) { Time.utc(2022, 2, 8) }
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[stairbought stairowned])
end
end
context "when 2023" do
let(:start_date) { Time.utc(2023, 2, 8) }
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[stairbought stairowned staircasesale])
end
end
end end
it "has the correct id" do it "has the correct id" do

33
spec/models/form/sales/pages/buyer2_ethnic_background_arab_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Buyer2EthnicBackgroundArab, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[ethnicbuy2])
end
it "has the correct id" do
expect(page.id).to eq("buyer_2_ethnic_background_arab")
end
it "has the correct header" do
expect(page.header).to be_nil
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "ethnic_group2" => 4 }])
end
end

33
spec/models/form/sales/pages/buyer2_ethnic_background_asian_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Buyer2EthnicBackgroundAsian, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[ethnicbuy2])
end
it "has the correct id" do
expect(page.id).to eq("buyer_2_ethnic_background_asian")
end
it "has the correct header" do
expect(page.header).to be_nil
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "ethnic_group2" => 2 }])
end
end

33
spec/models/form/sales/pages/buyer2_ethnic_background_black_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Buyer2EthnicBackgroundBlack, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[ethnicbuy2])
end
it "has the correct id" do
expect(page.id).to eq("buyer_2_ethnic_background_black")
end
it "has the correct header" do
expect(page.header).to be_nil
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "ethnic_group2" => 3 }])
end
end

33
spec/models/form/sales/pages/buyer2_ethnic_background_mixed_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Buyer2EthnicBackgroundMixed, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[ethnicbuy2])
end
it "has the correct id" do
expect(page.id).to eq("buyer_2_ethnic_background_mixed")
end
it "has the correct header" do
expect(page.header).to be_nil
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "ethnic_group2" => 1 }])
end
end

33
spec/models/form/sales/pages/buyer2_ethnic_background_white_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Buyer2EthnicBackgroundWhite, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[ethnicbuy2])
end
it "has the correct id" do
expect(page.id).to eq("buyer_2_ethnic_background_white")
end
it "has the correct header" do
expect(page.header).to be_nil
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "ethnic_group2" => 0 }])
end
end

42
spec/models/form/sales/pages/buyer2_ethnic_group_spec.rb

@ -0,0 +1,42 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Buyer2EthnicGroup, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[ethnic_group2])
end
it "has the correct id" do
expect(page.id).to eq("buyer_2_ethnic_group")
end
it "has the correct header" do
expect(page.header).to be_nil
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([
{
"jointpur" => 1,
"privacynotice" => 1,
},
{
"jointpur" => 1,
"noint" => 1,
},
])
end
end

2
spec/models/form/sales/questions/buyer1_income_value_check_spec.rb

@ -16,7 +16,7 @@ RSpec.describe Form::Sales::Questions::Buyer1IncomeValueCheck, type: :model do
end end
it "has the correct header" do it "has the correct header" do
expect(question.header).to eq("Are you sure this income is correct?") expect(question.header).to eq("Are you sure this is correct?")
end end
it "has the correct check_answer_label" do it "has the correct check_answer_label" do

48
spec/models/form/sales/questions/buyer2_ethnic_background_arab_spec.rb

@ -0,0 +1,48 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer2EthnicBackgroundArab, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("ethnicbuy2")
end
it "has the correct header" do
expect(question.header).to eq("Which of the following best describes the buyer 2’s Arab background?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Buyer 2’s ethnic background")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint_text" do
expect(question.hint_text).to be nil
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"16" => { "value" => "Other ethnic group" },
"19" => { "value" => "Arab" },
})
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(2)
end
end

51
spec/models/form/sales/questions/buyer2_ethnic_background_asian_spec.rb

@ -0,0 +1,51 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer2EthnicBackgroundAsian, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("ethnicbuy2")
end
it "has the correct header" do
expect(question.header).to eq("Which of the following best describes the buyer 2’s Asian or Asian British background?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Buyer 2’s ethnic background")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint_text" do
expect(question.hint_text).to be nil
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"10" => { "value" => "Bangladeshi" },
"11" => { "value" => "Any other Asian or Asian British background" },
"15" => { "value" => "Chinese" },
"8" => { "value" => "Indian" },
"9" => { "value" => "Pakistani" },
})
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(2)
end
end

49
spec/models/form/sales/questions/buyer2_ethnic_background_black_spec.rb

@ -0,0 +1,49 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer2EthnicBackgroundBlack, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("ethnicbuy2")
end
it "has the correct header" do
expect(question.header).to eq("Which of the following best describes the buyer 2’s Black, African, Caribbean or Black British background?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Buyer 2’s ethnic background")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint_text" do
expect(question.hint_text).to be nil
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"12" => { "value" => "Caribbean" },
"13" => { "value" => "African" },
"14" => { "value" => "Any other Black, African or Caribbean background" },
})
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(2)
end
end

50
spec/models/form/sales/questions/buyer2_ethnic_background_mixed_spec.rb

@ -0,0 +1,50 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer2EthnicBackgroundMixed, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("ethnicbuy2")
end
it "has the correct header" do
expect(question.header).to eq("Which of the following best describes the buyer 2’s Mixed or Multiple ethnic groups background?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Buyer 2’s ethnic background")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint_text" do
expect(question.hint_text).to be nil
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"4" => { "value" => "White and Black Caribbean" },
"5" => { "value" => "White and Black African" },
"6" => { "value" => "White and Asian" },
"7" => { "value" => "Any other Mixed or Multiple ethnic background" },
})
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(2)
end
end

50
spec/models/form/sales/questions/buyer2_ethnic_background_white_spec.rb

@ -0,0 +1,50 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer2EthnicBackgroundWhite, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("ethnicbuy2")
end
it "has the correct header" do
expect(question.header).to eq("Which of the following best describes the buyer 2’s White background?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Buyer 2’s ethnic background")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint_text" do
expect(question.hint_text).to be nil
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "English, Welsh, Northern Irish, Scottish or British" },
"18" => { "value" => "Gypsy or Irish Traveller" },
"2" => { "value" => "Irish" },
"3" => { "value" => "Any other White background" },
})
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(2)
end
end

62
spec/models/form/sales/questions/buyer2_ethnic_group_spec.rb

@ -0,0 +1,62 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer2EthnicGroup, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("ethnic_group2")
end
it "has the correct header" do
expect(question.header).to eq("What is buyer 2’s ethnic group?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Buyer 2’s ethnic group")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint_text" do
expect(question.hint_text).to be nil
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"0" => { "value" => "White" },
"1" => { "value" => "Mixed or Multiple ethnic groups" },
"17" => { "value" => "Buyer 1 prefers not to say" },
"2" => { "value" => "Asian or Asian British" },
"3" => { "value" => "Black, African, Caribbean or Black British" },
"4" => { "value" => "Arab or other ethnic group" },
"divider" => { "value" => true },
})
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(2)
end
it "has the correct inferred_check_answers_value" do
expect(question.inferred_check_answers_value).to eq([{
"condition" => {
"ethnic_group2" => 17,
},
"value" => "Prefers not to say",
}])
end
end

9
spec/models/form/sales/questions/previous_postcode_known_spec.rb

@ -47,4 +47,13 @@ RSpec.describe Form::Sales::Questions::PreviousPostcodeKnown, type: :model do
it "has the correct hint" do it "has the correct hint" do
expect(question.hint_text).to eq("This is also known as the household’s 'last settled home'") expect(question.hint_text).to eq("This is also known as the household’s 'last settled home'")
end end
it "has the correct hidden_in_check_answers" do
expect(question.hidden_in_check_answers).to eq({
"depends_on" => [
{ "ppcodenk" => 0 },
{ "ppcodenk" => 1 },
],
})
end
end end

49
spec/models/form/sales/questions/staircase_sale_spec.rb

@ -0,0 +1,49 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::StaircaseSale, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("staircasesale")
end
it "has the correct header" do
expect(question.header).to eq("Is this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Part of a back-to-back staircasing transaction")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
})
end
it "has correct conditional for" do
expect(question.conditional_for).to eq(nil)
end
it "has the correct hint" do
expect(question.hint_text).to be_nil
end
end

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

Loading…
Cancel
Save