Browse Source

Merge branch 'main' into CLDC-1864-single-log-numbering

# Conflicts:
#	app/models/form/lettings/questions/starter_tenancy.rb
#	app/models/form/lettings/questions/tenancy.rb
#	app/models/form/sales/questions/other_nationality1.rb
pull/1381/head
natdeanlewissoftwire 3 years ago
parent
commit
da16391c9e
  1. 5
      .github/workflows/production_pipeline.yml
  2. 2
      .github/workflows/review_pipeline.yml
  3. 5
      .github/workflows/staging_pipeline.yml
  4. 114
      Gemfile.lock
  5. 9
      app/components/bulk_upload_error_row_component.rb
  6. 2
      app/components/bulk_upload_error_summary_table_component.html.erb
  7. 6
      app/components/bulk_upload_error_summary_table_component.rb
  8. 2
      app/controllers/bulk_upload_lettings_logs_controller.rb
  9. 16
      app/controllers/lettings_logs_controller.rb
  10. 6
      app/controllers/organisations_controller.rb
  11. 24
      app/controllers/start_controller.rb
  12. 7
      app/helpers/logs_helper.rb
  13. 4
      app/jobs/email_csv_job.rb
  14. 11
      app/mailers/bulk_upload_mailer.rb
  15. 22
      app/models/bulk_upload.rb
  16. 2
      app/models/form/lettings/pages/lead_tenant_nationality.rb
  17. 4
      app/models/form/lettings/pages/max_rent_value_check.rb
  18. 4
      app/models/form/lettings/pages/min_rent_value_check.rb
  19. 6
      app/models/form/lettings/pages/starter_tenancy_type.rb
  20. 4
      app/models/form/lettings/pages/tenancy_length.rb
  21. 6
      app/models/form/lettings/pages/tenancy_type.rb
  22. 6
      app/models/form/lettings/questions/nationality.rb
  23. 7
      app/models/form/lettings/questions/reason.rb
  24. 23
      app/models/form/lettings/questions/starter_tenancy.rb
  25. 37
      app/models/form/lettings/questions/starter_tenancy_type.rb
  26. 23
      app/models/form/lettings/questions/tenancy.rb
  27. 2
      app/models/form/lettings/questions/tenancy_length.rb
  28. 2
      app/models/form/lettings/questions/tenancy_other.rb
  29. 37
      app/models/form/lettings/questions/tenancy_type.rb
  30. 6
      app/models/form/sales/pages/buyer2_live_in_property.rb
  31. 11
      app/models/form/sales/pages/buyer2_living_in.rb
  32. 11
      app/models/form/sales/pages/buyer2_nationality.rb
  33. 11
      app/models/form/sales/pages/buyer2_previous_housing_situation.rb
  34. 7
      app/models/form/sales/pages/nationality1.rb
  35. 4
      app/models/form/sales/questions/buyer1_previous_tenure.rb
  36. 17
      app/models/form/sales/questions/buyer2_living_in.rb
  37. 26
      app/models/form/sales/questions/buyer2_nationality.rb
  38. 10
      app/models/form/sales/questions/nationality1.rb
  39. 11
      app/models/form/sales/questions/other_nationality1.rb
  40. 23
      app/models/form/sales/questions/previous_tenure_buyer2.rb
  41. 19
      app/models/form/sales/subsections/household_characteristics.rb
  42. 12
      app/models/form/sales/subsections/household_situation.rb
  43. 2
      app/models/forms/bulk_upload_lettings/prepare_your_file.rb
  44. 5
      app/models/forms/bulk_upload_lettings/year.rb
  45. 28
      app/models/lettings_log.rb
  46. 12
      app/models/sales_log.rb
  47. 15
      app/models/validations/sales/financial_validations.rb
  48. 9
      app/services/bulk_upload/lettings/log_creator.rb
  49. 169
      app/services/bulk_upload/lettings/validator.rb
  50. 15
      app/services/bulk_upload/lettings/year2022/csv_parser.rb
  51. 158
      app/services/bulk_upload/lettings/year2022/row_parser.rb
  52. 81
      app/services/bulk_upload/lettings/year2023/csv_parser.rb
  53. 1241
      app/services/bulk_upload/lettings/year2023/row_parser.rb
  54. 6
      app/services/bulk_upload/processor.rb
  55. 176
      app/services/bulk_upload/sales/row_parser.rb
  56. 134
      app/services/bulk_upload/sales/validator.rb
  57. 308
      app/services/bulk_upload/sales/year2022/row_parser.rb
  58. 123
      app/services/csv/lettings_log_csv_service.rb
  59. 1
      app/services/imports/sales_logs_import_service.rb
  60. 22
      app/views/layouts/_collection_resources.html.erb
  61. 5
      app/views/logs/_log_list.html.erb
  62. 2
      app/views/logs/download_csv.html.erb
  63. 12
      app/views/logs/index.html.erb
  64. 12
      app/views/organisations/logs.html.erb
  65. 3
      config/forms/2022_2023.json
  66. 6
      config/initializers/feature_toggle.rb
  67. 8
      config/locales/en.yml
  68. 2
      config/rent_range_data/2023.csv
  69. 3
      config/routes.rb
  70. 8
      db/migrate/20230224083552_add_columns_to_sales_log.rb
  71. 5
      db/migrate/20230307111943_remove_othernational_from_sales_logs.rb
  72. 5
      db/migrate/20230307140937_add_nationalbuy2_to_sales_logs.rb
  73. 4
      db/schema.rb
  74. 6
      docs/Gemfile.lock
  75. BIN
      public/files/2022_23_sales_paper_form.pdf
  76. BIN
      public/files/2023_24_lettings_paper_form.pdf
  77. BIN
      public/files/2023_24_sales_paper_form.pdf
  78. 8
      spec/components/bulk_upload_error_summary_table_component_spec.rb
  79. 2
      spec/features/bulk_upload_sales_logs_spec.rb
  80. 2
      spec/features/form/accessible_autocomplete_spec.rb
  81. 12
      spec/features/form/conditional_questions_spec.rb
  82. 2
      spec/features/form/progressive_total_field_spec.rb
  83. 2
      spec/fixtures/files/lettings_logs_download_codes_only.csv
  84. 1
      spec/helpers/tasklist_helper_spec.rb
  85. 3
      spec/mailers/bulk_upload_mailer_spec.rb
  86. 31
      spec/models/form/lettings/pages/starter_tenancy_type_spec.rb
  87. 31
      spec/models/form/lettings/pages/tenancy_length_spec.rb
  88. 31
      spec/models/form/lettings/pages/tenancy_type_spec.rb
  89. 2
      spec/models/form/lettings/questions/la_spec.rb
  90. 48
      spec/models/form/lettings/questions/nationality_spec.rb
  91. 2
      spec/models/form/lettings/questions/prevloc_spec.rb
  92. 158
      spec/models/form/lettings/questions/reason_spec.rb
  93. 44
      spec/models/form/lettings/questions/tenancy_length_spec.rb
  94. 35
      spec/models/form/lettings/questions/tenancy_other_spec.rb
  95. 65
      spec/models/form/lettings/questions/tenancy_type_spec.rb
  96. 6
      spec/models/form/sales/pages/buyer2_live_in_property_spec.rb
  97. 31
      spec/models/form/sales/pages/buyer2_living_in_spec.rb
  98. 31
      spec/models/form/sales/pages/buyer2_nationality_spec.rb
  99. 31
      spec/models/form/sales/pages/buyer2_previous_housing_situation_spec.rb
  100. 12
      spec/models/form/sales/pages/nationality1_spec.rb
  101. Some files were not shown because too many files have changed in this diff Show More

5
.github/workflows/production_pipeline.yml

@ -42,6 +42,7 @@ jobs:
DB_USERNAME: postgres DB_USERNAME: postgres
DB_PASSWORD: password DB_PASSWORD: password
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
PARALLEL_TEST_PROCESSORS: 4
steps: steps:
- name: Get latest release with tag - name: Get latest release with tag
@ -71,7 +72,7 @@ jobs:
- name: Create database - name: Create database
run: | run: |
bundle exec rake db:prepare bundle exec rake parallel:setup
- name: Compile Assets - name: Compile Assets
run: | run: |
@ -79,7 +80,7 @@ jobs:
- name: Run tests - name: Run tests
run: | run: |
bundle exec rspec --exclude-pattern "features/*" --fail-fast bundle exec rake parallel:spec['spec\/(?!features)']
feature_test: feature_test:
name: Feature Tests name: Feature Tests

2
.github/workflows/review_pipeline.yml

@ -123,7 +123,7 @@ jobs:
cf set-env $APP_NAME IMPORT_PAAS_INSTANCE $IMPORT_PAAS_INSTANCE cf set-env $APP_NAME IMPORT_PAAS_INSTANCE $IMPORT_PAAS_INSTANCE
cf set-env $APP_NAME EXPORT_PAAS_INSTANCE "dluhc-core-review-export-bucket" cf set-env $APP_NAME EXPORT_PAAS_INSTANCE "dluhc-core-review-export-bucket"
cf set-env $APP_NAME S3_CONFIG $S3_CONFIG cf set-env $APP_NAME S3_CONFIG $S3_CONFIG
cf set-env $APP_NAME CSV_DOWNLOAD_PAAS_INSTANCE "dluhc-core-review-csv-bucket" cf set-env $APP_NAME CSV_DOWNLOAD_PAAS_INSTANCE "dluhc-core-staging-csv-bucket"
cf set-env $APP_NAME SENTRY_DSN $SENTRY_DSN cf set-env $APP_NAME SENTRY_DSN $SENTRY_DSN
cf set-env $APP_NAME APP_HOST "https://dluhc-core-review-${{ github.event.pull_request.number }}.london.cloudapps.digital" cf set-env $APP_NAME APP_HOST "https://dluhc-core-review-${{ github.event.pull_request.number }}.london.cloudapps.digital"

5
.github/workflows/staging_pipeline.yml

@ -45,6 +45,7 @@ jobs:
DB_USERNAME: postgres DB_USERNAME: postgres
DB_PASSWORD: password DB_PASSWORD: password
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
PARALLEL_TEST_PROCESSORS: 4
steps: steps:
- name: Checkout - name: Checkout
@ -63,7 +64,7 @@ jobs:
- name: Create database - name: Create database
run: | run: |
bundle exec rake db:prepare bundle exec rake parallel:setup
- name: Compile assets - name: Compile assets
run: | run: |
@ -71,7 +72,7 @@ jobs:
- name: Run tests - name: Run tests
run: | run: |
bundle exec rspec --exclude-pattern "features/*" --fail-fast bundle exec rake parallel:spec['spec\/(?!features)']
feature_test: feature_test:
name: Feature Tests name: Feature Tests

114
Gemfile.lock

@ -13,67 +13,67 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.0.4.2) actioncable (7.0.4.3)
actionpack (= 7.0.4.2) actionpack (= 7.0.4.3)
activesupport (= 7.0.4.2) activesupport (= 7.0.4.3)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (7.0.4.2) actionmailbox (7.0.4.3)
actionpack (= 7.0.4.2) actionpack (= 7.0.4.3)
activejob (= 7.0.4.2) activejob (= 7.0.4.3)
activerecord (= 7.0.4.2) activerecord (= 7.0.4.3)
activestorage (= 7.0.4.2) activestorage (= 7.0.4.3)
activesupport (= 7.0.4.2) activesupport (= 7.0.4.3)
mail (>= 2.7.1) mail (>= 2.7.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
actionmailer (7.0.4.2) actionmailer (7.0.4.3)
actionpack (= 7.0.4.2) actionpack (= 7.0.4.3)
actionview (= 7.0.4.2) actionview (= 7.0.4.3)
activejob (= 7.0.4.2) activejob (= 7.0.4.3)
activesupport (= 7.0.4.2) activesupport (= 7.0.4.3)
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.2) actionpack (7.0.4.3)
actionview (= 7.0.4.2) actionview (= 7.0.4.3)
activesupport (= 7.0.4.2) activesupport (= 7.0.4.3)
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.2) actiontext (7.0.4.3)
actionpack (= 7.0.4.2) actionpack (= 7.0.4.3)
activerecord (= 7.0.4.2) activerecord (= 7.0.4.3)
activestorage (= 7.0.4.2) activestorage (= 7.0.4.3)
activesupport (= 7.0.4.2) activesupport (= 7.0.4.3)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.0.4.2) actionview (7.0.4.3)
activesupport (= 7.0.4.2) activesupport (= 7.0.4.3)
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.2) activejob (7.0.4.3)
activesupport (= 7.0.4.2) activesupport (= 7.0.4.3)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.0.4.2) activemodel (7.0.4.3)
activesupport (= 7.0.4.2) activesupport (= 7.0.4.3)
activerecord (7.0.4.2) activerecord (7.0.4.3)
activemodel (= 7.0.4.2) activemodel (= 7.0.4.3)
activesupport (= 7.0.4.2) activesupport (= 7.0.4.3)
activestorage (7.0.4.2) activestorage (7.0.4.3)
actionpack (= 7.0.4.2) actionpack (= 7.0.4.3)
activejob (= 7.0.4.2) activejob (= 7.0.4.3)
activerecord (= 7.0.4.2) activerecord (= 7.0.4.3)
activesupport (= 7.0.4.2) activesupport (= 7.0.4.3)
marcel (~> 1.0) marcel (~> 1.0)
mini_mime (>= 1.1.0) mini_mime (>= 1.1.0)
activesupport (7.0.4.2) activesupport (7.0.4.3)
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)
@ -134,7 +134,7 @@ GEM
launchy launchy
childprocess (4.1.0) childprocess (4.1.0)
coderay (1.1.3) coderay (1.1.3)
concurrent-ruby (1.2.0) concurrent-ruby (1.2.2)
connection_pool (2.3.0) connection_pool (2.3.0)
crack (0.4.5) crack (0.4.5)
rexml rexml
@ -221,7 +221,7 @@ GEM
matrix (0.4.2) matrix (0.4.2)
method_source (1.0.0) method_source (1.0.0)
mini_mime (1.1.2) mini_mime (1.1.2)
minitest (5.17.0) minitest (5.18.0)
msgpack (1.6.0) msgpack (1.6.0)
net-imap (0.3.4) net-imap (0.3.4)
date date
@ -279,35 +279,35 @@ GEM
nio4r (~> 2.0) nio4r (~> 2.0)
raabro (1.4.0) raabro (1.4.0)
racc (1.6.2) racc (1.6.2)
rack (2.2.6.3) rack (2.2.6.4)
rack-attack (6.6.1) rack-attack (6.6.1)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rack-mini-profiler (2.3.4) rack-mini-profiler (2.3.4)
rack (>= 1.2.0) rack (>= 1.2.0)
rack-test (2.0.2) rack-test (2.1.0)
rack (>= 1.3) rack (>= 1.3)
rails (7.0.4.2) rails (7.0.4.3)
actioncable (= 7.0.4.2) actioncable (= 7.0.4.3)
actionmailbox (= 7.0.4.2) actionmailbox (= 7.0.4.3)
actionmailer (= 7.0.4.2) actionmailer (= 7.0.4.3)
actionpack (= 7.0.4.2) actionpack (= 7.0.4.3)
actiontext (= 7.0.4.2) actiontext (= 7.0.4.3)
actionview (= 7.0.4.2) actionview (= 7.0.4.3)
activejob (= 7.0.4.2) activejob (= 7.0.4.3)
activemodel (= 7.0.4.2) activemodel (= 7.0.4.3)
activerecord (= 7.0.4.2) activerecord (= 7.0.4.3)
activestorage (= 7.0.4.2) activestorage (= 7.0.4.3)
activesupport (= 7.0.4.2) activesupport (= 7.0.4.3)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.0.4.2) railties (= 7.0.4.3)
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.5.0) rails-html-sanitizer (1.5.0)
loofah (~> 2.19, >= 2.19.1) loofah (~> 2.19, >= 2.19.1)
railties (7.0.4.2) railties (7.0.4.3)
actionpack (= 7.0.4.2) actionpack (= 7.0.4.3)
activesupport (= 7.0.4.2) activesupport (= 7.0.4.3)
method_source method_source
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0) thor (~> 1.0)

9
app/components/bulk_upload_error_row_component.rb

@ -24,14 +24,7 @@ class BulkUploadErrorRowComponent < ViewComponent::Base
end end
def question_for_field(field) def question_for_field(field)
case bulk_upload.log_type bulk_upload.prefix_namespace::RowParser.question_for_field(field.to_sym)
when "lettings"
BulkUpload::Lettings::Validator.question_for_field(field.to_sym)
when "sales"
BulkUpload::Sales::Validator.question_for_field(field.to_sym)
else
"Unknown question"
end
end end
def bulk_upload def bulk_upload

2
app/components/bulk_upload_error_summary_table_component.html.erb

@ -16,7 +16,7 @@
<% body.row do |row| %> <% body.row do |row| %>
<% row.cell(text: error[0][0]) %> <% row.cell(text: error[0][0]) %>
<% row.cell(text: error[1]) %> <% row.cell(text: error[1]) %>
<% row.cell(text: BulkUpload::Lettings::Validator.question_for_field(error[0][1].to_sym)) %> <% row.cell(text: question_for_field(error[0][1].to_sym)) %>
<% row.cell(text: error[0][2]) %> <% row.cell(text: error[0][2]) %>
<% row.cell(text: error[0][1]) %> <% row.cell(text: error[0][1]) %>
<% end %> <% end %>

6
app/components/bulk_upload_error_summary_table_component.rb

@ -3,6 +3,8 @@ class BulkUploadErrorSummaryTableComponent < ViewComponent::Base
attr_reader :bulk_upload attr_reader :bulk_upload
delegate :question_for_field, to: :row_parser_class
def initialize(bulk_upload:) def initialize(bulk_upload:)
@bulk_upload = bulk_upload @bulk_upload = bulk_upload
@ -27,4 +29,8 @@ private
def display_threshold def display_threshold
DISPLAY_THRESHOLD DISPLAY_THRESHOLD
end end
def row_parser_class
bulk_upload.prefix_namespace::RowParser
end
end end

2
app/controllers/bulk_upload_lettings_logs_controller.rb

@ -33,6 +33,8 @@ private
end end
def in_crossover_period? def in_crossover_period?
return true if FeatureToggle.force_crossover?
FormHandler.instance.lettings_in_crossover_period? FormHandler.instance.lettings_in_crossover_period?
end end

16
app/controllers/lettings_logs_controller.rb

@ -2,10 +2,16 @@ 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, only: %i[index email_csv download_csv] before_action :session_filters, if: :current_user, only: %i[index email_csv download_csv]
before_action :set_session_filters, if: :current_user, only: %i[index email_csv download_csv] before_action :set_session_filters, if: :current_user, only: %i[index email_csv download_csv]
before_action :authenticate_scope!, only: %i[download_csv email_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]
def authenticate_scope!
codes_only_export = codes_only_export?(params)
head :unauthorized and return unless current_user.support? || !codes_only_export
end
def index def index
respond_to do |format| respond_to do |format|
format.html do format.html do
@ -80,13 +86,19 @@ class LettingsLogsController < LogsController
def download_csv def download_csv
unpaginated_filtered_logs = filtered_logs(current_user.lettings_logs, search_term, @session_filters) unpaginated_filtered_logs = filtered_logs(current_user.lettings_logs, search_term, @session_filters)
codes_only = codes_only_export?(params)
render "download_csv", locals: { search_term:, count: unpaginated_filtered_logs.size, post_path: email_csv_lettings_logs_path, codes_only: }
end
render "download_csv", locals: { search_term:, count: unpaginated_filtered_logs.size, post_path: email_csv_lettings_logs_path } def codes_only_export?(params)
params.require(:codes_only) == "true"
end end
def email_csv def email_csv
all_orgs = params["organisation_select"] == "all" all_orgs = params["organisation_select"] == "all"
EmailCsvJob.perform_later(current_user, search_term, @session_filters, all_orgs) codes_only_export = params.require(:codes_only) == "true"
EmailCsvJob.perform_later(current_user, search_term, @session_filters, all_orgs, nil, codes_only_export)
redirect_to csv_confirmation_lettings_logs_path redirect_to csv_confirmation_lettings_logs_path
end end

6
app/controllers/organisations_controller.rb

@ -107,12 +107,14 @@ class OrganisationsController < ApplicationController
def download_csv def download_csv
organisation_logs = LettingsLog.all.where(owning_organisation_id: @organisation.id) organisation_logs = LettingsLog.all.where(owning_organisation_id: @organisation.id)
unpaginated_filtered_logs = filtered_logs(organisation_logs, search_term, @session_filters) unpaginated_filtered_logs = filtered_logs(organisation_logs, search_term, @session_filters)
codes_only = params.require(:codes_only) == "true"
render "logs/download_csv", locals: { search_term:, count: unpaginated_filtered_logs.size, post_path: logs_email_csv_organisation_path } render "logs/download_csv", locals: { search_term:, count: unpaginated_filtered_logs.size, post_path: logs_email_csv_organisation_path, codes_only: }
end end
def email_csv def email_csv
EmailCsvJob.perform_later(current_user, search_term, @session_filters, false, @organisation) codes_only_export = params.require(:codes_only) == "true"
EmailCsvJob.perform_later(current_user, search_term, @session_filters, false, @organisation, codes_only_export)
redirect_to logs_csv_confirmation_organisation_path redirect_to logs_csv_confirmation_organisation_path
end end

24
app/controllers/start_controller.rb

@ -4,4 +4,28 @@ class StartController < ApplicationController
redirect_to(lettings_logs_path) redirect_to(lettings_logs_path)
end end
end end
def download_23_24_sales_form
send_file(
Rails.root.join("public/files/2023_24_sales_paper_form.pdf"),
filename: "2023-24 Sales paper form.pdf",
type: "application/pdf",
)
end
def download_22_23_sales_form
send_file(
Rails.root.join("public/files/2022_23_sales_paper_form.pdf"),
filename: "2022-23 Sales paper form.pdf",
type: "application/pdf",
)
end
def download_23_24_lettings_form
send_file(
Rails.root.join("public/files/2023_24_lettings_paper_form.pdf"),
filename: "2023-24 Lettings paper form.pdf",
type: "application/pdf",
)
end
end end

7
app/helpers/logs_helper.rb

@ -33,10 +33,9 @@ module LogsHelper
end end
end end
def csv_download_url_for_controller(controller) def csv_download_url_for_controller(controller_type:, search:, codes_only:)
case log_type_for_controller(controller) case log_type_for_controller(controller_type)
when "lettings" when "lettings" then csv_download_lettings_logs_path(search:, codes_only:)
csv_download_lettings_logs_path(search: params["search"])
end end
end end
end end

4
app/jobs/email_csv_job.rb

@ -5,14 +5,14 @@ class EmailCsvJob < ApplicationJob
EXPIRATION_TIME = 3.hours.to_i EXPIRATION_TIME = 3.hours.to_i
def perform(user, search_term = nil, filters = {}, all_orgs = false, organisation = nil) # rubocop:disable Style/OptionalBooleanParameter - sidekiq can't serialise named params def perform(user, search_term = nil, filters = {}, all_orgs = false, organisation = nil, codes_only_export = false) # rubocop:disable Style/OptionalBooleanParameter - sidekiq can't serialise named params
unfiltered_logs = organisation.present? && user.support? ? LettingsLog.where(owning_organisation_id: organisation.id) : user.lettings_logs unfiltered_logs = organisation.present? && user.support? ? LettingsLog.where(owning_organisation_id: organisation.id) : user.lettings_logs
filtered_logs = FilterService.filter_logs(unfiltered_logs, search_term, filters, all_orgs, user) filtered_logs = FilterService.filter_logs(unfiltered_logs, search_term, filters, all_orgs, user)
filename = organisation.present? ? "logs-#{organisation.name}-#{Time.zone.now}.csv" : "logs-#{Time.zone.now}.csv" filename = organisation.present? ? "logs-#{organisation.name}-#{Time.zone.now}.csv" : "logs-#{Time.zone.now}.csv"
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["CSV_DOWNLOAD_PAAS_INSTANCE"]) storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["CSV_DOWNLOAD_PAAS_INSTANCE"])
storage_service.write_file(filename, BYTE_ORDER_MARK + filtered_logs.to_csv(user)) storage_service.write_file(filename, BYTE_ORDER_MARK + filtered_logs.to_csv(user, codes_only_export:))
url = storage_service.get_presigned_url(filename, EXPIRATION_TIME) url = storage_service.get_presigned_url(filename, EXPIRATION_TIME)

11
app/mailers/bulk_upload_mailer.rb

@ -73,11 +73,7 @@ class BulkUploadMailer < NotifyMailer
start_bulk_upload_sales_logs_url start_bulk_upload_sales_logs_url
end end
validator_class = if bulk_upload.lettings? row_parser_class = bulk_upload.prefix_namespace::RowParser
BulkUpload::Lettings::Validator
else
BulkUpload::Sales::Validator
end
errors = bulk_upload errors = bulk_upload
.bulk_upload_errors .bulk_upload_errors
@ -87,7 +83,7 @@ class BulkUploadMailer < NotifyMailer
.keys .keys
.sort_by { |_col, field| field } .sort_by { |_col, field| field }
.map do |col, field| .map do |col, field|
"- Column #{col} (#{validator_class.question_for_field(field.to_sym)})" "- Column #{col} (#{row_parser_class.question_for_field(field.to_sym)})"
end end
send_email( send_email(
@ -104,7 +100,7 @@ class BulkUploadMailer < NotifyMailer
) )
end end
def send_bulk_upload_failed_service_error_mail(bulk_upload:) def send_bulk_upload_failed_service_error_mail(bulk_upload:, errors: [])
bulk_upload_link = if bulk_upload.lettings? bulk_upload_link = if bulk_upload.lettings?
start_bulk_upload_lettings_logs_url start_bulk_upload_lettings_logs_url
else else
@ -119,6 +115,7 @@ class BulkUploadMailer < NotifyMailer
upload_timestamp: bulk_upload.created_at, upload_timestamp: bulk_upload.created_at,
lettings_or_sales: bulk_upload.log_type, lettings_or_sales: bulk_upload.log_type,
year_combo: bulk_upload.year_combo, year_combo: bulk_upload.year_combo,
errors: errors.map { |e| "- #{e}" }.join("\n"),
bulk_upload_link:, bulk_upload_link:,
}, },
) )

22
app/models/bulk_upload.rb

@ -46,6 +46,28 @@ class BulkUpload < ApplicationRecord
needstype == 2 needstype == 2
end end
def prefix_namespace
type_class = case log_type
when "lettings"
"Lettings"
when "sales"
"Sales"
else
raise "unknown log type"
end
year_class = case year
when 2022
"Year2022"
when 2023
"Year2023"
else
raise "unknown year"
end
"BulkUpload::#{type_class}::#{year_class}".constantize
end
private private
def generate_identifier def generate_identifier

2
app/models/form/lettings/pages/lead_tenant_nationality.rb

@ -6,6 +6,6 @@ class Form::Lettings::Pages::LeadTenantNationality < ::Form::Page
end end
def questions def questions
@questions ||= [Form::Lettings::Questions::National.new(nil, nil, self)] @questions ||= [Form::Lettings::Questions::Nationality.new(nil, nil, self)]
end end
end end

4
app/models/form/lettings/pages/max_rent_value_check.rb

@ -4,11 +4,11 @@ class Form::Lettings::Pages::MaxRentValueCheck < ::Form::Page
@id = "max_rent_value_check" @id = "max_rent_value_check"
@depends_on = [{ "rent_in_soft_max_range?" => true }] @depends_on = [{ "rent_in_soft_max_range?" => true }]
@title_text = { @title_text = {
"translation" => "soft_validations.rent.max.title_text", "translation" => "soft_validations.rent.outside_range_title",
"arguments" => [{ "key" => "brent", "label" => true, "i18n_template" => "brent" }], "arguments" => [{ "key" => "brent", "label" => true, "i18n_template" => "brent" }],
} }
@informative_text = { @informative_text = {
"translation" => "soft_validations.rent.max.hint_text", "translation" => "soft_validations.rent.max_hint_text",
"arguments" => [ "arguments" => [
{ {
"key" => "soft_max_for_period", "key" => "soft_max_for_period",

4
app/models/form/lettings/pages/min_rent_value_check.rb

@ -4,11 +4,11 @@ class Form::Lettings::Pages::MinRentValueCheck < ::Form::Page
@id = "min_rent_value_check" @id = "min_rent_value_check"
@depends_on = [{ "rent_in_soft_min_range?" => true }] @depends_on = [{ "rent_in_soft_min_range?" => true }]
@title_text = { @title_text = {
"translation" => "soft_validations.rent.min.title_text", "translation" => "soft_validations.rent.outside_range_title",
"arguments" => [{ "key" => "brent", "label" => true, "i18n_template" => "brent" }], "arguments" => [{ "key" => "brent", "label" => true, "i18n_template" => "brent" }],
} }
@informative_text = { @informative_text = {
"translation" => "soft_validations.rent.min.hint_text", "translation" => "soft_validations.rent.min_hint_text",
"arguments" => [ "arguments" => [
{ {
"key" => "soft_min_for_period", "key" => "soft_min_for_period",

6
app/models/form/lettings/pages/starter_tenancy_type.rb

@ -2,13 +2,13 @@ class Form::Lettings::Pages::StarterTenancyType < ::Form::Page
def initialize(id, hsh, subsection) def initialize(id, hsh, subsection)
super super
@id = "starter_tenancy_type" @id = "starter_tenancy_type"
@depends_on = [{ "startertenancy" => 1 }] @depends_on = [{ "starter_tenancy?" => true }]
end end
def questions def questions
@questions ||= [ @questions ||= [
Form::Lettings::Questions::StarterTenancy.new(nil, nil, self), Form::Lettings::Questions::StarterTenancyType.new(nil, nil, self),
Form::Lettings::Questions::Tenancyother.new(nil, nil, self), Form::Lettings::Questions::TenancyOther.new(nil, nil, self),
] ]
end end
end end

4
app/models/form/lettings/pages/tenancy_length.rb

@ -2,10 +2,10 @@ class Form::Lettings::Pages::TenancyLength < ::Form::Page
def initialize(id, hsh, subsection) def initialize(id, hsh, subsection)
super super
@id = "tenancy_length" @id = "tenancy_length"
@depends_on = [{ "tenancy" => 4 }, { "tenancy" => 6 }, { "tenancy" => 3 }] @depends_on = [{ "tenancy_type_fixed_term?" => true }]
end end
def questions def questions
@questions ||= [Form::Lettings::Questions::Tenancylength.new(nil, nil, self)] @questions ||= [Form::Lettings::Questions::TenancyLength.new(nil, nil, self)]
end end
end end

6
app/models/form/lettings/pages/tenancy_type.rb

@ -2,13 +2,13 @@ class Form::Lettings::Pages::TenancyType < ::Form::Page
def initialize(id, hsh, subsection) def initialize(id, hsh, subsection)
super super
@id = "tenancy_type" @id = "tenancy_type"
@depends_on = [{ "startertenancy" => 2 }] @depends_on = [{ "starter_tenancy?" => false }]
end end
def questions def questions
@questions ||= [ @questions ||= [
Form::Lettings::Questions::Tenancy.new(nil, nil, self), Form::Lettings::Questions::TenancyType.new(nil, nil, self),
Form::Lettings::Questions::Tenancyother.new(nil, nil, self), Form::Lettings::Questions::TenancyOther.new(nil, nil, self),
] ]
end end
end end

6
app/models/form/lettings/questions/national.rb → app/models/form/lettings/questions/nationality.rb

@ -1,9 +1,9 @@
class Form::Lettings::Questions::National < ::Form::Question class Form::Lettings::Questions::Nationality < ::Form::Question
def initialize(id, hsh, page) def initialize(id, hsh, page)
super super
@id = "national" @id = "national"
@check_answer_label = "Lead tenant’s nationality" @check_answer_label = "Lead tenant’s nationality"
@header = "What is the lead tenant’s nationality?" @header = "What is the nationality of the lead tenant?"
@type = "radio" @type = "radio"
@check_answers_card_number = 1 @check_answers_card_number = 1
@hint_text = "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest." @hint_text = "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest."
@ -15,6 +15,8 @@ class Form::Lettings::Questions::National < ::Form::Question
"18" => { "value" => "United Kingdom" }, "18" => { "value" => "United Kingdom" },
"17" => { "value" => "Republic of Ireland" }, "17" => { "value" => "Republic of Ireland" },
"19" => { "value" => "European Economic Area (EEA) country, excluding Ireland" }, "19" => { "value" => "European Economic Area (EEA) country, excluding Ireland" },
"20" => { "value" => "Afghanistan" },
"21" => { "value" => "Ukraine" },
"12" => { "value" => "Other" }, "12" => { "value" => "Other" },
"divider" => true, "divider" => true,
"13" => { "value" => "Tenant prefers not to say" }, "13" => { "value" => "Tenant prefers not to say" },

7
app/models/form/lettings/questions/reason.rb

@ -59,8 +59,11 @@ class Form::Lettings::Questions::Reason < ::Form::Question
"17" => { "17" => {
"value" => "To move nearer to work", "value" => "To move nearer to work",
}, },
"7" => { "48" => {
"value" => "Domestic abuse", "value" => "Domestic abuse - previously joint tenancy with partner",
},
"49" => {
"value" => "Domestic abuse - other",
}, },
"31" => { "31" => {
"value" => "Hate crime", "value" => "Hate crime",

23
app/models/form/lettings/questions/starter_tenancy.rb

@ -1,23 +0,0 @@
class Form::Lettings::Questions::StarterTenancy < ::Form::Question
def initialize(id, hsh, page)
super
@id = "tenancy"
@check_answer_label = "Type of main tenancy after the starter period has ended?"
@header = "What is the type of tenancy after the starter period has ended?"
@type = "radio"
@check_answers_card_number = 0
@hint_text = "This is also known as an ‘introductory period’."
@answer_options = ANSWER_OPTIONS
@conditional_for = { "tenancyother" => [3] }
@question_number = 27
end
ANSWER_OPTIONS = {
"4" => { "value" => "Assured Shorthold Tenancy (AST) – Fixed term" },
"6" => { "value" => "Secure – fixed term" },
"2" => { "value" => "Assured – lifetime" },
"7" => { "value" => "Secure – lifetime" },
"5" => { "value" => "Licence agreement" },
"3" => { "value" => "Other" },
}.freeze
end

37
app/models/form/lettings/questions/starter_tenancy_type.rb

@ -0,0 +1,37 @@
class Form::Lettings::Questions::StarterTenancyType < ::Form::Question
def initialize(id, hsh, page)
super
@id = "tenancy"
@check_answer_label = "Type of main tenancy after the starter period has ended?"
@header = "What is the type of tenancy after the starter period has ended?"
@type = "radio"
@check_answers_card_number = 0
@hint_text = "This is also known as an ‘introductory period’."
@answer_options = ANSWER_OPTIONS
@conditional_for = { "tenancyother" => [3] }
end
ANSWER_OPTIONS = {
"4" => {
"value" => "Assured Shorthold Tenancy (AST) – Fixed term",
"hint" => "Mostly housing associations provide these. Fixed term tenancies are intended to be for a set amount of time up to 20 years.",
},
"6" => {
"value" => "Secure – fixed term",
"hint" => "Mostly local authorities provide these. Fixed term tenancies are intended to be for a set amount of time up to 20 years.",
},
"2" => {
"value" => "Assured – lifetime",
},
"7" => {
"value" => "Secure – lifetime",
},
"5" => {
"value" => "Licence agreement",
"hint" => "Licence agreements are mostly used for Supported Housing and work on a rolling basis.",
},
"3" => {
"value" => "Other",
},
}.freeze
end

23
app/models/form/lettings/questions/tenancy.rb

@ -1,23 +0,0 @@
class Form::Lettings::Questions::Tenancy < ::Form::Question
def initialize(id, hsh, page)
super
@id = "tenancy"
@check_answer_label = "Type of main tenancy"
@header = "What is the type of tenancy?"
@type = "radio"
@check_answers_card_number = 0
@hint_text = ""
@answer_options = ANSWER_OPTIONS
@conditional_for = { "tenancyother" => [3] }
@question_number = 27
end
ANSWER_OPTIONS = {
"4" => { "value" => "Assured Shorthold Tenancy (AST) – Fixed term" },
"6" => { "value" => "Secure – fixed term" },
"2" => { "value" => "Assured – lifetime" },
"7" => { "value" => "Secure – lifetime" },
"5" => { "value" => "Licence agreement" },
"3" => { "value" => "Other" },
}.freeze
end

2
app/models/form/lettings/questions/tenancylength.rb → app/models/form/lettings/questions/tenancy_length.rb

@ -1,4 +1,4 @@
class Form::Lettings::Questions::Tenancylength < ::Form::Question class Form::Lettings::Questions::TenancyLength < ::Form::Question
def initialize(id, hsh, page) def initialize(id, hsh, page)
super super
@id = "tenancylength" @id = "tenancylength"

2
app/models/form/lettings/questions/tenancyother.rb → app/models/form/lettings/questions/tenancy_other.rb

@ -1,4 +1,4 @@
class Form::Lettings::Questions::Tenancyother < ::Form::Question class Form::Lettings::Questions::TenancyOther < ::Form::Question
def initialize(id, hsh, page) def initialize(id, hsh, page)
super super
@id = "tenancyother" @id = "tenancyother"

37
app/models/form/lettings/questions/tenancy_type.rb

@ -0,0 +1,37 @@
class Form::Lettings::Questions::TenancyType < ::Form::Question
def initialize(id, hsh, page)
super
@id = "tenancy"
@check_answer_label = "Type of main tenancy"
@header = "What is the type of tenancy?"
@type = "radio"
@check_answers_card_number = 0
@hint_text = ""
@answer_options = ANSWER_OPTIONS
@conditional_for = { "tenancyother" => [3] }
end
ANSWER_OPTIONS = {
"4" => {
"value" => "Assured Shorthold Tenancy (AST) – Fixed term",
"hint" => "Mostly housing associations provide these. Fixed term tenancies are intended to be for a set amount of time up to 20 years.",
},
"6" => {
"value" => "Secure – fixed term",
"hint" => "Mostly local authorities provide these. Fixed term tenancies are intended to be for a set amount of time up to 20 years.",
},
"2" => {
"value" => "Assured – lifetime",
},
"7" => {
"value" => "Secure – lifetime",
},
"5" => {
"value" => "Licence agreement",
"hint" => "Licence agreements are mostly used for Supported Housing and work on a rolling basis.",
},
"3" => {
"value" => "Other",
},
}.freeze
end

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

@ -4,12 +4,12 @@ class Form::Sales::Pages::Buyer2LiveInProperty < ::Form::Page
@id = "buyer_2_live_in_property" @id = "buyer_2_live_in_property"
@depends_on = [ @depends_on = [
{ {
"jointpur" => 1, "joint_purchase?" => true,
"privacynotice" => 1, "privacynotice" => 1,
}, },
{ {
"jointpur" => 1, "joint_purchase?" => true,
"noint" => 1, "buyer_not_interviewed?" => true,
}, },
] ]
end end

11
app/models/form/sales/pages/buyer2_living_in.rb

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

11
app/models/form/sales/pages/buyer2_nationality.rb

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

11
app/models/form/sales/pages/buyer2_previous_housing_situation.rb

@ -0,0 +1,11 @@
class Form::Sales::Pages::Buyer2PreviousHousingSituation < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_previous_housing_situation"
@depends_on = [{ "buyer_two_not_already_living_in_property?" => true }]
end
def questions
@questions = [Form::Sales::Questions::PreviousTenureBuyer2.new(nil, nil, self)]
end
end

7
app/models/form/sales/pages/nationality1.rb

@ -7,15 +7,12 @@ class Form::Sales::Pages::Nationality1 < ::Form::Page
"privacynotice" => 1, "privacynotice" => 1,
}, },
{ {
"noint" => 1, "buyer_not_interviewed?" => true,
}, },
] ]
end end
def questions def questions
@questions ||= [ @questions ||= [Form::Sales::Questions::Nationality1.new(nil, nil, self)]
Form::Sales::Questions::Nationality1.new(nil, nil, self),
Form::Sales::Questions::OtherNationality1.new(nil, nil, self),
]
end end
end end

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

@ -11,13 +11,13 @@ class Form::Sales::Questions::Buyer1PreviousTenure < ::Form::Question
def answer_options def answer_options
{ {
"1" => { "value" => "Local Authority" }, "1" => { "value" => "Local authority tenant" },
"2" => { "value" => "Private registered provider or housing association tenant" }, "2" => { "value" => "Private registered provider or housing association tenant" },
"3" => { "value" => "Private tenant" }, "3" => { "value" => "Private tenant" },
"5" => { "value" => "Owner occupier" }, "5" => { "value" => "Owner occupier" },
"4" => { "value" => "Tied home or renting with job" }, "4" => { "value" => "Tied home or renting with job" },
"6" => { "value" => "Living with family or friends" }, "6" => { "value" => "Living with family or friends" },
"7" => { "value" => "Temporary accomodation" }, "7" => { "value" => "Temporary accommodation" },
"9" => { "value" => "Other" }, "9" => { "value" => "Other" },
"0" => { "value" => "Don’t know" }, "0" => { "value" => "Don’t know" },
} }

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

@ -0,0 +1,17 @@
class Form::Sales::Questions::Buyer2LivingIn < ::Form::Question
def initialize(id, hsh, page)
super
@id = "buy2living"
@check_answer_label = "Buyer 2 living at the same address"
@header = "At the time of purchase, was buyer 2 living at the same address as buyer 1?"
@type = "radio"
@hint_text = ""
@answer_options = ANSWER_OPTIONS
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
}.freeze
end

26
app/models/form/sales/questions/buyer2_nationality.rb

@ -0,0 +1,26 @@
class Form::Sales::Questions::Buyer2Nationality < ::Form::Question
def initialize(id, hsh, page)
super
@id = "nationalbuy2"
@check_answer_label = "Buyer 2’s nationality"
@header = "What is buyer 2’s nationality?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@check_answers_card_number = 2
@question_number = 32
@inferred_check_answers_value = [{
"condition" => {
"national" => 13,
},
"value" => "Prefers not to say",
}]
end
ANSWER_OPTIONS = {
"18" => { "value" => "United Kingdom" },
"17" => { "value" => "Republic of Ireland" },
"19" => { "value" => "European Economic Area (EEA), excluding ROI" },
"12" => { "value" => "Other" },
"13" => { "value" => "Buyer prefers not to say" },
}.freeze
end

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

@ -7,16 +7,6 @@ class Form::Sales::Questions::Nationality1 < ::Form::Question
@type = "radio" @type = "radio"
@hint_text = "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest." @hint_text = "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@answer_options = ANSWER_OPTIONS @answer_options = ANSWER_OPTIONS
@conditional_for = {
"othernational" => [12],
}
@hidden_in_check_answers = {
"depends_on" => [
{
"national" => 12,
},
],
}
@check_answers_card_number = 1 @check_answers_card_number = 1
@inferred_check_answers_value = [{ @inferred_check_answers_value = [{
"condition" => { "condition" => {

11
app/models/form/sales/questions/other_nationality1.rb

@ -1,11 +0,0 @@
class Form::Sales::Questions::OtherNationality1 < ::Form::Question
def initialize(id, hsh, page)
super
@id = "othernational"
@check_answer_label = "Buyer 1’s nationality"
@header = "Nationality"
@type = "text"
@check_answers_card_number = 1
@question_number = 24
end
end

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

@ -0,0 +1,23 @@
class Form::Sales::Questions::PreviousTenureBuyer2 < ::Form::Question
def initialize(id, hsh, page)
super
@id = "prevtenbuy2"
@check_answer_label = "Buyer 2’s previous tenure"
@header = "What was buyer 2’s previous tenure?"
@type = "radio"
@hint_text = ""
@answer_options = ANSWER_OPTIONS
end
ANSWER_OPTIONS = {
"1" => { "value" => "Local authority tenant" },
"2" => { "value" => "Private registered provider or housing association tenant" },
"3" => { "value" => "Private tenant" },
"5" => { "value" => "Owner occupier" },
"4" => { "value" => "Tied home or renting with job" },
"6" => { "value" => "Living with family or friends" },
"7" => { "value" => "Temporary accommodation" },
"9" => { "value" => "Other" },
"0" => { "value" => "Don't know" },
}.freeze
end

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

@ -32,7 +32,7 @@ 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, buyer_2_ethnicity_nationality_pages,
Form::Sales::Pages::Buyer2WorkingSituation.new(nil, nil, self), Form::Sales::Pages::Buyer2WorkingSituation.new(nil, nil, self),
Form::Sales::Pages::RetirementValueCheck.new("working_situation_2_retirement_value_check_joint_purchase", nil, self, person_index: 2), Form::Sales::Pages::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::Buyer2IncomeValueCheck.new("working_situation_buyer_2_income_value_check", nil, self),
@ -82,14 +82,17 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection
].flatten.compact ].flatten.compact
end end
def ethnic_pages_for_buyer_2 def buyer_2_ethnicity_nationality_pages
if form.start_date.year >= 2023 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::Buyer2EthnicGroup.new(nil, nil, self),
Form::Sales::Pages::Buyer2EthnicBackgroundAsian.new(nil, nil, self), Form::Sales::Pages::Buyer2EthnicBackgroundBlack.new(nil, nil, self),
Form::Sales::Pages::Buyer2EthnicBackgroundArab.new(nil, nil, self), Form::Sales::Pages::Buyer2EthnicBackgroundAsian.new(nil, nil, self),
Form::Sales::Pages::Buyer2EthnicBackgroundMixed.new(nil, nil, self), Form::Sales::Pages::Buyer2EthnicBackgroundArab.new(nil, nil, self),
Form::Sales::Pages::Buyer2EthnicBackgroundWhite.new(nil, nil, self)] Form::Sales::Pages::Buyer2EthnicBackgroundMixed.new(nil, nil, self),
Form::Sales::Pages::Buyer2EthnicBackgroundWhite.new(nil, nil, self),
Form::Sales::Pages::Buyer2Nationality.new(nil, nil, self),
]
end end
end end

12
app/models/form/sales/subsections/household_situation.rb

@ -12,6 +12,16 @@ class Form::Sales::Subsections::HouseholdSituation < ::Form::Subsection
Form::Sales::Pages::LastAccommodation.new(nil, nil, self), Form::Sales::Pages::LastAccommodation.new(nil, nil, self),
Form::Sales::Pages::LastAccommodationLa.new(nil, nil, self), Form::Sales::Pages::LastAccommodationLa.new(nil, nil, self),
Form::Sales::Pages::BuyersOrganisations.new(nil, nil, self), Form::Sales::Pages::BuyersOrganisations.new(nil, nil, self),
] buyer_2_situation_pages,
].flatten.compact
end
def buyer_2_situation_pages
if form.start_date.year >= 2023
[
Form::Sales::Pages::Buyer2LivingIn.new(nil, nil, self),
Form::Sales::Pages::Buyer2PreviousHousingSituation.new(nil, nil, self),
]
end
end end
end end

2
app/models/forms/bulk_upload_lettings/prepare_your_file.rb

@ -44,6 +44,8 @@ module Forms
private private
def in_crossover_period? def in_crossover_period?
return true if FeatureToggle.force_crossover?
FormHandler.instance.lettings_in_crossover_period? FormHandler.instance.lettings_in_crossover_period?
end end
end end

5
app/models/forms/bulk_upload_lettings/year.rb

@ -34,7 +34,10 @@ module Forms
private private
def possible_years def possible_years
[FormHandler.instance.lettings_forms["current_lettings"].start_date.year, FormHandler.instance.lettings_forms["previous_lettings"].start_date.year] [
FormHandler.instance.lettings_forms["next_lettings"].start_date.year,
FormHandler.instance.lettings_forms["current_lettings"].start_date.year,
]
end end
end end
end end

28
app/models/lettings_log.rb

@ -195,6 +195,14 @@ class LettingsLog < Log
renewal == 1 renewal == 1
end end
def starter_tenancy?
startertenancy == 1
end
def tenancy_type_fixed_term?
[4, 6].include? tenancy
end
def is_general_needs? def is_general_needs?
# 1: General Needs # 1: General Needs
needstype == 1 needstype == 1
@ -417,27 +425,13 @@ class LettingsLog < Log
created_by&.is_dpo created_by&.is_dpo
end end
delegate :service_name, :sensitive, :registered_under_care_act, :primary_client_group, :has_other_client_group, :secondary_client_group, :owning_organisation, :managing_organisation, :support_type, :intended_stay, :created_at, prefix: "scheme", to: :scheme, allow_nil: true
delegate :scheme_type, to: :scheme, allow_nil: true
def scheme_code def scheme_code
scheme&.id ? "S#{scheme.id}" : nil scheme&.id ? "S#{scheme.id}" : nil
end end
def scheme_owning_organisation_name def self.to_csv(user = nil, codes_only_export:)
scheme_owning_organisation&.name export_type = codes_only_export ? "codes" : "labels"
end Csv::LettingsLogCsvService.new(user, export_type:).to_csv
delegate :postcode, :name, :units, :type_of_unit, :mobility_type, :startdate, prefix: "location", to: :location, allow_nil: true
delegate :location_admin_district, to: :location, allow_nil: true
# This is not the location_code in the db, location.id is just called code in the UI
def location_code
location&.id
end
def self.to_csv(user = nil)
Csv::LettingsLogCsvService.new(user).to_csv
end end
def beds_for_la_rent_range def beds_for_la_rent_range

12
app/models/sales_log.rb

@ -151,6 +151,14 @@ class SalesLog < Log
inc1mort == 1 inc1mort == 1
end end
def buyer_two_will_live_in_property?
buy2livein == 1
end
def buyer_two_not_already_living_in_property?
buy2living == 2
end
def income2_used_for_mortgage? def income2_used_for_mortgage?
inc2mort == 1 inc2mort == 1
end end
@ -250,6 +258,10 @@ class SalesLog < Log
jointpur == 2 jointpur == 2
end end
def buyer_not_interviewed?
noint == 1
end
def old_persons_shared_ownership? def old_persons_shared_ownership?
type == 24 type == 24
end end

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

@ -52,6 +52,21 @@ module Validations::Sales::FinancialValidations
end end
end end
def validate_percentage_bought_at_least_threshold(record)
return unless record.stairbought && record.type
threshold = if [2, 16, 18, 24].include? record.type
10
else
1
end
if threshold && record.stairbought < threshold
record.errors.add :stairbought, I18n.t("validations.financial.staircasing.percentage_bought_must_be_at_least_threshold", threshold:)
record.errors.add :type, I18n.t("validations.setup.type.percentage_bought_must_be_at_least_threshold", threshold:)
end
end
def validate_child_income(record) def validate_child_income(record)
return unless record.income2 && record.ecstat2 return unless record.income2 && record.ecstat2

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

@ -26,7 +26,14 @@ class BulkUpload::Lettings::LogCreator
private private
def csv_parser def csv_parser
@csv_parser ||= BulkUpload::Lettings::CsvParser.new(path:) @csv_parser ||= case bulk_upload.year
when 2022
BulkUpload::Lettings::Year2022::CsvParser.new(path:)
when 2023
BulkUpload::Lettings::Year2023::CsvParser.new(path:)
else
raise "csv parser not found"
end
end end
def row_offset def row_offset

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

@ -6,146 +6,10 @@ class BulkUpload::Lettings::Validator
include ActiveModel::Validations include ActiveModel::Validations
QUESTIONS = {
field_1: "What is the letting type?",
field_2: "This question has been removed",
field_3: "This question has been removed",
field_4: "Management group code",
field_5: "Scheme code",
field_6: "This question has been removed",
field_7: "What is the tenant code?",
field_8: "Is this a starter tenancy?",
field_9: "What is the tenancy type?",
field_10: "If 'Other', what is the tenancy type?",
field_11: "What is the length of the fixed-term tenancy to the nearest year?",
field_12: "Age of Person 1",
field_13: "Age of Person 2",
field_14: "Age of Person 3",
field_15: "Age of Person 4",
field_16: "Age of Person 5",
field_17: "Age of Person 6",
field_18: "Age of Person 7",
field_19: "Age of Person 8",
field_20: "Gender identity of Person 1",
field_21: "Gender identity of Person 2",
field_22: "Gender identity of Person 3",
field_23: "Gender identity of Person 4",
field_24: "Gender identity of Person 5",
field_25: "Gender identity of Person 6",
field_26: "Gender identity of Person 7",
field_27: "Gender identity of Person 8",
field_28: "Relationship to Person 1 for Person 2",
field_29: "Relationship to Person 1 for Person 3",
field_30: "Relationship to Person 1 for Person 4",
field_31: "Relationship to Person 1 for Person 5",
field_32: "Relationship to Person 1 for Person 6",
field_33: "Relationship to Person 1 for Person 7",
field_34: "Relationship to Person 1 for Person 8",
field_35: "Working situation of Person 1",
field_36: "Working situation of Person 2",
field_37: "Working situation of Person 3",
field_38: "Working situation of Person 4",
field_39: "Working situation of Person 5",
field_40: "Working situation of Person 6",
field_41: "Working situation of Person 7",
field_42: "Working situation of Person 8",
field_43: "What is the lead tenant's ethnic group?",
field_44: "What is the lead tenant's nationality?",
field_45: "Does anybody in the household have links to the UK armed forces?",
field_46: "Was the person seriously injured or ill as a result of serving in the UK armed forces?",
field_47: "Is anybody in the household pregnant?",
field_48: "Is the tenant likely to be receiving benefits related to housing?",
field_49: "How much of the household's income is from Universal Credit, state pensions or benefits?",
field_50: "How much income does the household have in total?",
field_51: "Do you know the household's income?",
field_52: "What is the tenant's main reason for the household leaving their last settled home?",
field_53: "If 'Other', what was the main reason for leaving their last settled home?",
field_54: "This question has been removed",
field_55: "Does anybody in the household have any disabled access needs?",
field_56: "Does anybody in the household have any disabled access needs?",
field_57: "Does anybody in the household have any disabled access needs?",
field_58: "Does anybody in the household have any disabled access needs?",
field_59: "Does anybody in the household have any disabled access needs?",
field_60: "Does anybody in the household have any disabled access needs?",
field_61: "Where was the household immediately before this letting?",
field_62: "What is the local authority of the household's last settled home?",
field_63: "Part 1 of postcode of last settled home",
field_64: "Part 2 of postcode of last settled home",
field_65: "Do you know the postcode of last settled home?",
field_66: "How long has the household continuously lived in the local authority area of the new letting?",
field_67: "How long has the household been on the waiting list for the new letting?",
field_68: "Was the tenant homeless directly before this tenancy?",
field_69: "Was the household given 'reasonable preference' by the local authority?",
field_70: "Reasonable preference. They were homeless or about to lose their home (within 56 days)",
field_71: "Reasonable preference. They were living in insanitary, overcrowded or unsatisfactory housing",
field_72: "Reasonable preference. They needed to move on medical and welfare grounds (including a disability)",
field_73: "Reasonable preference. They needed to move to avoid hardship to themselves or others",
field_74: "Reasonable preference. Don't know",
field_75: "Was the letting made under any of the following allocations systems?",
field_76: "Was the letting made under any of the following allocations systems?",
field_77: "Was the letting made under any of the following allocations systems?",
field_78: "What was the source of referral for this letting?",
field_79: "How often does the household pay rent and other charges?",
field_80: "What is the basic rent?",
field_81: "What is the service charge?",
field_82: "What is the personal service charge?",
field_83: "What is the support charge?",
field_84: "Total Charge",
field_85: "If this is a care home, how much does the household pay every [time period]?",
field_86: "Does the household pay rent or other charges for the accommodation?",
field_87: "After the household has received any housing-related benefits, will they still need to pay basic rent and other charges?",
field_88: "What do you expect the outstanding amount to be?",
field_89: "What is the void or renewal date?",
field_90: "What is the void or renewal date?",
field_91: "What is the void or renewal date?",
field_92: "What date were major repairs completed on?",
field_93: "What date were major repairs completed on?",
field_94: "What date were major repairs completed on?",
field_95: "This question has been removed",
field_96: "What date did the tenancy start?",
field_97: "What date did the tenancy start?",
field_98: "What date did the tenancy start?",
field_99: "Since becoming available, how many times has the property been previously offered?",
field_100: "What is the property reference?",
field_101: "How many bedrooms does the property have?",
field_102: "What type of unit is the property?",
field_103: "Which type of building is the property?",
field_104: "Is the property built or adapted to wheelchair-user standards?",
field_105: "What type was the property most recently let as?",
field_106: "What is the reason for the property being vacant?",
field_107: "What is the local authority of the property?",
field_108: "Part 1 of postcode of the property",
field_109: "Part 2 of postcode of the property",
field_110: "This question has been removed",
field_111: "Which organisation owns this property?",
field_112: "Username field",
field_113: "Which organisation manages this property?",
field_114: "Is the person still serving in the UK armed forces?",
field_115: "This question has been removed",
field_116: "How often does the household receive income?",
field_117: "Is this letting sheltered accommodation?",
field_118: "Does anybody in the household have a physical or mental health condition (or other illness) expected to last for 12 months or more?",
field_119: "Vision, for example blindness or partial sight",
field_120: "Hearing, for example deafness or partial hearing",
field_121: "Mobility, for example walking short distances or climbing stairs",
field_122: "Dexterity, for example lifting and carrying objects, using a keyboard",
field_123: "Learning or understanding or concentrating",
field_124: "Memory",
field_125: "Mental health",
field_126: "Stamina or breathing or fatigue",
field_127: "Socially or behaviourally, for example associated with autism spectral disorder (ASD) which includes Aspergers' or attention deficit hyperactivity disorder (ADHD)",
field_128: "Other",
field_129: "Is this letting a London Affordable Rent letting?",
field_130: "Which type of Intermediate Rent is this letting?",
field_131: "Which 'Other' type of Intermediate Rent is this letting?",
field_132: "Data Protection",
field_133: "Is this a joint tenancy?",
field_134: "Is this letting a renewal?",
}.freeze
attr_reader :bulk_upload, :path attr_reader :bulk_upload, :path
validate :validate_file_not_empty validate :validate_file_not_empty
validate :validate_min_columns
validate :validate_max_columns validate :validate_max_columns
def initialize(bulk_upload:, path:) def initialize(bulk_upload:, path:)
@ -160,14 +24,16 @@ class BulkUpload::Lettings::Validator
row = index + row_offset + 1 row = index + row_offset + 1
row_parser.errors.each do |error| row_parser.errors.each do |error|
col = csv_parser.column_for_field(error.attribute.to_s)
bulk_upload.bulk_upload_errors.create!( bulk_upload.bulk_upload_errors.create!(
field: error.attribute, field: error.attribute,
error: error.message, error: error.message,
tenant_code: row_parser.field_7, tenant_code: row_parser.tenant_code,
property_ref: row_parser.field_100, property_ref: row_parser.property_ref,
row:, row:,
cell: "#{cols[field_number_for_attribute(error.attribute) - col_offset + 1]}#{row}", cell: "#{col}#{row}",
col: cols[field_number_for_attribute(error.attribute) - col_offset + 1], col:,
category: error.options[:category], category: error.options[:category],
) )
end end
@ -210,7 +76,14 @@ private
end end
def csv_parser def csv_parser
@csv_parser ||= BulkUpload::Lettings::CsvParser.new(path:) @csv_parser ||= case bulk_upload.year
when 2022
BulkUpload::Lettings::Year2022::CsvParser.new(path:)
when 2023
BulkUpload::Lettings::Year2023::CsvParser.new(path:)
else
raise "csv parser not found"
end
end end
def row_offset def row_offset
@ -257,12 +130,20 @@ private
end end
end end
def validate_min_columns
return if halt_validations?
column_count = rows.map(&:size).min
errors.add(:base, :under_min_column_count) if column_count < csv_parser.class::MIN_COLUMNS
end
def validate_max_columns def validate_max_columns
return if halt_validations? return if halt_validations?
max_row_size = rows.map(&:size).max column_count = rows.map(&:size).max
errors.add(:file, :max_row_size) if max_row_size > 136 errors.add(:base, :over_max_column_count) if column_count > csv_parser.class::MAX_COLUMNS
end end
def halt_validations! def halt_validations!

15
app/services/bulk_upload/lettings/csv_parser.rb → app/services/bulk_upload/lettings/year2022/csv_parser.rb

@ -1,6 +1,9 @@
require "csv" require "csv"
class BulkUpload::Lettings::CsvParser class BulkUpload::Lettings::Year2022::CsvParser
MIN_COLUMNS = 134
MAX_COLUMNS = 136
attr_reader :path attr_reader :path
def initialize(path:) def initialize(path:)
@ -25,7 +28,7 @@ class BulkUpload::Lettings::CsvParser
headers = ("field_1".."field_134").to_a headers = ("field_1".."field_134").to_a
hash = Hash[headers.zip(stripped_row)] hash = Hash[headers.zip(stripped_row)]
BulkUpload::Lettings::RowParser.new(hash) BulkUpload::Lettings::Year2022::RowParser.new(hash)
end end
end end
@ -37,8 +40,16 @@ class BulkUpload::Lettings::CsvParser
@rows ||= CSV.parse(normalised_string, row_sep:) @rows ||= CSV.parse(normalised_string, row_sep:)
end end
def column_for_field(field)
cols[headers.find_index(field) + col_offset]
end
private private
def headers
@headers ||= ("field_1".."field_134").to_a
end
def with_headers? def with_headers?
rows[0][0]&.match?(/\D+/) rows[0][0]&.match?(/\D+/)
end end

158
app/services/bulk_upload/lettings/row_parser.rb → app/services/bulk_upload/lettings/year2022/row_parser.rb

@ -1,7 +1,144 @@
class BulkUpload::Lettings::RowParser class BulkUpload::Lettings::Year2022::RowParser
include ActiveModel::Model include ActiveModel::Model
include ActiveModel::Attributes include ActiveModel::Attributes
QUESTIONS = {
field_1: "What is the letting type?",
field_2: "This question has been removed",
field_3: "This question has been removed",
field_4: "Management group code",
field_5: "Scheme code",
field_6: "This question has been removed",
field_7: "What is the tenant code?",
field_8: "Is this a starter tenancy?",
field_9: "What is the tenancy type?",
field_10: "If 'Other', what is the tenancy type?",
field_11: "What is the length of the fixed-term tenancy to the nearest year?",
field_12: "Age of Person 1",
field_13: "Age of Person 2",
field_14: "Age of Person 3",
field_15: "Age of Person 4",
field_16: "Age of Person 5",
field_17: "Age of Person 6",
field_18: "Age of Person 7",
field_19: "Age of Person 8",
field_20: "Gender identity of Person 1",
field_21: "Gender identity of Person 2",
field_22: "Gender identity of Person 3",
field_23: "Gender identity of Person 4",
field_24: "Gender identity of Person 5",
field_25: "Gender identity of Person 6",
field_26: "Gender identity of Person 7",
field_27: "Gender identity of Person 8",
field_28: "Relationship to Person 1 for Person 2",
field_29: "Relationship to Person 1 for Person 3",
field_30: "Relationship to Person 1 for Person 4",
field_31: "Relationship to Person 1 for Person 5",
field_32: "Relationship to Person 1 for Person 6",
field_33: "Relationship to Person 1 for Person 7",
field_34: "Relationship to Person 1 for Person 8",
field_35: "Working situation of Person 1",
field_36: "Working situation of Person 2",
field_37: "Working situation of Person 3",
field_38: "Working situation of Person 4",
field_39: "Working situation of Person 5",
field_40: "Working situation of Person 6",
field_41: "Working situation of Person 7",
field_42: "Working situation of Person 8",
field_43: "What is the lead tenant's ethnic group?",
field_44: "What is the lead tenant's nationality?",
field_45: "Does anybody in the household have links to the UK armed forces?",
field_46: "Was the person seriously injured or ill as a result of serving in the UK armed forces?",
field_47: "Is anybody in the household pregnant?",
field_48: "Is the tenant likely to be receiving benefits related to housing?",
field_49: "How much of the household's income is from Universal Credit, state pensions or benefits?",
field_50: "How much income does the household have in total?",
field_51: "Do you know the household's income?",
field_52: "What is the tenant's main reason for the household leaving their last settled home?",
field_53: "If 'Other', what was the main reason for leaving their last settled home?",
field_54: "This question has been removed",
field_55: "Does anybody in the household have any disabled access needs?",
field_56: "Does anybody in the household have any disabled access needs?",
field_57: "Does anybody in the household have any disabled access needs?",
field_58: "Does anybody in the household have any disabled access needs?",
field_59: "Does anybody in the household have any disabled access needs?",
field_60: "Does anybody in the household have any disabled access needs?",
field_61: "Where was the household immediately before this letting?",
field_62: "What is the local authority of the household's last settled home?",
field_63: "Part 1 of postcode of last settled home",
field_64: "Part 2 of postcode of last settled home",
field_65: "Do you know the postcode of last settled home?",
field_66: "How long has the household continuously lived in the local authority area of the new letting?",
field_67: "How long has the household been on the waiting list for the new letting?",
field_68: "Was the tenant homeless directly before this tenancy?",
field_69: "Was the household given 'reasonable preference' by the local authority?",
field_70: "Reasonable preference. They were homeless or about to lose their home (within 56 days)",
field_71: "Reasonable preference. They were living in insanitary, overcrowded or unsatisfactory housing",
field_72: "Reasonable preference. They needed to move on medical and welfare grounds (including a disability)",
field_73: "Reasonable preference. They needed to move to avoid hardship to themselves or others",
field_74: "Reasonable preference. Don't know",
field_75: "Was the letting made under any of the following allocations systems?",
field_76: "Was the letting made under any of the following allocations systems?",
field_77: "Was the letting made under any of the following allocations systems?",
field_78: "What was the source of referral for this letting?",
field_79: "How often does the household pay rent and other charges?",
field_80: "What is the basic rent?",
field_81: "What is the service charge?",
field_82: "What is the personal service charge?",
field_83: "What is the support charge?",
field_84: "Total Charge",
field_85: "If this is a care home, how much does the household pay every [time period]?",
field_86: "Does the household pay rent or other charges for the accommodation?",
field_87: "After the household has received any housing-related benefits, will they still need to pay basic rent and other charges?",
field_88: "What do you expect the outstanding amount to be?",
field_89: "What is the void or renewal date?",
field_90: "What is the void or renewal date?",
field_91: "What is the void or renewal date?",
field_92: "What date were major repairs completed on?",
field_93: "What date were major repairs completed on?",
field_94: "What date were major repairs completed on?",
field_95: "This question has been removed",
field_96: "What date did the tenancy start?",
field_97: "What date did the tenancy start?",
field_98: "What date did the tenancy start?",
field_99: "Since becoming available, how many times has the property been previously offered?",
field_100: "What is the property reference?",
field_101: "How many bedrooms does the property have?",
field_102: "What type of unit is the property?",
field_103: "Which type of building is the property?",
field_104: "Is the property built or adapted to wheelchair-user standards?",
field_105: "What type was the property most recently let as?",
field_106: "What is the reason for the property being vacant?",
field_107: "What is the local authority of the property?",
field_108: "Part 1 of postcode of the property",
field_109: "Part 2 of postcode of the property",
field_110: "This question has been removed",
field_111: "Which organisation owns this property?",
field_112: "Username field",
field_113: "Which organisation manages this property?",
field_114: "Is the person still serving in the UK armed forces?",
field_115: "This question has been removed",
field_116: "How often does the household receive income?",
field_117: "Is this letting sheltered accommodation?",
field_118: "Does anybody in the household have a physical or mental health condition (or other illness) expected to last for 12 months or more?",
field_119: "Vision, for example blindness or partial sight",
field_120: "Hearing, for example deafness or partial hearing",
field_121: "Mobility, for example walking short distances or climbing stairs",
field_122: "Dexterity, for example lifting and carrying objects, using a keyboard",
field_123: "Learning or understanding or concentrating",
field_124: "Memory",
field_125: "Mental health",
field_126: "Stamina or breathing or fatigue",
field_127: "Socially or behaviourally, for example associated with autism spectral disorder (ASD) which includes Aspergers' or attention deficit hyperactivity disorder (ADHD)",
field_128: "Other",
field_129: "Is this letting a London Affordable Rent letting?",
field_130: "Which type of Intermediate Rent is this letting?",
field_131: "Which 'Other' type of Intermediate Rent is this letting?",
field_132: "Data Protection",
field_133: "Is this a joint tenancy?",
field_134: "Is this letting a renewal?",
}.freeze
attribute :bulk_upload attribute :bulk_upload
attribute :block_log_creation, :boolean, default: -> { false } attribute :block_log_creation, :boolean, default: -> { false }
@ -188,6 +325,10 @@ class BulkUpload::Lettings::RowParser
validate :validate_location_exists validate :validate_location_exists
validate :validate_location_data_given validate :validate_location_data_given
def self.question_for_field(field)
QUESTIONS[field]
end
def valid? def valid?
errors.clear errors.clear
@ -211,7 +352,12 @@ class BulkUpload::Lettings::RowParser
end end
def blank_row? def blank_row?
attribute_set.to_hash.reject { |k, _| %w[bulk_upload block_log_creation].include?(k) }.values.compact.empty? attribute_set
.to_hash
.reject { |k, _| %w[bulk_upload block_log_creation].include?(k) }
.values
.compact
.empty?
end end
def log def log
@ -226,6 +372,14 @@ class BulkUpload::Lettings::RowParser
block_log_creation block_log_creation
end end
def tenant_code
field_7
end
def property_ref
field_100
end
private private
def validate_location_related def validate_location_related

81
app/services/bulk_upload/lettings/year2023/csv_parser.rb

@ -0,0 +1,81 @@
require "csv"
class BulkUpload::Lettings::Year2023::CsvParser
MIN_COLUMNS = 141
MAX_COLUMNS = 143
attr_reader :path
def initialize(path:)
@path = path
end
def row_offset
if with_headers?
rows.find_index { |row| row[0].match(/field number/i) } + 1
else
0
end
end
def col_offset
with_headers? ? 1 : 0
end
def cols
@cols ||= ("A".."EL").to_a
end
def row_parsers
@row_parsers ||= body_rows.map do |row|
stripped_row = row[col_offset..]
hash = Hash[field_numbers.zip(stripped_row)]
BulkUpload::Lettings::Year2023::RowParser.new(hash)
end
end
def body_rows
rows[row_offset..]
end
def rows
@rows ||= CSV.parse(normalised_string, row_sep:)
end
def column_for_field(field)
cols[field_numbers.find_index(field) + col_offset]
end
private
def default_field_numbers
[5, nil, nil, 15, 16, nil, 13, 40, 41, 42, 43, 46, 52, 56, 60, 64, 68, 72, 76, 47, 53, 57, 61, 65, 69, 73, 77, 51, 55, 59, 63, 67, 71, 75, 50, 54, 58, 62, 66, 70, 74, 78, 48, 49, 79, 81, 82, 123, 124, 122, 120, 102, 103, nil, 83, 84, 85, 86, 87, 88, 104, 109, 107, 108, 106, 100, 101, 105, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 126, 128, 129, 130, 131, 132, 127, 125, 133, 134, 33, 34, 35, 36, 37, 38, nil, 7, 8, 9, 28, 14, 32, 29, 30, 31, 26, 27, 25, 23, 24, nil, 1, 3, 2, 80, nil, 121, 44, 89, 98, 92, 95, 90, 91, 93, 94, 97, 96, 99, 10, 11, 12, 45, 39, 6, 4, 17, 18, 19, 20, 21, 22].map { |h| h.present? ? "field_#{h}" : "field_blank" }
end
def field_numbers
@field_numbers ||= if with_headers?
rows[row_offset - 1][col_offset..].map { |h| h.present? ? "field_#{h}" : "field_blank" }
else
default_field_numbers
end
end
def with_headers?
rows.map { |r| r[0] }.any? { |cell| cell&.match?(/field number/i) }
end
def row_sep
"\n"
end
def normalised_string
return @normalised_string if @normalised_string
@normalised_string = File.read(path, encoding: "bom|utf-8")
@normalised_string.gsub!("\r\n", "\n")
@normalised_string.scrub!("")
@normalised_string
end
end

1241
app/services/bulk_upload/lettings/year2023/row_parser.rb

File diff suppressed because it is too large Load Diff

6
app/services/bulk_upload/processor.rb

@ -8,7 +8,7 @@ class BulkUpload::Processor
def call def call
download download
return send_failure_mail if validator.invalid? return send_failure_mail(errors: validator.errors.full_messages) if validator.invalid?
validator.call validator.call
@ -62,9 +62,9 @@ private
validator.create_logs? && bulk_upload.logs.group(:status).count.keys == %w[completed] validator.create_logs? && bulk_upload.logs.group(:status).count.keys == %w[completed]
end end
def send_failure_mail def send_failure_mail(errors: [])
BulkUploadMailer BulkUploadMailer
.send_bulk_upload_failed_service_error_mail(bulk_upload:) .send_bulk_upload_failed_service_error_mail(bulk_upload:, errors:)
.deliver_later .deliver_later
end end

176
app/services/bulk_upload/sales/row_parser.rb

@ -1,176 +0,0 @@
class BulkUpload::Sales::RowParser
include ActiveModel::Model
include ActiveModel::Attributes
attribute :field_1, :string
attribute :field_2, :integer
attribute :field_3, :integer
attribute :field_4, :integer
attribute :field_5
attribute :field_6, :integer
attribute :field_7, :integer
attribute :field_8, :integer
attribute :field_9, :integer
attribute :field_10, :integer
attribute :field_11, :integer
attribute :field_12, :integer
attribute :field_13, :string
attribute :field_14, :string
attribute :field_15, :string
attribute :field_16, :string
attribute :field_17, :string
attribute :field_18, :string
attribute :field_19, :string
attribute :field_20, :integer
attribute :field_21, :integer
attribute :field_22, :integer
attribute :field_23, :integer
attribute :field_24, :integer
attribute :field_25, :integer
attribute :field_26, :integer
attribute :field_27, :integer
attribute :field_28, :integer
attribute :field_29, :integer
attribute :field_30, :integer
attribute :field_31, :integer
attribute :field_32, :integer
attribute :field_33, :integer
attribute :field_34, :integer
attribute :field_35, :integer
attribute :field_36, :integer
attribute :field_37, :integer
attribute :field_38
attribute :field_39, :integer
attribute :field_40, :string
attribute :field_41, :string
attribute :field_42, :string
attribute :field_43, :integer
attribute :field_44, :integer
attribute :field_45, :integer
attribute :field_46, :integer
attribute :field_47, :integer
attribute :field_48, :integer
attribute :field_49, :integer
attribute :field_50, :integer
attribute :field_51, :integer
attribute :field_52, :integer
attribute :field_53, :string
attribute :field_54, :string
attribute :field_55, :string
attribute :field_56, :integer
attribute :field_57, :integer
attribute :field_58, :integer
attribute :field_59, :integer
attribute :field_60, :integer
attribute :field_61, :integer
attribute :field_62, :integer
attribute :field_63, :integer
attribute :field_64, :integer
attribute :field_65, :integer
attribute :field_66, :integer
attribute :field_67, :integer
attribute :field_68, :integer
attribute :field_69, :integer
attribute :field_70, :integer
attribute :field_71, :integer
attribute :field_72, :integer
attribute :field_73, :integer
attribute :field_74, :decimal
attribute :field_75, :decimal
attribute :field_76, :integer
attribute :field_77, :integer
attribute :field_78, :integer
attribute :field_79, :integer
attribute :field_80, :integer
attribute :field_81, :integer
attribute :field_82, :integer
attribute :field_83, :integer
attribute :field_84, :integer
attribute :field_85, :string
attribute :field_86
attribute :field_87, :integer
attribute :field_88, :integer
attribute :field_89, :integer
attribute :field_90, :integer
attribute :field_91, :integer
attribute :field_92, :integer
attribute :field_93, :string
attribute :field_94
attribute :field_95, :integer
attribute :field_96
attribute :field_97, :integer
attribute :field_98, :integer
attribute :field_99, :string
attribute :field_100, :integer
attribute :field_101, :string
attribute :field_102, :integer
attribute :field_103, :string
attribute :field_104, :integer
attribute :field_105, :integer
attribute :field_106, :integer
attribute :field_107, :integer
attribute :field_108, :integer
attribute :field_109, :integer
attribute :field_110, :integer
attribute :field_111, :integer
attribute :field_112, :integer
attribute :field_113, :integer
attribute :field_114, :integer
attribute :field_115, :integer
attribute :field_116, :integer
attribute :field_117, :integer
attribute :field_118, :integer
attribute :field_119, :integer
attribute :field_120, :integer
attribute :field_121, :integer
attribute :field_122, :integer
attribute :field_123, :integer
attribute :field_124, :integer
attribute :field_125, :integer
# validates :field_1, presence: true, numericality: { in: (1..12) }
# validates :field_4, numericality: { in: (1..999), allow_blank: true }
# validates :field_4, presence: true, if: :field_4_presence_check
validate :validate_possible_answers
# delegate :valid?, to: :native_object
# delegate :errors, to: :native_object
private
def native_object
@native_object ||= SalesLog.new(attributes_for_log)
end
def field_mapping
{
field_117: :buy1livein,
}
end
def validate_possible_answers
field_mapping.each do |field, attribute|
possible_answers = FormHandler.instance.current_sales_form.questions.find { |q| q.id == attribute.to_s }.answer_options.keys
unless possible_answers.include?(public_send(field))
errors.add(field, "Value supplied is not one of the permitted values")
end
end
end
def attributes_for_log
hash = field_mapping.invert
attributes = {}
hash.map do |k, v|
attributes[k] = public_send(v)
end
attributes
end
# def field_4_presence_check
# [1, 3, 5, 7, 9, 11].include?(field_1)
# end
end

134
app/services/bulk_upload/sales/validator.rb

@ -1,138 +1,6 @@
class BulkUpload::Sales::Validator class BulkUpload::Sales::Validator
include ActiveModel::Validations include ActiveModel::Validations
QUESTIONS = {
field_1: "What is the purchaser code?",
field_2: "What is the day of the sale completion date? - DD",
field_3: "What is the month of the sale completion date? - MM",
field_4: "What is the year of the sale completion date? - YY",
field_5: "This question has been removed",
field_6: "Was the buyer interviewed for any of the answers you will provide on this log?",
field_7: "Age of Buyer 1",
field_8: "Age of Person 2",
field_9: "Age of Person 3",
field_10: "Age of Person 4",
field_11: "Age of Person 5",
field_12: "Age of Person 6",
field_13: "Gender identity of Buyer 1",
field_14: "Gender identity of Person 2",
field_15: "Gender identity of Person 3",
field_16: "Gender identity of Person 4",
field_17: "Gender identity of Person 5",
field_18: "Gender identity of Person 6",
field_19: "Relationship to Buyer 1 for Person 2",
field_20: "Relationship to Buyer 1 for Person 3",
field_21: "Relationship to Buyer 1 for Person 4",
field_22: "Relationship to Buyer 1 for Person 5",
field_23: "Relationship to Buyer 1 for Person 6",
field_24: "Working situation of Buyer 1",
field_25: "Working situation of Person 2",
field_26: "Working situation of Person 3",
field_27: "Working situation of Person 4",
field_28: "Working situation of Person 5",
field_29: "Working situation of Person 6",
field_30: "What is buyer 1's ethnic group?",
field_31: "What is buyer 1's nationality?",
field_32: "What is buyer 1's gross annual income?",
field_33: "What is buyer 2's gross annual income?",
field_34: "Was buyer 1's income used for a mortgage application?",
field_35: "Was buyer 2's income used for a mortgage application?",
field_36: "What is the total amount the buyers had in savings before they paid any deposit for the property?",
field_37: "Have any of the purchasers previously owned a property?",
field_38: "This question has been removed",
field_39: "What was buyer 1's previous tenure?",
field_40: "What is the local authority of buyer 1's last settled home?",
field_41: "Part 1 of postcode of buyer 1's last settled home",
field_42: "Part 2 of postcode of buyer 1's last settled home",
field_43: "Do you know the postcode of buyer 1's last settled home?",
field_44: "Was the buyer registered with their PRP (HA)?",
field_45: "Was the buyer registered with the local authority?",
field_46: "Was the buyer registered with a Help to Buy agent?",
field_47: "Was the buyer registered with another PRP (HA)?",
field_48: "Does anyone in the household consider themselves to have a disability?",
field_49: "Does anyone in the household use a wheelchair?",
field_50: "How many bedrooms does the property have?",
field_51: "What type of unit is the property?",
field_52: "Which type of bulding is the property?",
field_53: "What is the local authority of the property?",
field_54: "Part 1 of postcode of property",
field_55: "Part 2 of postcode of property",
field_56: "Is the property built or adapted to wheelchair user standards?",
field_57: "What is the type of shared ownership sale?",
field_58: "Is this a resale?",
field_59: "What is the day of the practical completion or handover date?",
field_60: "What is the month of the practical completion or handover date?",
field_61: "What is the day of the exchange of contracts date?",
field_62: "What is the day of the practical completion or handover date?",
field_63: "What is the month of the practical completion or handover date?",
field_64: "What is the year of the practical completion or handover date?",
field_65: "Was the household re-housed under a local authority nominations agreement?",
field_66: "How many bedrooms did the buyer's previous property have?",
field_67: "What was the type of the buyer's previous property?",
field_68: "What was the full purchase price?",
field_69: "What was the initial percentage equity stake purchased?",
field_70: "What is the mortgage amount?",
field_71: "Does this include any extra borrowing?",
field_72: "How much was the cash deposit paid on the property?",
field_73: "How much cash discount was given through Social Homebuy?",
field_74: "What is the basic monthly rent?",
field_75: "What are the total monthly leasehold charges for the property?",
field_76: "What is the type of discounted ownership sale?",
field_77: "What was the full purchase price?",
field_78: "What was the amount of any loan, grant, discount or subsidy given?",
field_79: "What was the percentage discount?",
field_80: "What is the mortgage amount?",
field_81: "Does this include any extra borrowing?",
field_82: "How much was the cash deposit paid on the property?",
field_83: "What are the total monthly leasehold charges for the property?",
field_84: "What is the type of outright sale?",
field_85: "If 'other', what is the 'other' type?",
field_86: "This question has been removed",
field_87: "What is the full purchase price?",
field_88: "What is the mortgage amount?",
field_89: "Does this include any extra borrowing?",
field_90: "How much was the cash deposit paid on the property?",
field_91: "What are the total monthly leasehold charges for the property?",
field_92: "Which organisation owned this property before the sale?",
field_93: "Username",
field_94: "This question has been removed",
field_95: "Has the buyer ever served in the UK Armed Forces and for how long?",
field_96: "This question has been removed",
field_97: "Are any of the buyers a spouse or civil partner of a UK Armed Forces regular who died in service within the last 2 years?",
field_98: "What is the name of the mortgage lender? - Shared ownership",
field_99: "If 'other', what is the name of the mortgage lender?",
field_100: "What is the name of the mortgage lender? - Discounted ownership",
field_101: "If 'other', what is the name of the mortgage lender?",
field_102: "What is the name of the mortgage lender? - Outright sale",
field_103: "If 'other', what is the name of the mortgage lender?",
field_104: "Were the buyers receiving any of these housing-related benefits immediately before buying this property?",
field_105: "What is the length of the mortgage in years? - Shared ownership",
field_106: "What is the length of the mortgage in years? - Discounted ownership",
field_107: "What is the length of the mortgage in years? - Outright sale",
field_108: "How long have the buyers been living in the property before the purchase? - Discounted ownership",
field_109: "Are there more than two joint purchasers of this property?",
field_110: "How long have the buyers been living in the property before the purchase? - Shared ownership",
field_111: "Is this a staircasing transaction?",
field_112: "Data Protection question",
field_113: "Was this purchase made through an ownership scheme?",
field_114: "Is the buyer a company?",
field_115: "Will the buyers live in the property?",
field_116: "Is this a joint purchase?",
field_117: "Will buyer 1 live in the property?",
field_118: "Will buyer 2 live in the property?",
field_119: "Besides the buyers, how many people will live in the property?",
field_120: "What percentage of the property has been bought in this staircasing transaction?",
field_121: "What percentage of the property does the buyer now own in total?",
field_122: "What was the rent type of the buyer's previous property?",
field_123: "Was a mortgage used for the purchase of this property? - Shared ownership",
field_124: "Was a mortgage used for the purchase of this property? - Discounted ownership",
field_125: "Was a mortgage used for the purchase of this property? - Outright sale",
}.freeze
def self.question_for_field(field)
QUESTIONS[field]
end
attr_reader :bulk_upload, :path attr_reader :bulk_upload, :path
validate :validate_file_not_empty validate :validate_file_not_empty
@ -193,7 +61,7 @@ private
headers = ("field_1".."field_125").to_a headers = ("field_1".."field_125").to_a
hash = Hash[headers.zip(stripped_row)] hash = Hash[headers.zip(stripped_row)]
BulkUpload::Sales::RowParser.new(hash) BulkUpload::Sales::Year2022::RowParser.new(hash)
end end
end end

308
app/services/bulk_upload/sales/year2022/row_parser.rb

@ -0,0 +1,308 @@
class BulkUpload::Sales::Year2022::RowParser
include ActiveModel::Model
include ActiveModel::Attributes
QUESTIONS = {
field_1: "What is the purchaser code?",
field_2: "What is the day of the sale completion date? - DD",
field_3: "What is the month of the sale completion date? - MM",
field_4: "What is the year of the sale completion date? - YY",
field_5: "This question has been removed",
field_6: "Was the buyer interviewed for any of the answers you will provide on this log?",
field_7: "Age of Buyer 1",
field_8: "Age of Person 2",
field_9: "Age of Person 3",
field_10: "Age of Person 4",
field_11: "Age of Person 5",
field_12: "Age of Person 6",
field_13: "Gender identity of Buyer 1",
field_14: "Gender identity of Person 2",
field_15: "Gender identity of Person 3",
field_16: "Gender identity of Person 4",
field_17: "Gender identity of Person 5",
field_18: "Gender identity of Person 6",
field_19: "Relationship to Buyer 1 for Person 2",
field_20: "Relationship to Buyer 1 for Person 3",
field_21: "Relationship to Buyer 1 for Person 4",
field_22: "Relationship to Buyer 1 for Person 5",
field_23: "Relationship to Buyer 1 for Person 6",
field_24: "Working situation of Buyer 1",
field_25: "Working situation of Person 2",
field_26: "Working situation of Person 3",
field_27: "Working situation of Person 4",
field_28: "Working situation of Person 5",
field_29: "Working situation of Person 6",
field_30: "What is buyer 1's ethnic group?",
field_31: "What is buyer 1's nationality?",
field_32: "What is buyer 1's gross annual income?",
field_33: "What is buyer 2's gross annual income?",
field_34: "Was buyer 1's income used for a mortgage application?",
field_35: "Was buyer 2's income used for a mortgage application?",
field_36: "What is the total amount the buyers had in savings before they paid any deposit for the property?",
field_37: "Have any of the purchasers previously owned a property?",
field_38: "This question has been removed",
field_39: "What was buyer 1's previous tenure?",
field_40: "What is the local authority of buyer 1's last settled home?",
field_41: "Part 1 of postcode of buyer 1's last settled home",
field_42: "Part 2 of postcode of buyer 1's last settled home",
field_43: "Do you know the postcode of buyer 1's last settled home?",
field_44: "Was the buyer registered with their PRP (HA)?",
field_45: "Was the buyer registered with the local authority?",
field_46: "Was the buyer registered with a Help to Buy agent?",
field_47: "Was the buyer registered with another PRP (HA)?",
field_48: "Does anyone in the household consider themselves to have a disability?",
field_49: "Does anyone in the household use a wheelchair?",
field_50: "How many bedrooms does the property have?",
field_51: "What type of unit is the property?",
field_52: "Which type of bulding is the property?",
field_53: "What is the local authority of the property?",
field_54: "Part 1 of postcode of property",
field_55: "Part 2 of postcode of property",
field_56: "Is the property built or adapted to wheelchair user standards?",
field_57: "What is the type of shared ownership sale?",
field_58: "Is this a resale?",
field_59: "What is the day of the practical completion or handover date?",
field_60: "What is the month of the practical completion or handover date?",
field_61: "What is the day of the exchange of contracts date?",
field_62: "What is the day of the practical completion or handover date?",
field_63: "What is the month of the practical completion or handover date?",
field_64: "What is the year of the practical completion or handover date?",
field_65: "Was the household re-housed under a local authority nominations agreement?",
field_66: "How many bedrooms did the buyer's previous property have?",
field_67: "What was the type of the buyer's previous property?",
field_68: "What was the full purchase price?",
field_69: "What was the initial percentage equity stake purchased?",
field_70: "What is the mortgage amount?",
field_71: "Does this include any extra borrowing?",
field_72: "How much was the cash deposit paid on the property?",
field_73: "How much cash discount was given through Social Homebuy?",
field_74: "What is the basic monthly rent?",
field_75: "What are the total monthly leasehold charges for the property?",
field_76: "What is the type of discounted ownership sale?",
field_77: "What was the full purchase price?",
field_78: "What was the amount of any loan, grant, discount or subsidy given?",
field_79: "What was the percentage discount?",
field_80: "What is the mortgage amount?",
field_81: "Does this include any extra borrowing?",
field_82: "How much was the cash deposit paid on the property?",
field_83: "What are the total monthly leasehold charges for the property?",
field_84: "What is the type of outright sale?",
field_85: "If 'other', what is the 'other' type?",
field_86: "This question has been removed",
field_87: "What is the full purchase price?",
field_88: "What is the mortgage amount?",
field_89: "Does this include any extra borrowing?",
field_90: "How much was the cash deposit paid on the property?",
field_91: "What are the total monthly leasehold charges for the property?",
field_92: "Which organisation owned this property before the sale?",
field_93: "Username",
field_94: "This question has been removed",
field_95: "Has the buyer ever served in the UK Armed Forces and for how long?",
field_96: "This question has been removed",
field_97: "Are any of the buyers a spouse or civil partner of a UK Armed Forces regular who died in service within the last 2 years?",
field_98: "What is the name of the mortgage lender? - Shared ownership",
field_99: "If 'other', what is the name of the mortgage lender?",
field_100: "What is the name of the mortgage lender? - Discounted ownership",
field_101: "If 'other', what is the name of the mortgage lender?",
field_102: "What is the name of the mortgage lender? - Outright sale",
field_103: "If 'other', what is the name of the mortgage lender?",
field_104: "Were the buyers receiving any of these housing-related benefits immediately before buying this property?",
field_105: "What is the length of the mortgage in years? - Shared ownership",
field_106: "What is the length of the mortgage in years? - Discounted ownership",
field_107: "What is the length of the mortgage in years? - Outright sale",
field_108: "How long have the buyers been living in the property before the purchase? - Discounted ownership",
field_109: "Are there more than two joint purchasers of this property?",
field_110: "How long have the buyers been living in the property before the purchase? - Shared ownership",
field_111: "Is this a staircasing transaction?",
field_112: "Data Protection question",
field_113: "Was this purchase made through an ownership scheme?",
field_114: "Is the buyer a company?",
field_115: "Will the buyers live in the property?",
field_116: "Is this a joint purchase?",
field_117: "Will buyer 1 live in the property?",
field_118: "Will buyer 2 live in the property?",
field_119: "Besides the buyers, how many people will live in the property?",
field_120: "What percentage of the property has been bought in this staircasing transaction?",
field_121: "What percentage of the property does the buyer now own in total?",
field_122: "What was the rent type of the buyer's previous property?",
field_123: "Was a mortgage used for the purchase of this property? - Shared ownership",
field_124: "Was a mortgage used for the purchase of this property? - Discounted ownership",
field_125: "Was a mortgage used for the purchase of this property? - Outright sale",
}.freeze
attribute :field_1, :string
attribute :field_2, :integer
attribute :field_3, :integer
attribute :field_4, :integer
attribute :field_5
attribute :field_6, :integer
attribute :field_7, :integer
attribute :field_8, :integer
attribute :field_9, :integer
attribute :field_10, :integer
attribute :field_11, :integer
attribute :field_12, :integer
attribute :field_13, :string
attribute :field_14, :string
attribute :field_15, :string
attribute :field_16, :string
attribute :field_17, :string
attribute :field_18, :string
attribute :field_19, :string
attribute :field_20, :integer
attribute :field_21, :integer
attribute :field_22, :integer
attribute :field_23, :integer
attribute :field_24, :integer
attribute :field_25, :integer
attribute :field_26, :integer
attribute :field_27, :integer
attribute :field_28, :integer
attribute :field_29, :integer
attribute :field_30, :integer
attribute :field_31, :integer
attribute :field_32, :integer
attribute :field_33, :integer
attribute :field_34, :integer
attribute :field_35, :integer
attribute :field_36, :integer
attribute :field_37, :integer
attribute :field_38
attribute :field_39, :integer
attribute :field_40, :string
attribute :field_41, :string
attribute :field_42, :string
attribute :field_43, :integer
attribute :field_44, :integer
attribute :field_45, :integer
attribute :field_46, :integer
attribute :field_47, :integer
attribute :field_48, :integer
attribute :field_49, :integer
attribute :field_50, :integer
attribute :field_51, :integer
attribute :field_52, :integer
attribute :field_53, :string
attribute :field_54, :string
attribute :field_55, :string
attribute :field_56, :integer
attribute :field_57, :integer
attribute :field_58, :integer
attribute :field_59, :integer
attribute :field_60, :integer
attribute :field_61, :integer
attribute :field_62, :integer
attribute :field_63, :integer
attribute :field_64, :integer
attribute :field_65, :integer
attribute :field_66, :integer
attribute :field_67, :integer
attribute :field_68, :integer
attribute :field_69, :integer
attribute :field_70, :integer
attribute :field_71, :integer
attribute :field_72, :integer
attribute :field_73, :integer
attribute :field_74, :decimal
attribute :field_75, :decimal
attribute :field_76, :integer
attribute :field_77, :integer
attribute :field_78, :integer
attribute :field_79, :integer
attribute :field_80, :integer
attribute :field_81, :integer
attribute :field_82, :integer
attribute :field_83, :integer
attribute :field_84, :integer
attribute :field_85, :string
attribute :field_86
attribute :field_87, :integer
attribute :field_88, :integer
attribute :field_89, :integer
attribute :field_90, :integer
attribute :field_91, :integer
attribute :field_92, :integer
attribute :field_93, :string
attribute :field_94
attribute :field_95, :integer
attribute :field_96
attribute :field_97, :integer
attribute :field_98, :integer
attribute :field_99, :string
attribute :field_100, :integer
attribute :field_101, :string
attribute :field_102, :integer
attribute :field_103, :string
attribute :field_104, :integer
attribute :field_105, :integer
attribute :field_106, :integer
attribute :field_107, :integer
attribute :field_108, :integer
attribute :field_109, :integer
attribute :field_110, :integer
attribute :field_111, :integer
attribute :field_112, :integer
attribute :field_113, :integer
attribute :field_114, :integer
attribute :field_115, :integer
attribute :field_116, :integer
attribute :field_117, :integer
attribute :field_118, :integer
attribute :field_119, :integer
attribute :field_120, :integer
attribute :field_121, :integer
attribute :field_122, :integer
attribute :field_123, :integer
attribute :field_124, :integer
attribute :field_125, :integer
# validates :field_1, presence: true, numericality: { in: (1..12) }
# validates :field_4, numericality: { in: (1..999), allow_blank: true }
# validates :field_4, presence: true, if: :field_4_presence_check
validate :validate_possible_answers
# delegate :valid?, to: :native_object
# delegate :errors, to: :native_object
def self.question_for_field(field)
QUESTIONS[field]
end
private
def native_object
@native_object ||= SalesLog.new(attributes_for_log)
end
def field_mapping
{
field_117: :buy1livein,
}
end
def validate_possible_answers
field_mapping.each do |field, attribute|
possible_answers = FormHandler.instance.current_sales_form.questions.find { |q| q.id == attribute.to_s }.answer_options.keys
unless possible_answers.include?(public_send(field))
errors.add(field, "Value supplied is not one of the permitted values")
end
end
end
def attributes_for_log
hash = field_mapping.invert
attributes = {}
hash.map do |k, v|
attributes[k] = public_send(v)
end
attributes
end
# def field_4_presence_check
# [1, 3, 5, 7, 9, 11].include?(field_1)
# end
end

123
app/services/csv/lettings_log_csv_service.rb

@ -2,8 +2,9 @@ module Csv
class LettingsLogCsvService class LettingsLogCsvService
CSV_FIELDS_TO_OMIT = %w[hhmemb net_income_value_check first_time_property_let_as_social_housing renttype needstype postcode_known is_la_inferred totchild totelder totadult net_income_known is_carehome previous_la_known is_previous_la_inferred age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known letting_allocation_unknown details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 rent_type_detail wrent wscharge wpschrge wsupchrg wtcharge wtshortfall rent_value_check old_form_id old_id retirement_value_check tshortfall_known pregnancy_value_check hhtype new_old vacdays la prevloc unresolved updated_by_id bulk_upload_id].freeze CSV_FIELDS_TO_OMIT = %w[hhmemb net_income_value_check first_time_property_let_as_social_housing renttype needstype postcode_known is_la_inferred totchild totelder totadult net_income_known is_carehome previous_la_known is_previous_la_inferred age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known letting_allocation_unknown details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 rent_type_detail wrent wscharge wpschrge wsupchrg wtcharge wtshortfall rent_value_check old_form_id old_id retirement_value_check tshortfall_known pregnancy_value_check hhtype new_old vacdays la prevloc unresolved updated_by_id bulk_upload_id].freeze
def initialize(user) def initialize(user, export_type:)
@user = user @user = user
@export_type = export_type
set_csv_attributes set_csv_attributes
end end
@ -12,30 +13,126 @@ module Csv
csv << @attributes csv << @attributes
LettingsLog.all.find_each do |record| LettingsLog.all.find_each do |record|
csv << @attributes.map do |att| csv << @attributes.map { |attribute| get_value(attribute, record) }
label_from_value(record, att)
end
end end
end end
end end
private private
def label_from_value(record, att) ATTRIBUTES_OF_RELATED_OBJECTS = {
if %w[la prevloc].include? att location_code: {
label_from_boolean_value(record.send(att)) labels: %i[location id],
elsif %w[mrcdate startdate voiddate].include? att codes: %i[location id],
record.send(att)&.to_formatted_s(:govuk_date) },
location_postcode: {
labels: %i[location postcode],
codes: %i[location postcode],
},
location_name: {
labels: %i[location name],
codes: %i[location name],
},
location_units: {
labels: %i[location units],
codes: %i[location units],
},
location_type_of_unit: {
labels: %i[location type_of_unit],
codes: %i[location type_of_unit_before_type_cast],
},
location_mobility_type: {
labels: %i[location mobility_type],
codes: %i[location mobility_type_before_type_cast],
},
location_admin_district: {
labels: %i[location location_admin_district],
codes: %i[location location_admin_district],
},
location_startdate: {
labels: %i[location startdate],
codes: %i[location startdate],
},
scheme_service_name: {
labels: %i[scheme service_name],
codes: %i[scheme service_name],
},
scheme_sensitive: {
labels: %i[scheme sensitive],
codes: %i[scheme sensitive_before_type_cast],
},
scheme_type: {
labels: %i[scheme scheme_type],
codes: %i[scheme scheme_type_before_type_cast],
},
scheme_registered_under_care_act: {
labels: %i[scheme registered_under_care_act],
codes: %i[scheme registered_under_care_act_before_type_cast],
},
scheme_owning_organisation_name: {
labels: %i[scheme owning_organisation name],
codes: %i[scheme owning_organisation name],
},
scheme_primary_client_group: {
labels: %i[scheme primary_client_group],
codes: %i[scheme primary_client_group_before_type_cast],
},
scheme_has_other_client_group: {
labels: %i[scheme has_other_client_group],
codes: %i[scheme has_other_client_group_before_type_cast],
},
scheme_secondary_client_group: {
labels: %i[scheme secondary_client_group],
codes: %i[scheme secondary_client_group_before_type_cast],
},
scheme_support_type: {
labels: %i[scheme support_type],
codes: %i[scheme support_type_before_type_cast],
},
scheme_intended_stay: {
labels: %i[scheme intended_stay],
codes: %i[scheme intended_stay_before_type_cast],
},
scheme_created_at: {
labels: %i[scheme created_at],
codes: %i[scheme created_at],
},
}.freeze
def get_value(attribute, record)
attribute = "rent_type" if attribute == "rent_type_detail" # rent_type_detail is the requested column header for rent_type, so as not to confuse with renttype
if ATTRIBUTES_OF_RELATED_OBJECTS.key? attribute.to_sym
call_chain = ATTRIBUTES_OF_RELATED_OBJECTS[attribute.to_sym][@export_type.to_sym]
call_chain.reduce(record) { |object, next_call| object&.send(next_call) }
elsif %w[la prevloc].include? attribute # for all exports we output both the codes and labels for these location attributes
record.send(attribute)
elsif %w[la_label prevloc_label].include? attribute # as above
attribute = attribute.remove("_label")
field_value = record.send(attribute)
get_label(field_value, attribute, record)
elsif %w[mrcdate startdate voiddate].include? attribute
record.send(attribute)&.to_formatted_s(:govuk_date)
else else
record.form.get_question(att.remove("_label"), record)&.label_from_value(record.send(att.remove("_label"))) || label_from_boolean_value(record.send(att.remove("_label"))) field_value = record.send(attribute)
case @export_type
when "codes"
field_value
when "labels"
answer_label = get_label(field_value, attribute, record)
answer_label || label_if_boolean_value(field_value) || field_value
end
end end
end end
def label_from_boolean_value(value) def get_label(value, attribute, record)
record.form
.get_question(attribute, record)
&.label_from_value(value)
end
def label_if_boolean_value(value)
return "Yes" if value == true return "Yes" if value == true
return "No" if value == false return "No" if value == false
value
end end
def set_csv_attributes def set_csv_attributes

1
app/services/imports/sales_logs_import_service.rb

@ -54,7 +54,6 @@ module Imports
attributes["details_known_#{index}"] = details_known(index, attributes) attributes["details_known_#{index}"] = details_known(index, attributes)
end end
attributes["national"] = unsafe_string_as_integer(xml_doc, "P1Nat") attributes["national"] = unsafe_string_as_integer(xml_doc, "P1Nat")
attributes["othernational"] = nil
attributes["ethnic"] = unsafe_string_as_integer(xml_doc, "P1Eth") attributes["ethnic"] = unsafe_string_as_integer(xml_doc, "P1Eth")
attributes["ethnic_group"] = ethnic_group(attributes["ethnic"]) attributes["ethnic_group"] = ethnic_group(attributes["ethnic"])
attributes["buy1livein"] = unsafe_string_as_integer(xml_doc, "LiveInBuyer1") attributes["buy1livein"] = unsafe_string_as_integer(xml_doc, "LiveInBuyer1")

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

@ -6,8 +6,8 @@
<%= render DocumentListComponent.new(items: [ <%= render DocumentListComponent.new(items: [
{ {
name: "Lettings log for tenants (2023/24)", name: "Lettings log for tenants (2023/24)",
href: "https://core.communities.gov.uk/public/download/guides-and-manuals/2023-24%20Lettings%20paper%20form.pdf?download-format=pdf", href: download_23_24_lettings_form_path,
metadata: "PDF, 281 KB, 8 pages", metadata: "PDF, 278 KB, 8 pages",
}, },
]) %> ]) %>
@ -19,4 +19,22 @@
metadata: "PDF, 654 KB, 4 pages", metadata: "PDF, 654 KB, 4 pages",
}, },
]) %> ]) %>
<h2 class="govuk-body-l govuk-!-margin-bottom-3">Sales 23/24</h2>
<%= render DocumentListComponent.new(items: [
{
name: "Sales log for buyers (2023/24)",
href: download_23_24_sales_form_path,
metadata: "PDF, 421 KB, 8 pages",
},
]) %>
<h2 class="govuk-body-l govuk-!-margin-bottom-3">Sales 22/23</h2>
<%= render DocumentListComponent.new(items: [
{
name: "Sales log for buyers (2022/23)",
href: download_22_23_sales_form_path,
metadata: "PDF, 397 KB, 5 pages",
},
]) %>
</div> </div>

5
app/views/logs/_log_list.html.erb

@ -1,7 +1,10 @@
<h2 class="govuk-body"> <h2 class="govuk-body">
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "logs", path: request.path)) %> <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "logs", path: request.path)) %>
<% if logs&.first&.lettings? %> <% if logs&.first&.lettings? %>
<%= govuk_link_to "Download (CSV)", csv_download_url, type: "text/csv" %> <%= govuk_link_to "Download (CSV)", csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4" %>
<% if @current_user.support? %>
<%= govuk_link_to "Download (CSV, codes only)", csv_codes_only_download_url, type: "text/csv" %>
<% end %>
<% end %> <% end %>
</h2> </h2>
<% logs.map do |log| %> <% logs.map do |log| %>

2
app/views/logs/download_csv.html.erb

@ -11,6 +11,6 @@
<p class="govuk-body">We'll send a secure download link to your email address <strong><%= @current_user.email %></strong>.</p> <p class="govuk-body">We'll send a secure download link to your email address <strong><%= @current_user.email %></strong>.</p>
<p class="govuk-body">You've selected <%= count %> logs.</p> <p class="govuk-body">You've selected <%= count %> logs.</p>
<%= govuk_button_to "Send email", post_path, method: :post, params: { search: search_term } %> <%= govuk_button_to "Send email", post_path, method: :post, params: { search: search_term, codes_only: } %>
</div> </div>
</div> </div>

12
app/views/logs/index.html.erb

@ -66,7 +66,17 @@
<div class="app-filter-layout__content"> <div class="app-filter-layout__content">
<%= render SearchComponent.new(current_user:, search_label: search_label_for_controller(controller), value: @searched) %> <%= render SearchComponent.new(current_user:, search_label: search_label_for_controller(controller), value: @searched) %>
<%= govuk_section_break(visible: true, size: "m") %> <%= govuk_section_break(visible: true, size: "m") %>
<%= render partial: "log_list", locals: { logs: @logs, title: "Logs", pagy: @pagy, searched: @searched, item_label:, total_count: @total_count, csv_download_url: csv_download_url_for_controller(controller) } %> <%= render partial: "log_list",
locals: {
logs: @logs,
title: "Logs",
pagy: @pagy,
searched: @searched,
item_label:,
total_count: @total_count,
csv_download_url: csv_download_url_for_controller(controller_type: controller, search: @search_term, codes_only: false),
csv_codes_only_download_url: csv_download_url_for_controller(controller_type: controller, search: @search_term, codes_only: true),
} %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %> <%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %>
</div> </div>
</div> </div>

12
app/views/organisations/logs.html.erb

@ -26,7 +26,17 @@
<div class="app-filter-layout__content"> <div class="app-filter-layout__content">
<%= render SearchComponent.new(current_user:, search_label: "Search by log ID, tenant code, property reference or postcode", value: @searched) %> <%= render SearchComponent.new(current_user:, search_label: "Search by log ID, tenant code, property reference or postcode", value: @searched) %>
<%= govuk_section_break(visible: true, size: "m") %> <%= govuk_section_break(visible: true, size: "m") %>
<%= render partial: "logs/log_list", locals: { logs: @logs, title: "Logs", pagy: @pagy, searched: @searched, item_label:, total_count: @total_count, csv_download_url: logs_csv_download_organisation_path(@organisation, search: @search_term) } %> <%= render partial: "logs/log_list",
locals: {
logs: @logs,
title: "Logs",
pagy: @pagy,
searched: @searched,
item_label:,
total_count: @total_count,
csv_download_url: logs_csv_download_organisation_path(@organisation, search: @search_term, codes_only: false),
csv_codes_only_download_url: logs_csv_download_organisation_path(@organisation, search: @search_term, codes_only: true),
} %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %> <%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %>
</div> </div>
</div> </div>

3
config/forms/2022_2023.json

@ -1110,9 +1110,6 @@
}, },
{ {
"tenancy": 6 "tenancy": 6
},
{
"tenancy": 3
} }
] ]
}, },

6
config/initializers/feature_toggle.rb

@ -40,6 +40,12 @@ class FeatureToggle
!Rails.env.development? !Rails.env.development?
end end
def self.force_crossover?
return false if Rails.env.test?
!Rails.env.production?
end
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

8
config/locales/en.yml

@ -41,6 +41,11 @@ en:
activemodel: activemodel:
errors: errors:
models: models:
bulk_upload/lettings/validator:
attributes:
base:
over_max_column_count: "too many columns, please ensure you have used the correct template"
under_min_column_count: "too few columns, please ensure you have used the correct template"
forms/bulk_upload_lettings/year: forms/bulk_upload_lettings/year:
attributes: attributes:
year: year:
@ -154,6 +159,8 @@ en:
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} 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: 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}" "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}"
type:
percentage_bought_must_be_at_least_threshold: "The minimum increase in equity while staircasing is %{threshold}% for this shared ownership type"
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"
@ -297,6 +304,7 @@ en:
staircasing: staircasing:
percentage_bought_must_be_greater_than_percentage_owned: "Total percentage buyer now owns must be more than percentage bought in this transaction" percentage_bought_must_be_greater_than_percentage_owned: "Total percentage buyer now owns must be more than percentage bought in this transaction"
older_person_percentage_owned_maximum_75: "Percentage cannot be above 75% under Older Person's Shared Ownership" older_person_percentage_owned_maximum_75: "Percentage cannot be above 75% under Older Person's Shared Ownership"
percentage_bought_must_be_at_least_threshold: "The minimum increase in equity while staircasing is %{threshold}%"
household: household:
reasonpref: reasonpref:

2
config/rent_range_data/2023.csv

@ -1,4 +1,4 @@
ranges_rent_id,lettype,la,beds,soft_min,soft_max,hard_min,hard_max ranges_rent_id,lettype,la,beds,soft_min,soft_max,hard_min,hard_max
1,1,E07000223,1,57.21,123.09,25.84,176.15 1,1,E07000223,1,57.21,123.09,25.84,176.15
327,1,E07000223,2,71.06,146.43,25.84,186.77 327,1,E07000223,2,71.06,146.43,25.84,186.77
653,1,E07000223,3,81.2,161.3,25.84,203.75 653,1,E07000223,3,81.2,161.3,25.84,203.75

1 ranges_rent_id lettype la beds soft_min soft_max hard_min hard_max
2 1 1 E07000223 1 57.21 123.09 25.84 176.15
3 327 1 E07000223 2 71.06 146.43 25.84 186.77
4 653 1 E07000223 3 81.2 161.3 25.84 203.75

3
config/routes.rb

@ -35,6 +35,9 @@ Rails.application.routes.draw do
get "/accessibility-statement", to: "content#accessibility_statement" get "/accessibility-statement", to: "content#accessibility_statement"
get "/privacy-notice", to: "content#privacy_notice" get "/privacy-notice", to: "content#privacy_notice"
get "/data-sharing-agreement", to: "content#data_sharing_agreement" get "/data-sharing-agreement", to: "content#data_sharing_agreement"
get "/download-23-24-sales-form", to: "start#download_23_24_sales_form"
get "/download-22-23-sales-form", to: "start#download_22_23_sales_form"
get "/download-23-24-lettings-form", to: "start#download_23_24_lettings_form"
resource :account, only: %i[show edit], controller: "users" do resource :account, only: %i[show edit], controller: "users" do
get "edit/password", to: "users#edit_password" get "edit/password", to: "users#edit_password"

8
db/migrate/20230224083552_add_columns_to_sales_log.rb

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

5
db/migrate/20230307111943_remove_othernational_from_sales_logs.rb

@ -0,0 +1,5 @@
class RemoveOthernationalFromSalesLogs < ActiveRecord::Migration[7.0]
def change
remove_column :sales_logs, :othernational, :string
end
end

5
db/migrate/20230307140937_add_nationalbuy2_to_sales_logs.rb

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

4
db/schema.rb

@ -403,7 +403,6 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_08_101826) do
t.integer "age1_known" t.integer "age1_known"
t.string "sex1" t.string "sex1"
t.integer "national" t.integer "national"
t.string "othernational"
t.integer "ethnic" t.integer "ethnic"
t.integer "ethnic_group" t.integer "ethnic_group"
t.integer "buy1livein" t.integer "buy1livein"
@ -543,6 +542,9 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_08_101826) do
t.integer "proplen_asked" t.integer "proplen_asked"
t.string "old_id" t.string "old_id"
t.integer "pregblank" t.integer "pregblank"
t.integer "buy2living"
t.integer "prevtenbuy2"
t.integer "nationalbuy2"
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 ["old_id"], name: "index_sales_logs_on_old_id", unique: true t.index ["old_id"], name: "index_sales_logs_on_old_id", unique: true

6
docs/Gemfile.lock

@ -1,7 +1,7 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
activesupport (7.0.4.2) activesupport (7.0.4.3)
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)
@ -14,7 +14,7 @@ GEM
coffee-script-source (1.11.1) coffee-script-source (1.11.1)
colorator (1.1.0) colorator (1.1.0)
commonmarker (0.23.8) commonmarker (0.23.8)
concurrent-ruby (1.2.0) concurrent-ruby (1.2.2)
dnsruby (1.61.9) dnsruby (1.61.9)
simpleidn (~> 0.1) simpleidn (~> 0.1)
em-websocket (0.5.3) em-websocket (0.5.3)
@ -209,7 +209,7 @@ 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.17.0) minitest (5.18.0)
nokogiri (1.14.2-arm64-darwin) nokogiri (1.14.2-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.14.2-x86_64-darwin) nokogiri (1.14.2-x86_64-darwin)

BIN
public/files/2022_23_sales_paper_form.pdf

Binary file not shown.

BIN
public/files/2023_24_lettings_paper_form.pdf

Binary file not shown.

BIN
public/files/2023_24_sales_paper_form.pdf

Binary file not shown.

8
spec/components/bulk_upload_error_summary_table_component_spec.rb

@ -3,7 +3,7 @@ require "rails_helper"
RSpec.describe BulkUploadErrorSummaryTableComponent, type: :component do RSpec.describe BulkUploadErrorSummaryTableComponent, type: :component do
subject(:component) { described_class.new(bulk_upload:) } subject(:component) { described_class.new(bulk_upload:) }
let(:bulk_upload) { create(:bulk_upload) } let(:bulk_upload) { create(:bulk_upload, :lettings) }
before do before do
stub_const("BulkUploadErrorSummaryTableComponent::DISPLAY_THRESHOLD", 0) stub_const("BulkUploadErrorSummaryTableComponent::DISPLAY_THRESHOLD", 0)
@ -52,7 +52,7 @@ RSpec.describe BulkUploadErrorSummaryTableComponent, type: :component do
expect(row_1).to eql([ expect(row_1).to eql([
"A", "A",
"1", "1",
BulkUpload::Lettings::Validator.question_for_field(error_1.field.to_sym), bulk_upload.prefix_namespace::RowParser.question_for_field(error_1.field.to_sym),
error_1.error, error_1.error,
error_1.field, error_1.field,
]) ])
@ -62,7 +62,7 @@ RSpec.describe BulkUploadErrorSummaryTableComponent, type: :component do
expect(row_2).to eql([ expect(row_2).to eql([
"B", "B",
"1", "1",
BulkUpload::Lettings::Validator.question_for_field(error_2.field.to_sym), bulk_upload.prefix_namespace::RowParser.question_for_field(error_2.field.to_sym),
error_2.error, error_2.error,
error_2.field, error_2.field,
]) ])
@ -89,7 +89,7 @@ RSpec.describe BulkUploadErrorSummaryTableComponent, type: :component do
expect(row_1).to eql([ expect(row_1).to eql([
"A", "A",
"2", "2",
BulkUpload::Lettings::Validator.question_for_field(error_1.field.to_sym), bulk_upload.prefix_namespace::RowParser.question_for_field(error_1.field.to_sym),
error_1.error, error_1.error,
error_1.field, error_1.field,
]) ])

2
spec/features/bulk_upload_sales_logs_spec.rb

@ -75,7 +75,7 @@ RSpec.describe "Bulk upload sales log" do
expect(page).to have_link("Upload sales logs in bulk") expect(page).to have_link("Upload sales logs in bulk")
click_link("Upload sales logs in bulk") click_link("Upload sales logs in bulk")
expect(page).to have_content("Upload sales logs in bulk (2022/23)") expect(page).to have_content("Upload sales logs in bulk (2023/24)")
click_button("Continue") click_button("Continue")
expect(page).to have_content("Upload your file") expect(page).to have_content("Upload your file")

2
spec/features/form/accessible_autocomplete_spec.rb

@ -1,7 +1,7 @@
require "rails_helper" require "rails_helper"
require_relative "helpers" require_relative "helpers"
RSpec.describe "Accessible Automcomplete" do RSpec.describe "Accessible Autocomplete" do
include Helpers include Helpers
let(:user) { FactoryBot.create(:user) } let(:user) { FactoryBot.create(:user) }
let(:lettings_log) do let(:lettings_log) do

12
spec/features/form/conditional_questions_spec.rb

@ -54,13 +54,13 @@ RSpec.describe "Form Conditional Questions" do
end end
it "gets cleared if the conditional question is hidden after editing the answer" do it "gets cleared if the conditional question is hidden after editing the answer" do
sales_log.update!(national: 12, othernational: "other") sales_log.update!(age1_known: 0, age1: 50)
visit("/sales-logs/#{sales_log.id}/buyer-1-nationality") visit("/sales-logs/#{sales_log.id}/buyer-1-age")
expect(page).to have_field("sales-log-othernational-field", with: "other") expect(page).to have_field("sales-log-age1-field", with: 50)
choose("sales-log-national-18-field", allow_label_click: true) choose("sales-log-age1-known-1-field", allow_label_click: true)
choose("sales-log-national-12-field", allow_label_click: true) choose("sales-log-age1-known-0-field", allow_label_click: true)
expect(page).to have_field("sales-log-othernational-field", with: "") expect(page).to have_field("sales-log-age1-field", with: "")
end end
end end
end end

2
spec/features/form/progressive_total_field_spec.rb

@ -1,7 +1,7 @@
require "rails_helper" require "rails_helper"
require_relative "helpers" require_relative "helpers"
RSpec.describe "Accessible Automcomplete" do RSpec.describe "Accessible Autocomplete" do
include Helpers include Helpers
let(:user) { FactoryBot.create(:user) } let(:user) { FactoryBot.create(:user) }
let(:lettings_log) do let(:lettings_log) do

2
spec/fixtures/files/lettings_logs_download_codes_only.csv vendored

@ -0,0 +1,2 @@
id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,needstype,renewal,startdate,rent_type_detail,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,hhmemb,relat2,age2,sex2,retirement_value_check,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,is_previous_la_inferred,prevloc_label,prevloc,illness_type_1,illness_type_2,is_la_inferred,la_label,la,postcode_known,postcode_full,previous_la_known,wchair,preg_occ,cbl,earnings,incfreq,net_income_value_check,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,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,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,lettype,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,tshortfall,chcharge,ppcodenk,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,has_benefits,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,rent_value_check,old_form_id,lar,irproduct,old_id,joint,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,unresolved,updated_by_id,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate
{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,false,DLUHC,DLUHC,2021,2,,2 October 2021,2,,,,,,,,,,,,,,,,,,,,false,,,,,false,Westminster,E09000033,,SE1 1TE,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,8,0,0,0,,0,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,0,,,,,,,,,,,,,,,,,,,9,1,,,,,,,,6,{scheme_code},{scheme_service_name},{scheme_sensitive},0,1,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,6,A,Westminster,{location_startdate}
1 id status created_at updated_at created_by_name is_dpo owning_organisation_name managing_organisation_name collection_start_year needstype renewal startdate rent_type_detail irproduct_other tenancycode propcode age1 sex1 ecstat1 hhmemb relat2 age2 sex2 retirement_value_check ecstat2 armedforces leftreg illness housingneeds_a housingneeds_b housingneeds_c housingneeds_h is_previous_la_inferred prevloc_label prevloc illness_type_1 illness_type_2 is_la_inferred la_label la postcode_known postcode_full previous_la_known wchair preg_occ cbl earnings incfreq net_income_value_check benefits hb period brent scharge pscharge supcharg tcharge offered layear ppostcode_full mrcdate declaration ethnic national prevten age3 sex3 ecstat3 age4 sex4 ecstat4 age5 sex5 ecstat5 age6 sex6 ecstat6 age7 sex7 ecstat7 age8 sex8 ecstat8 homeless underoccupation_benefitcap reservist startertenancy tenancylength tenancy rsnvac unittype_gn beds waityear reasonpref chr cap reasonother housingneeds_f housingneeds_g 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 property_owner_organisation property_manager_organisation purchaser_code reason majorrepairs hbrentshortfall property_relet incref first_time_property_let_as_social_housing unitletas builtype voiddate renttype lettype totchild totelder totadult net_income_known nocharge is_carehome household_charge referral tshortfall chcharge ppcodenk age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known ethnic_group letting_allocation_unknown details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 has_benefits wrent wscharge wpschrge wsupchrg wtcharge wtshortfall refused housingneeds wchchrg newprop relat3 relat4 relat5 relat6 relat7 relat8 rent_value_check old_form_id lar irproduct old_id joint tshortfall_known sheltered pregnancy_value_check hhtype new_old vacdays major_repairs_date_value_check void_date_value_check housingneeds_type housingneeds_other unresolved updated_by_id unittype_sh scheme_code scheme_service_name scheme_sensitive scheme_type scheme_registered_under_care_act scheme_owning_organisation_name scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at location_code location_postcode location_name location_units location_type_of_unit location_mobility_type location_admin_district location_startdate
2 {id} in_progress 2022-02-08 16:52:15 +0000 2022-02-08 16:52:15 +0000 Danny Rojas false DLUHC DLUHC 2021 2 2 October 2021 2 false false Westminster E09000033 SE1 1TE 2 2 8 0 0 0 0 0 0 9 1 6 {scheme_code} {scheme_service_name} {scheme_sensitive} 0 1 DLUHC {scheme_primary_client_group} {scheme_secondary_client_group} {scheme_support_type} {scheme_intended_stay} 2021-04-01 00:00:00 +0100 {location_code} SE1 1TE Downing Street 20 6 A Westminster {location_startdate}

1
spec/helpers/tasklist_helper_spec.rb

@ -9,7 +9,6 @@ RSpec.describe TasklistHelper do
example.run example.run
end end
Timecop.return Timecop.return
Singleton.__init__(FormHandler)
end end
describe "with lettings" do describe "with lettings" do

3
spec/mailers/bulk_upload_mailer_spec.rb

@ -72,11 +72,12 @@ RSpec.describe BulkUploadMailer do
upload_timestamp: bulk_upload.created_at, upload_timestamp: bulk_upload.created_at,
lettings_or_sales: bulk_upload.log_type, lettings_or_sales: bulk_upload.log_type,
year_combo: bulk_upload.year_combo, year_combo: bulk_upload.year_combo,
errors: "- foo\n- bar",
bulk_upload_link: start_bulk_upload_lettings_logs_url, bulk_upload_link: start_bulk_upload_lettings_logs_url,
}, },
) )
mailer.send_bulk_upload_failed_service_error_mail(bulk_upload:) mailer.send_bulk_upload_failed_service_error_mail(bulk_upload:, errors: %w[foo bar])
end end
end end

31
spec/models/form/lettings/pages/starter_tenancy_type_spec.rb

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Form::Lettings::Pages::StarterTenancyType, type: :model do
subject(:page) { described_class.new(nil, nil, subsection) }
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[tenancy tenancyother])
end
it "has the correct id" do
expect(page.id).to eq("starter_tenancy_type")
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 the correct depends_on" do
expect(page.depends_on).to eq([{ "starter_tenancy?" => true }])
end
end

31
spec/models/form/lettings/pages/tenancy_length_spec.rb

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Form::Lettings::Pages::TenancyLength, type: :model do
subject(:page) { described_class.new(nil, nil, subsection) }
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[tenancylength]
end
it "has the correct id" do
expect(page.id).to eq "tenancy_length"
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 the correct depends_on" do
expect(page.depends_on).to eq [{ "tenancy_type_fixed_term?" => true }]
end
end

31
spec/models/form/lettings/pages/tenancy_type_spec.rb

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Form::Lettings::Pages::TenancyType, type: :model do
subject(:page) { described_class.new(nil, nil, subsection) }
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[tenancy tenancyother]
end
it "has the correct id" do
expect(page.id).to eq "tenancy_type"
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 the correct depends_on" do
expect(page.depends_on).to eq [{ "starter_tenancy?" => false }]
end
end

2
spec/models/form/lettings/questions/la_spec.rb

@ -10,8 +10,6 @@ RSpec.describe Form::Lettings::Questions::La, type: :model do
let(:start_date) { Time.utc(2023, 4, 1) } let(:start_date) { Time.utc(2023, 4, 1) }
it "has the correct answer_options" do it "has the correct answer_options" do
Rails.application.load_seed
expect(question.answer_options).to eq({ expect(question.answer_options).to eq({
"" => "Select an option", "" => "Select an option",
"E06000001" => "Hartlepool", "E06000001" => "Hartlepool",

48
spec/models/form/lettings/questions/nationality_spec.rb

@ -0,0 +1,48 @@
require "rails_helper"
RSpec.describe Form::Lettings::Questions::Nationality, type: :model do
subject(:question) { described_class.new(nil, nil, page) }
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("national")
end
it "has the correct header" do
expect(question.header).to eq("What is the nationality of the lead tenant?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Lead tenant’s nationality")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "has the correct hint_text" do
expect(question.hint_text).to eq("The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.")
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"18" => { "value" => "United Kingdom" },
"17" => { "value" => "Republic of Ireland" },
"19" => { "value" => "European Economic Area (EEA) country, excluding Ireland" },
"20" => { "value" => "Afghanistan" },
"21" => { "value" => "Ukraine" },
"12" => { "value" => "Other" },
"divider" => true,
"13" => { "value" => "Tenant prefers not to say" },
})
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
end

2
spec/models/form/lettings/questions/prevloc_spec.rb

@ -10,8 +10,6 @@ RSpec.describe Form::Lettings::Questions::Prevloc, type: :model do
let(:start_date) { Time.utc(2023, 4, 1) } let(:start_date) { Time.utc(2023, 4, 1) }
it "has the correct answer_options" do it "has the correct answer_options" do
Rails.application.load_seed
expect(question.answer_options).to eq({ expect(question.answer_options).to eq({
"" => "Select an option", "" => "Select an option",
"S12000033" => "Aberdeen City", "S12000033" => "Aberdeen City",

158
spec/models/form/lettings/questions/reason_spec.rb

@ -0,0 +1,158 @@
require "rails_helper"
RSpec.describe Form::Lettings::Questions::Reason, 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("reason")
end
it "has the correct header" do
expect(question.header).to eq("What is the tenant’s main reason for the household leaving their last settled home?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Reason for leaving last settled home")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(0)
end
it "has the correct hint" do
expect(question.hint_text).to eq("The tenant’s ‘last settled home’ is their last long-standing home. For tenants who were in temporary accommodation or sleeping rough, their last settled home is where they were living previously.")
end
it "has the correct conditional_for" do
expect(question.conditional_for).to eq({ "reasonother" => [20] })
end
it "is not marked as derived" do
expect(question).not_to be_derived
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"40" => {
"value" => "End of assured shorthold tenancy (no fault)",
},
"41" => {
"value" => "End of assured shorthold tenancy (eviction or tenant at fault)",
},
"42" => {
"value" => "End of fixed term tenancy (no fault)",
},
"43" => {
"value" => "End of fixed term tenancy (eviction or tenant at fault)",
},
"1" => {
"value" => "Permanently decanted from another property owned by this landlord",
},
"46" => {
"value" => "Discharged from long-stay hospital or similar institution",
},
"45" => {
"value" => "Discharged from prison",
},
"2" => {
"value" => "Left home country as a refugee",
},
"4" => {
"value" => "Loss of tied accommodation",
},
"9" => {
"value" => "Asked to leave by family or friends",
},
"44" => {
"value" => "Death of household member in last settled accommodation",
},
"8" => {
"value" => "Relationship breakdown (non-violent) with partner",
},
"16" => {
"value" => "To move nearer to family, friends or school",
},
"17" => {
"value" => "To move nearer to work",
},
"48" => {
"value" => "Domestic abuse - previously joint tenancy with partner",
},
"49" => {
"value" => "Domestic abuse - other",
},
"31" => {
"value" => "Hate crime",
},
"10" => {
"value" => "Racial harassment",
},
"11" => {
"value" => "Other problems with neighbours",
},
"35" => {
"value" => "Couldn’t afford fees attached to renewing the tenancy",
},
"36" => {
"value" => "Couldn’t afford increase in rent",
},
"38" => {
"value" => "Couldn’t afford rent or mortgage (employment)",
},
"37" => {
"value" => "Couldn’t afford rent or mortgage (welfare reforms)",
},
"39" => {
"value" => "Couldn’t afford rent or mortgage (other)",
},
"34" => {
"value" => "Repossession",
},
"12" => {
"value" => "Property unsuitable because of overcrowding",
},
"13" => {
"value" => "Property unsuitable because of ill health or disability",
},
"14" => {
"value" => "Property unsuitable because of poor condition",
},
"18" => {
"value" => "To move to accommodation with support",
},
"19" => {
"value" => "To move to independent accommodation",
},
"30" => {
"value" => "Under occupation (no incentive)",
},
"29" => {
"value" => "Under occupation (offered incentive to downsize)",
},
"20" => {
"value" => "Other",
},
"47" => {
"value" => "Tenant prefers not to say",
},
"divider" => {
"value" => true,
},
"28" => {
"value" => "Don’t know",
},
})
end
end

44
spec/models/form/lettings/questions/tenancy_length_spec.rb

@ -0,0 +1,44 @@
require "rails_helper"
RSpec.describe Form::Lettings::Questions::TenancyLength, type: :model do
subject(:question) { described_class.new(nil, nil, page) }
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("tenancylength")
end
it "has the correct header" do
expect(question.header).to eq("What is the length of the fixed-term tenancy to the nearest year?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Length of fixed-term tenancy")
end
it "has the correct type" do
expect(question.type).to eq("numeric")
end
it "has the correct hint_text" do
expect(question.hint_text).to eq("Don’t include the starter or introductory period.")
end
it "has the correct minimum and maximum" do
expect(question.min).to eq 0
expect(question.max).to eq 150
end
it "has the correct step" do
expect(question.step).to eq 1
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
end

35
spec/models/form/lettings/questions/tenancy_other_spec.rb

@ -0,0 +1,35 @@
require "rails_helper"
RSpec.describe Form::Lettings::Questions::TenancyOther, type: :model do
subject(:question) { described_class.new(nil, nil, page) }
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("tenancyother")
end
it "has the correct header" do
expect(question.header).to eq("Please state the tenancy type")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("")
end
it "has the correct type" do
expect(question.type).to eq("text")
end
it "has the correct hint_text" do
expect(question.hint_text).to eq("")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
end

65
spec/models/form/lettings/questions/tenancy_type_spec.rb

@ -0,0 +1,65 @@
require "rails_helper"
RSpec.describe Form::Lettings::Questions::TenancyType, type: :model do
subject(:question) { described_class.new(nil, nil, page) }
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("tenancy")
end
it "has the correct header" do
expect(question.header).to eq("What is the type of tenancy?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Type of main tenancy")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "has the correct hint_text" do
expect(question.hint_text).to eq("")
end
it "has the correct conditional_for" do
expect(question.conditional_for).to eq({ "tenancyother" => [3] })
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"4" => {
"value" => "Assured Shorthold Tenancy (AST) – Fixed term",
"hint" => "Mostly housing associations provide these. Fixed term tenancies are intended to be for a set amount of time up to 20 years.",
},
"6" => {
"value" => "Secure – fixed term",
"hint" => "Mostly local authorities provide these. Fixed term tenancies are intended to be for a set amount of time up to 20 years.",
},
"2" => {
"value" => "Assured – lifetime",
},
"7" => {
"value" => "Secure – lifetime",
},
"5" => {
"value" => "Licence agreement",
"hint" => "Licence agreements are mostly used for Supported Housing and work on a rolling basis.",
},
"3" => {
"value" => "Other",
},
})
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
end

6
spec/models/form/sales/pages/buyer2_live_in_property_spec.rb

@ -30,12 +30,12 @@ RSpec.describe Form::Sales::Pages::Buyer2LiveInProperty, type: :model do
it "has correct depends_on" do it "has correct depends_on" do
expect(page.depends_on).to eq([ expect(page.depends_on).to eq([
{ {
"jointpur" => 1, "joint_purchase?" => true,
"privacynotice" => 1, "privacynotice" => 1,
}, },
{ {
"jointpur" => 1, "joint_purchase?" => true,
"noint" => 1, "buyer_not_interviewed?" => true,
}, },
]) ])
end end

31
spec/models/form/sales/pages/buyer2_living_in_spec.rb

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Buyer2LivingIn, type: :model do
subject(:page) { described_class.new(nil, nil, subsection) }
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[buy2living])
end
it "has the correct id" do
expect(page.id).to eq("buyer_2_living_in")
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([{ "buyer_two_will_live_in_property?" => true }])
end
end

31
spec/models/form/sales/pages/buyer2_nationality_spec.rb

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Buyer2Nationality, type: :model do
subject(:page) { described_class.new(nil, nil, subsection) }
let(:subsection) { instance_double(Form::Subsection) }
it "has correct subsection" do
expect(page.subsection).to be subsection
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq %w[nationalbuy2]
end
it "has the correct id" do
expect(page.id).to eq "buyer_2_nationality"
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 [{ "joint_purchase?" => true }]
end
end

31
spec/models/form/sales/pages/buyer2_previous_housing_situation_spec.rb

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Buyer2PreviousHousingSituation, type: :model do
subject(:page) { described_class.new(nil, nil, subsection) }
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[prevtenbuy2])
end
it "has the correct id" do
expect(page.id).to eq("buyer_2_previous_housing_situation")
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([{ "buyer_two_not_already_living_in_property?" => true }])
end
end

12
spec/models/form/sales/pages/nationality1_spec.rb

@ -1,22 +1,20 @@
require "rails_helper" require "rails_helper"
RSpec.describe Form::Sales::Pages::Nationality1, type: :model do RSpec.describe Form::Sales::Pages::Nationality1, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) } subject(:page) { described_class.new(nil, nil, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) } let(:subsection) { instance_double(Form::Subsection) }
it "has correct subsection" do it "has correct subsection" do
expect(page.subsection).to eq(subsection) expect(page.subsection).to be subsection
end end
it "has correct questions" do it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[national othernational]) expect(page.questions.map(&:id)).to eq %w[national]
end end
it "has the correct id" do it "has the correct id" do
expect(page.id).to eq("buyer_1_nationality") expect(page.id).to eq "buyer_1_nationality"
end end
it "has the correct header" do it "has the correct header" do
@ -28,6 +26,6 @@ RSpec.describe Form::Sales::Pages::Nationality1, type: :model do
end end
it "has correct depends_on" do it "has correct depends_on" do
expect(page.depends_on).to eq([{ "privacynotice" => 1 }, { "noint" => 1 }]) expect(page.depends_on).to eq [{ "privacynotice" => 1 }, { "buyer_not_interviewed?" => true }]
end end
end end

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

Loading…
Cancel
Save