diff --git a/.github/workflows/review_pipeline.yml b/.github/workflows/review_pipeline.yml index 1e7ae0efb..5656898bc 100644 --- a/.github/workflows/review_pipeline.yml +++ b/.github/workflows/review_pipeline.yml @@ -72,6 +72,9 @@ jobs: runs-on: ubuntu-latest environment: staging needs: [postgres, redis] + permissions: + issues: write + pull-requests: write steps: - name: Checkout code diff --git a/Gemfile b/Gemfile index b20b7fe97..358e71070 100644 --- a/Gemfile +++ b/Gemfile @@ -20,7 +20,7 @@ gem "bootsnap", ">= 1.4.4", require: false # GOV UK frontend components gem "govuk-components" # GOV UK component form builder DSL -gem "govuk_design_system_formbuilder" +gem "govuk_design_system_formbuilder", "3.1.2" # Convert Markdown into GOV.UK frontend-styled HTML gem "govuk_markdown" # GOV UK Notify @@ -68,6 +68,7 @@ group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem "byebug", platforms: %i[mri mingw x64_mingw] gem "dotenv-rails" + gem "factory_bot_rails" gem "pry-byebug" gem "parallel_tests" @@ -90,7 +91,7 @@ end group :test do gem "capybara", require: false gem "capybara-lockstep" - gem "factory_bot_rails" + gem "capybara-screenshot" gem "faker" gem "rspec-rails", require: false gem "selenium-webdriver", require: false diff --git a/Gemfile.lock b/Gemfile.lock index 76ab1021c..7da2ef437 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -13,67 +13,67 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.0.4.1) - actionpack (= 7.0.4.1) - activesupport (= 7.0.4.1) + actioncable (7.0.4.2) + actionpack (= 7.0.4.2) + activesupport (= 7.0.4.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.4.1) - actionpack (= 7.0.4.1) - activejob (= 7.0.4.1) - activerecord (= 7.0.4.1) - activestorage (= 7.0.4.1) - activesupport (= 7.0.4.1) + actionmailbox (7.0.4.2) + actionpack (= 7.0.4.2) + activejob (= 7.0.4.2) + activerecord (= 7.0.4.2) + activestorage (= 7.0.4.2) + activesupport (= 7.0.4.2) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.4.1) - actionpack (= 7.0.4.1) - actionview (= 7.0.4.1) - activejob (= 7.0.4.1) - activesupport (= 7.0.4.1) + actionmailer (7.0.4.2) + actionpack (= 7.0.4.2) + actionview (= 7.0.4.2) + activejob (= 7.0.4.2) + activesupport (= 7.0.4.2) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.4.1) - actionview (= 7.0.4.1) - activesupport (= 7.0.4.1) + actionpack (7.0.4.2) + actionview (= 7.0.4.2) + activesupport (= 7.0.4.2) rack (~> 2.0, >= 2.2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.4.1) - actionpack (= 7.0.4.1) - activerecord (= 7.0.4.1) - activestorage (= 7.0.4.1) - activesupport (= 7.0.4.1) + actiontext (7.0.4.2) + actionpack (= 7.0.4.2) + activerecord (= 7.0.4.2) + activestorage (= 7.0.4.2) + activesupport (= 7.0.4.2) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.4.1) - activesupport (= 7.0.4.1) + actionview (7.0.4.2) + activesupport (= 7.0.4.2) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.4.1) - activesupport (= 7.0.4.1) + activejob (7.0.4.2) + activesupport (= 7.0.4.2) globalid (>= 0.3.6) - activemodel (7.0.4.1) - activesupport (= 7.0.4.1) - activerecord (7.0.4.1) - activemodel (= 7.0.4.1) - activesupport (= 7.0.4.1) - activestorage (7.0.4.1) - actionpack (= 7.0.4.1) - activejob (= 7.0.4.1) - activerecord (= 7.0.4.1) - activesupport (= 7.0.4.1) + activemodel (7.0.4.2) + activesupport (= 7.0.4.2) + activerecord (7.0.4.2) + activemodel (= 7.0.4.2) + activesupport (= 7.0.4.2) + activestorage (7.0.4.2) + actionpack (= 7.0.4.2) + activejob (= 7.0.4.2) + activerecord (= 7.0.4.2) + activesupport (= 7.0.4.2) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.4.1) + activesupport (7.0.4.2) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -84,20 +84,20 @@ GEM auto_strip_attributes (2.6.0) activerecord (>= 4.0) aws-eventstream (1.2.0) - aws-partitions (1.635.0) - aws-sdk-core (3.153.0) + aws-partitions (1.714.0) + aws-sdk-core (3.170.0) aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.525.0) - aws-sigv4 (~> 1.1) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.58.0) - aws-sdk-core (~> 3, >= 3.127.0) + aws-sdk-kms (1.62.0) + aws-sdk-core (~> 3, >= 3.165.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.114.0) - aws-sdk-core (~> 3, >= 3.127.0) + aws-sdk-s3 (1.119.1) + aws-sdk-core (~> 3, >= 3.165.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.1) + aws-sigv4 (1.5.2) aws-eventstream (~> 1, >= 1.0.2) bcrypt (3.1.18) better_html (2.0.1) @@ -108,14 +108,14 @@ GEM parser (>= 2.4) smart_properties bindex (0.8.1) - bootsnap (1.13.0) + bootsnap (1.16.0) msgpack (~> 1.2) builder (3.2.4) bundler-audit (0.9.1) bundler (>= 1.2.0, < 3) thor (~> 1.0) byebug (11.1.3) - capybara (3.37.1) + capybara (3.38.0) addressable matrix mini_mime (>= 0.1.3) @@ -124,14 +124,17 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - capybara-lockstep (1.2.1) + capybara-lockstep (1.3.0) activesupport (>= 3.2) capybara (>= 2.0) ruby2_keywords selenium-webdriver (>= 3) + capybara-screenshot (1.0.26) + capybara (>= 1.0, < 4) + launchy childprocess (4.1.0) coderay (1.1.3) - concurrent-ruby (1.1.10) + concurrent-ruby (1.2.0) connection_pool (2.3.0) crack (0.4.5) rexml @@ -150,7 +153,7 @@ GEM dotenv (= 2.8.1) railties (>= 3.2) encryptor (3.0.0) - erb_lint (0.2.0) + erb_lint (0.3.1) activesupport better_html (>= 2.0.1) parser (>= 2.7.1.4) @@ -160,19 +163,19 @@ GEM erubi (1.12.0) et-orbi (1.2.7) tzinfo - excon (0.92.5) + excon (0.99.0) factory_bot (6.2.1) activesupport (>= 5.0.0) factory_bot_rails (6.2.0) factory_bot (~> 6.2.0) railties (>= 5.0.0) - faker (2.23.0) + faker (3.1.1) i18n (>= 1.8.11, < 2) ffi (1.15.5) fugit (1.8.1) et-orbi (~> 1, >= 1.2.7) raabro (~> 1.4) - globalid (1.0.1) + globalid (1.1.0) activesupport (>= 5.0) govuk-components (3.2.1) actionpack (>= 6.1) @@ -186,7 +189,7 @@ GEM activemodel (>= 6.1) activesupport (>= 6.1) html-attributes-utils (~> 0.9, >= 0.9.2) - govuk_markdown (1.0.0) + govuk_markdown (2.0.0) activesupport redcarpet hashdiff (1.0.1) @@ -195,19 +198,21 @@ GEM i18n (1.12.0) concurrent-ruby (~> 1.0) iniparse (1.5.0) - jmespath (1.6.1) - jsbundling-rails (1.0.3) + jmespath (1.6.2) + jsbundling-rails (1.1.1) railties (>= 6.0.0) json-schema (3.0.0) addressable (>= 2.8) - jwt (2.5.0) - listen (3.7.1) + jwt (2.7.0) + launchy (2.5.2) + addressable (~> 2.8) + listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) loofah (2.19.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.8.0.1) + mail (2.8.1) mini_mime (>= 0.1.1) net-imap net-pop @@ -217,7 +222,7 @@ GEM method_source (1.0.0) mini_mime (1.1.2) minitest (5.17.0) - msgpack (1.5.6) + msgpack (1.6.0) net-imap (0.3.4) date net-protocol @@ -228,33 +233,33 @@ GEM net-smtp (0.3.3) net-protocol nio4r (2.5.8) - nokogiri (1.14.0-arm64-darwin) + nokogiri (1.14.2-arm64-darwin) racc (~> 1.4) - nokogiri (1.14.0-x86_64-darwin) + nokogiri (1.14.2-x86_64-darwin) racc (~> 1.4) - nokogiri (1.14.0-x86_64-linux) + nokogiri (1.14.2-x86_64-linux) racc (~> 1.4) - notifications-ruby-client (5.3.0) + notifications-ruby-client (5.4.0) jwt (>= 1.5, < 3) orm_adapter (0.5.0) - overcommit (0.59.1) + overcommit (0.60.0) childprocess (>= 0.6.3, < 5) iniparse (~> 1.4) rexml (~> 3.2) pagy (5.10.1) activesupport - paper_trail (13.0.0) - activerecord (>= 5.2) - request_store (~> 1.1) + paper_trail (14.0.0) + activerecord (>= 6.0) + request_store (~> 1.4) paper_trail-globalid (0.2.0) globalid paper_trail (>= 3.0.0) parallel (1.22.1) - parallel_tests (4.0.0) + parallel_tests (4.2.0) parallel - parser (3.1.2.1) + parser (3.2.1.0) ast (~> 2.4.1) - pg (1.4.3) + pg (1.4.5) possessive (1.0.1) postcodes_io (0.4.0) excon (~> 0.39) @@ -263,13 +268,13 @@ GEM activesupport (>= 7.0.0) rack railties (>= 7.0.0) - pry (0.14.1) + pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) pry-byebug (3.10.1) byebug (~> 11.0) pry (>= 0.13, < 0.15) - public_suffix (5.0.0) + public_suffix (5.0.1) puma (5.6.5) nio4r (~> 2.0) raabro (1.4.0) @@ -281,28 +286,28 @@ GEM rack (>= 1.2.0) rack-test (2.0.2) rack (>= 1.3) - rails (7.0.4.1) - actioncable (= 7.0.4.1) - actionmailbox (= 7.0.4.1) - actionmailer (= 7.0.4.1) - actionpack (= 7.0.4.1) - actiontext (= 7.0.4.1) - actionview (= 7.0.4.1) - activejob (= 7.0.4.1) - activemodel (= 7.0.4.1) - activerecord (= 7.0.4.1) - activestorage (= 7.0.4.1) - activesupport (= 7.0.4.1) + rails (7.0.4.2) + actioncable (= 7.0.4.2) + actionmailbox (= 7.0.4.2) + actionmailer (= 7.0.4.2) + actionpack (= 7.0.4.2) + actiontext (= 7.0.4.2) + actionview (= 7.0.4.2) + activejob (= 7.0.4.2) + activemodel (= 7.0.4.2) + activerecord (= 7.0.4.2) + activestorage (= 7.0.4.2) + activesupport (= 7.0.4.2) bundler (>= 1.15.0) - railties (= 7.0.4.1) + railties (= 7.0.4.2) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.4.4) + rails-html-sanitizer (1.5.0) loofah (~> 2.19, >= 2.19.1) - railties (7.0.4.1) - actionpack (= 7.0.4.1) - activesupport (= 7.0.4.1) + railties (7.0.4.2) + actionpack (= 7.0.4.2) + activesupport (= 7.0.4.2) method_source rake (>= 12.2) thor (~> 1.0) @@ -313,36 +318,38 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - redcarpet (3.5.1) - redis (4.8.0) - regexp_parser (2.5.0) + redcarpet (3.6.0) + redis (4.8.1) + redis-client (0.12.2) + connection_pool + regexp_parser (2.7.0) request_store (1.5.1) rack (>= 1.4) - responders (3.0.1) - actionpack (>= 5.0) - railties (>= 5.0) + responders (3.1.0) + actionpack (>= 5.2) + railties (>= 5.2) rexml (3.2.5) - roo (2.9.0) + roo (2.10.0) nokogiri (~> 1) rubyzip (>= 1.3.0, < 3.0.0) - rotp (6.2.0) - rspec-core (3.11.0) - rspec-support (~> 3.11.0) - rspec-expectations (3.11.1) + rotp (6.2.2) + rspec-core (3.12.1) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.2) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-mocks (3.11.1) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.3) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-rails (5.1.2) - actionpack (>= 5.2) - activesupport (>= 5.2) - railties (>= 5.2) - rspec-core (~> 3.10) - rspec-expectations (~> 3.10) - rspec-mocks (~> 3.10) - rspec-support (~> 3.10) - rspec-support (3.11.1) + rspec-support (~> 3.12.0) + rspec-rails (6.0.1) + actionpack (>= 6.1) + activesupport (>= 6.1) + railties (>= 6.1) + rspec-core (~> 3.11) + rspec-expectations (~> 3.11) + rspec-mocks (~> 3.11) + rspec-support (~> 3.11) + rspec-support (3.12.0) rubocop (1.25.0) parallel (~> 1.10) parser (>= 3.1.0.0) @@ -360,7 +367,7 @@ GEM rubocop-rails (= 2.13.2) rubocop-rake (= 0.6.0) rubocop-rspec (= 2.7.0) - rubocop-performance (1.15.0) + rubocop-performance (1.16.0) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) rubocop-rails (2.13.2) @@ -374,39 +381,39 @@ GEM ruby-progressbar (1.11.0) ruby2_keywords (0.0.5) rubyzip (2.3.2) - selenium-webdriver (4.4.0) - childprocess (>= 0.5, < 5.0) + selenium-webdriver (4.8.1) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - sentry-rails (5.4.2) + sentry-rails (5.8.0) railties (>= 5.0) - sentry-ruby (~> 5.4.2) - sentry-ruby (5.4.2) + sentry-ruby (~> 5.8.0) + sentry-ruby (5.8.0) concurrent-ruby (~> 1.0, >= 1.0.2) - sidekiq (6.5.5) - connection_pool (>= 2.2.2) - rack (~> 2.0) - redis (>= 4.5.0) - sidekiq-cron (1.8.0) - fugit (~> 1) + sidekiq (7.0.5) + concurrent-ruby (< 2) + connection_pool (>= 2.3.0) + rack (>= 2.2.4) + redis-client (>= 0.11.0) + sidekiq-cron (1.9.1) + fugit (~> 1.8) sidekiq (>= 4.2.1) - simplecov (0.21.2) + simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) smart_properties (1.17.0) - stimulus-rails (1.1.0) + stimulus-rails (1.2.1) railties (>= 6.0.0) thor (1.2.1) - timecop (0.9.5) - timeout (0.3.1) - tzinfo (2.0.5) + timecop (0.9.6) + timeout (0.3.2) + tzinfo (2.0.6) concurrent-ruby (~> 1.0) uk_postcode (2.1.8) - unicode-display_width (2.3.0) + unicode-display_width (2.4.2) view_component (2.69.0) activesupport (>= 5.0.0, < 8.0) concurrent-ruby (~> 1.0) @@ -428,7 +435,7 @@ GEM websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.6) + zeitwerk (2.6.7) PLATFORMS arm64-darwin-21 @@ -447,6 +454,7 @@ DEPENDENCIES byebug capybara capybara-lockstep + capybara-screenshot devise! devise_two_factor_authentication dotenv-rails @@ -454,7 +462,7 @@ DEPENDENCIES factory_bot_rails faker govuk-components - govuk_design_system_formbuilder + govuk_design_system_formbuilder (= 3.1.2) govuk_markdown jsbundling-rails json-schema diff --git a/app/controllers/lettings_logs_controller.rb b/app/controllers/lettings_logs_controller.rb index 5181d21ca..f86a55a27 100644 --- a/app/controllers/lettings_logs_controller.rb +++ b/app/controllers/lettings_logs_controller.rb @@ -1,7 +1,7 @@ class LettingsLogsController < LogsController before_action :find_resource, except: %i[create index edit] - before_action :session_filters, if: :current_user - before_action :set_session_filters, if: :current_user + before_action :session_filters, if: :current_user, only: %i[index email_csv download_csv] + before_action :set_session_filters, if: :current_user, only: %i[index email_csv download_csv] before_action :extract_bulk_upload_from_session_filters, only: [:index] before_action :redirect_if_bulk_upload_resolved, only: [:index] diff --git a/app/controllers/organisations_controller.rb b/app/controllers/organisations_controller.rb index 0c8c4a172..211283ffe 100644 --- a/app/controllers/organisations_controller.rb +++ b/app/controllers/organisations_controller.rb @@ -6,8 +6,8 @@ class OrganisationsController < ApplicationController before_action :authenticate_user! before_action :find_resource, except: %i[index new create] before_action :authenticate_scope!, except: [:index] - before_action -> { session_filters(specific_org: true) }, if: -> { current_user.support? || current_user.organisation.has_managing_agents? } - before_action :set_session_filters, if: -> { current_user.support? || current_user.organisation.has_managing_agents? } + before_action -> { session_filters(specific_org: true) }, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_csv download_csv] + before_action :set_session_filters, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_csv download_csv] def index redirect_to organisation_path(current_user.organisation) unless current_user.support? diff --git a/app/controllers/sales_logs_controller.rb b/app/controllers/sales_logs_controller.rb index 8a6c9937f..ecfeabcad 100644 --- a/app/controllers/sales_logs_controller.rb +++ b/app/controllers/sales_logs_controller.rb @@ -1,6 +1,6 @@ class SalesLogsController < LogsController - before_action :session_filters, if: :current_user - before_action :set_session_filters, if: :current_user + before_action :session_filters, if: :current_user, only: %i[index email_csv download_csv] + before_action :set_session_filters, if: :current_user, only: %i[index email_csv download_csv] def create super { SalesLog.new(log_params) } diff --git a/app/helpers/collection_time_helper.rb b/app/helpers/collection_time_helper.rb index 8478d06e7..23014b8d1 100644 --- a/app/helpers/collection_time_helper.rb +++ b/app/helpers/collection_time_helper.rb @@ -1,16 +1,42 @@ module CollectionTimeHelper + def collection_start_year(date) + window_end_date = Time.zone.local(date.year, 4, 1) + date < window_end_date ? date.year - 1 : date.year + end + def current_collection_start_year - today = Time.zone.now - window_end_date = Time.zone.local(today.year, 4, 1) - today < window_end_date ? today.year - 1 : today.year + collection_start_year(Time.zone.now) end def collection_start_date(date) - window_end_date = Time.zone.local(date.year, 4, 1) - date < window_end_date ? Time.zone.local(date.year - 1, 4, 1) : Time.zone.local(date.year, 4, 1) + Time.zone.local(collection_start_year(date), 4, 1) end def current_collection_start_date Time.zone.local(current_collection_start_year, 4, 1) end + + def collection_end_date(date) + Time.zone.local(collection_start_year(date) + 1, 3, 31) + end + + def current_collection_end_date + Time.zone.local(current_collection_start_year + 1, 3, 31) + end + + def previous_collection_end_date + current_collection_end_date - 1.year + end + + def next_collection_start_year + current_collection_start_year + 1 + end + + def previous_collection_start_year + current_collection_start_year - 1 + end + + def previous_collection_start_date + current_collection_start_date - 1.year + end end diff --git a/app/helpers/filters_helper.rb b/app/helpers/filters_helper.rb index a1875081c..7f906b4ea 100644 --- a/app/helpers/filters_helper.rb +++ b/app/helpers/filters_helper.rb @@ -27,4 +27,12 @@ module FiltersHelper organisation_options = user.support? ? Organisation.all : [user.organisation] + user.organisation.managing_agents [OpenStruct.new(id: "", name: "Select an option")] + organisation_options.map { |org| OpenStruct.new(id: org.id, name: org.name) } end + + def collection_year_options + if FeatureToggle.collection_2023_2024_year_enabled? + { "2023": "2023/24", "2022": "2022/23", "2021": "2021/22" } + else + { "2022": "2022/23", "2021": "2021/22" } + end + end end diff --git a/app/helpers/interruption_screen_helper.rb b/app/helpers/interruption_screen_helper.rb index c30ba7bfa..939f70bb2 100644 --- a/app/helpers/interruption_screen_helper.rb +++ b/app/helpers/interruption_screen_helper.rb @@ -1,17 +1,10 @@ module InterruptionScreenHelper - def display_informative_text(informative_text, lettings_log) + def display_informative_text(informative_text, log) return "" unless informative_text["arguments"] translation_params = {} informative_text["arguments"].each do |argument| - value = if argument["label"] - pre_casing_value = lettings_log.form.get_question(argument["key"], lettings_log).answer_label(lettings_log) - pre_casing_value.downcase - elsif argument["currency"] - number_to_currency(lettings_log.public_send(argument["key"]), delimiter: ",", format: "%n", unit: "£") - else - lettings_log.public_send(argument["key"]) - end + value = get_value_from_argument(log, argument) translation_params[argument["i18n_template"].to_sym] = value end @@ -24,21 +17,27 @@ module InterruptionScreenHelper end end - def display_title_text(title_text, lettings_log) + def display_title_text(title_text, log) return "" if title_text.nil? translation_params = {} arguments = title_text["arguments"] || {} arguments.each do |argument| - value = if argument["label"] - lettings_log.form.get_question(argument["key"], lettings_log).answer_label(lettings_log).downcase - elsif argument["currency"] - number_to_currency(lettings_log.public_send(argument["key"]), delimiter: ",", format: "%n", unit: "£") - else - lettings_log.public_send(argument["key"]) - end + value = get_value_from_argument(log, argument) translation_params[argument["i18n_template"].to_sym] = value end I18n.t(title_text["translation"], **translation_params).to_s end + +private + + def get_value_from_argument(log, argument) + if argument["label"] + log.form.get_question(argument["key"], log).answer_label(log).downcase + elsif argument["arguments_for_key"] + log.public_send(argument["key"], argument["arguments_for_key"]) + else + log.public_send(argument["key"]) + end + end end diff --git a/app/helpers/tasklist_helper.rb b/app/helpers/tasklist_helper.rb index f297de2ff..30112894f 100644 --- a/app/helpers/tasklist_helper.rb +++ b/app/helpers/tasklist_helper.rb @@ -11,15 +11,6 @@ module TasklistHelper log.form.subsections.count { |subsection| subsection.status(log) == status && subsection.applicable_questions(log).count.positive? } end - def next_page_or_check_answers(subsection, log, current_user) - path = if subsection.is_started?(log) - "#{log.class.name.underscore}_#{subsection.id}_check_answers_path" - else - "#{log.class.name.underscore}_#{next_question_page(subsection, log, current_user)}_path" - end - send(path, log) - end - def next_question_page(subsection, log, current_user) if subsection.pages.first.routed_to?(log, current_user) subsection.pages.first.id @@ -39,9 +30,23 @@ module TasklistHelper def review_log_text(log) if log.collection_period_open? - "You can #{govuk_link_to 'review and make changes to this log', review_lettings_log_path(log)} until #{log.form.end_date.to_formatted_s(:govuk_date)}.".html_safe + link = log.sales? ? review_sales_log_path(id: log, sales_log: true) : review_lettings_log_path(log) + + "You can #{govuk_link_to 'review and make changes to this log', link} until #{log.form.end_date.to_formatted_s(:govuk_date)}.".html_safe else "This log is from the #{log.form.start_date.year}/#{log.form.start_date.year + 1} collection window, which is now closed." end end + +private + + def next_page_or_check_answers(subsection, log, current_user) + path = if subsection.is_started?(log) + "#{log.class.name.underscore}_#{subsection.id}_check_answers_path" + else + "#{log.class.name.underscore}_#{next_question_page(subsection, log, current_user)}_path" + end + + send(path, log) + end end diff --git a/app/mailers/bulk_upload_mailer.rb b/app/mailers/bulk_upload_mailer.rb index 59d03ce9b..be0a71e05 100644 --- a/app/mailers/bulk_upload_mailer.rb +++ b/app/mailers/bulk_upload_mailer.rb @@ -33,17 +33,29 @@ class BulkUploadMailer < NotifyMailer ) end - def send_bulk_upload_failed_csv_errors_mail(user, bulk_upload) + def columns_with_errors(bulk_upload:) + array = bulk_upload.columns_with_errors + + if array.size > 3 + "#{array.take(3).join(', ')} and more" + else + array.join(", ") + end + end + + def send_correct_and_upload_again_mail(bulk_upload:) + error_description = "We noticed that you have a lot of similar errors in column #{columns_with_errors(bulk_upload:)}. Please correct your data export and upload again." + send_email( - user.email, + bulk_upload.user.email, BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID, { - filename: "[#{bulk_upload} filename]", - upload_timestamp: "[#{bulk_upload} upload_timestamp]", - year_combo: "[#{bulk_upload} year_combo]", - lettings_or_sales: "[#{bulk_upload} lettings_or_sales]", - error_description: "[#{bulk_upload} error_description]", - summary_report_link: "[#{bulk_upload} summary_report_link]", + filename: bulk_upload.filename, + upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), + year_combo: bulk_upload.year_combo, + lettings_or_sales: bulk_upload.log_type, + error_description:, + summary_report_link: summary_bulk_upload_lettings_result_url(bulk_upload), }, ) end diff --git a/app/models/bulk_upload.rb b/app/models/bulk_upload.rb index cf2965446..0adf0d84a 100644 --- a/app/models/bulk_upload.rb +++ b/app/models/bulk_upload.rb @@ -30,10 +30,22 @@ class BulkUpload < ApplicationRecord end end + def columns_with_errors + bulk_upload_errors + .select(:col) + .distinct(:col) + .pluck(:col) + .sort_by { |col| col.rjust(2, "0") } + end + def general_needs? needstype == 1 end + def supported_housing? + needstype == 2 + end + private def generate_identifier diff --git a/app/models/derived_variables/lettings_log_variables.rb b/app/models/derived_variables/lettings_log_variables.rb index 96b937ab4..7d664e48f 100644 --- a/app/models/derived_variables/lettings_log_variables.rb +++ b/app/models/derived_variables/lettings_log_variables.rb @@ -45,6 +45,9 @@ module DerivedVariables::LettingsLogVariables self.underoccupation_benefitcap = 2 if collection_start_year == 2021 self.referral = 1 self.waityear = 2 + self.offered = 0 + self.voiddate = startdate + self.first_time_property_let_as_social_housing = 0 if is_general_needs? # fixed term self.prevten = 32 if managing_organisation&.provider_type == "PRP" @@ -60,7 +63,6 @@ module DerivedVariables::LettingsLogVariables if is_supported_housing? && location self.wchair = location.mobility_type_before_type_cast == "W" ? 1 : 2 end - self.voiddate = startdate if is_renewal? self.vacdays = property_vacant_days set_housingneeds_fields if housingneeds? diff --git a/app/models/form.rb b/app/models/form.rb index 3da85d52e..f9959959e 100644 --- a/app/models/form.rb +++ b/app/models/form.rb @@ -6,7 +6,11 @@ class Form def initialize(form_path, start_year = "", sections_in_form = [], type = "lettings") if sales_or_start_year_after_2022?(type, start_year) @start_date = Time.zone.local(start_year, 4, 1) - @end_date = Time.zone.local(start_year + 1, 7, 1) + @end_date = if start_year && start_year.to_i > 2022 + Time.zone.local(start_year + 1, 7, 9) + else + Time.zone.local(start_year + 1, 7, 7) + end @setup_sections = type == "sales" ? [Form::Sales::Sections::Setup.new(nil, nil, self)] : [Form::Lettings::Sections::Setup.new(nil, nil, self)] @form_sections = sections_in_form.map { |sec| sec.new(nil, nil, self) } @type = type diff --git a/app/models/form/lettings/questions/location_id.rb b/app/models/form/lettings/questions/location_id.rb index 5a99e7734..fc197cf82 100644 --- a/app/models/form/lettings/questions/location_id.rb +++ b/app/models/form/lettings/questions/location_id.rb @@ -2,7 +2,7 @@ class Form::Lettings::Questions::LocationId < ::Form::Question def initialize(_id, hsh, page) super("location_id", hsh, page) @check_answer_label = "Location" - @header = "Which location is this log for?" + @header = header_text @type = "radio" @answer_options = answer_options @inferred_answers = { @@ -47,4 +47,12 @@ private def selected_answer_option_is_derived?(_lettings_log) false end + + def header_text + if form.start_date && form.start_date.year >= 2023 + "Which location is this letting for?" + else + "Which location is this log for?" + end + end end diff --git a/app/models/form/lettings/questions/working_situation1.rb b/app/models/form/lettings/questions/working_situation1.rb index 866271f4d..20f5dc984 100644 --- a/app/models/form/lettings/questions/working_situation1.rb +++ b/app/models/form/lettings/questions/working_situation1.rb @@ -11,8 +11,8 @@ class Form::Lettings::Questions::WorkingSituation1 < ::Form::Question end ANSWER_OPTIONS = { - "2" => { "value" => "Part-time – Less than 30 hours" }, "1" => { "value" => "Full-time – 30 hours or more" }, + "2" => { "value" => "Part-time – Less than 30 hours" }, "7" => { "value" => "Full-time student" }, "3" => { "value" => "In government training into work, such as New Deal" }, "4" => { "value" => "Jobseeker" }, diff --git a/app/models/form/sales/pages/about_price_shared_ownership_value_check.rb b/app/models/form/sales/pages/about_price_shared_ownership_value_check.rb index f4f0955cd..5b668006a 100644 --- a/app/models/form/sales/pages/about_price_shared_ownership_value_check.rb +++ b/app/models/form/sales/pages/about_price_shared_ownership_value_check.rb @@ -20,14 +20,12 @@ class Form::Sales::Pages::AboutPriceSharedOwnershipValueCheck < ::Form::Page "translation" => "soft_validations.purchase_price.hint_text", "arguments" => [ { - "key" => "purchase_price_soft_min_or_soft_max", - "label" => false, + "key" => "field_formatted_as_currency", + "arguments_for_key" => "purchase_price_soft_min_or_soft_max", "i18n_template" => "soft_min_or_soft_max", - "currency" => true, }, { "key" => "purchase_price_min_or_max_text", - "label" => false, "i18n_template" => "min_or_max", }, ], diff --git a/app/models/form/sales/pages/about_staircase.rb b/app/models/form/sales/pages/about_staircase.rb index fd740c10e..2d1a17db4 100644 --- a/app/models/form/sales/pages/about_staircase.rb +++ b/app/models/form/sales/pages/about_staircase.rb @@ -12,6 +12,13 @@ class Form::Sales::Pages::AboutStaircase < ::Form::Page @questions ||= [ Form::Sales::Questions::StaircaseBought.new(nil, nil, self), Form::Sales::Questions::StaircaseOwned.new(nil, nil, self), - ] + staircase_sale_question, + ].compact + end + + def staircase_sale_question + if form.start_date.year >= 2023 + Form::Sales::Questions::StaircaseSale.new(nil, nil, self) + end end end diff --git a/app/models/form/sales/pages/buyer1_income_value_check.rb b/app/models/form/sales/pages/buyer1_income_value_check.rb index 04540c47f..48d8f5fff 100644 --- a/app/models/form/sales/pages/buyer1_income_value_check.rb +++ b/app/models/form/sales/pages/buyer1_income_value_check.rb @@ -6,6 +6,21 @@ class Form::Sales::Pages::Buyer1IncomeValueCheck < ::Form::Page "income1_under_soft_min?" => true, }, ] + @title_text = { + "translation" => "soft_validations.income.under_soft_min_for_economic_status", + "arguments" => [ + { + "key" => "field_formatted_as_currency", + "arguments_for_key" => "income1", + "i18n_template" => "income", + }, + { + "key" => "income_soft_min_for_ecstat", + "arguments_for_key" => "ecstat1", + "i18n_template" => "minimum", + }, + ], + } @informative_text = {} end diff --git a/app/models/form/sales/pages/buyer2_ethnic_background_arab.rb b/app/models/form/sales/pages/buyer2_ethnic_background_arab.rb new file mode 100644 index 000000000..593e43ebc --- /dev/null +++ b/app/models/form/sales/pages/buyer2_ethnic_background_arab.rb @@ -0,0 +1,15 @@ +class Form::Sales::Pages::Buyer2EthnicBackgroundArab < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "buyer_2_ethnic_background_arab" + @depends_on = [{ + "ethnic_group2" => 4, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::Buyer2EthnicBackgroundArab.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/buyer2_ethnic_background_asian.rb b/app/models/form/sales/pages/buyer2_ethnic_background_asian.rb new file mode 100644 index 000000000..98a36476e --- /dev/null +++ b/app/models/form/sales/pages/buyer2_ethnic_background_asian.rb @@ -0,0 +1,15 @@ +class Form::Sales::Pages::Buyer2EthnicBackgroundAsian < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "buyer_2_ethnic_background_asian" + @depends_on = [{ + "ethnic_group2" => 2, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::Buyer2EthnicBackgroundAsian.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/buyer2_ethnic_background_black.rb b/app/models/form/sales/pages/buyer2_ethnic_background_black.rb new file mode 100644 index 000000000..a772f46ac --- /dev/null +++ b/app/models/form/sales/pages/buyer2_ethnic_background_black.rb @@ -0,0 +1,15 @@ +class Form::Sales::Pages::Buyer2EthnicBackgroundBlack < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "buyer_2_ethnic_background_black" + @depends_on = [{ + "ethnic_group2" => 3, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::Buyer2EthnicBackgroundBlack.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/buyer2_ethnic_background_mixed.rb b/app/models/form/sales/pages/buyer2_ethnic_background_mixed.rb new file mode 100644 index 000000000..f02165dfc --- /dev/null +++ b/app/models/form/sales/pages/buyer2_ethnic_background_mixed.rb @@ -0,0 +1,15 @@ +class Form::Sales::Pages::Buyer2EthnicBackgroundMixed < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "buyer_2_ethnic_background_mixed" + @depends_on = [{ + "ethnic_group2" => 1, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::Buyer2EthnicBackgroundMixed.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/buyer2_ethnic_background_white.rb b/app/models/form/sales/pages/buyer2_ethnic_background_white.rb new file mode 100644 index 000000000..2013c8682 --- /dev/null +++ b/app/models/form/sales/pages/buyer2_ethnic_background_white.rb @@ -0,0 +1,15 @@ +class Form::Sales::Pages::Buyer2EthnicBackgroundWhite < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "buyer_2_ethnic_background_white" + @depends_on = [{ + "ethnic_group2" => 0, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::Buyer2EthnicBackgroundWhite.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/buyer2_ethnic_group.rb b/app/models/form/sales/pages/buyer2_ethnic_group.rb new file mode 100644 index 000000000..1b815996e --- /dev/null +++ b/app/models/form/sales/pages/buyer2_ethnic_group.rb @@ -0,0 +1,22 @@ +class Form::Sales::Pages::Buyer2EthnicGroup < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "buyer_2_ethnic_group" + @depends_on = [ + { + "jointpur" => 1, + "privacynotice" => 1, + }, + { + "jointpur" => 1, + "noint" => 1, + }, + ] + end + + def questions + @questions ||= [ + Form::Sales::Questions::Buyer2EthnicGroup.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/buyer2_income_value_check.rb b/app/models/form/sales/pages/buyer2_income_value_check.rb new file mode 100644 index 000000000..598c4c7a6 --- /dev/null +++ b/app/models/form/sales/pages/buyer2_income_value_check.rb @@ -0,0 +1,35 @@ +class Form::Sales::Pages::Buyer2IncomeValueCheck < ::Form::Page + def initialize(id, hsh, subsection) + super + @header = "" + @description = "" + @subsection = subsection + @depends_on = [ + { + "income2_under_soft_min?" => true, + }, + ] + @title_text = { + "translation" => "soft_validations.income.under_soft_min_for_economic_status", + "arguments" => [ + { + "key" => "field_formatted_as_currency", + "arguments_for_key" => "income2", + "i18n_template" => "income", + }, + { + "key" => "income_soft_min_for_ecstat", + "arguments_for_key" => "ecstat2", + "i18n_template" => "minimum", + }, + ], + } + @informative_text = {} + end + + def questions + @questions ||= [ + Form::Sales::Questions::Buyer2IncomeValueCheck.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/questions/buyer1_income_value_check.rb b/app/models/form/sales/questions/buyer1_income_value_check.rb index 0913dd788..8843d6736 100644 --- a/app/models/form/sales/questions/buyer1_income_value_check.rb +++ b/app/models/form/sales/questions/buyer1_income_value_check.rb @@ -3,7 +3,7 @@ class Form::Sales::Questions::Buyer1IncomeValueCheck < ::Form::Question super @id = "income1_value_check" @check_answer_label = "Income confirmation" - @header = "Are you sure this income is correct?" + @header = "Are you sure this is correct?" @type = "interruption_screen" @answer_options = { "0" => { "value" => "Yes" }, diff --git a/app/models/form/sales/questions/buyer2_ethnic_background_arab.rb b/app/models/form/sales/questions/buyer2_ethnic_background_arab.rb new file mode 100644 index 000000000..1766780b7 --- /dev/null +++ b/app/models/form/sales/questions/buyer2_ethnic_background_arab.rb @@ -0,0 +1,16 @@ +class Form::Sales::Questions::Buyer2EthnicBackgroundArab < ::Form::Question + def initialize(id, hsh, page) + super + @id = "ethnicbuy2" + @check_answer_label = "Buyer 2’s ethnic background" + @header = "Which of the following best describes the buyer 2’s Arab background?" + @type = "radio" + @answer_options = ANSWER_OPTIONS + @check_answers_card_number = 2 + end + + ANSWER_OPTIONS = { + "19" => { "value" => "Arab" }, + "16" => { "value" => "Other ethnic group" }, + }.freeze +end diff --git a/app/models/form/sales/questions/buyer2_ethnic_background_asian.rb b/app/models/form/sales/questions/buyer2_ethnic_background_asian.rb new file mode 100644 index 000000000..2578d220a --- /dev/null +++ b/app/models/form/sales/questions/buyer2_ethnic_background_asian.rb @@ -0,0 +1,19 @@ +class Form::Sales::Questions::Buyer2EthnicBackgroundAsian < ::Form::Question + def initialize(id, hsh, page) + super + @id = "ethnicbuy2" + @check_answer_label = "Buyer 2’s ethnic background" + @header = "Which of the following best describes the buyer 2’s Asian or Asian British background?" + @type = "radio" + @answer_options = ANSWER_OPTIONS + @check_answers_card_number = 2 + end + + ANSWER_OPTIONS = { + "10" => { "value" => "Bangladeshi" }, + "15" => { "value" => "Chinese" }, + "8" => { "value" => "Indian" }, + "9" => { "value" => "Pakistani" }, + "11" => { "value" => "Any other Asian or Asian British background" }, + }.freeze +end diff --git a/app/models/form/sales/questions/buyer2_ethnic_background_black.rb b/app/models/form/sales/questions/buyer2_ethnic_background_black.rb new file mode 100644 index 000000000..11d47790c --- /dev/null +++ b/app/models/form/sales/questions/buyer2_ethnic_background_black.rb @@ -0,0 +1,17 @@ +class Form::Sales::Questions::Buyer2EthnicBackgroundBlack < ::Form::Question + def initialize(id, hsh, page) + super + @id = "ethnicbuy2" + @check_answer_label = "Buyer 2’s ethnic background" + @header = "Which of the following best describes the buyer 2’s Black, African, Caribbean or Black British background?" + @type = "radio" + @answer_options = ANSWER_OPTIONS + @check_answers_card_number = 2 + end + + ANSWER_OPTIONS = { + "13" => { "value" => "African" }, + "12" => { "value" => "Caribbean" }, + "14" => { "value" => "Any other Black, African or Caribbean background" }, + }.freeze +end diff --git a/app/models/form/sales/questions/buyer2_ethnic_background_mixed.rb b/app/models/form/sales/questions/buyer2_ethnic_background_mixed.rb new file mode 100644 index 000000000..78600af34 --- /dev/null +++ b/app/models/form/sales/questions/buyer2_ethnic_background_mixed.rb @@ -0,0 +1,18 @@ +class Form::Sales::Questions::Buyer2EthnicBackgroundMixed < ::Form::Question + def initialize(id, hsh, page) + super + @id = "ethnicbuy2" + @check_answer_label = "Buyer 2’s ethnic background" + @header = "Which of the following best describes the buyer 2’s Mixed or Multiple ethnic groups background?" + @type = "radio" + @answer_options = ANSWER_OPTIONS + @check_answers_card_number = 2 + end + + ANSWER_OPTIONS = { + "4" => { "value" => "White and Black Caribbean" }, + "5" => { "value" => "White and Black African" }, + "6" => { "value" => "White and Asian" }, + "7" => { "value" => "Any other Mixed or Multiple ethnic background" }, + }.freeze +end diff --git a/app/models/form/sales/questions/buyer2_ethnic_background_white.rb b/app/models/form/sales/questions/buyer2_ethnic_background_white.rb new file mode 100644 index 000000000..5b5fc8e2c --- /dev/null +++ b/app/models/form/sales/questions/buyer2_ethnic_background_white.rb @@ -0,0 +1,18 @@ +class Form::Sales::Questions::Buyer2EthnicBackgroundWhite < ::Form::Question + def initialize(id, hsh, page) + super + @id = "ethnicbuy2" + @check_answer_label = "Buyer 2’s ethnic background" + @header = "Which of the following best describes the buyer 2’s White background?" + @type = "radio" + @answer_options = ANSWER_OPTIONS + @check_answers_card_number = 2 + end + + ANSWER_OPTIONS = { + "1" => { "value" => "English, Welsh, Northern Irish, Scottish or British" }, + "2" => { "value" => "Irish" }, + "18" => { "value" => "Gypsy or Irish Traveller" }, + "3" => { "value" => "Any other White background" }, + }.freeze +end diff --git a/app/models/form/sales/questions/buyer2_ethnic_group.rb b/app/models/form/sales/questions/buyer2_ethnic_group.rb new file mode 100644 index 000000000..5366910fe --- /dev/null +++ b/app/models/form/sales/questions/buyer2_ethnic_group.rb @@ -0,0 +1,27 @@ +class Form::Sales::Questions::Buyer2EthnicGroup < ::Form::Question + def initialize(id, hsh, page) + super + @id = "ethnic_group2" + @check_answer_label = "Buyer 2’s ethnic group" + @header = "What is buyer 2’s ethnic group?" + @type = "radio" + @answer_options = ANSWER_OPTIONS + @inferred_check_answers_value = [{ + "condition" => { + "ethnic_group2" => 17, + }, + "value" => "Prefers not to say", + }] + @check_answers_card_number = 2 + end + + ANSWER_OPTIONS = { + "0" => { "value" => "White" }, + "1" => { "value" => "Mixed or Multiple ethnic groups" }, + "2" => { "value" => "Asian or Asian British" }, + "3" => { "value" => "Black, African, Caribbean or Black British" }, + "4" => { "value" => "Arab or other ethnic group" }, + "divider" => { "value" => true }, + "17" => { "value" => "Buyer 1 prefers not to say" }, + }.freeze +end diff --git a/app/models/form/sales/questions/buyer2_income.rb b/app/models/form/sales/questions/buyer2_income.rb index 680bf8ae7..f7d96720b 100644 --- a/app/models/form/sales/questions/buyer2_income.rb +++ b/app/models/form/sales/questions/buyer2_income.rb @@ -7,6 +7,7 @@ class Form::Sales::Questions::Buyer2Income < ::Form::Question @type = "numeric" @hint_text = "Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments." @min = 0 + @max = 999_999 @step = 1 @width = 5 @prefix = "£" diff --git a/app/models/form/sales/questions/buyer2_income_value_check.rb b/app/models/form/sales/questions/buyer2_income_value_check.rb new file mode 100644 index 000000000..9508cc59a --- /dev/null +++ b/app/models/form/sales/questions/buyer2_income_value_check.rb @@ -0,0 +1,25 @@ +class Form::Sales::Questions::Buyer2IncomeValueCheck < ::Form::Question + def initialize(id, hsh, page) + super + @id = "income2_value_check" + @check_answer_label = "Income confirmation" + @header = "Are you sure this is correct?" + @type = "interruption_screen" + @answer_options = { + "0" => { "value" => "Yes" }, + "1" => { "value" => "No" }, + } + @hidden_in_check_answers = { + "depends_on" => [ + { + "income2_value_check" => 0, + }, + { + "income2_value_check" => 1, + }, + ], + } + @check_answers_card_number = 2 + @page = page + end +end diff --git a/app/models/form/sales/questions/staircase_sale.rb b/app/models/form/sales/questions/staircase_sale.rb new file mode 100644 index 000000000..fb56572cf --- /dev/null +++ b/app/models/form/sales/questions/staircase_sale.rb @@ -0,0 +1,16 @@ +class Form::Sales::Questions::StaircaseSale < ::Form::Question + def initialize(id, hsh, page) + super + @id = "staircasesale" + @check_answer_label = "Part of a back-to-back staircasing transaction" + @header = "Is this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?" + @type = "radio" + @answer_options = ANSWER_OPTIONS + end + + ANSWER_OPTIONS = { + "1" => { "value" => "Yes" }, + "2" => { "value" => "No" }, + "3" => { "value" => "Don't know" }, + }.freeze +end diff --git a/app/models/form/sales/subsections/household_characteristics.rb b/app/models/form/sales/subsections/household_characteristics.rb index 7e342499e..181460ae1 100644 --- a/app/models/form/sales/subsections/household_characteristics.rb +++ b/app/models/form/sales/subsections/household_characteristics.rb @@ -32,8 +32,10 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection Form::Sales::Pages::RetirementValueCheck.new("age_2_buyer_retirement_value_check", nil, self, person_index: 2), Form::Sales::Pages::GenderIdentity2.new(nil, nil, self), Form::Sales::Pages::RetirementValueCheck.new("gender_2_buyer_retirement_value_check", nil, self, person_index: 2), + ethnic_pages_for_buyer_2, Form::Sales::Pages::Buyer2WorkingSituation.new(nil, nil, self), - Form::Sales::Pages::RetirementValueCheck.new("working_situation_2_buyer_retirement_value_check", nil, self, person_index: 2), + Form::Sales::Pages::RetirementValueCheck.new("working_situation_2_retirement_value_check_joint_purchase", nil, self, person_index: 2), + Form::Sales::Pages::Buyer2IncomeValueCheck.new("working_situation_buyer_2_income_value_check", nil, self), Form::Sales::Pages::Buyer2LiveInProperty.new(nil, nil, self), Form::Sales::Pages::NumberOfOthersInProperty.new(nil, nil, self), Form::Sales::Pages::PersonKnown.new("person_2_known", nil, self, person_index: 2), @@ -68,6 +70,17 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection Form::Sales::Pages::RetirementValueCheck.new("gender_5_retirement_value_check", nil, self, person_index: 5), Form::Sales::Pages::PersonWorkingSituation.new("person_5_working_situation", nil, self, person_index: 5), Form::Sales::Pages::RetirementValueCheck.new("working_situation_5_retirement_value_check", nil, self, person_index: 5), - ] + ].flatten.compact + end + + def ethnic_pages_for_buyer_2 + if form.start_date.year >= 2023 + [Form::Sales::Pages::Buyer2EthnicGroup.new(nil, nil, self), + Form::Sales::Pages::Buyer2EthnicBackgroundBlack.new(nil, nil, self), + Form::Sales::Pages::Buyer2EthnicBackgroundAsian.new(nil, nil, self), + Form::Sales::Pages::Buyer2EthnicBackgroundArab.new(nil, nil, self), + Form::Sales::Pages::Buyer2EthnicBackgroundMixed.new(nil, nil, self), + Form::Sales::Pages::Buyer2EthnicBackgroundWhite.new(nil, nil, self)] + end end end diff --git a/app/models/form/sales/subsections/income_benefits_and_savings.rb b/app/models/form/sales/subsections/income_benefits_and_savings.rb index fe95bfd8c..7642e84bd 100644 --- a/app/models/form/sales/subsections/income_benefits_and_savings.rb +++ b/app/models/form/sales/subsections/income_benefits_and_savings.rb @@ -15,6 +15,7 @@ class Form::Sales::Subsections::IncomeBenefitsAndSavings < ::Form::Subsection Form::Sales::Pages::MortgageValueCheck.new("buyer_1_mortgage_value_check", nil, self, 1), Form::Sales::Pages::Buyer2Income.new(nil, nil, self), Form::Sales::Pages::MortgageValueCheck.new("buyer_2_income_mortgage_value_check", nil, self, 2), + Form::Sales::Pages::Buyer2IncomeValueCheck.new("buyer_2_income_value_check", nil, self), Form::Sales::Pages::Buyer2Mortgage.new(nil, nil, self), Form::Sales::Pages::MortgageValueCheck.new("buyer_2_mortgage_value_check", nil, self, 2), Form::Sales::Pages::HousingBenefits.new(nil, nil, self), diff --git a/app/models/form_handler.rb b/app/models/form_handler.rb index 8f34288b3..4ba300552 100644 --- a/app/models/form_handler.rb +++ b/app/models/form_handler.rb @@ -35,8 +35,8 @@ class FormHandler def sales_forms { "current_sales" => Form.new(nil, current_collection_start_year, SALES_SECTIONS, "sales"), - "previous_sales" => Form.new(nil, current_collection_start_year - 1, SALES_SECTIONS, "sales"), - "next_sales" => Form.new(nil, current_collection_start_year + 1, SALES_SECTIONS, "sales"), + "previous_sales" => Form.new(nil, previous_collection_start_year, SALES_SECTIONS, "sales"), + "next_sales" => Form.new(nil, next_collection_start_year, SALES_SECTIONS, "sales"), } end @@ -52,10 +52,10 @@ class FormHandler end if forms["previous_lettings"].blank? && current_collection_start_year >= 2022 - forms["previous_lettings"] = Form.new(nil, current_collection_start_year - 1, LETTINGS_SECTIONS, "lettings") + forms["previous_lettings"] = Form.new(nil, previous_collection_start_year, LETTINGS_SECTIONS, "lettings") end forms["current_lettings"] = Form.new(nil, current_collection_start_year, LETTINGS_SECTIONS, "lettings") if forms["current_lettings"].blank? - forms["next_lettings"] = Form.new(nil, current_collection_start_year + 1, LETTINGS_SECTIONS, "lettings") if forms["next_lettings"].blank? + forms["next_lettings"] = Form.new(nil, next_collection_start_year, LETTINGS_SECTIONS, "lettings") if forms["next_lettings"].blank? forms end diff --git a/app/models/lettings_log.rb b/app/models/lettings_log.rb index 66f622270..02236f70b 100644 --- a/app/models/lettings_log.rb +++ b/app/models/lettings_log.rb @@ -70,6 +70,14 @@ class LettingsLog < Log collection_start_year end + def lettings? + true + end + + def sales? + false + end + def form_name return unless startdate @@ -481,10 +489,6 @@ class LettingsLog < Log location.type_of_unit_before_type_cast if location end - def lettings? - true - end - def rent_type_detail form.get_question("rent_type", self)&.label_from_value(rent_type) end diff --git a/app/models/location.rb b/app/models/location.rb index 36a134f81..3798c57e8 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -366,6 +366,12 @@ class Location < ApplicationRecord enum type_of_unit: TYPE_OF_UNIT + def self.find_by_id_on_mulitple_fields(id) + return if id.nil? + + where(id:).or(where(old_visible_id: id)).first + end + def postcode=(postcode) if postcode super UKPostcode.parse(postcode).to_s diff --git a/app/models/log.rb b/app/models/log.rb index 1b43fd1cb..d8aa9e236 100644 --- a/app/models/log.rb +++ b/app/models/log.rb @@ -43,6 +43,10 @@ class Log < ApplicationRecord false end + def sales? + false + end + def ethnic_refused? ethnic_group == 17 end @@ -143,4 +147,8 @@ private self[is_inferred_key] = false self[postcode_key] = nil end + + def format_as_currency(num_string) + ActionController::Base.helpers.number_to_currency(num_string, unit: "£") + end end diff --git a/app/models/organisation.rb b/app/models/organisation.rb index 89d7d1f34..32d3540d1 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -17,8 +17,21 @@ class Organisation < ApplicationRecord has_many :managing_agent_relationships, foreign_key: :parent_organisation_id, class_name: "OrganisationRelationship" has_many :managing_agents, through: :managing_agent_relationships, source: :child_organisation + def affiliated_stock_owners + ids = [] + + if holds_own_stock? && persisted? + ids << id + end + + ids.concat(stock_owners.pluck(:id)) + + Organisation.where(id: ids) + end + scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") } scope :search_by, ->(param) { search_by_name(param) } + has_paper_trail auto_strip_attributes :name @@ -35,6 +48,20 @@ class Organisation < ApplicationRecord validates :name, presence: { message: I18n.t("validations.organisation.name_missing") } validates :provider_type, presence: { message: I18n.t("validations.organisation.provider_type_missing") } + def self.find_by_id_on_mulitple_fields(id) + return if id.nil? + + if id.start_with?("ORG") + where(id: id[3..]).first + else + where(old_visible_id: id).first + end + end + + def can_be_managed_by?(organisation:) + organisation == self || managing_agents.include?(organisation) + end + def lettings_logs LettingsLog.filter_by_organisation(self) end diff --git a/app/models/sales_log.rb b/app/models/sales_log.rb index 463381559..447f37597 100644 --- a/app/models/sales_log.rb +++ b/app/models/sales_log.rb @@ -38,6 +38,14 @@ class SalesLog < Log OPTIONAL_FIELDS = %w[saledate_check purchid monthly_charges_value_check old_persons_shared_ownership_value_check].freeze RETIREMENT_AGES = { "M" => 65, "F" => 60, "X" => 65 }.freeze + def lettings? + false + end + + def sales? + true + end + def startdate saledate end @@ -116,6 +124,10 @@ class SalesLog < Log la && LONDON_BOROUGHS.include?(la) end + def property_not_in_london? + !london_property? + end + def income1_used_for_mortgage? inc1mort == 1 end @@ -223,6 +235,10 @@ class SalesLog < Log type == 24 end + def is_bedsit? + proptype == 2 + end + def shared_ownership_scheme? ownershipsch == 1 end @@ -244,4 +260,18 @@ class SalesLog < Log def purchase_price_soft_max LaSaleRange.find_by(start_year: collection_start_year, la:, bedrooms: beds).soft_max end + + def income_soft_min_for_ecstat(ecstat_field) + economic_status_code = public_send(ecstat_field) + + return unless ALLOWED_INCOME_RANGES_SALES + + soft_min = ALLOWED_INCOME_RANGES_SALES[economic_status_code]&.soft_min + format_as_currency(soft_min) + end + + def field_formatted_as_currency(field_name) + field_value = public_send(field_name) + format_as_currency(field_value) + end end diff --git a/app/models/scheme.rb b/app/models/scheme.rb index f8cf6bf63..ca3624e3a 100644 --- a/app/models/scheme.rb +++ b/app/models/scheme.rb @@ -110,6 +110,16 @@ class Scheme < ApplicationRecord enum arrangement_type: ARRANGEMENT_TYPE, _suffix: true + def self.find_by_id_on_mulitple_fields(id) + return if id.nil? + + if id.start_with?("S") + where(id: id[1..]).first + else + where(old_visible_id: id).first + end + end + def id_to_display "S#{id}" end diff --git a/app/models/validations/sales/financial_validations.rb b/app/models/validations/sales/financial_validations.rb index df7dc8df5..21f3743ca 100644 --- a/app/models/validations/sales/financial_validations.rb +++ b/app/models/validations/sales/financial_validations.rb @@ -3,20 +3,36 @@ module Validations::Sales::FinancialValidations # or 'validate_' to run on submit as well def validate_income1(record) - if record.ecstat1 && record.income1 && record.la && record.ownershipsch == 1 - if record.london_property? - record.errors.add :income1, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 - record.errors.add :ecstat1, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 - record.errors.add :ownershipsch, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 - record.errors.add :la, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 - record.errors.add :postcode_full, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 - elsif record.income1 > 80_000 - record.errors.add :income1, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) - record.errors.add :ecstat1, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) - record.errors.add :ownershipsch, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) - record.errors.add :la, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) if record.income1 > 80_000 - record.errors.add :postcode_full, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) if record.income1 > 80_000 - end + return unless record.income1 && record.la && record.shared_ownership_scheme? + + relevant_fields = %i[income1 ownershipsch la postcode_full] + if record.london_property? && record.income1 > 90_000 + relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.over_hard_max_for_london") } + elsif record.property_not_in_london? && record.income1 > 80_000 + relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.over_hard_max_for_outside_london") } + end + end + + def validate_income2(record) + return unless record.income2 && record.la && record.shared_ownership_scheme? + + relevant_fields = %i[income2 ownershipsch la postcode_full] + if record.london_property? && record.income2 > 90_000 + relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.over_hard_max_for_london") } + elsif record.property_not_in_london? && record.income2 > 80_000 + relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.over_hard_max_for_outside_london") } + end + end + + def validate_combined_income(record) + return unless record.income1 && record.income2 && record.la && record.shared_ownership_scheme? + + combined_income = record.income1 + record.income2 + relevant_fields = %i[income1 income2 ownershipsch la postcode_full] + if record.london_property? && combined_income > 90_000 + relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.combined_over_hard_max_for_london") } + elsif record.property_not_in_london? && combined_income > 80_000 + relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.combined_over_hard_max_for_outside_london") } end end @@ -36,6 +52,15 @@ module Validations::Sales::FinancialValidations end end + def validate_child_income(record) + return unless record.income2 && record.ecstat2 + + if record.income2.positive? && is_economic_status_child?(record.ecstat2) + record.errors.add :ecstat2, I18n.t("validations.financial.income.child_has_income") + record.errors.add :income2, I18n.t("validations.financial.income.child_has_income") + end + end + def validate_percentage_owned_not_too_much_if_older_person(record) return unless record.old_persons_shared_ownership? && record.stairowned @@ -44,4 +69,14 @@ module Validations::Sales::FinancialValidations record.errors.add :type, I18n.t("validations.financial.staircasing.older_person_percentage_owned_maximum_75") end end + +private + + def is_relationship_child?(relationship) + relationship == "C" + end + + def is_economic_status_child?(economic_status) + economic_status == 9 + end end diff --git a/app/models/validations/sales/property_validations.rb b/app/models/validations/sales/property_validations.rb index 879e37ff1..83f86ec8f 100644 --- a/app/models/validations/sales/property_validations.rb +++ b/app/models/validations/sales/property_validations.rb @@ -7,4 +7,13 @@ module Validations::Sales::PropertyValidations record.errors.add :ppostcode_full, I18n.t("validations.property.postcode.must_match_previous") end end + + def validate_bedsit_number_of_beds(record) + return unless record.proptype.present? && record.beds.present? + + if record.is_bedsit? && record.beds > 1 + record.errors.add :proptype, I18n.t("validations.property.proptype.bedsits_have_max_one_bedroom") + record.errors.add :beds, I18n.t("validations.property.beds.bedsits_have_max_one_bedroom") + end + end end diff --git a/app/models/validations/sales/setup_validations.rb b/app/models/validations/sales/setup_validations.rb index dadf85650..e7090aabf 100644 --- a/app/models/validations/sales/setup_validations.rb +++ b/app/models/validations/sales/setup_validations.rb @@ -1,11 +1,45 @@ module Validations::Sales::SetupValidations include Validations::SharedValidations + include CollectionTimeHelper def validate_saledate(record) return unless record.saledate && date_valid?("saledate", record) - unless record.saledate.between?(Time.zone.local(2022, 4, 1), Time.zone.local(2023, 3, 31)) || !FeatureToggle.saledate_collection_window_validation_enabled? - record.errors.add :saledate, I18n.t("validations.setup.saledate.financial_year") + unless record.saledate.between?(active_collection_start_date, current_collection_end_date) || !FeatureToggle.saledate_collection_window_validation_enabled? + record.errors.add :saledate, validation_error_message + end + end + +private + + def active_collection_start_date + if FormHandler.instance.sales_in_crossover_period? + previous_collection_start_date + else + current_collection_start_date + end + end + + def validation_error_message + current_end_year_long = current_collection_end_date.strftime("#{current_collection_end_date.day.ordinalize} %B %Y") + + if FormHandler.instance.sales_in_crossover_period? + I18n.t( + "validations.setup.saledate.previous_and_current_financial_year", + previous_start_year_short: previous_collection_start_date.strftime("%y"), + previous_end_year_short: previous_collection_end_date.strftime("%y"), + previous_start_year_long: previous_collection_start_date.strftime("#{previous_collection_start_date.day.ordinalize} %B %Y"), + current_end_year_short: current_collection_end_date.strftime("%y"), + current_end_year_long:, + ) + else + I18n.t( + "validations.setup.saledate.current_financial_year", + current_start_year_short: current_collection_start_date.strftime("%y"), + current_end_year_short: current_collection_end_date.strftime("%y"), + current_start_year_long: current_collection_start_date.strftime("#{current_collection_start_date.day.ordinalize} %B %Y"), + current_end_year_long:, + ) end end end diff --git a/app/models/validations/sales/soft_validations.rb b/app/models/validations/sales/soft_validations.rb index a7b9fd4c0..c1704d948 100644 --- a/app/models/validations/sales/soft_validations.rb +++ b/app/models/validations/sales/soft_validations.rb @@ -1,5 +1,5 @@ module Validations::Sales::SoftValidations - ALLOWED_INCOME_RANGES = { + ALLOWED_INCOME_RANGES_SALES = { 1 => OpenStruct.new(soft_min: 5000), 2 => OpenStruct.new(soft_min: 1500), 3 => OpenStruct.new(soft_min: 1000), @@ -8,9 +8,15 @@ module Validations::Sales::SoftValidations }.freeze def income1_under_soft_min? - return false unless ecstat1 && income1 && ALLOWED_INCOME_RANGES[ecstat1] + return false unless ecstat1 && income1 && ALLOWED_INCOME_RANGES_SALES[ecstat1] - income1 < ALLOWED_INCOME_RANGES[ecstat1][:soft_min] + income1 < ALLOWED_INCOME_RANGES_SALES[ecstat1][:soft_min] + end + + def income2_under_soft_min? + return false unless ecstat2 && income2 && ALLOWED_INCOME_RANGES_SALES[ecstat2] + + income2 < ALLOWED_INCOME_RANGES_SALES[ecstat2][:soft_min] end def staircase_bought_above_fifty? diff --git a/app/services/bulk_upload/lettings/log_creator.rb b/app/services/bulk_upload/lettings/log_creator.rb index 9dd4a2f66..625d53f43 100644 --- a/app/services/bulk_upload/lettings/log_creator.rb +++ b/app/services/bulk_upload/lettings/log_creator.rb @@ -10,6 +10,8 @@ class BulkUpload::Lettings::LogCreator row_parsers.each do |row_parser| row_parser.valid? + next if row_parser.blank_row? + row_parser.log.blank_invalid_non_setup_fields! row_parser.log.bulk_upload = bulk_upload diff --git a/app/services/bulk_upload/lettings/row_parser.rb b/app/services/bulk_upload/lettings/row_parser.rb index cee5fa319..dcba8be28 100644 --- a/app/services/bulk_upload/lettings/row_parser.rb +++ b/app/services/bulk_upload/lettings/row_parser.rb @@ -3,11 +3,12 @@ class BulkUpload::Lettings::RowParser include ActiveModel::Attributes attribute :bulk_upload + attribute :block_log_creation, :boolean, default: -> { false } attribute :field_1, :integer attribute :field_2 attribute :field_3 - attribute :field_4, :integer + attribute :field_4, :string attribute :field_5, :integer attribute :field_6 attribute :field_7, :string @@ -114,9 +115,9 @@ class BulkUpload::Lettings::RowParser attribute :field_108, :string attribute :field_109, :string attribute :field_110 - attribute :field_111, :integer + attribute :field_111, :string attribute :field_112, :string - attribute :field_113, :integer + attribute :field_113, :string attribute :field_114, :integer attribute :field_115 attribute :field_116, :integer @@ -142,17 +143,38 @@ class BulkUpload::Lettings::RowParser validates :field_1, presence: { message: I18n.t("validations.not_answered", question: "letting type") }, inclusion: { in: (1..12).to_a, message: I18n.t("validations.invalid_option", question: "letting type") } validates :field_4, presence: { if: proc { [2, 4, 6, 8, 10, 12].include?(field_1) } } + validates :field_98, format: { with: /\A\d{2}\z/, message: I18n.t("validations.setup.startdate.year_not_two_digits") } validate :validate_data_types validate :validate_nulls validate :validate_relevant_collection_window validate :validate_la_with_local_housing_referral - validate :validate_cannot_be_la_referral_if_general_needs - validate :leaving_reason_for_renewal + validate :validate_cannot_be_la_referral_if_general_needs_and_la + validate :validate_leaving_reason_for_renewal + validate :validate_lettings_type_matches_bulk_upload + validate :validate_only_one_housing_needs_type + validate :validate_no_disabled_needs_conjunction + validate :validate_dont_know_disabled_needs_conjunction + validate :validate_no_and_dont_know_disabled_needs_conjunction + + validate :validate_owning_org_permitted + validate :validate_owning_org_owns_stock + validate :validate_owning_org_exists + + validate :validate_managing_org_related + validate :validate_managing_org_exists + + validate :validate_scheme_related + validate :validate_scheme_exists + + validate :validate_location_related + validate :validate_location_exists def valid? errors.clear + return true if blank_row? + super log.valid? @@ -165,14 +187,139 @@ class BulkUpload::Lettings::RowParser errors.blank? end + def blank_row? + attribute_set.to_hash.reject { |k, _| %w[bulk_upload block_log_creation].include?(k) }.values.compact.empty? + end + def log @log ||= LettingsLog.new(attributes_for_log) end + def block_log_creation! + self.block_log_creation = true + end + + def block_log_creation? + block_log_creation + end + private - def validate_cannot_be_la_referral_if_general_needs - if field_78 == 4 && bulk_upload.general_needs? + def validate_location_related + return if scheme.blank? || location.blank? + + unless location.scheme == scheme + block_log_creation! + errors.add(:field_5, "Scheme code must relate to a location that is owned by owning organisation or managing organisation") + end + end + + def location + return if scheme.nil? + + @location ||= scheme.locations.find_by_id_on_mulitple_fields(field_5) + end + + def validate_location_exists + if scheme && field_5.present? && location.nil? + errors.add(:field_5, "Location could be found with provided scheme code") + end + end + + def validate_scheme_related + return unless field_4.present? && scheme.present? + + owned_by_owning_org = owning_organisation && scheme.owning_organisation == owning_organisation + owned_by_managing_org = managing_organisation && scheme.owning_organisation == managing_organisation + + unless owned_by_owning_org || owned_by_managing_org + block_log_creation! + errors.add(:field_4, "This management group code does not belong to your organisation, or any of your stock owners / managing agents") + end + end + + def validate_scheme_exists + if field_4.present? && scheme.nil? + errors.add(:field_4, "The management group code is not correct") + end + end + + def validate_managing_org_related + if owning_organisation && managing_organisation && !owning_organisation.can_be_managed_by?(organisation: managing_organisation) + block_log_creation! + errors.add(:field_113, "This managing organisation does not have a relationship with the owning organisation") + end + end + + def validate_managing_org_exists + if managing_organisation.nil? + errors.delete(:field_113) + errors.add(:field_113, "The managing organisation code is incorrect") + end + end + + def validate_owning_org_owns_stock + if owning_organisation && !owning_organisation.holds_own_stock? + block_log_creation! + errors.delete(:field_111) + errors.add(:field_111, "The owning organisation code provided is for an organisation that does not own stock") + end + end + + def validate_owning_org_exists + if owning_organisation.nil? + errors.delete(:field_111) + errors.add(:field_111, "The owning organisation code is incorrect") + end + end + + def validate_owning_org_permitted + if owning_organisation && !bulk_upload.user.organisation.affiliated_stock_owners.include?(owning_organisation) + block_log_creation! + errors.delete(:field_111) + errors.add(:field_111, "You do not have permission to add logs for this owning organisation") + end + end + + def validate_no_and_dont_know_disabled_needs_conjunction + if field_59 == 1 && field_60 == 1 + errors.add(:field_59, I18n.t("validations.household.housingneeds.no_and_dont_know_disabled_needs_conjunction")) + errors.add(:field_60, I18n.t("validations.household.housingneeds.no_and_dont_know_disabled_needs_conjunction")) + end + end + + def validate_dont_know_disabled_needs_conjunction + if field_60 == 1 && [field_55, field_56, field_57, field_58].compact.count.positive? + errors.add(:field_60, I18n.t("validations.household.housingneeds.dont_know_disabled_needs_conjunction")) + end + end + + def validate_no_disabled_needs_conjunction + if field_59 == 1 && [field_55, field_56, field_57, field_58].compact.count.positive? + errors.add(:field_59, I18n.t("validations.household.housingneeds.no_disabled_needs_conjunction")) + end + end + + def validate_only_one_housing_needs_type + if [field_55, field_56, field_57].compact.count.positive? + errors.add(:field_55, I18n.t("validations.household.housingneeds_type.only_one_option_permitted")) + errors.add(:field_56, I18n.t("validations.household.housingneeds_type.only_one_option_permitted")) + errors.add(:field_57, I18n.t("validations.household.housingneeds_type.only_one_option_permitted")) + end + end + + def validate_lettings_type_matches_bulk_upload + if [1, 3, 5, 7, 9, 11].include?(field_1) && !bulk_upload.general_needs? + errors.add(:field_1, I18n.t("validations.setup.lettype.supported_housing_mismatch")) + end + + if [2, 4, 6, 8, 10, 12].include?(field_1) && !bulk_upload.supported_housing? + errors.add(:field_1, I18n.t("validations.setup.lettype.general_needs_mismatch")) + end + end + + def validate_cannot_be_la_referral_if_general_needs_and_la + if field_78 == 4 && bulk_upload.general_needs? && owning_organisation && owning_organisation.la? errors.add :field_78, I18n.t("validations.household.referral.la_general_needs.prp_referred_by_la") end end @@ -183,7 +330,7 @@ private end end - def leaving_reason_for_renewal + def validate_leaving_reason_for_renewal if field_134 == 1 && ![40, 42].include?(field_52) errors.add(:field_52, I18n.t("validations.household.reason.renewal_reason_needed")) end @@ -399,6 +546,8 @@ private def startdate Date.new(field_98 + 2000, field_97, field_96) if field_98.present? && field_97.present? && field_96.present? + rescue Date::Error + Date.new end def renttype @@ -435,15 +584,19 @@ private end def owning_organisation - Organisation.find_by(old_visible_id: field_111) + Organisation.find_by_id_on_mulitple_fields(field_111) end def owning_organisation_id owning_organisation&.id end + def managing_organisation + Organisation.find_by_id_on_mulitple_fields(field_113) + end + def managing_organisation_id - Organisation.find_by(old_visible_id: field_113)&.id + managing_organisation&.id end def attributes_for_log @@ -458,6 +611,7 @@ private attributes["managing_organisation_id"] = managing_organisation_id attributes["renewal"] = renewal attributes["scheme"] = scheme + attributes["location"] = location attributes["created_by"] = bulk_upload.user attributes["needstype"] = bulk_upload.needstype attributes["rent_type"] = rent_type @@ -535,6 +689,8 @@ private attributes["preg_occ"] = field_47 attributes["housingneeds"] = housingneeds + attributes["housingneeds_type"] = housingneeds_type + attributes["housingneeds_other"] = housingneeds_other attributes["illness"] = field_118 @@ -791,7 +947,7 @@ private def housingneeds if field_59 == 1 - 1 + 2 elsif field_60 == 1 3 else @@ -799,6 +955,20 @@ private end end + def housingneeds_type + if field_55 == 1 + 0 + elsif field_56 == 1 + 1 + elsif field_57 == 1 + 2 + end + end + + def housingneeds_other + return 1 if field_58 == 1 + end + def ethnic_group_from_ethnic return nil if field_43.blank? @@ -819,6 +989,6 @@ private end def scheme - @scheme ||= Scheme.find_by(old_visible_id: field_4) + @scheme ||= Scheme.find_by_id_on_mulitple_fields(field_4) end end diff --git a/app/services/bulk_upload/lettings/validator.rb b/app/services/bulk_upload/lettings/validator.rb index 6f37c0f3a..992f06196 100644 --- a/app/services/bulk_upload/lettings/validator.rb +++ b/app/services/bulk_upload/lettings/validator.rb @@ -176,6 +176,7 @@ class BulkUpload::Lettings::Validator def create_logs? return false if any_setup_sections_incomplete? return false if over_column_error_threshold? + return false if row_parsers.any?(&:block_log_creation?) row_parsers.all? { |row_parser| row_parser.log.valid? } end diff --git a/app/services/bulk_upload/processor.rb b/app/services/bulk_upload/processor.rb index d69f76243..bbf7e6cd1 100644 --- a/app/services/bulk_upload/processor.rb +++ b/app/services/bulk_upload/processor.rb @@ -13,6 +13,7 @@ class BulkUpload::Processor validator.call create_logs if validator.create_logs? + send_correct_and_upload_again_mail unless validator.create_logs? send_fix_errors_mail if created_logs_but_incompleted? send_success_mail if created_logs_and_all_completed? @@ -25,6 +26,10 @@ class BulkUpload::Processor private + def send_correct_and_upload_again_mail + BulkUploadMailer.send_correct_and_upload_again_mail(bulk_upload:).deliver_later + end + def send_fix_errors_mail BulkUploadMailer.send_bulk_upload_with_errors_mail(bulk_upload:).deliver_later end diff --git a/app/services/exports/lettings_log_export_constants.rb b/app/services/exports/lettings_log_export_constants.rb index 9184efb25..3ae7d71fa 100644 --- a/app/services/exports/lettings_log_export_constants.rb +++ b/app/services/exports/lettings_log_export_constants.rb @@ -7,13 +7,6 @@ module Exports::LettingsLogExportConstants csv: 2, }.freeze - QUARTERS = { - 0 => "jan_mar", - 1 => "apr_jun", - 2 => "jul_sep", - 3 => "oct_dec", - }.freeze - EXPORT_FIELDS = Set[ "armedforces", "beds", @@ -30,7 +23,7 @@ module Exports::LettingsLogExportConstants "confidential", "earnings", "ethnic", - "form", + "formid", "has_benefits", "hb", "hbrentshortfall", diff --git a/app/services/exports/lettings_log_export_service.rb b/app/services/exports/lettings_log_export_service.rb index 6cc5c1f52..197e0ed4b 100644 --- a/app/services/exports/lettings_log_export_service.rb +++ b/app/services/exports/lettings_log_export_service.rb @@ -1,6 +1,7 @@ module Exports class LettingsLogExportService include Exports::LettingsLogExportConstants + include CollectionTimeHelper def initialize(storage_service, logger = Rails.logger) @storage_service = storage_service @@ -66,11 +67,11 @@ module Exports return unless lettings_log.startdate collection_start = lettings_log.collection_start_year - month = lettings_log.startdate.month - quarter = QUARTERS[(month - 1) / 3] + start_month = collection_start_date(lettings_log.startdate).strftime("%b") + end_month = collection_end_date(lettings_log.startdate).strftime("%b") base_number_str = "f#{base_number.to_s.rjust(4, '0')}" increment_str = "inc#{increment.to_s.rjust(4, '0')}" - "core_#{collection_start}_#{collection_start + 1}_#{quarter}_#{base_number_str}_#{increment_str}" + "core_#{collection_start}_#{collection_start + 1}_#{start_month}_#{end_month}_#{base_number_str}_#{increment_str}".downcase end def write_export_archive(export, lettings_logs) @@ -152,7 +153,7 @@ module Exports def apply_cds_transformation(lettings_log, export_mode) attribute_hash = lettings_log.attributes_before_type_cast - attribute_hash["form"] = attribute_hash["old_form_id"] || (attribute_hash["id"] + LOG_ID_OFFSET) + attribute_hash["formid"] = attribute_hash["old_form_id"] || (attribute_hash["id"] + LOG_ID_OFFSET) # We can't have a variable number of columns in CSV unless export_mode == EXPORT_MODE[:csv] @@ -214,7 +215,7 @@ module Exports attribute_hash["reghome"] = scheme.registered_under_care_act_before_type_cast attribute_hash["schtype"] = scheme.scheme_type_before_type_cast attribute_hash["support"] = scheme.support_type_before_type_cast - attribute_hash["units_scheme"] = scheme.locations.map(&:units).sum + attribute_hash["units_scheme"] = scheme.locations.map(&:units).compact.sum end def add_location_fields!(location, attribute_hash) diff --git a/app/views/content/privacy_notice.md b/app/views/content/privacy_notice.md index f80a5e4ca..b1188eb5a 100644 --- a/app/views/content/privacy_notice.md +++ b/app/views/content/privacy_notice.md @@ -1,46 +1,47 @@ -## How are we using your information? +## How do we use your information? -If your household has entered a new social housing tenancy, social housing providers will share your personal information with the Department for Levelling Up, Housing & Communities (DLUHC) for research and statistical purposes. +If your household enters a new social housing tenancy or purchases a social housing property, social housing providers will share your personal information with the Department for Levelling Up, Housing & Communities (DLUHC) for research and statistical purposes only. -## How is this information provided? +## How do we get this information? -The information is provided via ‘<%= t('service_name') %>’, a service funded and managed by DLUHC. It collects information on the tenants or buyers, tenancy or sale, and the dwelling itself. Some of this information is personal and sensitive, so DLUHC is responsible for ensuring that all data is processed in line with data protection legislation. +The information is provided via ‘<%= t('service_name') %>’, a service funded and managed by DLUHC. It collects information on the tenants or residents, tenancy or sale, and the dwelling itself. Some of this data is personal and sensitive, so DLUHC is responsible for ensuring it’s processed in line with data protection legislation. -## Why are we sharing this information? -Information collected using this service is shared with other government departments and agencies. Data is shared with the Greater London Authority and the Regulator of Social Housing. Data providers can also access data for their organisations via the online service. Data is only shared for research and statistical purposes. +## Why do we share this information? + +Information collected via CORE is shared with other government departments and agencies. It’s shared with the Greater London Authority and the Regulator of Social Housing. Data providers can also access data for their organisations via CORE. Data is only shared for research and statistical purposes. ## How does this affect you? -It will not affect your benefits, services or any treatments you receive. The information shared is anonymous and handled in accordance with the law. We are collecting and sharing your information to help us better understand the social housing market and inform social housing policy. +Information sharing will not affect your benefits, services or any treatments you receive. It’s anonymous and handled in accordance with the law. We collect and share your information to help us better understand the social housing market and inform social housing policy. -## If you want to know more +## To find out more… -Social housing lettings and sales data is collected on behalf of DLUHC for research and statistical purposes only. Data providers do not require the consent of tenants to provide the information, but tenants have the right to know how and for what purpose data is being collected, held and used. +Social housing lettings and sales data is collected on DLUHC’s behalf. Data providers do not require the tenant or buyer’s consent to provide this information, but tenants and buyers have the right to know how and for what purpose data is being collected, held and used. -The processing must have a lawful basis. In this case the processing is necessary for the performance of a task carried out in the public interest to meet a function of the Crown, a Minister of the Crown, or a government department. +Data processing must have a lawful basis. In this case it’s necessary for a task carried out in the public interest meeting a function of the Crown, a Minister of the Crown, or government department. -You have the right to object and you have the right to obtain confirmation that your data is being processed, and to access your personal data. You also have the right to have any incorrect personal data corrected. +You have the right to object, and obtain confirmation that your data is being processed, as well as access your personal data, and have any incorrect personal data corrected. -The information collected via this service relates to your tenancy, the dwelling you are living in or buying, and your household. Some of the information may have been provided by you as a tenant when signing the new tenancy or buying your property. Other information has been gathered from the housing management systems of social housing providers. +Information collected via CORE relates to your tenancy, the dwelling you are living in or buying, and your household. Some information may have been provided by you (as a tenant or buyer) when signing the new tenancy or buying your property. Other information has been gathered from the housing management systems of social housing providers. -Data collected will be held for as long as necessary for research and statistical purposes. When no longer needed, data will be deleted in a safe manner. We are aware that some of the data collected is particularly sensitive. For example: +Collected data will be held for as long as necessary for research and statistical purposes. When no longer needed, data will be deleted in a safe manner. We’re aware some collected data is particularly sensitive. For example: * ethnic group -* if previous tenure is a hospital or prison or approved probation hostel support +* if previous tenure is a hospital, prison or approved probation hostel support * if household left last settled home because discharged from prison, a long stay hospital or other institution -* if source of referral is probation or prison, youth offending team, community mental health team or health service +* if referral source is probation or prison, youth offending or community mental health team, or health service -All the information collected via this service is treated in accordance with data protection requirements and guidelines. +DLUHC publishes data annually, in aggregate form, as part of a report and complementary tables. -Data is published by DLUHC in aggregate form on an annual basis as part of a report and complementary tables. +* For annual lettings data, visit: [www.gov.uk/government/collections/rents-lettings-and-tenancies](www.gov.uk/government/collections/rents-lettings-and-tenancies) -You can visit to access the annual publications on lettings. Or visit to view the publications on sales. +* For annual sales data, visit: [www.gov.uk/government/collections/social-housing-sales-including-right-to-buy-and-transfers](www.gov.uk/government/collections/social-housing-sales-including-right-to-buy-and-transfers) -The detail level data is anonymised and protected to minimise the risk of identification and held with the UK Data Archive for research purposes. +Detail-level data is anonymised and protected, minimising identification risk. It's held with the UK Data Archive. -## Making a complaint +## Complaints -If you are unhappy with any aspect of this privacy notice, or how your personal information is being processed, contact the Department Data Protection Officer at: +If you’re unhappy with any privacy notice aspect, or how we process your information, contact the Department Data Protection Officer: -If you are still not happy, you have the right to lodge a complaint with the Information Commissioner’s Office (ICO) at [ico.org.uk/concern](https://ico.org.uk/concern). +You also have the right to complain to the Information Commissioner’s Office (ICO): [www.ico.org.uk/concern](www.ico.org.uk/concern) diff --git a/app/views/form/review.html.erb b/app/views/form/review.html.erb index a95b577bd..bc46c89d8 100644 --- a/app/views/form/review.html.erb +++ b/app/views/form/review.html.erb @@ -1,9 +1,18 @@ -<% content_for :title, "Review lettings log" %> -<% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: { - "Logs" => "/logs", - "Log #{@log.id}" => "/lettings-logs/#{@log.id}", - "Review lettings log" => "", -}) %> +<% if @log.sales? %> + <% content_for :title, "Review sales log" %> + <% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: { + "Logs" => "/logs", + "Log #{@log.id}" => "/sales-logs/#{@log.id}", + "Review sales log" => "", + }) %> +<% else %> + <% content_for :title, "Review lettings log" %> + <% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: { + "Logs" => "/logs", + "Log #{@log.id}" => "/lettings-logs/#{@log.id}", + "Review lettings log" => "", + }) %> +<% end %>
@@ -16,17 +25,16 @@ <% @log.form.sections.map do |section| %>

<%= section.label %>

<% section.subsections.map do |subsection| %> -
-
-

<%= subsection.label %>

+ <% if total_applicable_questions(subsection, @log, current_user).any? %> +
+
+

<%= subsection.label %>

+
+
+ <%= render partial: "form/check_answers_summary_list", locals: { subsection: } %> +
-
- <%= render partial: "form/check_answers_summary_list", locals: { - subsection:, - lettings_log: @log, - } %> -
-
+ <% end %> <% end %> <% end %>
diff --git a/app/views/logs/_log_filters.erb b/app/views/logs/_log_filters.erb index 8fb4f2ba4..a1aa752c3 100644 --- a/app/views/logs/_log_filters.erb +++ b/app/views/logs/_log_filters.erb @@ -6,7 +6,6 @@
<%= form_with html: { method: :get } do |f| %> - <% years = { "2021": "2021/22", "2022": "2022/23" } %> <% all_or_yours = { "all": { label: "All" }, "yours": { label: "Yours" } } %> <% if bulk_upload_options(@bulk_upload).present? %> @@ -23,7 +22,7 @@ <%= render partial: "filters/checkbox_filter", locals: { f: f, - options: years, + options: collection_year_options, label: "Collection year", category: "years", } %> diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc index 69dad9f7a..c9d564782 100644 --- a/config/credentials.yml.enc +++ b/config/credentials.yml.enc @@ -1 +1 @@ -PWC9A1AmalyFv4A63MG9jCm2YtwE1Eyu1BJKodtbmq2nW7FD9svZZ3V4yC0JO9J8sLfCS6UrxQC4TOKKYWP5iYpOr5TKPi9MqNCVueLsoErN+nMAPhCJVpl8+eJ4BOvNYGlmle4bcwbAvHLq6IoANTBjktFh5/tgJS+IHBWK1FwPzK1eAhgQ1fE1jluWslee4iYesmh+ufHIMZAkoWuGJVky4i4uN5nKnwQfaPN5MUxBlvGiDH+s+yex4pcIaJ6hxYWAQRZXJRQVpZcf/agwU0Tk/S/fuDMm8zVeHCQkkmsCYzH5czB8b8IWYwyslqFBZNCix7YnbgwgYk8MUh7wPBuF8CFoPKVyteqic9HgUp9KY8kkt/RcWJ4zpv+Vwz3cre+iZ3S1bxFcSxXqO0MGRug2H9iwhAnBQDLl3vXLNNRYEL5LhNv0Z9Cy7at1fnYe1FcvF+3DR9kG/RAYzR8S2eEDdzBl797+DG81yhfkjP3/gfWxD+J+Mx0F4SDEOaGK5c/MqNTdiRRbnhzuRaQFMg6itoJbZybe+EOQScNft1QLqC4QwPd4Qevhj/A=--QffpsNB1u+1Lk/tD--spkCqBIGHl8g6HGwd89yCg== \ No newline at end of file +EZNV2LiNWzf52erbQ41Dz3Bh+2f3Uih8liEyhXp5XzHCLzAbmN6/IJqr7b9cTZiCiroFo4n/dFoG3yYrospp3frKsDXxF1K2/MTCJWjpgnn7wc+HiPQWG0W3HRtQCNkyyrHes0YKcYyDWIP6kztYv1I/Me3p0pGEx6t3CpSTg1v46eRnOlDWiUz3rVxPauwq9IYZ75gmnThqvg/Z8wcYsWLx0arago0SXtRPASCNj4uO/lbqTcAfyIXOTSiOlcAIjoPFRSQY7UqY0o2p8jRR/1L16SmGDsk8ijm+UygNmMexa3Khy5WcKctpQICakHs4NRjHNflqgXpXKL9dVBmNc9d7h+gbhbGJQ53Y0d+a35UbhPRMiv4SRH98FwB+WEsLCDdGSHvdmM6ArfOLljTrqrsmSRf0JfUrvzyVYmMCxjv4xgJwUS/TD5lQD1yPwkp2ss00kQJqzNmB7qwFhA8a3e2iNzV8qtAV/Nj+tMlr99Hb7vZZs98/38G2p5RAsE/5Xl9taKhc/ACnVc/bwJND4JWaBB7duCa08xVB8nkjlt5cCwMurzAcy1ZT+e8JepR+g6s8fpScMEWVJXE0hd8=--rZ41rY9TMXmiBUJw--QiLRVNVXZzTW446s7cec1g== \ No newline at end of file diff --git a/config/forms/2022_2023.json b/config/forms/2022_2023.json index ebd1bd594..e0ef19198 100644 --- a/config/forms/2022_2023.json +++ b/config/forms/2022_2023.json @@ -1,7 +1,7 @@ { "form_type": "lettings", "start_date": "2022-04-01T00:00:00.000+01:00", - "end_date": "2023-07-01T00:00:00.000+01:00", + "end_date": "2023-07-09T00:00:00.000+01:00", "unresolved_log_redirect_page_id": "tenancy_start_date", "sections": { "tenancy_and_property": { diff --git a/config/initializers/feature_toggle.rb b/config/initializers/feature_toggle.rb index 37f6aa653..dda281e10 100644 --- a/config/initializers/feature_toggle.rb +++ b/config/initializers/feature_toggle.rb @@ -1,14 +1,15 @@ class FeatureToggle - def self.startdate_two_week_validation_enabled? - Rails.env.production? || Rails.env.test? + # Disable check on preview apps to allow for testing of future forms + def self.saledate_collection_window_validation_enabled? + Rails.env.production? || Rails.env.test? || Rails.env.staging? end - def self.startdate_collection_window_validation_enabled? - Rails.env.production? || Rails.env.test? + def self.startdate_two_week_validation_enabled? + Rails.env.production? || Rails.env.test? || Rails.env.staging? end - def self.saledate_collection_window_validation_enabled? - Rails.env.production? || Rails.env.test? + def self.startdate_collection_window_validation_enabled? + Rails.env.production? || Rails.env.test? || Rails.env.staging? end def self.sales_log_enabled? @@ -42,4 +43,8 @@ class FeatureToggle def self.validate_valid_radio_options? !(Rails.env.production? || Rails.env.staging?) end + + def self.collection_2023_2024_year_enabled? + !Rails.env.production? + end end diff --git a/config/locales/en.yml b/config/locales/en.yml index a97863c30..8665c022a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -150,12 +150,17 @@ en: intermediate_rent_product_name: blank: "Enter name of other intermediate rent product" saledate: - financial_year: "Date must be from 22/23 financial year, which is between 1st April 2022 and 31st March 2023" + current_financial_year: + Enter a date within the %{current_start_year_short}/%{current_end_year_short} financial year, which is between %{current_start_year_long} and %{current_end_year_long} + previous_and_current_financial_year: + "Enter a date within the %{previous_start_year_short}/%{previous_end_year_short} or %{previous_end_year_short}/%{current_end_year_short} financial years, which is between %{previous_start_year_long} and %{current_end_year_long}" + startdate: later_than_14_days_after: "The tenancy start date must not be later than 14 days from today’s date" before_scheme_end_date: "The tenancy start date must be before the end date for this supported housing scheme" after_void_date: "Enter a tenancy start date that is after the void date" after_major_repair_date: "Enter a tenancy start date that is after the major repair date" + year_not_two_digits: Tenancy start year must be 2 digits location: deactivated: "The location %{postcode} was deactivated on %{date} and was not available on the day you entered." reactivating_soon: "The location %{postcode} is not available until %{date}. Select another location or edit the tenancy start date" @@ -170,6 +175,9 @@ en: invalid: "Please select owning organisation or managing organisation that you belong to" created_by: invalid: "Please select owning organisation or managing organisation that you belong to" + lettype: + general_needs_mismatch: Lettings type must be a general needs type because you selected general needs when uploading the file + supported_housing_mismatch: Lettings type must be a supported housing type because you selected supported housing when uploading the file property: mrcdate: @@ -199,6 +207,9 @@ en: beds: non_positive: "Number of bedrooms has to be greater than 0" over_max: "Number of bedrooms cannot be more than 12" + bedsits_have_max_one_bedroom: "Number of bedrooms must be 1 if the property is a bedsit" + proptype: + bedsits_have_max_one_bedroom: "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms" postcode: must_match_previous: "Buyer's last accommodation and discounted ownership postcodes must match" @@ -216,8 +227,12 @@ en: under_hard_min: "Net income cannot be less than £%{hard_min} per week given the tenant’s working situation" freq_missing: "Select how often the household receives income" earnings_missing: "Enter how much income the household has in total" - income1: - over_hard_max: "Income must be lower than £%{hard_max}" + income: + over_hard_max_for_london: "Income must not exceed £90,000 for properties within London local authorities" + over_hard_max_for_outside_london: "Income must not exceed £80,000 for properties outside London local authorities" + combined_over_hard_max_for_london: "Combined income must not exceed £90,000 for properties within London local authorities" + combined_over_hard_max_for_outside_london: "Combined income must not exceed £80,000 for properties outside London local authorities" + child_has_income: "Child's income must be £0" negative_currency: "Enter an amount above 0" rent: less_than_shortfall: "Enter an amount that is more than the shortfall in basic rent" @@ -341,6 +356,12 @@ en: must_be_child: "Answer must be ‘child’ if the person is aged 16-19 and a student" housingneeds_a: one_or_two_choices: "You can only select one option or ‘other disabled access needs’ plus ‘wheelchair-accessible housing’, ‘wheelchair access to essential rooms’ or ‘level access housing’" + housingneeds_type: + only_one_option_permitted: "Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected" + housingneeds: + no_disabled_needs_conjunction: "No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs" + dont_know_disabled_needs_conjunction: "Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs" + no_and_dont_know_disabled_needs_conjunction: "No disabled access needs and don’t know disabled access needs cannot be selected together" prevten: non_temp_accommodation: "Answer cannot be non-temporary accommodation as this is a re-let to a tenant who occupied the same property as temporary accommodation" over_20_foster_care: "Answer cannot be a children’s home or foster care as the lead tenant is 20 or older" @@ -454,6 +475,8 @@ en: message: "Net income is lower than expected based on the lead tenant’s working situation. Are you sure this is correct?" in_soft_max_range: message: "Net income is higher than expected based on the lead tenant’s working situation. Are you sure this is correct?" + income: + under_soft_min_for_economic_status: "You said income was %{income}, which is below this working situation's minimum (%{minimum})" rent: outside_range_title: "You told us the rent is %{brent}" min_hint_text: "The minimum rent expected for this type of property in this local authority is £%{soft_min_for_period}." @@ -546,4 +569,4 @@ en: one_argument: "This is based on the tenant’s work situation: %{ecstat1}" title_text: no_argument: "Some test text" - one_argument: "You said this: %{ecstat1}" + one_argument: "You said this: %{argument}" diff --git a/config/routes.rb b/config/routes.rb index be765c8c0..74b613989 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -171,6 +171,10 @@ Rails.application.routes.draw do resources :bulk_upload_sales_results, path: "bulk-upload-results", only: [:show] end + member do + get "review", to: "form#review" + end + FormHandler.instance.sales_forms.each do |_key, form| form.pages.map do |page| get page.id.to_s.dasherize, to: "form#show_page" diff --git a/config/sale_range_data/2023.csv b/config/sale_range_data/2023.csv new file mode 100644 index 000000000..ebe873c07 --- /dev/null +++ b/config/sale_range_data/2023.csv @@ -0,0 +1,1185 @@ +la_name,la,bedrooms,soft_min,soft_max +Adur,E07000223,1,126000,447000 +Adur,E07000223,2,220000,480000 +Adur,E07000223,3,312000,658000 +Adur,E07000223,4,383000,938000 +Amber Valley,E07000032,1,99000,306000 +Amber Valley,E07000032,2,99000,306000 +Amber Valley,E07000032,3,123000,407000 +Amber Valley,E07000032,4,236000,842000 +Arun,E07000224,1,102000,486000 +Arun,E07000224,2,182000,486000 +Arun,E07000224,3,248000,569000 +Arun,E07000224,4,351000,966000 +Ashfield,E07000170,1,77000,250000 +Ashfield,E07000170,2,87000,250000 +Ashfield,E07000170,3,103000,298000 +Ashfield,E07000170,4,198000,547000 +Ashford,E07000105,1,102000,435000 +Ashford,E07000105,2,105000,435000 +Ashford,E07000105,3,239000,543000 +Ashford,E07000105,4,340000,1120000 +Babergh,E07000200,1,113000,428000 +Babergh,E07000200,2,149000,428000 +Babergh,E07000200,3,215000,557000 +Babergh,E07000200,4,341000,1230000 +Barking and Dagenham,E09000002,1,88000,383000 +Barking and Dagenham,E09000002,2,103000,401000 +Barking and Dagenham,E09000002,3,220000,506000 +Barking and Dagenham,E09000002,4,308000,661000 +Barnet,E09000003,1,142000,538000 +Barnet,E09000003,2,205000,696000 +Barnet,E09000003,3,416000,1071000 +Barnet,E09000003,4,654000,2254000 +Barnsley,E08000016,1,70000,225000 +Barnsley,E08000016,2,70000,225000 +Barnsley,E08000016,3,93000,289000 +Barnsley,E08000016,4,175000,574000 +Basildon,E07000066,1,96000,457000 +Basildon,E07000066,2,145000,457000 +Basildon,E07000066,3,273000,567000 +Basildon,E07000066,4,376000,1118000 +Basingstoke and Deane,E07000084,1,113000,448000 +Basingstoke and Deane,E07000084,2,139000,448000 +Basingstoke and Deane,E07000084,3,275000,578000 +Basingstoke and Deane,E07000084,4,394000,1115000 +Bassetlaw,E07000171,1,92000,313000 +Bassetlaw,E07000171,2,92000,313000 +Bassetlaw,E07000171,3,105000,356000 +Bassetlaw,E07000171,4,186000,627000 +Bath and North East Somerset,E06000022,1,119000,609000 +Bath and North East Somerset,E06000022,2,154000,609000 +Bath and North East Somerset,E06000022,3,249000,762000 +Bath and North East Somerset,E06000022,4,380000,1604000 +Bedford,E06000055,1,96000,349000 +Bedford,E06000055,2,120000,349000 +Bedford,E06000055,3,210000,467000 +Bedford,E06000055,4,371000,946000 +Bexley,E09000004,1,109000,449000 +Bexley,E09000004,2,142000,449000 +Bexley,E09000004,3,312000,581000 +Bexley,E09000004,4,412000,972000 +Birmingham,E08000025,1,79000,373000 +Birmingham,E08000025,2,107000,373000 +Birmingham,E08000025,3,137000,452000 +Birmingham,E08000025,4,198000,1006000 +Blaby,E07000129,1,111000,357000 +Blaby,E07000129,2,111000,357000 +Blaby,E07000129,3,194000,389000 +Blaby,E07000129,4,285000,723000 +Blackburn with Darwen,E06000008,1,61000,234000 +Blackburn with Darwen,E06000008,2,74000,234000 +Blackburn with Darwen,E06000008,3,92000,310000 +Blackburn with Darwen,E06000008,4,148000,608000 +Blackpool,E06000009,1,59000,228000 +Blackpool,E06000009,2,83000,228000 +Blackpool,E06000009,3,100000,265000 +Blackpool,E06000009,4,114000,450000 +Bolsover,E07000033,1,81000,222000 +Bolsover,E07000033,2,81000,222000 +Bolsover,E07000033,3,87000,279000 +Bolsover,E07000033,4,169000,543000 +Bolton,E08000001,1,77000,251000 +Bolton,E08000001,2,84000,251000 +Bolton,E08000001,3,104000,340000 +Bolton,E08000001,4,175000,762000 +Boston,E07000136,1,58000,258000 +Boston,E07000136,2,58000,258000 +Boston,E07000136,3,111000,308000 +Boston,E07000136,4,212000,510000 +Bournemouth, Christchurch and Poole,E06000058,1,140000,500000 +Bournemouth, Christchurch and Poole,E06000058,2,197000,500000 +Bournemouth, Christchurch and Poole,E06000058,3,285000,657000 +Bournemouth, Christchurch and Poole,E06000058,4,370000,1321000 +Bracknell Forest,E06000036,1,95000,488000 +Bracknell Forest,E06000036,2,157000,488000 +Bracknell Forest,E06000036,3,341000,654000 +Bracknell Forest,E06000036,4,417000,1185000 +Bradford,E08000032,1,65000,291000 +Bradford,E08000032,2,77000,291000 +Bradford,E08000032,3,96000,365000 +Bradford,E08000032,4,124000,799000 +Braintree,E07000067,1,126000,425000 +Braintree,E07000067,2,166000,425000 +Braintree,E07000067,3,273000,534000 +Braintree,E07000067,4,370000,1177000 +Breckland,E07000143,1,105000,315000 +Breckland,E07000143,2,130000,315000 +Breckland,E07000143,3,173000,415000 +Breckland,E07000143,4,262000,795000 +Brent,E09000005,1,109000,625000 +Brent,E09000005,2,160000,817000 +Brent,E09000005,3,387000,1319000 +Brent,E09000005,4,555000,2717000 +Brentwood,E07000068,1,146000,530000 +Brentwood,E07000068,2,244000,703000 +Brentwood,E07000068,3,402000,897000 +Brentwood,E07000068,4,508000,1851000 +Brighton and Hove,E06000043,1,185000,526000 +Brighton and Hove,E06000043,2,270000,610000 +Brighton and Hove,E06000043,3,350000,846000 +Brighton and Hove,E06000043,4,458000,1487000 +Bristol, City of,E06000023,1,145000,417000 +Bristol, City of,E06000023,2,184000,562000 +Bristol, City of,E06000023,3,242000,685000 +Bristol, City of,E06000023,4,331000,1394000 +Broadland,E07000144,1,126000,334000 +Broadland,E07000144,2,140000,334000 +Broadland,E07000144,3,225000,433000 +Broadland,E07000144,4,302000,749000 +Bromley,E09000006,1,198000,559000 +Bromley,E09000006,2,276000,629000 +Bromley,E09000006,3,352000,824000 +Bromley,E09000006,4,499000,1553000 +Bromsgrove,E07000234,1,119000,485000 +Bromsgrove,E07000234,2,121000,485000 +Bromsgrove,E07000234,3,195000,556000 +Bromsgrove,E07000234,4,347000,1090000 +Broxbourne,E07000095,1,170000,480000 +Broxbourne,E07000095,2,252000,480000 +Broxbourne,E07000095,3,371000,658000 +Broxbourne,E07000095,4,485000,1428000 +Broxtowe,E07000172,1,99000,297000 +Broxtowe,E07000172,2,109000,297000 +Broxtowe,E07000172,3,151000,381000 +Broxtowe,E07000172,4,235000,789000 +Buckinghamshire,E06000060,1,157000,650000 +Buckinghamshire,E06000060,2,171000,650000 +Buckinghamshire,E06000060,3,314000,867000 +Buckinghamshire,E06000060,4,459000,1926000 +Burnley,E07000117,1,52000,225000 +Burnley,E07000117,2,62000,225000 +Burnley,E07000117,3,83000,283000 +Burnley,E07000117,4,147000,475000 +Bury,E08000002,1,89000,319000 +Bury,E08000002,2,99000,319000 +Bury,E08000002,3,136000,390000 +Bury,E08000002,4,224000,792000 +Calderdale,E08000033,1,74000,283000 +Calderdale,E08000033,2,79000,283000 +Calderdale,E08000033,3,103000,393000 +Calderdale,E08000033,4,163000,749000 +Cambridge,E07000008,1,139000,603000 +Cambridge,E07000008,2,174000,663000 +Cambridge,E07000008,3,339000,870000 +Cambridge,E07000008,4,530000,1952000 +Camden,E09000007,1,256000,806000 +Camden,E09000007,2,449000,1468000 +Camden,E09000007,3,559000,2535000 +Camden,E09000007,4,1009000,6026000 +Cannock Chase,E07000192,1,75000,248000 +Cannock Chase,E07000192,2,111000,248000 +Cannock Chase,E07000192,3,147000,326000 +Cannock Chase,E07000192,4,228000,511000 +Canterbury,E07000106,1,96000,465000 +Canterbury,E07000106,2,167000,465000 +Canterbury,E07000106,3,239000,591000 +Canterbury,E07000106,4,356000,1076000 +Castle Point,E07000069,1,155000,465000 +Castle Point,E07000069,2,219000,465000 +Castle Point,E07000069,3,283000,543000 +Castle Point,E07000069,4,378000,1017000 +Central Bedfordshire,E06000056,1,106000,418000 +Central Bedfordshire,E06000056,2,134000,418000 +Central Bedfordshire,E06000056,3,255000,537000 +Central Bedfordshire,E06000056,4,395000,942000 +Charnwood,E07000130,1,95000,338000 +Charnwood,E07000130,2,110000,338000 +Charnwood,E07000130,3,168000,397000 +Charnwood,E07000130,4,286000,803000 +Chelmsford,E07000070,1,89000,498000 +Chelmsford,E07000070,2,125000,498000 +Chelmsford,E07000070,3,330000,675000 +Chelmsford,E07000070,4,449000,1189000 +Cheltenham,E07000078,1,118000,487000 +Cheltenham,E07000078,2,175000,511000 +Cheltenham,E07000078,3,226000,638000 +Cheltenham,E07000078,4,328000,1280000 +Cherwell,E07000177,1,114000,432000 +Cherwell,E07000177,2,114000,432000 +Cherwell,E07000177,3,235000,543000 +Cherwell,E07000177,4,359000,999000 +Cheshire East,E06000049,1,98000,454000 +Cheshire East,E06000049,2,98000,454000 +Cheshire East,E06000049,3,132000,566000 +Cheshire East,E06000049,4,288000,1199000 +Cheshire West and Chester,E06000050,1,84000,375000 +Cheshire West and Chester,E06000050,2,94000,375000 +Cheshire West and Chester,E06000050,3,122000,450000 +Cheshire West and Chester,E06000050,4,253000,975000 +Chesterfield,E07000034,1,73000,240000 +Chesterfield,E07000034,2,95000,240000 +Chesterfield,E07000034,3,117000,353000 +Chesterfield,E07000034,4,194000,629000 +Chichester,E07000225,1,101000,610000 +Chichester,E07000225,2,115000,610000 +Chichester,E07000225,3,282000,787000 +Chichester,E07000225,4,410000,2095000 +Chorley,E07000118,1,73000,333000 +Chorley,E07000118,2,100000,333000 +Chorley,E07000118,3,123000,393000 +Chorley,E07000118,4,234000,716000 +City of London,E09000001,1,467000,1026000 +City of London,E09000001,2,681000,2732000 +City of London,E09000001,3,1741000,5775000 +City of London,E09000001,4,1932000,2288000 +Colchester,E07000071,1,114000,383000 +Colchester,E07000071,2,164000,383000 +Colchester,E07000071,3,256000,490000 +Colchester,E07000071,4,364000,928000 +Cornwall,E06000052,1,98000,406000 +Cornwall,E06000052,2,102000,406000 +Cornwall,E06000052,3,161000,524000 +Cornwall,E06000052,4,250000,864000 +Cotswold,E07000079,1,74000,659000 +Cotswold,E07000079,2,121000,659000 +Cotswold,E07000079,3,181000,812000 +Cotswold,E07000079,4,408000,1603000 +County Durham,E06000047,1,55000,232000 +County Durham,E06000047,2,55000,232000 +County Durham,E06000047,3,72000,299000 +County Durham,E06000047,4,148000,593000 +Coventry,E08000026,1,88000,277000 +Coventry,E08000026,2,118000,277000 +Coventry,E08000026,3,165000,396000 +Coventry,E08000026,4,237000,783000 +Crawley,E07000226,1,159000,387000 +Crawley,E07000226,2,230000,416000 +Crawley,E07000226,3,315000,528000 +Crawley,E07000226,4,378000,794000 +Croydon,E09000008,1,115000,444000 +Croydon,E09000008,2,183000,553000 +Croydon,E09000008,3,341000,692000 +Croydon,E09000008,4,464000,1215000 +Cumberland,E06000063,1,67000,271000 +Cumberland,E06000063,2,81000,271000 +Cumberland,E06000063,3,96000,349000 +Cumberland,E06000063,4,153000,577000 +Dacorum,E07000096,1,174000,618000 +Dacorum,E07000096,2,234000,621000 +Dacorum,E07000096,3,354000,839000 +Dacorum,E07000096,4,478000,1765000 +Darlington,E06000005,1,56000,240000 +Darlington,E06000005,2,69000,240000 +Darlington,E06000005,3,91000,310000 +Darlington,E06000005,4,185000,614000 +Dartford,E07000107,1,117000,431000 +Dartford,E07000107,2,117000,431000 +Dartford,E07000107,3,307000,559000 +Dartford,E07000107,4,426000,963000 +Derby,E06000015,1,67000,258000 +Derby,E06000015,2,94000,258000 +Derby,E06000015,3,117000,352000 +Derby,E06000015,4,237000,646000 +Derbyshire Dales,E07000035,1,73000,474000 +Derbyshire Dales,E07000035,2,88000,474000 +Derbyshire Dales,E07000035,3,181000,601000 +Derbyshire Dales,E07000035,4,300000,973000 +Doncaster,E08000017,1,77000,233000 +Doncaster,E08000017,2,78000,233000 +Doncaster,E08000017,3,93000,286000 +Doncaster,E08000017,4,173000,582000 +Dorset,E06000059,1,112000,471000 +Dorset,E06000059,2,159000,471000 +Dorset,E06000059,3,231000,615000 +Dorset,E06000059,4,328000,1150000 +Dover,E07000108,1,109000,384000 +Dover,E07000108,2,120000,384000 +Dover,E07000108,3,190000,501000 +Dover,E07000108,4,265000,956000 +Dudley,E08000027,1,76000,281000 +Dudley,E08000027,2,102000,281000 +Dudley,E08000027,3,137000,371000 +Dudley,E08000027,4,209000,650000 +Ealing,E09000009,1,129000,574000 +Ealing,E09000009,2,195000,760000 +Ealing,E09000009,3,338000,1095000 +Ealing,E09000009,4,523000,2198000 +East Cambridgeshire,E07000009,1,120000,385000 +East Cambridgeshire,E07000009,2,128000,385000 +East Cambridgeshire,E07000009,3,227000,499000 +East Cambridgeshire,E07000009,4,331000,916000 +East Devon,E07000040,1,91000,459000 +East Devon,E07000040,2,114000,459000 +East Devon,E07000040,3,181000,593000 +East Devon,E07000040,4,324000,1123000 +East Hampshire,E07000085,1,122000,577000 +East Hampshire,E07000085,2,152000,577000 +East Hampshire,E07000085,3,288000,727000 +East Hampshire,E07000085,4,438000,1371000 +East Hertfordshire,E07000242,1,143000,578000 +East Hertfordshire,E07000242,2,170000,578000 +East Hertfordshire,E07000242,3,349000,816000 +East Hertfordshire,E07000242,4,512000,1582000 +East Lindsey,E07000137,1,68000,320000 +East Lindsey,E07000137,2,85000,320000 +East Lindsey,E07000137,3,112000,359000 +East Lindsey,E07000137,4,199000,699000 +East Riding of Yorkshire,E06000011,1,79000,322000 +East Riding of Yorkshire,E06000011,2,101000,322000 +East Riding of Yorkshire,E06000011,3,133000,376000 +East Riding of Yorkshire,E06000011,4,205000,717000 +East Staffordshire,E07000193,1,80000,330000 +East Staffordshire,E07000193,2,100000,330000 +East Staffordshire,E07000193,3,128000,379000 +East Staffordshire,E07000193,4,236000,753000 +East Suffolk,E07000244,1,94000,414000 +East Suffolk,E07000244,2,125000,414000 +East Suffolk,E07000244,3,147000,481000 +East Suffolk,E07000244,4,247000,913000 +Eastbourne,E07000061,1,121000,371000 +Eastbourne,E07000061,2,169000,371000 +Eastbourne,E07000061,3,238000,496000 +Eastbourne,E07000061,4,307000,870000 +Eastleigh,E07000086,1,113000,389000 +Eastleigh,E07000086,2,117000,389000 +Eastleigh,E07000086,3,202000,514000 +Eastleigh,E07000086,4,369000,936000 +Elmbridge,E07000207,1,145000,857000 +Elmbridge,E07000207,2,265000,857000 +Elmbridge,E07000207,3,462000,1175000 +Elmbridge,E07000207,4,668000,2805000 +Enfield,E09000010,1,126000,507000 +Enfield,E09000010,2,175000,600000 +Enfield,E09000010,3,336000,858000 +Enfield,E09000010,4,498000,1612000 +Epping Forest,E07000072,1,131000,697000 +Epping Forest,E07000072,2,287000,697000 +Epping Forest,E07000072,3,391000,923000 +Epping Forest,E07000072,4,545000,2007000 +Epsom and Ewell,E07000208,1,183000,693000 +Epsom and Ewell,E07000208,2,270000,693000 +Epsom and Ewell,E07000208,3,430000,929000 +Epsom and Ewell,E07000208,4,627000,1574000 +Erewash,E07000036,1,90000,270000 +Erewash,E07000036,2,99000,270000 +Erewash,E07000036,3,133000,362000 +Erewash,E07000036,4,211000,701000 +Exeter,E07000041,1,112000,402000 +Exeter,E07000041,2,130000,402000 +Exeter,E07000041,3,226000,500000 +Exeter,E07000041,4,329000,1005000 +Fareham,E07000087,1,127000,418000 +Fareham,E07000087,2,190000,418000 +Fareham,E07000087,3,265000,520000 +Fareham,E07000087,4,356000,943000 +Fenland,E07000010,1,85000,268000 +Fenland,E07000010,2,120000,268000 +Fenland,E07000010,3,154000,348000 +Fenland,E07000010,4,230000,594000 +Folkestone and Hythe,E07000112,1,106000,406000 +Folkestone and Hythe,E07000112,2,115000,406000 +Folkestone and Hythe,E07000112,3,208000,549000 +Folkestone and Hythe,E07000112,4,287000,883000 +Forest of Dean,E07000080,1,101000,439000 +Forest of Dean,E07000080,2,119000,439000 +Forest of Dean,E07000080,3,176000,522000 +Forest of Dean,E07000080,4,262000,872000 +Fylde,E07000119,1,87000,394000 +Fylde,E07000119,2,109000,407000 +Fylde,E07000119,3,127000,407000 +Fylde,E07000119,4,240000,763000 +Gateshead,E08000037,1,59000,229000 +Gateshead,E08000037,2,71000,229000 +Gateshead,E08000037,3,94000,337000 +Gateshead,E08000037,4,151000,583000 +Gedling,E07000173,1,77000,288000 +Gedling,E07000173,2,102000,288000 +Gedling,E07000173,3,147000,376000 +Gedling,E07000173,4,232000,747000 +Gloucester,E07000081,1,80000,302000 +Gloucester,E07000081,2,126000,302000 +Gloucester,E07000081,3,181000,397000 +Gloucester,E07000081,4,286000,619000 +Gosport,E07000088,1,107000,369000 +Gosport,E07000088,2,139000,370000 +Gosport,E07000088,3,201000,425000 +Gosport,E07000088,4,270000,791000 +Gravesham,E07000109,1,85000,432000 +Gravesham,E07000109,2,124000,432000 +Gravesham,E07000109,3,272000,549000 +Gravesham,E07000109,4,365000,1107000 +Great Yarmouth,E07000145,1,89000,287000 +Great Yarmouth,E07000145,2,93000,287000 +Great Yarmouth,E07000145,3,116000,352000 +Great Yarmouth,E07000145,4,178000,586000 +Greenwich,E09000011,1,115000,592000 +Greenwich,E09000011,2,158000,705000 +Greenwich,E09000011,3,283000,888000 +Greenwich,E09000011,4,410000,1778000 +Guildford,E07000209,1,124000,629000 +Guildford,E07000209,2,240000,738000 +Guildford,E07000209,3,387000,946000 +Guildford,E07000209,4,525000,2156000 +Hackney,E09000012,1,181000,698000 +Hackney,E09000012,2,236000,935000 +Hackney,E09000012,3,423000,1443000 +Hackney,E09000012,4,732000,2120000 +Halton,E06000006,1,76000,255000 +Halton,E06000006,2,87000,255000 +Halton,E06000006,3,102000,327000 +Halton,E06000006,4,174000,581000 +Hammersmith and Fulham,E09000013,1,206000,790000 +Hammersmith and Fulham,E09000013,2,395000,1248000 +Hammersmith and Fulham,E09000013,3,484000,1767000 +Hammersmith and Fulham,E09000013,4,1058000,3462000 +Harborough,E07000131,1,65000,458000 +Harborough,E07000131,2,65000,458000 +Harborough,E07000131,3,154000,535000 +Harborough,E07000131,4,352000,1070000 +Haringey,E09000014,1,155000,589000 +Haringey,E09000014,2,251000,862000 +Haringey,E09000014,3,406000,1279000 +Haringey,E09000014,4,644000,2638000 +Harlow,E07000073,1,128000,369000 +Harlow,E07000073,2,166000,422000 +Harlow,E07000073,3,283000,536000 +Harlow,E07000073,4,384000,920000 +Harrow,E09000015,1,129000,536000 +Harrow,E09000015,2,282000,623000 +Harrow,E09000015,3,418000,836000 +Harrow,E09000015,4,570000,1607000 +Hart,E07000089,1,108000,576000 +Hart,E07000089,2,150000,576000 +Hart,E07000089,3,351000,705000 +Hart,E07000089,4,508000,1431000 +Hartlepool,E06000001,1,56000,242000 +Hartlepool,E06000001,2,56000,242000 +Hartlepool,E06000001,3,77000,261000 +Hartlepool,E06000001,4,133000,559000 +Hastings,E07000062,1,106000,340000 +Hastings,E07000062,2,155000,381000 +Hastings,E07000062,3,214000,437000 +Hastings,E07000062,4,282000,727000 +Havant,E07000090,1,98000,414000 +Havant,E07000090,2,144000,414000 +Havant,E07000090,3,229000,501000 +Havant,E07000090,4,309000,855000 +Havering,E09000016,1,137000,472000 +Havering,E09000016,2,204000,481000 +Havering,E09000016,3,336000,657000 +Havering,E09000016,4,412000,1232000 +Herefordshire, County of,E06000019,1,98000,419000 +Herefordshire, County of,E06000019,2,105000,419000 +Herefordshire, County of,E06000019,3,162000,499000 +Herefordshire, County of,E06000019,4,283000,885000 +Hertsmere,E07000098,1,178000,666000 +Hertsmere,E07000098,2,316000,666000 +Hertsmere,E07000098,3,440000,918000 +Hertsmere,E07000098,4,591000,2232000 +High Peak,E07000037,1,110000,322000 +High Peak,E07000037,2,126000,322000 +High Peak,E07000037,3,161000,433000 +High Peak,E07000037,4,247000,804000 +Hillingdon,E09000017,1,132000,502000 +Hillingdon,E09000017,2,196000,577000 +Hillingdon,E09000017,3,401000,788000 +Hillingdon,E09000017,4,507000,1414000 +Hinckley and Bosworth,E07000132,1,99000,328000 +Hinckley and Bosworth,E07000132,2,110000,328000 +Hinckley and Bosworth,E07000132,3,164000,418000 +Hinckley and Bosworth,E07000132,4,270000,840000 +Horsham,E07000227,1,140000,529000 +Horsham,E07000227,2,140000,529000 +Horsham,E07000227,3,297000,745000 +Horsham,E07000227,4,496000,1513000 +Hounslow,E09000018,1,119000,563000 +Hounslow,E09000018,2,154000,733000 +Hounslow,E09000018,3,330000,1036000 +Hounslow,E09000018,4,475000,2743000 +Huntingdonshire,E07000011,1,91000,389000 +Huntingdonshire,E07000011,2,107000,389000 +Huntingdonshire,E07000011,3,208000,478000 +Huntingdonshire,E07000011,4,318000,814000 +Hyndburn,E07000120,1,66000,214000 +Hyndburn,E07000120,2,66000,214000 +Hyndburn,E07000120,3,87000,307000 +Hyndburn,E07000120,4,168000,587000 +Ipswich,E07000202,1,97000,309000 +Ipswich,E07000202,2,125000,309000 +Ipswich,E07000202,3,182000,399000 +Ipswich,E07000202,4,253000,851000 +Isle of Wight,E06000046,1,96000,352000 +Isle of Wight,E06000046,2,131000,352000 +Isle of Wight,E06000046,3,185000,452000 +Isle of Wight,E06000046,4,239000,827000 +Islington,E09000019,1,203000,741000 +Islington,E09000019,2,398000,1223000 +Islington,E09000019,3,490000,1891000 +Islington,E09000019,4,762000,2824000 +Kensington and Chelsea,E09000020,1,234000,1261000 +Kensington and Chelsea,E09000020,2,599000,2368000 +Kensington and Chelsea,E09000020,3,706000,4202000 +Kensington and Chelsea,E09000020,4,1405000,13278000 +King's Lynn and West Norfolk,E07000146,1,77000,346000 +King's Lynn and West Norfolk,E07000146,2,123000,346000 +King's Lynn and West Norfolk,E07000146,3,161000,408000 +King's Lynn and West Norfolk,E07000146,4,243000,778000 +Kingston upon Hull, City of,E06000010,1,63000,189000 +Kingston upon Hull, City of,E06000010,2,67000,189000 +Kingston upon Hull, City of,E06000010,3,84000,259000 +Kingston upon Hull, City of,E06000010,4,110000,415000 +Kingston upon Thames,E09000021,1,156000,649000 +Kingston upon Thames,E09000021,2,325000,708000 +Kingston upon Thames,E09000021,3,398000,935000 +Kingston upon Thames,E09000021,4,549000,1957000 +Kirklees,E08000034,1,70000,261000 +Kirklees,E08000034,2,85000,261000 +Kirklees,E08000034,3,115000,356000 +Kirklees,E08000034,4,198000,752000 +Knowsley,E08000011,1,74000,223000 +Knowsley,E08000011,2,74000,223000 +Knowsley,E08000011,3,94000,302000 +Knowsley,E08000011,4,134000,481000 +Lambeth,E09000022,1,166000,639000 +Lambeth,E09000022,2,242000,842000 +Lambeth,E09000022,3,392000,1259000 +Lambeth,E09000022,4,573000,2054000 +Lancaster,E07000121,1,79000,312000 +Lancaster,E07000121,2,93000,312000 +Lancaster,E07000121,3,126000,378000 +Lancaster,E07000121,4,147000,699000 +Leeds,E08000035,1,87000,323000 +Leeds,E08000035,2,101000,323000 +Leeds,E08000035,3,126000,435000 +Leeds,E08000035,4,181000,878000 +Leicester,E06000016,1,47000,282000 +Leicester,E06000016,2,82000,282000 +Leicester,E06000016,3,102000,364000 +Leicester,E06000016,4,235000,728000 +Lewes,E07000063,1,140000,528000 +Lewes,E07000063,2,204000,528000 +Lewes,E07000063,3,251000,725000 +Lewes,E07000063,4,363000,1374000 +Lewisham,E09000023,1,169000,506000 +Lewisham,E09000023,2,238000,675000 +Lewisham,E09000023,3,357000,881000 +Lewisham,E09000023,4,486000,1540000 +Lichfield,E07000194,1,100000,433000 +Lichfield,E07000194,2,100000,433000 +Lichfield,E07000194,3,176000,528000 +Lichfield,E07000194,4,296000,990000 +Lincoln,E07000138,1,63000,258000 +Lincoln,E07000138,2,85000,258000 +Lincoln,E07000138,3,132000,335000 +Lincoln,E07000138,4,195000,563000 +Liverpool,E08000012,1,76000,323000 +Liverpool,E08000012,2,76000,323000 +Liverpool,E08000012,3,95000,381000 +Liverpool,E08000012,4,138000,790000 +Luton,E06000032,1,123000,349000 +Luton,E06000032,2,154000,349000 +Luton,E06000032,3,253000,462000 +Luton,E06000032,4,310000,755000 +Maidstone,E07000110,1,75000,455000 +Maidstone,E07000110,2,121000,455000 +Maidstone,E07000110,3,217000,566000 +Maidstone,E07000110,4,402000,1099000 +Maldon,E07000074,1,127000,516000 +Maldon,E07000074,2,127000,516000 +Maldon,E07000074,3,293000,612000 +Maldon,E07000074,4,400000,1389000 +Malvern Hills,E07000235,1,103000,448000 +Malvern Hills,E07000235,2,103000,448000 +Malvern Hills,E07000235,3,201000,588000 +Malvern Hills,E07000235,4,330000,942000 +Manchester,E08000003,1,106000,348000 +Manchester,E08000003,2,106000,438000 +Manchester,E08000003,3,109000,517000 +Manchester,E08000003,4,165000,1108000 +Mansfield,E07000174,1,77000,270000 +Mansfield,E07000174,2,80000,270000 +Mansfield,E07000174,3,103000,305000 +Mansfield,E07000174,4,184000,544000 +Medway,E06000035,1,72000,375000 +Medway,E06000035,2,148000,375000 +Medway,E06000035,3,226000,484000 +Medway,E06000035,4,309000,758000 +Melton,E07000133,1,62000,393000 +Melton,E07000133,2,62000,393000 +Melton,E07000133,3,173000,464000 +Melton,E07000133,4,264000,1049000 +Mendip,E07000187,1,110000,417000 +Mendip,E07000187,2,121000,417000 +Mendip,E07000187,3,222000,525000 +Mendip,E07000187,4,320000,1134000 +Merton,E09000024,1,205000,696000 +Merton,E09000024,2,263000,792000 +Merton,E09000024,3,370000,1016000 +Merton,E09000024,4,508000,2679000 +Mid Devon,E07000042,1,100000,412000 +Mid Devon,E07000042,2,115000,412000 +Mid Devon,E07000042,3,189000,526000 +Mid Devon,E07000042,4,292000,931000 +Mid Suffolk,E07000203,1,111000,389000 +Mid Suffolk,E07000203,2,124000,389000 +Mid Suffolk,E07000203,3,218000,502000 +Mid Suffolk,E07000203,4,315000,962000 +Mid Sussex,E07000228,1,187000,548000 +Mid Sussex,E07000228,2,245000,548000 +Mid Sussex,E07000228,3,352000,713000 +Mid Sussex,E07000228,4,471000,1406000 +Middlesbrough,E06000002,1,54000,228000 +Middlesbrough,E06000002,2,63000,228000 +Middlesbrough,E06000002,3,80000,272000 +Middlesbrough,E06000002,4,183000,533000 +Milton Keynes,E06000042,1,67000,377000 +Milton Keynes,E06000042,2,93000,377000 +Milton Keynes,E06000042,3,207000,495000 +Milton Keynes,E06000042,4,354000,921000 +Mole Valley,E07000210,1,173000,743000 +Mole Valley,E07000210,2,261000,753000 +Mole Valley,E07000210,3,429000,981000 +Mole Valley,E07000210,4,617000,1913000 +New Forest,E07000091,1,127000,505000 +New Forest,E07000091,2,199000,556000 +New Forest,E07000091,3,272000,756000 +New Forest,E07000091,4,378000,1262000 +Newark and Sherwood,E07000175,1,92000,353000 +Newark and Sherwood,E07000175,2,92000,353000 +Newark and Sherwood,E07000175,3,122000,402000 +Newark and Sherwood,E07000175,4,233000,829000 +Newcastle upon Tyne,E08000021,1,58000,293000 +Newcastle upon Tyne,E08000021,2,69000,293000 +Newcastle upon Tyne,E08000021,3,88000,381000 +Newcastle upon Tyne,E08000021,4,170000,828000 +Newcastle-under-Lyme,E07000195,1,92000,245000 +Newcastle-under-Lyme,E07000195,2,92000,245000 +Newcastle-under-Lyme,E07000195,3,121000,348000 +Newcastle-under-Lyme,E07000195,4,205000,684000 +Newham,E09000025,1,111000,515000 +Newham,E09000025,2,149000,665000 +Newham,E09000025,3,246000,794000 +Newham,E09000025,4,425000,1108000 +North Devon,E07000043,1,92000,415000 +North Devon,E07000043,2,110000,415000 +North Devon,E07000043,3,170000,468000 +North Devon,E07000043,4,255000,831000 +North East Derbyshire,E07000038,1,79000,322000 +North East Derbyshire,E07000038,2,79000,322000 +North East Derbyshire,E07000038,3,117000,424000 +North East Derbyshire,E07000038,4,230000,882000 +North East Lincolnshire,E06000012,1,53000,255000 +North East Lincolnshire,E06000012,2,69000,255000 +North East Lincolnshire,E06000012,3,87000,283000 +North East Lincolnshire,E06000012,4,161000,562000 +North Hertfordshire,E07000099,1,108000,528000 +North Hertfordshire,E07000099,2,157000,528000 +North Hertfordshire,E07000099,3,318000,752000 +North Hertfordshire,E07000099,4,453000,1558000 +North Kesteven,E07000139,1,85000,339000 +North Kesteven,E07000139,2,85000,339000 +North Kesteven,E07000139,3,149000,367000 +North Kesteven,E07000139,4,250000,634000 +North Lincolnshire,E06000013,1,71000,291000 +North Lincolnshire,E06000013,2,84000,291000 +North Lincolnshire,E06000013,3,100000,305000 +North Lincolnshire,E06000013,4,186000,560000 +North Norfolk,E07000147,1,106000,403000 +North Norfolk,E07000147,2,136000,403000 +North Norfolk,E07000147,3,176000,507000 +North Norfolk,E07000147,4,251000,1072000 +North Northamptonshire,E06000061,1,79000,330000 +North Northamptonshire,E06000061,2,95000,330000 +North Northamptonshire,E06000061,3,170000,407000 +North Northamptonshire,E06000061,4,270000,782000 +North Somerset,E06000024,1,108000,430000 +North Somerset,E06000024,2,145000,430000 +North Somerset,E06000024,3,222000,588000 +North Somerset,E06000024,4,306000,1105000 +North Tyneside,E08000022,1,69000,263000 +North Tyneside,E08000022,2,69000,263000 +North Tyneside,E08000022,3,91000,373000 +North Tyneside,E08000022,4,205000,619000 +North Warwickshire,E07000218,1,112000,369000 +North Warwickshire,E07000218,2,118000,369000 +North Warwickshire,E07000218,3,153000,434000 +North Warwickshire,E07000218,4,260000,968000 +North West Leicestershire,E07000134,1,103000,315000 +North West Leicestershire,E07000134,2,103000,315000 +North West Leicestershire,E07000134,3,130000,398000 +North West Leicestershire,E07000134,4,271000,733000 +North Yorkshire,E06000065,1,81000,384000 +North Yorkshire,E06000065,2,108000,384000 +North Yorkshire,E06000065,3,143000,496000 +North Yorkshire,E06000065,4,234000,1005000 +Northumberland,E06000057,1,62000,333000 +Northumberland,E06000057,2,64000,333000 +Northumberland,E06000057,3,95000,376000 +Northumberland,E06000057,4,188000,790000 +Norwich,E07000148,1,83000,263000 +Norwich,E07000148,2,83000,346000 +Norwich,E07000148,3,149000,466000 +Norwich,E07000148,4,230000,960000 +Nottingham,E06000018,1,72000,304000 +Nottingham,E06000018,2,79000,304000 +Nottingham,E06000018,3,89000,350000 +Nottingham,E06000018,4,165000,761000 +Nuneaton and Bedworth,E07000219,1,95000,266000 +Nuneaton and Bedworth,E07000219,2,98000,266000 +Nuneaton and Bedworth,E07000219,3,142000,356000 +Nuneaton and Bedworth,E07000219,4,225000,637000 +Oadby and Wigston,E07000135,1,90000,303000 +Oadby and Wigston,E07000135,2,125000,303000 +Oadby and Wigston,E07000135,3,188000,401000 +Oadby and Wigston,E07000135,4,295000,911000 +Oldham,E08000004,1,85000,262000 +Oldham,E08000004,2,85000,262000 +Oldham,E08000004,3,107000,357000 +Oldham,E08000004,4,160000,789000 +Oxford,E07000178,1,126000,656000 +Oxford,E07000178,2,198000,707000 +Oxford,E07000178,3,328000,794000 +Oxford,E07000178,4,440000,2267000 +Pendle,E07000122,1,59000,251000 +Pendle,E07000122,2,60000,251000 +Pendle,E07000122,3,81000,366000 +Pendle,E07000122,4,155000,700000 +Peterborough,E06000031,1,75000,282000 +Peterborough,E06000031,2,89000,282000 +Peterborough,E06000031,3,152000,337000 +Peterborough,E06000031,4,223000,697000 +Plymouth,E06000026,1,91000,320000 +Plymouth,E06000026,2,95000,320000 +Plymouth,E06000026,3,148000,370000 +Plymouth,E06000026,4,218000,635000 +Portsmouth,E06000044,1,107000,332000 +Portsmouth,E06000044,2,145000,341000 +Portsmouth,E06000044,3,202000,451000 +Portsmouth,E06000044,4,276000,875000 +Preston,E07000123,1,47000,253000 +Preston,E07000123,2,82000,253000 +Preston,E07000123,3,106000,329000 +Preston,E07000123,4,197000,649000 +Reading,E06000038,1,141000,434000 +Reading,E06000038,2,228000,471000 +Reading,E06000038,3,319000,662000 +Reading,E06000038,4,428000,1149000 +Redbridge,E09000026,1,102000,497000 +Redbridge,E09000026,2,162000,639000 +Redbridge,E09000026,3,355000,826000 +Redbridge,E09000026,4,488000,1329000 +Redcar and Cleveland,E06000003,1,62000,244000 +Redcar and Cleveland,E06000003,2,62000,244000 +Redcar and Cleveland,E06000003,3,82000,273000 +Redcar and Cleveland,E06000003,4,158000,482000 +Redditch,E07000236,1,57000,296000 +Redditch,E07000236,2,102000,296000 +Redditch,E07000236,3,157000,389000 +Redditch,E07000236,4,258000,627000 +Reigate and Banstead,E07000211,1,154000,574000 +Reigate and Banstead,E07000211,2,154000,574000 +Reigate and Banstead,E07000211,3,383000,864000 +Reigate and Banstead,E07000211,4,522000,1872000 +Ribble Valley,E07000124,1,116000,350000 +Ribble Valley,E07000124,2,116000,350000 +Ribble Valley,E07000124,3,131000,483000 +Ribble Valley,E07000124,4,282000,935000 +Richmond upon Thames,E09000027,1,218000,798000 +Richmond upon Thames,E09000027,2,346000,978000 +Richmond upon Thames,E09000027,3,481000,1355000 +Richmond upon Thames,E09000027,4,686000,2642000 +Rochdale,E08000005,1,73000,224000 +Rochdale,E08000005,2,75000,224000 +Rochdale,E08000005,3,102000,316000 +Rochdale,E08000005,4,174000,594000 +Rochford,E07000075,1,147000,494000 +Rochford,E07000075,2,237000,494000 +Rochford,E07000075,3,320000,585000 +Rochford,E07000075,4,406000,1011000 +Rossendale,E07000125,1,64000,283000 +Rossendale,E07000125,2,77000,283000 +Rossendale,E07000125,3,108000,383000 +Rossendale,E07000125,4,188000,736000 +Rother,E07000064,1,104000,406000 +Rother,E07000064,2,151000,456000 +Rother,E07000064,3,233000,628000 +Rother,E07000064,4,350000,1165000 +Rotherham,E08000018,1,69000,246000 +Rotherham,E08000018,2,73000,246000 +Rotherham,E08000018,3,90000,305000 +Rotherham,E08000018,4,174000,574000 +Rugby,E07000220,1,109000,343000 +Rugby,E07000220,2,109000,343000 +Rugby,E07000220,3,184000,441000 +Rugby,E07000220,4,314000,835000 +Runnymede,E07000212,1,143000,543000 +Runnymede,E07000212,2,177000,582000 +Runnymede,E07000212,3,398000,819000 +Runnymede,E07000212,4,537000,1644000 +Rushcliffe,E07000176,1,99000,512000 +Rushcliffe,E07000176,2,99000,512000 +Rushcliffe,E07000176,3,178000,528000 +Rushcliffe,E07000176,4,329000,950000 +Rushmoor,E07000092,1,107000,413000 +Rushmoor,E07000092,2,183000,413000 +Rushmoor,E07000092,3,317000,555000 +Rushmoor,E07000092,4,433000,831000 +Rutland,E06000017,1,89000,390000 +Rutland,E06000017,2,89000,452000 +Rutland,E06000017,3,191000,615000 +Rutland,E06000017,4,333000,1262000 +Salford,E08000006,1,94000,355000 +Salford,E08000006,2,105000,355000 +Salford,E08000006,3,131000,396000 +Salford,E08000006,4,210000,784000 +Sandwell,E08000028,1,72000,241000 +Sandwell,E08000028,2,96000,241000 +Sandwell,E08000028,3,125000,323000 +Sandwell,E08000028,4,185000,465000 +Sefton,E08000014,1,78000,361000 +Sefton,E08000014,2,86000,361000 +Sefton,E08000014,3,105000,376000 +Sefton,E08000014,4,182000,841000 +Sevenoaks,E07000111,1,186000,677000 +Sevenoaks,E07000111,2,186000,677000 +Sevenoaks,E07000111,3,327000,940000 +Sevenoaks,E07000111,4,470000,2260000 +Sheffield,E08000019,1,76000,318000 +Sheffield,E08000019,2,79000,318000 +Sheffield,E08000019,3,108000,423000 +Sheffield,E08000019,4,211000,921000 +Shropshire,E06000051,1,85000,407000 +Shropshire,E06000051,2,108000,407000 +Shropshire,E06000051,3,164000,465000 +Shropshire,E06000051,4,252000,849000 +Slough,E06000039,1,163000,446000 +Slough,E06000039,2,224000,462000 +Slough,E06000039,3,352000,685000 +Slough,E06000039,4,432000,1004000 +Solihull,E08000029,1,93000,516000 +Solihull,E08000029,2,124000,516000 +Solihull,E08000029,3,162000,571000 +Solihull,E08000029,4,343000,1276000 +Somerset,E06000066,1,97000,376000 +Somerset,E06000066,2,120000,376000 +Somerset,E06000066,3,181000,469000 +Somerset,E06000066,4,278000,917000 +South Cambridgeshire,E07000012,1,137000,510000 +South Cambridgeshire,E07000012,2,164000,510000 +South Cambridgeshire,E07000012,3,271000,685000 +South Cambridgeshire,E07000012,4,403000,1219000 +South Derbyshire,E07000039,1,93000,330000 +South Derbyshire,E07000039,2,93000,330000 +South Derbyshire,E07000039,3,150000,380000 +South Derbyshire,E07000039,4,257000,747000 +South Gloucestershire,E06000025,1,95000,395000 +South Gloucestershire,E06000025,2,143000,395000 +South Gloucestershire,E06000025,3,263000,510000 +South Gloucestershire,E06000025,4,387000,890000 +South Hams,E07000044,1,106000,503000 +South Hams,E07000044,2,106000,503000 +South Hams,E07000044,3,207000,696000 +South Hams,E07000044,4,335000,1182000 +South Holland,E07000140,1,69000,278000 +South Holland,E07000140,2,69000,278000 +South Holland,E07000140,3,138000,367000 +South Holland,E07000140,4,238000,622000 +South Kesteven,E07000141,1,85000,358000 +South Kesteven,E07000141,2,92000,358000 +South Kesteven,E07000141,3,143000,448000 +South Kesteven,E07000141,4,250000,903000 +South Norfolk,E07000149,1,107000,364000 +South Norfolk,E07000149,2,107000,364000 +South Norfolk,E07000149,3,214000,482000 +South Norfolk,E07000149,4,295000,810000 +South Oxfordshire,E07000179,1,152000,615000 +South Oxfordshire,E07000179,2,152000,615000 +South Oxfordshire,E07000179,3,290000,784000 +South Oxfordshire,E07000179,4,440000,1898000 +South Ribble,E07000126,1,96000,282000 +South Ribble,E07000126,2,96000,282000 +South Ribble,E07000126,3,136000,337000 +South Ribble,E07000126,4,226000,640000 +South Staffordshire,E07000196,1,90000,369000 +South Staffordshire,E07000196,2,98000,369000 +South Staffordshire,E07000196,3,169000,475000 +South Staffordshire,E07000196,4,258000,850000 +South Tyneside,E08000023,1,58000,248000 +South Tyneside,E08000023,2,67000,248000 +South Tyneside,E08000023,3,80000,324000 +South Tyneside,E08000023,4,162000,624000 +Southampton,E06000045,1,80000,254000 +Southampton,E06000045,2,149000,348000 +Southampton,E06000045,3,216000,449000 +Southampton,E06000045,4,265000,794000 +Southend-on-Sea,E06000033,1,128000,451000 +Southend-on-Sea,E06000033,2,154000,503000 +Southend-on-Sea,E06000033,3,277000,658000 +Southend-on-Sea,E06000033,4,376000,1133000 +Southwark,E09000028,1,144000,676000 +Southwark,E09000028,2,185000,928000 +Southwark,E09000028,3,342000,1257000 +Southwark,E09000028,4,574000,2501000 +Spelthorne,E07000213,1,227000,560000 +Spelthorne,E07000213,2,293000,580000 +Spelthorne,E07000213,3,392000,768000 +Spelthorne,E07000213,4,524000,1262000 +St Albans,E07000240,1,194000,818000 +St Albans,E07000240,2,303000,818000 +St Albans,E07000240,3,443000,1077000 +St Albans,E07000240,4,679000,2067000 +St. Helens,E08000013,1,76000,226000 +St. Helens,E08000013,2,78000,226000 +St. Helens,E08000013,3,97000,315000 +St. Helens,E08000013,4,174000,628000 +Stafford,E07000197,1,76000,360000 +Stafford,E07000197,2,87000,360000 +Stafford,E07000197,3,150000,432000 +Stafford,E07000197,4,257000,736000 +Staffordshire Moorlands,E07000198,1,96000,347000 +Staffordshire Moorlands,E07000198,2,109000,347000 +Staffordshire Moorlands,E07000198,3,141000,442000 +Staffordshire Moorlands,E07000198,4,216000,826000 +Stevenage,E07000243,1,88000,392000 +Stevenage,E07000243,2,109000,392000 +Stevenage,E07000243,3,297000,504000 +Stevenage,E07000243,4,328000,848000 +Stockport,E08000007,1,95000,396000 +Stockport,E08000007,2,100000,396000 +Stockport,E08000007,3,174000,527000 +Stockport,E08000007,4,302000,1084000 +Stockton-on-Tees,E06000004,1,59000,245000 +Stockton-on-Tees,E06000004,2,59000,245000 +Stockton-on-Tees,E06000004,3,86000,302000 +Stockton-on-Tees,E06000004,4,178000,643000 +Stoke-on-Trent,E06000021,1,71000,197000 +Stoke-on-Trent,E06000021,2,72000,197000 +Stoke-on-Trent,E06000021,3,85000,287000 +Stoke-on-Trent,E06000021,4,178000,540000 +Stratford-on-Avon,E07000221,1,112000,487000 +Stratford-on-Avon,E07000221,2,112000,487000 +Stratford-on-Avon,E07000221,3,198000,649000 +Stratford-on-Avon,E07000221,4,384000,1338000 +Stroud,E07000082,1,99000,436000 +Stroud,E07000082,2,121000,436000 +Stroud,E07000082,3,220000,589000 +Stroud,E07000082,4,330000,1123000 +Sunderland,E08000024,1,60000,213000 +Sunderland,E08000024,2,66000,213000 +Sunderland,E08000024,3,81000,282000 +Sunderland,E08000024,4,139000,514000 +Surrey Heath,E07000214,1,160000,574000 +Surrey Heath,E07000214,2,160000,574000 +Surrey Heath,E07000214,3,366000,731000 +Surrey Heath,E07000214,4,528000,1387000 +Sutton,E09000029,1,128000,443000 +Sutton,E09000029,2,236000,495000 +Sutton,E09000029,3,362000,692000 +Sutton,E09000029,4,506000,1252000 +Swale,E07000113,1,116000,371000 +Swale,E07000113,2,121000,371000 +Swale,E07000113,3,207000,487000 +Swale,E07000113,4,318000,943000 +Swindon,E06000030,1,114000,317000 +Swindon,E06000030,2,118000,317000 +Swindon,E06000030,3,208000,433000 +Swindon,E06000030,4,322000,768000 +Tameside,E08000008,1,98000,237000 +Tameside,E08000008,2,110000,237000 +Tameside,E08000008,3,139000,336000 +Tameside,E08000008,4,209000,679000 +Tamworth,E07000199,1,102000,269000 +Tamworth,E07000199,2,116000,269000 +Tamworth,E07000199,3,159000,381000 +Tamworth,E07000199,4,243000,609000 +Tandridge,E07000215,1,189000,601000 +Tandridge,E07000215,2,225000,601000 +Tandridge,E07000215,3,392000,876000 +Tandridge,E07000215,4,532000,1958000 +Teignbridge,E07000045,1,98000,419000 +Teignbridge,E07000045,2,112000,419000 +Teignbridge,E07000045,3,208000,511000 +Teignbridge,E07000045,4,302000,952000 +Telford and Wrekin,E06000020,1,86000,260000 +Telford and Wrekin,E06000020,2,96000,260000 +Telford and Wrekin,E06000020,3,118000,332000 +Telford and Wrekin,E06000020,4,210000,618000 +Tendring,E07000076,1,82000,360000 +Tendring,E07000076,2,137000,360000 +Tendring,E07000076,3,184000,471000 +Tendring,E07000076,4,281000,915000 +Test Valley,E07000093,1,112000,503000 +Test Valley,E07000093,2,114000,511000 +Test Valley,E07000093,3,216000,577000 +Test Valley,E07000093,4,357000,1156000 +Tewkesbury,E07000083,1,72000,365000 +Tewkesbury,E07000083,2,102000,365000 +Tewkesbury,E07000083,3,155000,510000 +Tewkesbury,E07000083,4,352000,928000 +Thanet,E07000114,1,101000,377000 +Thanet,E07000114,2,153000,377000 +Thanet,E07000114,3,217000,484000 +Thanet,E07000114,4,292000,814000 +Three Rivers,E07000102,1,215000,574000 +Three Rivers,E07000102,2,287000,772000 +Three Rivers,E07000102,3,423000,1000000 +Three Rivers,E07000102,4,612000,2138000 +Thurrock,E06000034,1,134000,400000 +Thurrock,E06000034,2,176000,400000 +Thurrock,E06000034,3,277000,513000 +Thurrock,E06000034,4,377000,804000 +Tonbridge and Malling,E07000115,1,123000,472000 +Tonbridge and Malling,E07000115,2,133000,472000 +Tonbridge and Malling,E07000115,3,293000,691000 +Tonbridge and Malling,E07000115,4,439000,1315000 +Torbay,E06000027,1,92000,383000 +Torbay,E06000027,2,120000,383000 +Torbay,E06000027,3,174000,443000 +Torbay,E06000027,4,235000,779000 +Torridge,E07000046,1,110000,372000 +Torridge,E07000046,2,136000,372000 +Torridge,E07000046,3,181000,513000 +Torridge,E07000046,4,249000,807000 +Tower Hamlets,E09000030,1,153000,616000 +Tower Hamlets,E09000030,2,204000,872000 +Tower Hamlets,E09000030,3,261000,1335000 +Tower Hamlets,E09000030,4,448000,1896000 +Trafford,E08000009,1,102000,518000 +Trafford,E08000009,2,151000,518000 +Trafford,E08000009,3,212000,619000 +Trafford,E08000009,4,346000,1630000 +Tunbridge Wells,E07000116,1,102000,578000 +Tunbridge Wells,E07000116,2,143000,578000 +Tunbridge Wells,E07000116,3,326000,843000 +Tunbridge Wells,E07000116,4,490000,1729000 +Uttlesford,E07000077,1,83000,543000 +Uttlesford,E07000077,2,141000,543000 +Uttlesford,E07000077,3,266000,687000 +Uttlesford,E07000077,4,476000,1497000 +Vale of White Horse,E07000180,1,98000,473000 +Vale of White Horse,E07000180,2,132000,473000 +Vale of White Horse,E07000180,3,274000,585000 +Vale of White Horse,E07000180,4,409000,1157000 +Wakefield,E08000036,1,74000,233000 +Wakefield,E08000036,2,88000,233000 +Wakefield,E08000036,3,107000,322000 +Wakefield,E08000036,4,192000,605000 +Walsall,E08000030,1,79000,275000 +Walsall,E08000030,2,99000,275000 +Walsall,E08000030,3,121000,383000 +Walsall,E08000030,4,204000,853000 +Waltham Forest,E09000031,1,146000,538000 +Waltham Forest,E09000031,2,256000,643000 +Waltham Forest,E09000031,3,401000,821000 +Waltham Forest,E09000031,4,510000,1068000 +Wandsworth,E09000032,1,172000,715000 +Wandsworth,E09000032,2,284000,973000 +Wandsworth,E09000032,3,459000,1399000 +Wandsworth,E09000032,4,802000,2725000 +Warrington,E06000007,1,92000,361000 +Warrington,E06000007,2,108000,361000 +Warrington,E06000007,3,134000,449000 +Warrington,E06000007,4,251000,946000 +Warwick,E07000222,1,104000,473000 +Warwick,E07000222,2,139000,473000 +Warwick,E07000222,3,265000,647000 +Warwick,E07000222,4,421000,1253000 +Watford,E07000103,1,198000,516000 +Watford,E07000103,2,281000,547000 +Watford,E07000103,3,402000,849000 +Watford,E07000103,4,520000,1495000 +Waverley,E07000216,1,162000,723000 +Waverley,E07000216,2,206000,723000 +Waverley,E07000216,3,382000,929000 +Waverley,E07000216,4,565000,2204000 +Wealden,E07000065,1,129000,521000 +Wealden,E07000065,2,174000,521000 +Wealden,E07000065,3,264000,691000 +Wealden,E07000065,4,390000,1515000 +Welwyn Hatfield,E07000241,1,104000,482000 +Welwyn Hatfield,E07000241,2,149000,590000 +Welwyn Hatfield,E07000241,3,331000,824000 +Welwyn Hatfield,E07000241,4,493000,1714000 +West Berkshire,E06000037,1,97000,515000 +West Berkshire,E06000037,2,136000,515000 +West Berkshire,E06000037,3,312000,696000 +West Berkshire,E06000037,4,452000,1531000 +West Devon,E07000047,1,95000,421000 +West Devon,E07000047,2,145000,421000 +West Devon,E07000047,3,205000,527000 +West Devon,E07000047,4,259000,1018000 +West Lancashire,E07000127,1,57000,411000 +West Lancashire,E07000127,2,81000,411000 +West Lancashire,E07000127,3,81000,432000 +West Lancashire,E07000127,4,133000,880000 +West Lindsey,E07000142,1,78000,319000 +West Lindsey,E07000142,2,78000,319000 +West Lindsey,E07000142,3,110000,372000 +West Lindsey,E07000142,4,195000,691000 +West Northamptonshire,E06000062,1,59000,371000 +West Northamptonshire,E06000062,2,99000,371000 +West Northamptonshire,E06000062,3,185000,477000 +West Northamptonshire,E06000062,4,320000,947000 +West Oxfordshire,E07000181,1,119000,483000 +West Oxfordshire,E07000181,2,122000,483000 +West Oxfordshire,E07000181,3,279000,636000 +West Oxfordshire,E07000181,4,377000,1202000 +West Suffolk,E07000245,1,104000,367000 +West Suffolk,E07000245,2,126000,367000 +West Suffolk,E07000245,3,192000,475000 +West Suffolk,E07000245,4,278000,893000 +Westminster,E09000033,1,281000,1026000 +Westminster,E09000033,2,478000,2045000 +Westminster,E09000033,3,560000,3377000 +Westminster,E09000033,4,869000,11513000 +Westmorland,E06000064,1,81000,346000 +Westmorland,E06000064,2,81000,346000 +Westmorland,E06000064,3,114000,458000 +Westmorland,E06000064,4,195000,770000 +Wigan,E08000010,1,81000,243000 +Wigan,E08000010,2,81000,243000 +Wigan,E08000010,3,99000,318000 +Wigan,E08000010,4,197000,530000 +Wiltshire,E06000054,1,105000,438000 +Wiltshire,E06000054,2,120000,438000 +Wiltshire,E06000054,3,225000,565000 +Wiltshire,E06000054,4,341000,1139000 +Winchester,E07000094,1,124000,624000 +Winchester,E07000094,2,138000,660000 +Winchester,E07000094,3,298000,893000 +Winchester,E07000094,4,458000,1615000 +Windsor and Maidenhead,E06000040,1,228000,695000 +Windsor and Maidenhead,E06000040,2,307000,695000 +Windsor and Maidenhead,E06000040,3,422000,974000 +Windsor and Maidenhead,E06000040,4,597000,2059000 +Wirral,E08000015,1,77000,350000 +Wirral,E08000015,2,89000,350000 +Wirral,E08000015,3,112000,375000 +Wirral,E08000015,4,161000,818000 +Woking,E07000217,1,191000,570000 +Woking,E07000217,2,279000,570000 +Woking,E07000217,3,393000,875000 +Woking,E07000217,4,538000,1750000 +Wokingham,E06000041,1,140000,546000 +Wokingham,E06000041,2,151000,546000 +Wokingham,E06000041,3,290000,686000 +Wokingham,E06000041,4,518000,1299000 +Wolverhampton,E08000031,1,43000,249000 +Wolverhampton,E08000031,2,69000,249000 +Wolverhampton,E08000031,3,101000,327000 +Wolverhampton,E08000031,4,170000,703000 +Worcester,E07000237,1,90000,309000 +Worcester,E07000237,2,138000,309000 +Worcester,E07000237,3,190000,423000 +Worcester,E07000237,4,290000,793000 +Worthing,E07000229,1,148000,460000 +Worthing,E07000229,2,188000,465000 +Worthing,E07000229,3,297000,586000 +Worthing,E07000229,4,393000,864000 +Wychavon,E07000238,1,100000,432000 +Wychavon,E07000238,2,100000,452000 +Wychavon,E07000238,3,167000,546000 +Wychavon,E07000238,4,320000,969000 +Wyre,E07000128,1,72000,328000 +Wyre,E07000128,2,87000,328000 +Wyre,E07000128,3,114000,381000 +Wyre,E07000128,4,199000,648000 +Wyre Forest,E07000239,1,93000,358000 +Wyre Forest,E07000239,2,100000,358000 +Wyre Forest,E07000239,3,146000,398000 +Wyre Forest,E07000239,4,231000,825000 +York,E06000014,1,105000,414000 +York,E06000014,2,173000,414000 +York,E06000014,3,220000,529000 +York,E06000014,4,322000,983000 diff --git a/db/migrate/20230105103134_add_income2_value_check.rb b/db/migrate/20230105103134_add_income2_value_check.rb new file mode 100644 index 000000000..38a3ebc65 --- /dev/null +++ b/db/migrate/20230105103134_add_income2_value_check.rb @@ -0,0 +1,5 @@ +class AddIncome2ValueCheck < ActiveRecord::Migration[7.0] + def change + add_column :sales_logs, :income2_value_check, :integer + end +end diff --git a/db/migrate/20230210122037_add_staircasesale_to_sales_logs.rb b/db/migrate/20230210122037_add_staircasesale_to_sales_logs.rb new file mode 100644 index 000000000..824549cff --- /dev/null +++ b/db/migrate/20230210122037_add_staircasesale_to_sales_logs.rb @@ -0,0 +1,5 @@ +class AddStaircasesaleToSalesLogs < ActiveRecord::Migration[7.0] + def change + add_column :sales_logs, :staircasesale, :integer + end +end diff --git a/db/migrate/20230210143120_add_ethnic_fields_for_buyer2.rb b/db/migrate/20230210143120_add_ethnic_fields_for_buyer2.rb new file mode 100644 index 000000000..c545a0539 --- /dev/null +++ b/db/migrate/20230210143120_add_ethnic_fields_for_buyer2.rb @@ -0,0 +1,8 @@ +class AddEthnicFieldsForBuyer2 < ActiveRecord::Migration[7.0] + def change + change_table :sales_logs, bulk: true do |t| + t.column :ethnic_group2, :integer + t.column :ethnicbuy2, :integer + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 1c8e30afd..6cb5420d5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_02_13_140932) do +ActiveRecord::Schema[7.0].define(version: 2023_02_10_143120) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -520,12 +520,15 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_13_140932) do t.integer "value_value_check" t.integer "old_persons_shared_ownership_value_check" t.integer "staircase_bought_value_check" + t.integer "income2_value_check" t.integer "monthly_charges_value_check" t.integer "details_known_5" t.integer "details_known_6" t.integer "saledate_check" t.integer "prevshared" - t.integer "proplen_asked" + t.integer "staircasesale" + t.integer "ethnic_group2" + t.integer "ethnicbuy2" t.index ["bulk_upload_id"], name: "index_sales_logs_on_bulk_upload_id" t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id" t.index ["owning_organisation_id"], name: "index_sales_logs_on_owning_organisation_id" diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 8d38e2e0a..c8a433b8f 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -1,12 +1,11 @@ GEM remote: https://rubygems.org/ specs: - activesupport (6.0.6) + activesupport (7.0.4.2) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) addressable (2.8.1) public_suffix (>= 2.0.2, < 6.0) coffee-script (2.4.1) @@ -14,30 +13,30 @@ GEM execjs coffee-script-source (1.11.1) colorator (1.1.0) - commonmarker (0.23.6) - concurrent-ruby (1.1.10) + commonmarker (0.23.8) + concurrent-ruby (1.2.0) dnsruby (1.61.9) simpleidn (~> 0.1) em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) - ethon (0.15.0) + ethon (0.16.0) ffi (>= 1.15.0) eventmachine (1.2.7) execjs (2.8.1) - faraday (2.5.2) + faraday (2.7.4) faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) - faraday-net_http (3.0.0) + faraday-net_http (3.0.2) ffi (1.15.5) forwardable-extended (2.6.0) gemoji (3.0.1) - github-pages (227) + github-pages (228) github-pages-health-check (= 1.17.9) - jekyll (= 3.9.2) + jekyll (= 3.9.3) jekyll-avatar (= 0.7.0) jekyll-coffeescript (= 1.1.1) - jekyll-commonmark-ghpages (= 0.2.0) + jekyll-commonmark-ghpages (= 0.4.0) jekyll-default-layout (= 0.1.4) jekyll-feed (= 0.15.1) jekyll-gist (= 1.5.0) @@ -71,7 +70,7 @@ GEM jemoji (= 0.12.0) kramdown (= 2.3.2) kramdown-parser-gfm (= 1.1.0) - liquid (= 4.0.3) + liquid (= 4.0.4) mercenary (~> 0.3) minima (= 2.5.1) nokogiri (>= 1.13.6, < 2.0) @@ -83,17 +82,17 @@ GEM octokit (~> 4.0) public_suffix (>= 3.0, < 5.0) typhoeus (~> 1.3) - html-pipeline (2.14.2) + html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.8.0) - i18n (0.9.5) + i18n (1.12.0) concurrent-ruby (~> 1.0) - jekyll (3.9.2) + jekyll (3.9.3) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) - i18n (~> 0.7) + i18n (>= 0.7, < 2) jekyll-sass-converter (~> 1.0) jekyll-watch (~> 2.0) kramdown (>= 1.17, < 3) @@ -109,11 +108,11 @@ GEM coffee-script-source (~> 1.11.1) jekyll-commonmark (1.4.0) commonmarker (~> 0.22) - jekyll-commonmark-ghpages (0.2.0) - commonmarker (~> 0.23.4) + jekyll-commonmark-ghpages (0.4.0) + commonmarker (~> 0.23.7) jekyll (~> 3.9.0) jekyll-commonmark (~> 1.4.0) - rouge (>= 2.0, < 4.0) + rouge (>= 2.0, < 5.0) jekyll-default-layout (0.1.4) jekyll (~> 3.0) jekyll-feed (0.15.1) @@ -201,8 +200,8 @@ GEM rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - liquid (4.0.3) - listen (3.7.1) + liquid (4.0.4) + listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.3.6) @@ -210,12 +209,12 @@ GEM jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minitest (5.16.3) - nokogiri (1.13.10-arm64-darwin) + minitest (5.17.0) + nokogiri (1.14.2-arm64-darwin) racc (~> 1.4) - nokogiri (1.13.10-x86_64-darwin) + nokogiri (1.14.2-x86_64-darwin) racc (~> 1.4) - nokogiri (1.13.10-x86_64-linux) + nokogiri (1.14.2-x86_64-linux) racc (~> 1.4) octokit (4.25.1) faraday (>= 1, < 3) @@ -223,7 +222,7 @@ GEM pathutil (0.16.2) forwardable-extended (~> 2.6) public_suffix (4.0.7) - racc (1.6.1) + racc (1.6.2) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) @@ -244,17 +243,15 @@ GEM unf (~> 0.1.4) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) typhoeus (1.4.0) ethon (>= 0.9.0) - tzinfo (1.2.10) - thread_safe (~> 0.1) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) unf (0.1.4) unf_ext unf_ext (0.0.8.2) unicode-display_width (1.8.0) - webrick (1.7.0) - zeitwerk (2.6.0) + webrick (1.8.1) PLATFORMS arm64-darwin-21 diff --git a/lib/tasks/data_export.rake b/lib/tasks/data_export.rake index 17f258293..a86712d83 100644 --- a/lib/tasks/data_export.rake +++ b/lib/tasks/data_export.rake @@ -11,21 +11,3 @@ namespace :core do DataExportXmlJob.perform_later(full_update:) end end - -namespace :illness_type_0 do - desc "Export log data where illness_type_0 == 1" - task export: :environment do |_task| - logs = LettingsLog.where(illness_type_0: 1, status: "completed").includes(created_by: :organisation) - puts "log_id,created_by_id,organisation_id,organisation_name,startdate" - - logs.each do |log| - puts [ - log.id, - log.created_by_id, - log.created_by.organisation.id, - log.created_by.organisation.name, - log.startdate&.strftime("%d/%m/%Y"), - ].join(",") - end - end -end diff --git a/spec/factories/location.rb b/spec/factories/location.rb index 75b4380f5..f43da0ac8 100644 --- a/spec/factories/location.rb +++ b/spec/factories/location.rb @@ -10,6 +10,7 @@ FactoryBot.define do startdate { Time.zone.local(2022, 4, 1) } confirmed { true } scheme + trait :export do postcode { "SW1A 2AA" } name { "Downing Street" } @@ -19,5 +20,9 @@ FactoryBot.define do scheme { FactoryBot.create(:scheme, :export) } old_visible_id { "111" } end + + trait :with_old_visible_id do + old_visible_id { rand(9_999_999).to_s } + end end end diff --git a/spec/factories/organisation.rb b/spec/factories/organisation.rb index 147f847d6..fa2650eb5 100644 --- a/spec/factories/organisation.rb +++ b/spec/factories/organisation.rb @@ -13,6 +13,14 @@ FactoryBot.define do trait :with_old_visible_id do old_visible_id { rand(9_999_999).to_s } end + + trait :prp do + provider_type { "PRP" } + end + + trait :does_not_own_stock do + holds_own_stock { false } + end end factory :organisation_rent_period do diff --git a/spec/factories/scheme.rb b/spec/factories/scheme.rb index 6c08e269d..155dea11a 100644 --- a/spec/factories/scheme.rb +++ b/spec/factories/scheme.rb @@ -21,5 +21,9 @@ FactoryBot.define do primary_client_group { "G" } secondary_client_group { "M" } end + + trait :with_old_visible_id do + old_visible_id { rand(9_999_999) } + end end end diff --git a/spec/fixtures/exports/general_needs_log.csv b/spec/fixtures/exports/general_needs_log.csv index afaa9e745..8cc656f19 100644 --- a/spec/fixtures/exports/general_needs_log.csv +++ b/spec/fixtures/exports/general_needs_log.csv @@ -1,2 +1,2 @@ -status,tenancycode,age1,sex1,ethnic,national,prevten,ecstat1,hhmemb,age2,sex2,ecstat2,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,leftreg,reservist,illness,preg_occ,startertenancy,tenancylength,tenancy,ppostcode_full,rsnvac,unittype_gn,beds,offered,wchair,earnings,incfreq,benefits,period,layear,waityear,postcode_full,reasonpref,cbl,chr,cap,reasonother,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_f,housingneeds_g,housingneeds_h,illness_type_1,illness_type_2,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,irproduct_other,reason,propcode,la,prevloc,hb,hbrentshortfall,mrcdate,incref,startdate,armedforces,unitletas,builtype,voiddate,renttype,needstype,lettype,totchild,totelder,totadult,nocharge,referral,brent,scharge,pscharge,supcharg,tcharge,tshortfall,chcharge,ppcodenk,has_benefits,renewal,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat2,relat3,relat4,relat5,relat6,relat7,relat8,lar,irproduct,joint,sheltered,hhtype,new_old,vacdays,form,owningorgid,owningorgname,hcnum,maningorgid,maningorgname,manhcnum,createddate,uploaddate +status,tenancycode,age1,sex1,ethnic,national,prevten,ecstat1,hhmemb,age2,sex2,ecstat2,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,leftreg,reservist,illness,preg_occ,startertenancy,tenancylength,tenancy,ppostcode_full,rsnvac,unittype_gn,beds,offered,wchair,earnings,incfreq,benefits,period,layear,waityear,postcode_full,reasonpref,cbl,chr,cap,reasonother,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_f,housingneeds_g,housingneeds_h,illness_type_1,illness_type_2,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,irproduct_other,reason,propcode,la,prevloc,hb,hbrentshortfall,mrcdate,incref,startdate,armedforces,unitletas,builtype,voiddate,renttype,needstype,lettype,totchild,totelder,totadult,nocharge,referral,brent,scharge,pscharge,supcharg,tcharge,tshortfall,chcharge,ppcodenk,has_benefits,renewal,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat2,relat3,relat4,relat5,relat6,relat7,relat8,lar,irproduct,joint,sheltered,hhtype,new_old,vacdays,formid,owningorgid,owningorgname,hcnum,maningorgid,maningorgname,manhcnum,createddate,uploaddate 2,BZ737,35,F,2,4,6,0,2,32,M,6,,,,,,,,,,,,,,,,,,,1,4,1,1,1,2,1,5,1,SE2 6RT,6,7,3,2,1,68,1,1,2,2,1,NW1 5TY,1,2,1,2,,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,,,4,123,E09000003,E07000105,6,1,2020-05-05T10:36:49+01:00,0,2022-02-02T10:36:49+00:00,1,2,1,2019-11-03T00:00:00+00:00,2,1,7,0,0,2,0,2,200.0,50.0,40.0,35.0,325.0,12.0,,1,1,0,100.0,25.0,20.0,17.5,162.5,6.0,0,1,,2,P,,,,,,,,,,,4,2,638,{id},{owning_org_id},DLUHC,1234,{managing_org_id},DLUHC,1234,2022-02-08T16:52:15+00:00,2022-02-08T16:52:15+00:00 diff --git a/spec/fixtures/exports/general_needs_log.xml b/spec/fixtures/exports/general_needs_log.xml index 1e60333dc..62542af54 100644 --- a/spec/fixtures/exports/general_needs_log.xml +++ b/spec/fixtures/exports/general_needs_log.xml @@ -135,7 +135,7 @@ 4 2 638 -
{id}
+ {id} {owning_org_id} DLUHC 1234 diff --git a/spec/fixtures/exports/supported_housing_logs.xml b/spec/fixtures/exports/supported_housing_logs.xml index 7674984b4..69f52161c 100644 --- a/spec/fixtures/exports/supported_housing_logs.xml +++ b/spec/fixtures/exports/supported_housing_logs.xml @@ -134,7 +134,7 @@ 4 2 638 -
{id}
+ {id} {owning_org_id} DLUHC 1234 diff --git a/spec/helpers/collection_time_helper_spec.rb b/spec/helpers/collection_time_helper_spec.rb index 3b02802f2..c2eb2fedb 100644 --- a/spec/helpers/collection_time_helper_spec.rb +++ b/spec/helpers/collection_time_helper_spec.rb @@ -21,6 +21,10 @@ RSpec.describe CollectionTimeHelper do it "returns the correct current start date" do expect(current_collection_start_date).to eq(Time.zone.local(2022, 4, 1)) end + + it "returns the correct current end date" do + expect(current_collection_end_date).to eq(Time.zone.local(2023, 3, 31)) + end end context "with the date before 1st of April" do @@ -29,6 +33,48 @@ RSpec.describe CollectionTimeHelper do it "returns the previous year as the current start year" do expect(current_collection_start_year).to eq(2021) end + + it "returns the correct current start date" do + expect(current_collection_start_date).to eq(Time.zone.local(2021, 4, 1)) + end + + it "returns the correct current end date" do + expect(current_collection_end_date).to eq(Time.zone.local(2022, 3, 31)) + end + end + end + + describe "Any collection year" do + context "when the date is after 1st of April" do + let(:now) { Time.utc(2022, 8, 3) } + + it "returns the same year as the current start year" do + expect(collection_start_year(now)).to eq(2022) + end + + it "returns the correct current start date" do + expect(collection_start_date(now)).to eq(Time.zone.local(2022, 4, 1)) + end + + it "returns the correct current end date" do + expect(collection_end_date(now)).to eq(Time.zone.local(2023, 3, 31)) + end + end + + context "with the date before 1st of April" do + let(:now) { Time.utc(2022, 2, 3) } + + it "returns the previous year as the current start year" do + expect(collection_start_year(now)).to eq(2021) + end + + it "returns the correct current start date" do + expect(collection_start_date(now)).to eq(Time.zone.local(2021, 4, 1)) + end + + it "returns the correct current end date" do + expect(collection_end_date(now)).to eq(Time.zone.local(2022, 3, 31)) + end end end end diff --git a/spec/helpers/filters_helper_spec.rb b/spec/helpers/filters_helper_spec.rb index 656578326..3eccac743 100644 --- a/spec/helpers/filters_helper_spec.rb +++ b/spec/helpers/filters_helper_spec.rb @@ -116,4 +116,30 @@ RSpec.describe FiltersHelper do end end end + + describe "#collection_year_options" do + context "when not production" do + it "includes 2023/2024 option" do + expect(collection_year_options).to eq( + { + "2021": "2021/22", "2022": "2022/23", "2023": "2023/24" + }, + ) + end + end + + context "when production" do + before do + allow(Rails.env).to receive(:production?).and_return(true) + end + + it "includes 2023/2024 option" do + expect(collection_year_options).to eq( + { + "2021": "2021/22", "2022": "2022/23" + }, + ) + end + end + end end diff --git a/spec/helpers/interruption_screen_helper_spec.rb b/spec/helpers/interruption_screen_helper_spec.rb index 8153b9d7b..f46aca9ae 100644 --- a/spec/helpers/interruption_screen_helper_spec.rb +++ b/spec/helpers/interruption_screen_helper_spec.rb @@ -13,6 +13,7 @@ RSpec.describe InterruptionScreenHelper do earnings: 750, incfreq: 1, created_by: user, + sex1: "F", ) end @@ -94,6 +95,54 @@ RSpec.describe InterruptionScreenHelper do .to eq("") end end + + context "when an argument is given not for a label" do + translation = "test.title_text.one_argument" + it "returns the correct text" do + informative_text_hash = { + "translation" => translation, + "arguments" => [ + { + "key" => "earnings", + "i18n_template" => "argument", + }, + ], + } + expect(display_informative_text(informative_text_hash, lettings_log)).to eq(I18n.t(translation, argument: lettings_log.earnings)) + end + end + + context "when and argument is given with a key and arguments for the key" do + it "makes the correct method call" do + informative_text_hash = { + "arguments" => [ + { + "key" => "retirement_age_for_person", + "arguments_for_key" => 1, + "i18n_template" => "argument", + }, + ], + } + allow(lettings_log).to receive(:retirement_age_for_person) + display_informative_text(informative_text_hash, lettings_log) + expect(lettings_log).to have_received(:retirement_age_for_person).with(1) + end + + it "returns the correct text" do + translation = "test.title_text.one_argument" + informative_text_hash = { + "translation" => translation, + "arguments" => [ + { + "key" => "retirement_age_for_person", + "arguments_for_key" => 1, + "i18n_template" => "argument", + }, + ], + } + expect(display_informative_text(informative_text_hash, lettings_log)).to eq(I18n.t(translation, argument: lettings_log.retirement_age_for_person(1))) + end + end end describe "display_title_text" do @@ -113,12 +162,12 @@ RSpec.describe InterruptionScreenHelper do { "key" => "ecstat1", "label" => true, - "i18n_template" => "ecstat1", + "i18n_template" => "argument", }, ], } expect(display_title_text(title_text, lettings_log)) - .to eq(I18n.t("test.title_text.one_argument", ecstat1: lettings_log.form.get_question("ecstat1", lettings_log).answer_label(lettings_log).downcase)) + .to eq(I18n.t("test.title_text.one_argument", argument: lettings_log.form.get_question("ecstat1", lettings_log).answer_label(lettings_log).downcase)) end end diff --git a/spec/helpers/tasklist_helper_spec.rb b/spec/helpers/tasklist_helper_spec.rb index f7726745d..b5bfe89f5 100644 --- a/spec/helpers/tasklist_helper_spec.rb +++ b/spec/helpers/tasklist_helper_spec.rb @@ -1,100 +1,55 @@ require "rails_helper" RSpec.describe TasklistHelper do - describe "with lettings" do - let(:empty_lettings_log) { FactoryBot.create(:lettings_log) } - let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, needstype: 1) } - let(:fake_2021_2022_form) { Form.new("spec/fixtures/forms/2021_2022.json") } + let(:now) { Time.utc(2022, 6, 1) } - context "with 2021 2022 form" do - before do - allow(FormHandler.instance).to receive(:current_lettings_form).and_return(fake_2021_2022_form) - end + around do |example| + Timecop.freeze(now) do + Singleton.__init__(FormHandler) + example.run + end + Timecop.return + Singleton.__init__(FormHandler) + end - describe "get next incomplete section" do - it "returns the first subsection name if it is not completed" do - expect(get_next_incomplete_section(lettings_log).id).to eq("household_characteristics") - end + describe "with lettings" do + let(:empty_lettings_log) { create(:lettings_log) } + let(:lettings_log) { build(:lettings_log, :in_progress, needstype: 1) } - it "returns the first subsection name if it is partially completed" do - lettings_log["tenancycode"] = 123 - expect(get_next_incomplete_section(lettings_log).id).to eq("household_characteristics") - end + describe "get next incomplete section" do + it "returns the first subsection name if it is not completed" do + expect(get_next_incomplete_section(lettings_log).id).to eq("household_characteristics") end - describe "get sections count" do - it "returns the total of sections if no status is given" do - expect(get_subsections_count(empty_lettings_log)).to eq(8) - end - - it "returns 0 sections for completed sections if no sections are completed" do - expect(get_subsections_count(empty_lettings_log, :completed)).to eq(0) - end - - it "returns the number of not started sections" do - expect(get_subsections_count(empty_lettings_log, :not_started)).to eq(8) - end - - it "returns the number of sections in progress" do - expect(get_subsections_count(lettings_log, :in_progress)).to eq(3) - end - - it "returns 0 for invalid state" do - expect(get_subsections_count(lettings_log, :fake)).to eq(0) - end + it "returns the first subsection name if it is partially completed" do + lettings_log["tenancycode"] = 123 + expect(get_next_incomplete_section(lettings_log).id).to eq("household_characteristics") end + end - describe "get_next_page_or_check_answers" do - let(:subsection) { lettings_log.form.get_subsection("household_characteristics") } - let(:user) { FactoryBot.build(:user) } - - it "returns the check answers page path if the section has been started already" do - expect(next_page_or_check_answers(subsection, lettings_log, user)).to match(/check-answers/) - end - - it "returns the first question page path for the section if it has not been started yet" do - expect(next_page_or_check_answers(subsection, empty_lettings_log, user)).to match(/tenant-code-test/) - end - - it "when first question being not routed to returns the next routed question link" do - empty_lettings_log.housingneeds_a = "No" - expect(next_page_or_check_answers(subsection, empty_lettings_log, user)).to match(/person-1-gender/) - end + describe "get sections count" do + it "returns the total of sections if no status is given" do + expect(get_subsections_count(empty_lettings_log)).to eq(1) end - describe "subsection link" do - let(:subsection) { lettings_log.form.get_subsection("household_characteristics") } - let(:user) { FactoryBot.build(:user) } - - context "with a subsection that's enabled" do - it "returns the subsection link url" do - expect(subsection_link(subsection, lettings_log, user)).to match(/household-characteristics/) - end - end + it "returns 0 sections for completed sections if no sections are completed" do + expect(get_subsections_count(empty_lettings_log, :completed)).to eq(0) + end - context "with a subsection that cannot be started yet" do - before do - allow(subsection).to receive(:status).with(lettings_log).and_return(:cannot_start_yet) - end + it "returns the number of not started sections" do + expect(get_subsections_count(empty_lettings_log, :not_started)).to eq(1) + end - it "returns the label instead of a link" do - expect(subsection_link(subsection, lettings_log, user)).to match(subsection.label) - end - end + it "returns the number of sections in progress" do + expect(get_subsections_count(lettings_log, :in_progress)).to eq(2) end - end - end - describe "#review_log_text" do - around do |example| - Timecop.freeze(now) do - Singleton.__init__(FormHandler) - example.run + it "returns 0 for invalid state" do + expect(get_subsections_count(lettings_log, :fake)).to eq(0) end - Singleton.__init__(FormHandler) end - context "with lettings log" do + describe "review_log_text" do context "when collection_period_open? == true" do context "with 2023 deadline" do let(:now) { Time.utc(2022, 6, 1) } @@ -113,7 +68,7 @@ RSpec.describe TasklistHelper do it "returns relevant text" do expect(review_log_text(lettings_log)).to eq( - "You can #{govuk_link_to 'review and make changes to this log', review_lettings_log_path(lettings_log)} until 1 July 2024.".html_safe, + "You can #{govuk_link_to 'review and make changes to this log', review_lettings_log_path(lettings_log)} until 9 July 2024.".html_safe, ) end end @@ -129,6 +84,30 @@ RSpec.describe TasklistHelper do end end + describe "subsection link" do + let(:lettings_log) { create(:lettings_log, :completed) } + let(:subsection) { lettings_log.form.get_subsection("household_characteristics") } + let(:user) { build(:user) } + + context "with a subsection that's enabled" do + it "returns the subsection link url" do + expect(subsection_link(subsection, lettings_log, user)).to match(/household-characteristics/) + end + end + + context "with a subsection that cannot be started yet" do + before do + allow(subsection).to receive(:status).with(lettings_log).and_return(:cannot_start_yet) + end + + it "returns the label instead of a link" do + expect(subsection_link(subsection, lettings_log, user)).to match(subsection.label) + end + end + end + end + + describe "#review_log_text" do context "with sales log" do context "when collection_period_open? == true" do let(:now) { Time.utc(2022, 6, 1) } @@ -136,17 +115,19 @@ RSpec.describe TasklistHelper do it "returns relevant text" do expect(review_log_text(sales_log)).to eq( - "You can #{govuk_link_to 'review and make changes to this log', review_lettings_log_path(sales_log)} until 1 July 2023.".html_safe, + "You can #{govuk_link_to 'review and make changes to this log', review_sales_log_path(id: sales_log, sales_log: true)} until 7 July 2023.".html_safe, ) end end context "when collection_period_open? == false" do - let(:now) { Time.utc(2023, 7, 8) } - let(:sales_log) { create(:sales_log, :completed, saledate: Time.utc(2023, 2, 8)) } + let(:now) { Time.utc(2022, 6, 1) } + let!(:sales_log) { create(:sales_log, :completed) } it "returns relevant text" do - expect(review_log_text(sales_log)).to eq("This log is from the 2022/2023 collection window, which is now closed.") + Timecop.freeze(now + 1.year) do + expect(review_log_text(sales_log)).to eq("This log is from the 2021/2022 collection window, which is now closed.") + end end end end diff --git a/spec/mailers/bulk_upload_mailer_spec.rb b/spec/mailers/bulk_upload_mailer_spec.rb index a3706beb3..d0217bfaf 100644 --- a/spec/mailers/bulk_upload_mailer_spec.rb +++ b/spec/mailers/bulk_upload_mailer_spec.rb @@ -5,7 +5,7 @@ RSpec.describe BulkUploadMailer do let(:notify_client) { instance_double(Notifications::Client) } let(:user) { create(:user, email: "user@example.com") } - let(:bulk_upload) { build(:bulk_upload, :lettings, user:) } + let(:bulk_upload) { create(:bulk_upload, :lettings, user:) } before do allow(Notifications::Client).to receive(:new).and_return(notify_client) @@ -75,4 +75,56 @@ RSpec.describe BulkUploadMailer do end end end + + describe "#send_correct_and_upload_again_mail" do + context "when 2 columns with errors" do + before do + create(:bulk_upload_error, bulk_upload:, col: "A") + create(:bulk_upload_error, bulk_upload:, col: "B") + end + + it "sends correctly formed email with A, B" do + expect(notify_client).to receive(:send_email).with( + email_address: user.email, + template_id: described_class::BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID, + personalisation: { + filename: bulk_upload.filename, + upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), + year_combo: bulk_upload.year_combo, + lettings_or_sales: bulk_upload.log_type, + error_description: "We noticed that you have a lot of similar errors in column A, B. Please correct your data export and upload again.", + summary_report_link: "http://localhost:3000/lettings-logs/bulk-upload-results/#{bulk_upload.id}/summary", + }, + ) + + mailer.send_correct_and_upload_again_mail(bulk_upload:) + end + end + + context "when 4 columns with errors" do + before do + create(:bulk_upload_error, bulk_upload:, col: "A") + create(:bulk_upload_error, bulk_upload:, col: "B") + create(:bulk_upload_error, bulk_upload:, col: "C") + create(:bulk_upload_error, bulk_upload:, col: "D") + end + + it "sends correctly formed email with A, B, C and more" do + expect(notify_client).to receive(:send_email).with( + email_address: user.email, + template_id: described_class::BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID, + personalisation: { + filename: bulk_upload.filename, + upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), + year_combo: bulk_upload.year_combo, + lettings_or_sales: bulk_upload.log_type, + error_description: "We noticed that you have a lot of similar errors in column A, B, C and more. Please correct your data export and upload again.", + summary_report_link: "http://localhost:3000/lettings-logs/bulk-upload-results/#{bulk_upload.id}/summary", + }, + ) + + mailer.send_correct_and_upload_again_mail(bulk_upload:) + end + end + end end diff --git a/spec/models/form/lettings/pages/location_spec.rb b/spec/models/form/lettings/pages/location_spec.rb index aefcd59a9..659a4dd26 100644 --- a/spec/models/form/lettings/pages/location_spec.rb +++ b/spec/models/form/lettings/pages/location_spec.rb @@ -6,6 +6,12 @@ RSpec.describe Form::Lettings::Pages::Location, type: :model do let(:page_id) { nil } let(:page_definition) { nil } let(:subsection) { instance_double(Form::Subsection) } + let(:form) { instance_double(Form) } + + before do + allow(form).to receive(:start_date).and_return(Time.zone.local(2022, 4, 1)) + allow(subsection).to receive(:form).and_return(form) + end it "has correct subsection" do expect(page.subsection).to eq(subsection) diff --git a/spec/models/form/lettings/questions/location_id_spec.rb b/spec/models/form/lettings/questions/location_id_spec.rb index ca3d402a9..afb39ce11 100644 --- a/spec/models/form/lettings/questions/location_id_spec.rb +++ b/spec/models/form/lettings/questions/location_id_spec.rb @@ -6,6 +6,14 @@ RSpec.describe Form::Lettings::Questions::LocationId, type: :model do let(:question_id) { nil } let(:question_definition) { nil } let(:page) { instance_double(Form::Page) } + let(:subsection) { instance_double(Form::Subsection) } + let(:form) { instance_double(Form) } + + before do + allow(form).to receive(:start_date).and_return(Time.zone.local(2022, 4, 1)) + allow(page).to receive(:subsection).and_return(subsection) + allow(subsection).to receive(:form).and_return(form) + end it "has correct page" do expect(question.page).to eq(page) @@ -103,4 +111,14 @@ RSpec.describe Form::Lettings::Questions::LocationId, type: :model do end end end + + context "with collection year on or after 2023" do + before do + allow(form).to receive(:start_date).and_return(Time.zone.local(2023, 4, 1)) + end + + it "has the correct header" do + expect(question.header).to eq("Which location is this letting for?") + end + end end diff --git a/spec/models/form/sales/pages/about_staircase_spec.rb b/spec/models/form/sales/pages/about_staircase_spec.rb index a2fba103e..3828f52e4 100644 --- a/spec/models/form/sales/pages/about_staircase_spec.rb +++ b/spec/models/form/sales/pages/about_staircase_spec.rb @@ -11,8 +11,24 @@ RSpec.describe Form::Sales::Pages::AboutStaircase, type: :model do expect(page.subsection).to eq(subsection) end - it "has correct questions" do - expect(page.questions.map(&:id)).to eq(%w[stairbought stairowned]) + describe "questions" do + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date:)) } + + context "when 2022" do + let(:start_date) { Time.utc(2022, 2, 8) } + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[stairbought stairowned]) + end + end + + context "when 2023" do + let(:start_date) { Time.utc(2023, 2, 8) } + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[stairbought stairowned staircasesale]) + end + end end it "has the correct id" do diff --git a/spec/models/form/sales/pages/buyer2_ethnic_background_arab_spec.rb b/spec/models/form/sales/pages/buyer2_ethnic_background_arab_spec.rb new file mode 100644 index 000000000..1fbbe3bc8 --- /dev/null +++ b/spec/models/form/sales/pages/buyer2_ethnic_background_arab_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::Buyer2EthnicBackgroundArab, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[ethnicbuy2]) + end + + it "has the correct id" do + expect(page.id).to eq("buyer_2_ethnic_background_arab") + end + + it "has the correct header" do + expect(page.header).to be_nil + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([{ "ethnic_group2" => 4 }]) + end +end diff --git a/spec/models/form/sales/pages/buyer2_ethnic_background_asian_spec.rb b/spec/models/form/sales/pages/buyer2_ethnic_background_asian_spec.rb new file mode 100644 index 000000000..60878f99a --- /dev/null +++ b/spec/models/form/sales/pages/buyer2_ethnic_background_asian_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::Buyer2EthnicBackgroundAsian, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[ethnicbuy2]) + end + + it "has the correct id" do + expect(page.id).to eq("buyer_2_ethnic_background_asian") + end + + it "has the correct header" do + expect(page.header).to be_nil + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([{ "ethnic_group2" => 2 }]) + end +end diff --git a/spec/models/form/sales/pages/buyer2_ethnic_background_black_spec.rb b/spec/models/form/sales/pages/buyer2_ethnic_background_black_spec.rb new file mode 100644 index 000000000..2643f460b --- /dev/null +++ b/spec/models/form/sales/pages/buyer2_ethnic_background_black_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::Buyer2EthnicBackgroundBlack, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[ethnicbuy2]) + end + + it "has the correct id" do + expect(page.id).to eq("buyer_2_ethnic_background_black") + end + + it "has the correct header" do + expect(page.header).to be_nil + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([{ "ethnic_group2" => 3 }]) + end +end diff --git a/spec/models/form/sales/pages/buyer2_ethnic_background_mixed_spec.rb b/spec/models/form/sales/pages/buyer2_ethnic_background_mixed_spec.rb new file mode 100644 index 000000000..4505ce08b --- /dev/null +++ b/spec/models/form/sales/pages/buyer2_ethnic_background_mixed_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::Buyer2EthnicBackgroundMixed, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[ethnicbuy2]) + end + + it "has the correct id" do + expect(page.id).to eq("buyer_2_ethnic_background_mixed") + end + + it "has the correct header" do + expect(page.header).to be_nil + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([{ "ethnic_group2" => 1 }]) + end +end diff --git a/spec/models/form/sales/pages/buyer2_ethnic_background_white_spec.rb b/spec/models/form/sales/pages/buyer2_ethnic_background_white_spec.rb new file mode 100644 index 000000000..7647f35af --- /dev/null +++ b/spec/models/form/sales/pages/buyer2_ethnic_background_white_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::Buyer2EthnicBackgroundWhite, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[ethnicbuy2]) + end + + it "has the correct id" do + expect(page.id).to eq("buyer_2_ethnic_background_white") + end + + it "has the correct header" do + expect(page.header).to be_nil + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([{ "ethnic_group2" => 0 }]) + end +end diff --git a/spec/models/form/sales/pages/buyer2_ethnic_group_spec.rb b/spec/models/form/sales/pages/buyer2_ethnic_group_spec.rb new file mode 100644 index 000000000..2767a2168 --- /dev/null +++ b/spec/models/form/sales/pages/buyer2_ethnic_group_spec.rb @@ -0,0 +1,42 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::Buyer2EthnicGroup, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[ethnic_group2]) + end + + it "has the correct id" do + expect(page.id).to eq("buyer_2_ethnic_group") + end + + it "has the correct header" do + expect(page.header).to be_nil + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([ + { + "jointpur" => 1, + "privacynotice" => 1, + }, + { + "jointpur" => 1, + "noint" => 1, + }, + ]) + end +end diff --git a/spec/models/form/sales/questions/buyer1_income_value_check_spec.rb b/spec/models/form/sales/questions/buyer1_income_value_check_spec.rb index 552f4f56f..89801a398 100644 --- a/spec/models/form/sales/questions/buyer1_income_value_check_spec.rb +++ b/spec/models/form/sales/questions/buyer1_income_value_check_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Form::Sales::Questions::Buyer1IncomeValueCheck, type: :model do end it "has the correct header" do - expect(question.header).to eq("Are you sure this income is correct?") + expect(question.header).to eq("Are you sure this is correct?") end it "has the correct check_answer_label" do diff --git a/spec/models/form/sales/questions/buyer2_ethnic_background_arab_spec.rb b/spec/models/form/sales/questions/buyer2_ethnic_background_arab_spec.rb new file mode 100644 index 000000000..9fd408db9 --- /dev/null +++ b/spec/models/form/sales/questions/buyer2_ethnic_background_arab_spec.rb @@ -0,0 +1,48 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::Buyer2EthnicBackgroundArab, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page) } + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("ethnicbuy2") + end + + it "has the correct header" do + expect(question.header).to eq("Which of the following best describes the buyer 2’s Arab background?") + end + + it "has the correct check_answer_label" do + expect(question.check_answer_label).to eq("Buyer 2’s ethnic background") + end + + it "has the correct type" do + expect(question.type).to eq("radio") + end + + it "is not marked as derived" do + expect(question.derived?).to be false + end + + it "has the correct hint_text" do + expect(question.hint_text).to be nil + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq({ + "16" => { "value" => "Other ethnic group" }, + "19" => { "value" => "Arab" }, + }) + end + + it "has the correct check_answers_card_number" do + expect(question.check_answers_card_number).to eq(2) + end +end diff --git a/spec/models/form/sales/questions/buyer2_ethnic_background_asian_spec.rb b/spec/models/form/sales/questions/buyer2_ethnic_background_asian_spec.rb new file mode 100644 index 000000000..67ce60e43 --- /dev/null +++ b/spec/models/form/sales/questions/buyer2_ethnic_background_asian_spec.rb @@ -0,0 +1,51 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::Buyer2EthnicBackgroundAsian, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page) } + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("ethnicbuy2") + end + + it "has the correct header" do + expect(question.header).to eq("Which of the following best describes the buyer 2’s Asian or Asian British background?") + end + + it "has the correct check_answer_label" do + expect(question.check_answer_label).to eq("Buyer 2’s ethnic background") + end + + it "has the correct type" do + expect(question.type).to eq("radio") + end + + it "is not marked as derived" do + expect(question.derived?).to be false + end + + it "has the correct hint_text" do + expect(question.hint_text).to be nil + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq({ + "10" => { "value" => "Bangladeshi" }, + "11" => { "value" => "Any other Asian or Asian British background" }, + "15" => { "value" => "Chinese" }, + "8" => { "value" => "Indian" }, + "9" => { "value" => "Pakistani" }, + }) + end + + it "has the correct check_answers_card_number" do + expect(question.check_answers_card_number).to eq(2) + end +end diff --git a/spec/models/form/sales/questions/buyer2_ethnic_background_black_spec.rb b/spec/models/form/sales/questions/buyer2_ethnic_background_black_spec.rb new file mode 100644 index 000000000..8e772f870 --- /dev/null +++ b/spec/models/form/sales/questions/buyer2_ethnic_background_black_spec.rb @@ -0,0 +1,49 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::Buyer2EthnicBackgroundBlack, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page) } + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("ethnicbuy2") + end + + it "has the correct header" do + expect(question.header).to eq("Which of the following best describes the buyer 2’s Black, African, Caribbean or Black British background?") + end + + it "has the correct check_answer_label" do + expect(question.check_answer_label).to eq("Buyer 2’s ethnic background") + end + + it "has the correct type" do + expect(question.type).to eq("radio") + end + + it "is not marked as derived" do + expect(question.derived?).to be false + end + + it "has the correct hint_text" do + expect(question.hint_text).to be nil + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq({ + "12" => { "value" => "Caribbean" }, + "13" => { "value" => "African" }, + "14" => { "value" => "Any other Black, African or Caribbean background" }, + }) + end + + it "has the correct check_answers_card_number" do + expect(question.check_answers_card_number).to eq(2) + end +end diff --git a/spec/models/form/sales/questions/buyer2_ethnic_background_mixed_spec.rb b/spec/models/form/sales/questions/buyer2_ethnic_background_mixed_spec.rb new file mode 100644 index 000000000..6c1b12865 --- /dev/null +++ b/spec/models/form/sales/questions/buyer2_ethnic_background_mixed_spec.rb @@ -0,0 +1,50 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::Buyer2EthnicBackgroundMixed, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page) } + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("ethnicbuy2") + end + + it "has the correct header" do + expect(question.header).to eq("Which of the following best describes the buyer 2’s Mixed or Multiple ethnic groups background?") + end + + it "has the correct check_answer_label" do + expect(question.check_answer_label).to eq("Buyer 2’s ethnic background") + end + + it "has the correct type" do + expect(question.type).to eq("radio") + end + + it "is not marked as derived" do + expect(question.derived?).to be false + end + + it "has the correct hint_text" do + expect(question.hint_text).to be nil + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq({ + "4" => { "value" => "White and Black Caribbean" }, + "5" => { "value" => "White and Black African" }, + "6" => { "value" => "White and Asian" }, + "7" => { "value" => "Any other Mixed or Multiple ethnic background" }, + }) + end + + it "has the correct check_answers_card_number" do + expect(question.check_answers_card_number).to eq(2) + end +end diff --git a/spec/models/form/sales/questions/buyer2_ethnic_background_white_spec.rb b/spec/models/form/sales/questions/buyer2_ethnic_background_white_spec.rb new file mode 100644 index 000000000..d3f2da508 --- /dev/null +++ b/spec/models/form/sales/questions/buyer2_ethnic_background_white_spec.rb @@ -0,0 +1,50 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::Buyer2EthnicBackgroundWhite, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page) } + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("ethnicbuy2") + end + + it "has the correct header" do + expect(question.header).to eq("Which of the following best describes the buyer 2’s White background?") + end + + it "has the correct check_answer_label" do + expect(question.check_answer_label).to eq("Buyer 2’s ethnic background") + end + + it "has the correct type" do + expect(question.type).to eq("radio") + end + + it "is not marked as derived" do + expect(question.derived?).to be false + end + + it "has the correct hint_text" do + expect(question.hint_text).to be nil + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq({ + "1" => { "value" => "English, Welsh, Northern Irish, Scottish or British" }, + "18" => { "value" => "Gypsy or Irish Traveller" }, + "2" => { "value" => "Irish" }, + "3" => { "value" => "Any other White background" }, + }) + end + + it "has the correct check_answers_card_number" do + expect(question.check_answers_card_number).to eq(2) + end +end diff --git a/spec/models/form/sales/questions/buyer2_ethnic_group_spec.rb b/spec/models/form/sales/questions/buyer2_ethnic_group_spec.rb new file mode 100644 index 000000000..2e9e0c28c --- /dev/null +++ b/spec/models/form/sales/questions/buyer2_ethnic_group_spec.rb @@ -0,0 +1,62 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::Buyer2EthnicGroup, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page) } + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("ethnic_group2") + end + + it "has the correct header" do + expect(question.header).to eq("What is buyer 2’s ethnic group?") + end + + it "has the correct check_answer_label" do + expect(question.check_answer_label).to eq("Buyer 2’s ethnic group") + end + + it "has the correct type" do + expect(question.type).to eq("radio") + end + + it "is not marked as derived" do + expect(question.derived?).to be false + end + + it "has the correct hint_text" do + expect(question.hint_text).to be nil + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq({ + "0" => { "value" => "White" }, + "1" => { "value" => "Mixed or Multiple ethnic groups" }, + "17" => { "value" => "Buyer 1 prefers not to say" }, + "2" => { "value" => "Asian or Asian British" }, + "3" => { "value" => "Black, African, Caribbean or Black British" }, + "4" => { "value" => "Arab or other ethnic group" }, + "divider" => { "value" => true }, + }) + end + + it "has the correct check_answers_card_number" do + expect(question.check_answers_card_number).to eq(2) + end + + it "has the correct inferred_check_answers_value" do + expect(question.inferred_check_answers_value).to eq([{ + "condition" => { + "ethnic_group2" => 17, + }, + "value" => "Prefers not to say", + }]) + end +end diff --git a/spec/models/form/sales/questions/staircase_sale_spec.rb b/spec/models/form/sales/questions/staircase_sale_spec.rb new file mode 100644 index 000000000..39927c967 --- /dev/null +++ b/spec/models/form/sales/questions/staircase_sale_spec.rb @@ -0,0 +1,49 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::StaircaseSale, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page) } + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("staircasesale") + end + + it "has the correct header" do + expect(question.header).to eq("Is this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?") + end + + it "has the correct check_answer_label" do + expect(question.check_answer_label).to eq("Part of a back-to-back staircasing transaction") + end + + it "has the correct type" do + expect(question.type).to eq("radio") + end + + it "is not marked as derived" do + expect(question.derived?).to be false + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq({ + "1" => { "value" => "Yes" }, + "2" => { "value" => "No" }, + "3" => { "value" => "Don't know" }, + }) + end + + it "has correct conditional for" do + expect(question.conditional_for).to eq(nil) + end + + it "has the correct hint" do + expect(question.hint_text).to be_nil + end +end diff --git a/spec/models/form/sales/subsections/household_characteristics_spec.rb b/spec/models/form/sales/subsections/household_characteristics_spec.rb index 681b4b50b..33e9a1409 100644 --- a/spec/models/form/sales/subsections/household_characteristics_spec.rb +++ b/spec/models/form/sales/subsections/household_characteristics_spec.rb @@ -6,76 +6,166 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model let(:subsection_id) { nil } let(:subsection_definition) { nil } let(:section) { instance_double(Form::Sales::Sections::Household) } + let(:form) { instance_double(Form) } it "has correct section" do expect(household_characteristics.section).to eq(section) end - it "has correct pages" do - expect(household_characteristics.pages.map(&:id)).to eq( - %w[ - buyer_interview - privacy_notice - buyer_1_age - age_1_retirement_value_check - age_1_old_persons_shared_ownership_value_check - buyer_1_gender_identity - gender_1_retirement_value_check - buyer_1_ethnic_group - buyer_1_ethnic_background_black - buyer_1_ethnic_background_asian - buyer_1_ethnic_background_arab - buyer_1_ethnic_background_mixed - buyer_1_ethnic_background_white - buyer_1_nationality - buyer_1_working_situation - working_situation_1_retirement_value_check - working_situation_buyer_1_income_value_check - buyer_1_live_in_property - buyer_2_relationship_to_buyer_1 - buyer_2_age - age_2_old_persons_shared_ownership_value_check - age_2_buyer_retirement_value_check - buyer_2_gender_identity - gender_2_buyer_retirement_value_check - buyer_2_working_situation - working_situation_2_buyer_retirement_value_check - buyer_2_live_in_property - number_of_others_in_property - person_2_known - person_2_relationship_to_buyer_1 - person_2_age - age_2_retirement_value_check - person_2_gender_identity - gender_2_retirement_value_check - person_2_working_situation - working_situation_2_retirement_value_check - person_3_known - person_3_relationship_to_buyer_1 - person_3_age - age_3_retirement_value_check - person_3_gender_identity - gender_3_retirement_value_check - person_3_working_situation - working_situation_3_retirement_value_check - person_4_known - person_4_relationship_to_buyer_1 - person_4_age - age_4_retirement_value_check - person_4_gender_identity - gender_4_retirement_value_check - person_4_working_situation - working_situation_4_retirement_value_check - person_5_known - person_5_relationship_to_buyer_1 - person_5_age - age_5_retirement_value_check - person_5_gender_identity - gender_5_retirement_value_check - person_5_working_situation - working_situation_5_retirement_value_check - ], - ) + context "with 2022/23 form" do + before do + allow(form).to receive(:start_date).and_return(Time.zone.local(2022, 4, 1)) + allow(section).to receive(:form).and_return(form) + end + + it "has correct pages" do + expect(household_characteristics.pages.map(&:id)).to eq( + %w[ + buyer_interview + privacy_notice + buyer_1_age + age_1_retirement_value_check + age_1_old_persons_shared_ownership_value_check + buyer_1_gender_identity + gender_1_retirement_value_check + buyer_1_ethnic_group + buyer_1_ethnic_background_black + buyer_1_ethnic_background_asian + buyer_1_ethnic_background_arab + buyer_1_ethnic_background_mixed + buyer_1_ethnic_background_white + buyer_1_nationality + buyer_1_working_situation + working_situation_1_retirement_value_check + working_situation_buyer_1_income_value_check + buyer_1_live_in_property + buyer_2_relationship_to_buyer_1 + buyer_2_age + age_2_old_persons_shared_ownership_value_check + age_2_buyer_retirement_value_check + buyer_2_gender_identity + gender_2_buyer_retirement_value_check + buyer_2_working_situation + working_situation_2_retirement_value_check_joint_purchase + working_situation_buyer_2_income_value_check + buyer_2_live_in_property + number_of_others_in_property + person_2_known + person_2_relationship_to_buyer_1 + person_2_age + age_2_retirement_value_check + person_2_gender_identity + gender_2_retirement_value_check + person_2_working_situation + working_situation_2_retirement_value_check + person_3_known + person_3_relationship_to_buyer_1 + person_3_age + age_3_retirement_value_check + person_3_gender_identity + gender_3_retirement_value_check + person_3_working_situation + working_situation_3_retirement_value_check + person_4_known + person_4_relationship_to_buyer_1 + person_4_age + age_4_retirement_value_check + person_4_gender_identity + gender_4_retirement_value_check + person_4_working_situation + working_situation_4_retirement_value_check + person_5_known + person_5_relationship_to_buyer_1 + person_5_age + age_5_retirement_value_check + person_5_gender_identity + gender_5_retirement_value_check + person_5_working_situation + working_situation_5_retirement_value_check + ], + ) + end + end + + context "with 2023/24 form" do + before do + allow(form).to receive(:start_date).and_return(Time.zone.local(2023, 4, 1)) + allow(section).to receive(:form).and_return(form) + end + + it "has correct pages" do + expect(household_characteristics.pages.map(&:id)).to eq( + %w[ + buyer_interview + privacy_notice + buyer_1_age + age_1_retirement_value_check + age_1_old_persons_shared_ownership_value_check + buyer_1_gender_identity + gender_1_retirement_value_check + buyer_1_ethnic_group + buyer_1_ethnic_background_black + buyer_1_ethnic_background_asian + buyer_1_ethnic_background_arab + buyer_1_ethnic_background_mixed + buyer_1_ethnic_background_white + buyer_1_nationality + buyer_1_working_situation + working_situation_1_retirement_value_check + working_situation_buyer_1_income_value_check + buyer_1_live_in_property + buyer_2_relationship_to_buyer_1 + buyer_2_age + age_2_old_persons_shared_ownership_value_check + age_2_buyer_retirement_value_check + buyer_2_gender_identity + gender_2_buyer_retirement_value_check + buyer_2_ethnic_group + buyer_2_ethnic_background_black + buyer_2_ethnic_background_asian + buyer_2_ethnic_background_arab + buyer_2_ethnic_background_mixed + buyer_2_ethnic_background_white + buyer_2_working_situation + working_situation_2_retirement_value_check_joint_purchase + working_situation_buyer_2_income_value_check + buyer_2_live_in_property + number_of_others_in_property + person_2_known + person_2_relationship_to_buyer_1 + person_2_age + age_2_retirement_value_check + person_2_gender_identity + gender_2_retirement_value_check + person_2_working_situation + working_situation_2_retirement_value_check + person_3_known + person_3_relationship_to_buyer_1 + person_3_age + age_3_retirement_value_check + person_3_gender_identity + gender_3_retirement_value_check + person_3_working_situation + working_situation_3_retirement_value_check + person_4_known + person_4_relationship_to_buyer_1 + person_4_age + age_4_retirement_value_check + person_4_gender_identity + gender_4_retirement_value_check + person_4_working_situation + working_situation_4_retirement_value_check + person_5_known + person_5_relationship_to_buyer_1 + person_5_age + age_5_retirement_value_check + person_5_gender_identity + gender_5_retirement_value_check + person_5_working_situation + working_situation_5_retirement_value_check + ], + ) + end end it "has the correct id" do diff --git a/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb b/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb index 74002d5e0..cfc677233 100644 --- a/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb +++ b/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb @@ -27,6 +27,7 @@ RSpec.describe Form::Sales::Subsections::IncomeBenefitsAndSavings, type: :model buyer_1_mortgage_value_check buyer_2_income buyer_2_income_mortgage_value_check + buyer_2_income_value_check buyer_2_mortgage buyer_2_mortgage_value_check housing_benefits @@ -52,6 +53,7 @@ RSpec.describe Form::Sales::Subsections::IncomeBenefitsAndSavings, type: :model buyer_1_mortgage_value_check buyer_2_income buyer_2_income_mortgage_value_check + buyer_2_income_value_check buyer_2_mortgage buyer_2_mortgage_value_check housing_benefits diff --git a/spec/models/form_handler_spec.rb b/spec/models/form_handler_spec.rb index 9b4920198..b04980ce4 100644 --- a/spec/models/form_handler_spec.rb +++ b/spec/models/form_handler_spec.rb @@ -54,14 +54,14 @@ RSpec.describe FormHandler do it "is able to load a current sales form" do form = form_handler.get_form("current_sales") expect(form).to be_a(Form) - expect(form.pages.count).to eq(179) + expect(form.pages.count).to eq(181) expect(form.name).to eq("2022_2023_sales") end it "is able to load a previous sales form" do form = form_handler.get_form("previous_sales") expect(form).to be_a(Form) - expect(form.pages.count).to eq(179) + expect(form.pages.count).to eq(181) expect(form.name).to eq("2021_2022_sales") end end diff --git a/spec/models/form_spec.rb b/spec/models/form_spec.rb index 72223c1b5..cd58587dc 100644 --- a/spec/models/form_spec.rb +++ b/spec/models/form_spec.rb @@ -223,7 +223,7 @@ RSpec.describe Form, type: :model do expect(form.questions.count).to eq(16) expect(form.questions.first.id).to eq("owning_organisation_id") expect(form.start_date).to eq(Time.zone.parse("2022-04-01")) - expect(form.end_date).to eq(Time.zone.parse("2023-07-01")) + expect(form.end_date).to eq(Time.zone.parse("2023-07-07")) expect(form.unresolved_log_redirect_page_id).to eq(nil) end diff --git a/spec/models/lettings_log_spec.rb b/spec/models/lettings_log_spec.rb index c8cdc466e..d4bbd8378 100644 --- a/spec/models/lettings_log_spec.rb +++ b/spec/models/lettings_log_spec.rb @@ -18,6 +18,11 @@ RSpec.describe LettingsLog do expect(described_class).to be < ApplicationRecord end + it "is a not a sales log" do + lettings_log = FactoryBot.build(:lettings_log, created_by: created_by_user) + expect(lettings_log.sales?).to be false + end + it "is a lettings log" do lettings_log = FactoryBot.build(:lettings_log, created_by: created_by_user) expect(lettings_log).to be_lettings @@ -1454,6 +1459,15 @@ RSpec.describe LettingsLog do expect(record_from_db["vacdays"]).to eq(0) expect(lettings_log["vacdays"]).to eq(0) end + + it "correctly derives and saves first_time_property_let_as_social_housing" do + record_from_db = ActiveRecord::Base.connection.execute( + "select first_time_property_let_as_social_housing" \ + " from lettings_logs where id=#{lettings_log.id}", + ).to_a[0] + expect(record_from_db["first_time_property_let_as_social_housing"]).to eq(0) + expect(lettings_log["first_time_property_let_as_social_housing"]).to eq(0) + end end context "when answering the household characteristics questions" do @@ -1946,12 +1960,12 @@ RSpec.describe LettingsLog do end context "when a non select question associated with several pages is routed to" do - let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, period: 2) } + let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, period: 2, needstype: 1) } it "does not clear the answer value" do - lettings_log.update!({ offered: 4 }) + lettings_log.update!({ unitletas: 1 }) lettings_log.reload - expect(lettings_log.offered).to eq(4) + expect(lettings_log.unitletas).to eq(1) end end diff --git a/spec/models/sales_log_spec.rb b/spec/models/sales_log_spec.rb index 42bf9d4cd..7b9f7c7ef 100644 --- a/spec/models/sales_log_spec.rb +++ b/spec/models/sales_log_spec.rb @@ -12,11 +12,16 @@ RSpec.describe SalesLog, type: :model do expect(described_class).to be < ApplicationRecord end - it "is a sales log" do + it "is a not a lettings log" do sales_log = build(:sales_log, created_by: created_by_user) expect(sales_log.lettings?).to be false end + it "is a sales log" do + sales_log = build(:sales_log, created_by: created_by_user) + expect(sales_log.sales?).to be true + end + describe "#new" do context "when creating a record" do let(:sales_log) do @@ -261,6 +266,7 @@ RSpec.describe SalesLog, type: :model do relat4: "X", relat5: "X", relat6: "P", + income2: 0, ecstat2: 9, ecstat3: 7, age1: 47, @@ -365,4 +371,26 @@ RSpec.describe SalesLog, type: :model do expect(completed_sales_log.expected_shared_ownership_deposit_value).to eq(500) end end + + describe "#field_formatted_as_currency" do + let(:completed_sales_log) { FactoryBot.create(:sales_log, :completed) } + + it "returns small numbers correctly formatted as currency" do + completed_sales_log.update!(savings: 4) + + expect(completed_sales_log.field_formatted_as_currency("savings")).to eq("£4.00") + end + + it "returns quite large numbers correctly formatted as currency" do + completed_sales_log.update!(savings: 40_000) + + expect(completed_sales_log.field_formatted_as_currency("savings")).to eq("£40,000.00") + end + + it "returns very large numbers correctly formatted as currency" do + completed_sales_log.update!(savings: 400_000_000) + + expect(completed_sales_log.field_formatted_as_currency("savings")).to eq("£400,000,000.00") + end + end end diff --git a/spec/models/validations/sales/financial_validations_spec.rb b/spec/models/validations/sales/financial_validations_spec.rb index 88f36943f..fae0e4536 100644 --- a/spec/models/validations/sales/financial_validations_spec.rb +++ b/spec/models/validations/sales/financial_validations_spec.rb @@ -5,75 +5,110 @@ RSpec.describe Validations::Sales::FinancialValidations do let(:validator_class) { Class.new { include Validations::Sales::FinancialValidations } } - describe "income validations" do - let(:record) { FactoryBot.create(:sales_log, ownershipsch: 1, la: "E08000035") } - - context "with shared ownership" do - context "and non london borough" do - (0..8).each do |ecstat| - it "adds an error when buyer 1 income is over hard max for ecstat #{ecstat}" do - record.income1 = 85_000 - record.ecstat1 = ecstat - financial_validator.validate_income1(record) - expect(record.errors["income1"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000)) - expect(record.errors["ecstat1"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000)) - expect(record.errors["ownershipsch"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000)) - expect(record.errors["la"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000)) - expect(record.errors["postcode_full"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000)) - end - end - - it "validates that the income is within the expected range for the tenant’s employment status" do - record.income1 = 75_000 - record.ecstat1 = 1 - financial_validator.validate_income1(record) - expect(record.errors["income1"]).to be_empty - expect(record.errors["ecstat1"]).to be_empty - expect(record.errors["ownershipsch"]).to be_empty - expect(record.errors["la"]).to be_empty - expect(record.errors["postcode_full"]).to be_empty - end - end - - context "and a london borough" do - before do - record.update!(la: "E09000030") - record.reload - end - - (0..8).each do |ecstat| - it "adds an error when buyer 1 income is over hard max for ecstat #{ecstat}" do - record.income1 = 95_000 - record.ecstat1 = ecstat - financial_validator.validate_income1(record) - expect(record.errors["income1"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000)) - expect(record.errors["ecstat1"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000)) - expect(record.errors["ownershipsch"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000)) - expect(record.errors["la"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000)) - expect(record.errors["postcode_full"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000)) - end - end - - it "validates that the income is within the expected range for the tenant’s employment status" do - record.income1 = 85_000 - record.ecstat1 = 1 - financial_validator.validate_income1(record) - expect(record.errors["income1"]).to be_empty - expect(record.errors["ecstat1"]).to be_empty - expect(record.errors["ownershipsch"]).to be_empty - expect(record.errors["la"]).to be_empty - expect(record.errors["postcode_full"]).to be_empty - end + describe "income validations for shared ownership" do + let(:record) { FactoryBot.create(:sales_log, ownershipsch: 1) } + + context "when buying in a non london borough" do + before do + record.update!(la: "E08000035") + record.reload + end + + it "adds errors if buyer 1 has income over 80,000" do + record.income1 = 85_000 + financial_validator.validate_income1(record) + expect(record.errors["income1"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_outside_london")) + expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_outside_london")) + expect(record.errors["la"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_outside_london")) + expect(record.errors["postcode_full"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_outside_london")) + end + + it "adds errors if buyer 2 has income over 80,000" do + record.income2 = 85_000 + financial_validator.validate_income2(record) + expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_outside_london")) + expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_outside_london")) + expect(record.errors["la"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_outside_london")) + expect(record.errors["postcode_full"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_outside_london")) + end + + it "does not add errors if buyer 1 has income below 80_000" do + record.income1 = 75_000 + financial_validator.validate_income1(record) + expect(record.errors).to be_empty + end + + it "does not add errors if buyer 2 has income below 80_000" do + record.income2 = 75_000 + financial_validator.validate_income2(record) + expect(record.errors).to be_empty + end + + it "adds errors when combined income is over 80_000" do + record.income1 = 45_000 + record.income2 = 40_000 + financial_validator.validate_combined_income(record) + expect(record.errors["income1"]).to include(match I18n.t("validations.financial.income.combined_over_hard_max_for_outside_london")) + expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.combined_over_hard_max_for_outside_london")) + end + + it "does not add errors when combined income is under 80_000" do + record.income1 = 35_000 + record.income2 = 40_000 + financial_validator.validate_combined_income(record) + expect(record.errors).to be_empty + end + end + + context "when buying in a london borough" do + before do + record.update!(la: "E09000030") + record.reload + end + + it "adds errors if buyer 1 has income over 90,000" do + record.income1 = 95_000 + financial_validator.validate_income1(record) + expect(record.errors["income1"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_london")) + expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_london")) + expect(record.errors["la"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_london")) + expect(record.errors["postcode_full"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_london")) + end + + it "adds errors if buyer 2 has income over 90,000" do + record.income2 = 95_000 + financial_validator.validate_income2(record) + expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_london")) + expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_london")) + expect(record.errors["la"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_london")) + expect(record.errors["postcode_full"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_london")) + end + + it "does not add errors if buyer 1 has income below 90_000" do + record.income1 = 75_000 + financial_validator.validate_income1(record) + expect(record.errors).to be_empty + end + + it "does not add errors if buyer 2 has income below 90_000" do + record.income2 = 75_000 + financial_validator.validate_income2(record) + expect(record.errors).to be_empty + end + + it "adds errors when combined income is over 90_000" do + record.income1 = 55_000 + record.income2 = 40_000 + financial_validator.validate_combined_income(record) + expect(record.errors["income1"]).to include(match I18n.t("validations.financial.income.combined_over_hard_max_for_london")) + expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.combined_over_hard_max_for_london")) + end + + it "does not add errors when combined income is under 90_000" do + record.income1 = 35_000 + record.income2 = 40_000 + financial_validator.validate_combined_income(record) + expect(record.errors).to be_empty end end end @@ -96,7 +131,7 @@ RSpec.describe Validations::Sales::FinancialValidations do it "does not add an error if the cash discount is in the expected range" do record.cashdis = 10_000 financial_validator.validate_cash_discount(record) - expect(record.errors["cashdis"]).to be_empty + expect(record.errors).to be_empty end end @@ -107,16 +142,14 @@ RSpec.describe Validations::Sales::FinancialValidations do record.stairbought = 20 record.stairowned = 40 financial_validator.validate_percentage_bought_not_greater_than_percentage_owned(record) - expect(record.errors["stairbought"]).to be_empty - expect(record.errors["stairowned"]).to be_empty + expect(record.errors).to be_empty end it "does not add an error if the percentage bought is equal to the percentage owned" do record.stairbought = 30 record.stairowned = 30 financial_validator.validate_percentage_bought_not_greater_than_percentage_owned(record) - expect(record.errors["stairbought"]).to be_empty - expect(record.errors["stairowned"]).to be_empty + expect(record.errors).to be_empty end it "adds an error to stairowned and not stairbought if the percentage bought is more than the percentage owned" do @@ -135,8 +168,7 @@ RSpec.describe Validations::Sales::FinancialValidations do record.type = 2 record.stairowned = 80 financial_validator.validate_percentage_owned_not_too_much_if_older_person(record) - expect(record.errors["stairowned"]).to be_empty - expect(record.errors["type"]).to be_empty + expect(record.errors).to be_empty end end @@ -145,8 +177,7 @@ RSpec.describe Validations::Sales::FinancialValidations do record.type = 24 record.stairowned = 50 financial_validator.validate_percentage_owned_not_too_much_if_older_person(record) - expect(record.errors["stairowned"]).to be_empty - expect(record.errors["type"]).to be_empty + expect(record.errors).to be_empty end it "adds an error when percentage owned after staircasing transaction exceeds 75%" do @@ -158,4 +189,39 @@ RSpec.describe Validations::Sales::FinancialValidations do end end end + + describe "#validate_child_income" do + let(:record) { FactoryBot.create(:sales_log) } + + context "when buyer 2 is not a child" do + before do + record.update!(ecstat2: rand(0..8)) + record.reload + end + + it "does not add an error if buyer 2 has an income" do + record.ecstat2 = rand(0..8) + record.income2 = 40_000 + financial_validator.validate_child_income(record) + expect(record.errors).to be_empty + end + end + + context "when buyer 2 is a child" do + it "does not add an error if buyer 2 has no income" do + record.ecstat2 = 9 + record.income2 = 0 + financial_validator.validate_child_income(record) + expect(record.errors).to be_empty + end + + it "adds errors if buyer 2 has an income" do + record.ecstat2 = 9 + record.income2 = 40_000 + financial_validator.validate_child_income(record) + expect(record.errors["ecstat2"]).to include(match I18n.t("validations.financial.income.child_has_income")) + expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.child_has_income")) + end + end + end end diff --git a/spec/models/validations/sales/property_validations_spec.rb b/spec/models/validations/sales/property_validations_spec.rb index fc3fdd1a8..180f16021 100644 --- a/spec/models/validations/sales/property_validations_spec.rb +++ b/spec/models/validations/sales/property_validations_spec.rb @@ -48,4 +48,40 @@ RSpec.describe Validations::Sales::PropertyValidations do end end end + + describe "#validate_property_unit_type" do + context "when number of bedrooms is 1" do + let(:record) { FactoryBot.build(:sales_log, beds: 1, proptype: 2) } + + it "does not add an error if it's a bedsit" do + property_validator.validate_bedsit_number_of_beds(record) + expect(record.errors).not_to be_present + end + end + + context "when number of bedrooms is > 1" do + let(:record) { FactoryBot.build(:sales_log, beds: 2, proptype: 2) } + + it "does add an error if it's a bedsit" do + property_validator.validate_bedsit_number_of_beds(record) + expect(record.errors.added?(:proptype, "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms")).to be true + expect(record.errors.added?(:beds, "Number of bedrooms must be 1 if the property is a bedsit")).to be true + end + + it "does not add an error if proptype is undefined" do + record.update!(proptype: nil) + property_validator.validate_bedsit_number_of_beds(record) + expect(record.errors).not_to be_present + end + end + + context "when number of bedrooms is undefined" do + let(:record) { FactoryBot.build(:sales_log, beds: nil, proptype: 2) } + + it "does not add an error if it's a bedsit" do + property_validator.validate_bedsit_number_of_beds(record) + expect(record.errors).not_to be_present + end + end + end end diff --git a/spec/models/validations/sales/setup_validations_spec.rb b/spec/models/validations/sales/setup_validations_spec.rb index 74b7834ab..4571ae5cd 100644 --- a/spec/models/validations/sales/setup_validations_spec.rb +++ b/spec/models/validations/sales/setup_validations_spec.rb @@ -6,43 +6,96 @@ RSpec.describe Validations::Sales::SetupValidations do let(:validator_class) { Class.new { include Validations::Sales::SetupValidations } } describe "#validate_saledate" do - context "when saledate is blank" do - let(:record) { FactoryBot.build(:sales_log, saledate: nil) } + context "with sales_in_crossover_period == false" do + context "when saledate is blank" do + let(:record) { build(:sales_log, saledate: nil) } - it "does not add an error" do - setup_validator.validate_saledate(record) + it "does not add an error" do + setup_validator.validate_saledate(record) - expect(record.errors).to be_empty + expect(record.errors).to be_empty + end end - end - context "when saledate is in the 22/23 financial year" do - let(:record) { FactoryBot.build(:sales_log, saledate: Time.zone.local(2023, 1, 1)) } + context "when saledate is in the 22/23 financial year" do + let(:record) { build(:sales_log, saledate: Time.zone.local(2023, 1, 1)) } - it "does not add an error" do - setup_validator.validate_saledate(record) + it "does not add an error" do + setup_validator.validate_saledate(record) - expect(record.errors).to be_empty + expect(record.errors).to be_empty + end + end + + context "when saledate is before the 22/23 financial year" do + let(:record) { build(:sales_log, saledate: Time.zone.local(2020, 1, 1)) } + + it "adds error" do + setup_validator.validate_saledate(record) + + expect(record.errors[:saledate]).to include("Enter a date within the 22/23 financial year, which is between 1st April 2022 and 31st March 2023") + end end - end - context "when saledate is before the 22/23 financial year" do - let(:record) { FactoryBot.build(:sales_log, saledate: Time.zone.local(2022, 1, 1)) } + context "when saledate is after the 22/23 financial year" do + let(:record) { build(:sales_log, saledate: Time.zone.local(2025, 4, 1)) } - it "adds error" do - setup_validator.validate_saledate(record) + it "adds error" do + setup_validator.validate_saledate(record) - expect(record.errors[:saledate]).to include(I18n.t("validations.setup.saledate.financial_year")) + expect(record.errors[:saledate]).to include("Enter a date within the 22/23 financial year, which is between 1st April 2022 and 31st March 2023") + end end end - context "when saledate is after the 22/23 financial year" do - let(:record) { FactoryBot.build(:sales_log, saledate: Time.zone.local(2023, 4, 1)) } + context "with sales_in_crossover_period == true" do + around do |example| + Timecop.freeze(Time.zone.local(2024, 5, 1)) do + Singleton.__init__(FormHandler) + example.run + end + Timecop.return + Singleton.__init__(FormHandler) + end + + context "when saledate is blank" do + let(:record) { build(:sales_log, saledate: nil) } + + it "does not add an error" do + setup_validator.validate_saledate(record) + + expect(record.errors).to be_empty + end + end + + context "when saledate is in the 22/23 financial year" do + let(:record) { build(:sales_log, saledate: Time.zone.local(2024, 1, 1)) } + + it "does not add an error" do + setup_validator.validate_saledate(record) + + expect(record.errors).to be_empty + end + end + + context "when saledate is before the 22/23 financial year" do + let(:record) { build(:sales_log, saledate: Time.zone.local(2020, 5, 1)) } + + it "adds error" do + setup_validator.validate_saledate(record) + + expect(record.errors[:saledate]).to include("Enter a date within the 23/24 or 24/25 financial years, which is between 1st April 2023 and 31st March 2025") + end + end + + context "when saledate is after the 22/23 financial year" do + let(:record) { build(:sales_log, saledate: Time.zone.local(2025, 4, 1)) } - it "adds error" do - setup_validator.validate_saledate(record) + it "adds error" do + setup_validator.validate_saledate(record) - expect(record.errors[:saledate]).to include(I18n.t("validations.setup.saledate.financial_year")) + expect(record.errors[:saledate]).to include("Enter a date within the 23/24 or 24/25 financial years, which is between 1st April 2023 and 31st March 2025") + end end end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 7ae06f7e4..8e175ccf6 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -6,6 +6,7 @@ require File.expand_path("../config/environment", __dir__) abort("The Rails environment is running in production mode!") if Rails.env.production? require "rspec/rails" require "capybara/rspec" +require "capybara-screenshot/rspec" require "selenium-webdriver" require "view_component/test_helpers" @@ -13,7 +14,7 @@ Capybara.register_driver :headless do |app| options = Selenium::WebDriver::Firefox::Options.new options.add_argument("--headless") - Capybara::Selenium::Driver.new(app, browser: :firefox, capabilities: options) + Capybara::Selenium::Driver.new(app, browser: :firefox, options:) end Capybara.javascript_driver = :headless diff --git a/spec/requests/form_controller_spec.rb b/spec/requests/form_controller_spec.rb index dcf67cb71..d5a8dbcf8 100644 --- a/spec/requests/form_controller_spec.rb +++ b/spec/requests/form_controller_spec.rb @@ -314,6 +314,12 @@ RSpec.describe FormController, type: :request do get "/lettings-logs/#{setup_complete_lettings_log.id}/review", headers: headers, params: {} expect(response.body).to match("Review lettings log") end + + it "renders the review page for the sales log" do + log = create(:sales_log, :completed, created_by: user) + get "/sales-logs/#{log.id}/review", headers: headers, params: { sales_log: true } + expect(response.body).to match("Review sales log") + end end context "when viewing a user dependent page" do diff --git a/spec/requests/sales_logs_controller_spec.rb b/spec/requests/sales_logs_controller_spec.rb index 925e3f1f3..e45fee440 100644 --- a/spec/requests/sales_logs_controller_spec.rb +++ b/spec/requests/sales_logs_controller_spec.rb @@ -61,6 +61,31 @@ RSpec.describe SalesLogsController, type: :request do expect(response).to have_http_status(:unauthorized) end end + + context "with a request containing invalid json parameters" do + let(:params) do + { + "saledate": Date.new(2022, 4, 1), + "purchid": "1", + "ownershipsch": 1, + "type": 2, + "jointpur": 1, + "jointmore": 1, + "beds": 2, + "proptype": 2, + } + end + + before do + post "/sales-logs", headers:, params: params.to_json + end + + it "validates sales log parameters" do + json_response = JSON.parse(response.body) + expect(response).to have_http_status(:unprocessable_entity) + expect(json_response["errors"]).to match_array([["beds", ["Number of bedrooms must be 1 if the property is a bedsit"]], ["proptype", ["Answer cannot be 'Bedsit' if the property has 2 or more bedrooms"]]]) + end + end end context "when UI" do diff --git a/spec/services/bulk_upload/lettings/log_creator_spec.rb b/spec/services/bulk_upload/lettings/log_creator_spec.rb index fc7d1cdc0..39dfd00fe 100644 --- a/spec/services/bulk_upload/lettings/log_creator_spec.rb +++ b/spec/services/bulk_upload/lettings/log_creator_spec.rb @@ -24,6 +24,23 @@ RSpec.describe BulkUpload::Lettings::LogCreator do end end + context "when a valid csv with several blank rows" do + let(:file) { Tempfile.new } + let(:path) { file.path } + let(:log) { LettingsLog.new } + + before do + file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_csv_row) + file.rewind + end + + it "ignores them and does not create the logs" do + expect { service.call }.not_to change(LettingsLog, :count) + end + end + context "when a valid csv with row with one invalid non setup field" do let(:file) { Tempfile.new } let(:path) { file.path } diff --git a/spec/services/bulk_upload/lettings/row_parser_spec.rb b/spec/services/bulk_upload/lettings/row_parser_spec.rb index 297b9fd46..7a09ba4b8 100644 --- a/spec/services/bulk_upload/lettings/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/row_parser_spec.rb @@ -8,8 +8,12 @@ RSpec.describe BulkUpload::Lettings::RowParser do let(:attributes) { { bulk_upload: } } let(:bulk_upload) { create(:bulk_upload, :lettings, user:) } let(:user) { create(:user, organisation: owning_org) } + let(:owning_org) { create(:organisation, :with_old_visible_id) } let(:managing_org) { create(:organisation, :with_old_visible_id) } + let(:scheme) { create(:scheme, :with_old_visible_id, owning_organisation: owning_org) } + let(:location) { create(:location, :with_old_visible_id, scheme:) } + let(:setup_section_params) do { bulk_upload:, @@ -23,6 +27,10 @@ RSpec.describe BulkUpload::Lettings::RowParser do } end + before do + create(:organisation_relationship, parent_organisation: owning_org, child_organisation: managing_org) + end + around do |example| FormHandler.instance.use_real_forms! @@ -31,6 +39,24 @@ RSpec.describe BulkUpload::Lettings::RowParser do FormHandler.instance.use_fake_forms! end + describe "#blank_row?" do + context "when a new object" do + it "returns true" do + expect(parser).to be_blank_row + end + end + + context "when any field is populated" do + before do + parser.field_1 = "1" + end + + it "returns false" do + expect(parser).not_to be_blank_row + end + end + end + describe "validations" do before do stub_request(:get, /api.postcodes.io/) @@ -40,6 +66,14 @@ RSpec.describe BulkUpload::Lettings::RowParser do end describe "#valid?" do + context "when the row is blank" do + let(:attributes) { { bulk_upload: } } + + it "returns true" do + expect(parser).to be_valid + end + end + context "when calling the method multiple times" do let(:attributes) { { bulk_upload:, field_134: 2 } } @@ -53,7 +87,7 @@ RSpec.describe BulkUpload::Lettings::RowParser do { bulk_upload:, field_1: "1", - field_4: "1", + field_4: scheme.old_visible_id, field_7: "123", field_96: now.day.to_s, field_97: now.month.to_s, @@ -172,7 +206,7 @@ RSpec.describe BulkUpload::Lettings::RowParser do describe "#field_1" do context "when null" do - let(:attributes) { { bulk_upload:, field_1: nil } } + let(:attributes) { { bulk_upload:, field_1: nil, field_4: "1" } } it "returns an error" do expect(parser.errors[:field_1]).to be_present @@ -202,6 +236,46 @@ RSpec.describe BulkUpload::Lettings::RowParser do expect(parser.errors[:field_1]).to be_blank end end + + context "when bulk upload is for general needs" do + let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: "1") } + + context "when general needs option selected" do + let(:attributes) { { bulk_upload:, field_1: "1" } } + + it "is permitted" do + expect(parser.errors[:field_1]).to be_blank + end + end + + context "when supported housing option selected" do + let(:attributes) { { bulk_upload:, field_1: "2" } } + + it "is not permitted" do + expect(parser.errors[:field_1]).to include("Lettings type must be a general needs type because you selected general needs when uploading the file") + end + end + end + + context "when bulk upload is for supported housing" do + let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: "2") } + + context "when general needs option selected" do + let(:attributes) { { bulk_upload:, field_1: "1" } } + + it "is not permitted" do + expect(parser.errors[:field_1]).to include("Lettings type must be a supported housing type because you selected supported housing when uploading the file") + end + end + + context "when supported housing option selected" do + let(:attributes) { { bulk_upload:, field_1: "2" } } + + it "is permitted" do + expect(parser.errors[:field_1]).to be_blank + end + end + end end describe "#field_4" do @@ -224,10 +298,90 @@ RSpec.describe BulkUpload::Lettings::RowParser do context "when matching scheme cannot be found" do let(:attributes) { { bulk_upload:, field_1: "1", field_4: "123" } } - xit "returns an error" do + it "returns an error" do expect(parser.errors[:field_4]).to be_present end end + + context "when scheme belongs to someone else" do + let(:other_scheme) { create(:scheme, :with_old_visible_id) } + let(:attributes) { { bulk_upload:, field_1: "1", field_4: other_scheme.old_visible_id, field_111: owning_org.old_visible_id } } + + it "returns an error" do + expect(parser.errors[:field_4]).to include("This management group code does not belong to your organisation, or any of your stock owners / managing agents") + end + end + + context "when scheme belongs to owning org" do + let(:scheme) { create(:scheme, :with_old_visible_id, owning_organisation: owning_org) } + let(:attributes) { { bulk_upload:, field_1: "1", field_4: scheme.old_visible_id, field_111: owning_org.old_visible_id } } + + it "does not return an error" do + expect(parser.errors[:field_4]).to be_blank + end + end + + context "when scheme belongs to managing org" do + let(:scheme) { create(:scheme, :with_old_visible_id, owning_organisation: managing_org) } + let(:attributes) { { bulk_upload:, field_1: "1", field_4: scheme.old_visible_id, field_113: managing_org.old_visible_id } } + + it "does not return an error" do + expect(parser.errors[:field_4]).to be_blank + end + end + end + + describe "#field_5" do + context "when location does not exist" do + let(:scheme) { create(:scheme, :with_old_visible_id, owning_organisation: owning_org) } + let(:attributes) do + { + bulk_upload:, + field_1: "1", + field_4: scheme.old_visible_id, + field_5: "dontexist", + field_111: owning_org.old_visible_id, + } + end + + it "returns an error" do + expect(parser.errors[:field_5]).to be_present + end + end + + context "when location exists" do + let(:scheme) { create(:scheme, :with_old_visible_id, owning_organisation: owning_org) } + let(:attributes) do + { + bulk_upload:, + field_1: "1", + field_4: scheme.old_visible_id, + field_5: location.old_visible_id, + field_111: owning_org.old_visible_id, + } + end + + it "does not return an error" do + expect(parser.errors[:field_5]).to be_blank + end + end + + context "when location exists but not related" do + let(:location) { create(:scheme, :with_old_visible_id) } + let(:attributes) do + { + bulk_upload:, + field_1: "1", + field_4: scheme.old_visible_id, + field_5: location.old_visible_id, + field_111: owning_org.old_visible_id, + } + end + + it "returns an error" do + expect(parser.errors[:field_5]).to be_present + end + end end describe "#field_7" do @@ -278,6 +432,49 @@ RSpec.describe BulkUpload::Lettings::RowParser do end end + describe "#field_55, #field_56, #field_57" do + context "when more than one item selected" do + let(:attributes) { { bulk_upload:, field_55: "1", field_56: "1" } } + + it "is not permitted" do + expect(parser.errors[:field_55]).to be_present + expect(parser.errors[:field_56]).to be_present + expect(parser.errors[:field_57]).to be_present + end + end + end + + describe "#field_59" do + context "when 1 and another disability field selected" do + let(:attributes) { { bulk_upload:, field_59: "1", field_58: "1" } } + + it "is not permitted" do + expect(parser.errors[:field_59]).to be_present + end + end + end + + describe "#field_60" do + context "when 1 and another disability field selected" do + let(:attributes) { { bulk_upload:, field_60: "1", field_58: "1" } } + + it "is not permitted" do + expect(parser.errors[:field_60]).to be_present + end + end + end + + describe "#field_59, #field_60" do + context "when both 1" do + let(:attributes) { { bulk_upload:, field_59: "1", field_60: "1" } } + + it "is not permitted" do + expect(parser.errors[:field_59]).to be_present + expect(parser.errors[:field_60]).to be_present + end + end + end + describe "#field_78" do # referral context "when 3 ie PRP nominated by LA and owning org is LA" do let(:attributes) { { bulk_upload:, field_78: "3", field_111: owning_org.old_visible_id } } @@ -287,14 +484,24 @@ RSpec.describe BulkUpload::Lettings::RowParser do end end - context "when 4 ie referred by LA and is general needs" do - let(:attributes) { { bulk_upload:, field_78: "4" } } + context "when 4 ie referred by LA and is general needs and owning org is LA" do + let(:attributes) { { bulk_upload:, field_78: "4", field_111: owning_org.old_visible_id.to_s } } it "is not permitted" do expect(parser.errors[:field_78]).to be_present end end + context "when 4 ie referred by LA and is general needs and owning org is PRP" do + let(:owning_org) { create(:organisation, :prp, :with_old_visible_id) } + + let(:attributes) { { bulk_upload:, field_78: "4", field_111: owning_org.old_visible_id.to_s } } + + it "is permitted" do + expect(parser.errors[:field_78]).to be_blank + end + end + context "when 4 ie referred by LA and is not general needs" do let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: 2) } let(:attributes) { { bulk_upload:, field_78: "4" } } @@ -307,7 +514,7 @@ RSpec.describe BulkUpload::Lettings::RowParser do describe "fields 96, 97, 98 => startdate" do context "when any one of these fields is blank" do - let(:attributes) { { bulk_upload:, field_96: nil, field_97: nil, field_98: nil } } + let(:attributes) { { bulk_upload:, field_1: "1", field_96: nil, field_97: nil, field_98: nil } } it "returns an error" do parser.valid? @@ -318,6 +525,24 @@ RSpec.describe BulkUpload::Lettings::RowParser do end end + context "when field 98 is 4 digits instead of 2" do + let(:attributes) { { bulk_upload:, field_98: "2022" } } + + it "returns an error" do + parser.valid? + + expect(parser.errors[:field_98]).to include("Tenancy start year must be 2 digits") + end + end + + context "when invalid date given" do + let(:attributes) { { bulk_upload:, field_1: "1", field_96: "a", field_97: "12", field_98: "2022" } } + + it "does not raise an error" do + expect { parser.valid? }.not_to raise_error + end + end + context "when inside of collection year" do let(:attributes) { { bulk_upload:, field_96: "1", field_97: "10", field_98: "22" } } @@ -353,6 +578,68 @@ RSpec.describe BulkUpload::Lettings::RowParser do end end + describe "#field_111" do # owning org + context "when cannot find owning org" do + let(:attributes) { { bulk_upload:, field_111: "donotexist" } } + + it "is not permitted" do + expect(parser.errors[:field_111]).to eql(["The owning organisation code is incorrect"]) + end + end + + context "when org is not stock owning" do + let(:owning_org) { create(:organisation, :with_old_visible_id, :does_not_own_stock) } + + let(:attributes) { { bulk_upload:, field_111: owning_org.old_visible_id } } + + it "is not permitted" do + expect(parser.errors[:field_111]).to eql(["The owning organisation code provided is for an organisation that does not own stock"]) + end + + it "blocks log creation" do + expect(parser).to be_block_log_creation + end + end + + context "when not affiliated with owning org" do + let(:unaffiliated_org) { create(:organisation, :with_old_visible_id) } + + let(:attributes) { { bulk_upload:, field_111: unaffiliated_org.old_visible_id } } + + it "is not permitted" do + expect(parser.errors[:field_111]).to eql(["You do not have permission to add logs for this owning organisation"]) + end + + it "blocks log creation" do + expect(parser).to be_block_log_creation + end + end + end + + describe "#field_113" do # managing org + context "when cannot find managing org" do + let(:attributes) { { bulk_upload:, field_113: "donotexist" } } + + it "is not permitted" do + expect(parser.errors[:field_113]).to eql(["The managing organisation code is incorrect"]) + end + end + + context "when not affiliated with managing org" do + let(:unaffiliated_org) { create(:organisation, :with_old_visible_id) } + + let(:attributes) { { bulk_upload:, field_111: owning_org.old_visible_id, field_113: unaffiliated_org.old_visible_id } } + + it "is not permitted" do + expect(parser.errors[:field_113]).to eql(["This managing organisation does not have a relationship with the owning organisation"]) + end + + it "blocks log creation" do + expect(parser).to be_block_log_creation + end + end + end + describe "#field_134" do context "when an unpermitted value" do let(:attributes) { { bulk_upload:, field_134: 3 } } @@ -387,6 +674,46 @@ RSpec.describe BulkUpload::Lettings::RowParser do end describe "#log" do + describe "#location" do + context "when lookup is via new core id" do + let(:attributes) { { bulk_upload:, field_4: scheme.old_visible_id, field_5: location.id, field_111: owning_org } } + + it "assigns the correct location" do + expect(parser.log.location).to eql(location) + end + end + end + + describe "#scheme" do + context "when lookup is via id prefixed with S" do + let(:attributes) { { bulk_upload:, field_4: "S#{scheme.id}", field_111: owning_org } } + + it "assigns the correct scheme" do + expect(parser.log.scheme).to eql(scheme) + end + end + end + + describe "#owning_organisation" do + context "when lookup is via id prefixed with ORG" do + let(:attributes) { { bulk_upload:, field_111: "ORG#{owning_org.id}" } } + + it "assigns the correct org" do + expect(parser.log.owning_organisation).to eql(owning_org) + end + end + end + + describe "#managing_organisation" do + context "when lookup is via id prefixed with ORG" do + let(:attributes) { { bulk_upload:, field_113: "ORG#{managing_org.id}" } } + + it "assigns the correct org" do + expect(parser.log.managing_organisation).to eql(managing_org) + end + end + end + describe "#cbl" do context "when field_75 is yes ie 1" do let(:attributes) { { bulk_upload:, field_75: 1 } } @@ -828,6 +1155,60 @@ RSpec.describe BulkUpload::Lettings::RowParser do end end end + + describe "#housingneeds" do + context "when no disabled needs" do + let(:attributes) { { bulk_upload:, field_59: "1" } } + + it "sets to 2" do + expect(parser.log.housingneeds).to eq(2) + end + end + + context "when dont know about disabled needs" do + let(:attributes) { { bulk_upload:, field_60: "1" } } + + it "sets to 3" do + expect(parser.log.housingneeds).to eq(3) + end + end + end + + describe "#housingneeds_type" do + context "when field_55 is 1" do + let(:attributes) { { bulk_upload:, field_55: "1" } } + + it "set to 0" do + expect(parser.log.housingneeds_type).to eq(0) + end + end + + context "when field_56 is 1" do + let(:attributes) { { bulk_upload:, field_56: "1" } } + + it "set to 1" do + expect(parser.log.housingneeds_type).to eq(1) + end + end + + context "when field_57 is 1" do + let(:attributes) { { bulk_upload:, field_57: "1" } } + + it "set to 2" do + expect(parser.log.housingneeds_type).to eq(2) + end + end + end + + describe "#housingneeds_other" do + context "when field_58 is 1" do + let(:attributes) { { bulk_upload:, field_58: "1" } } + + it "sets to 1" do + expect(parser.log.housingneeds_other).to eq(1) + end + end + end end describe "#start_date" do diff --git a/spec/services/bulk_upload/lettings/validator_spec.rb b/spec/services/bulk_upload/lettings/validator_spec.rb index 263c83163..0aaeaac78 100644 --- a/spec/services/bulk_upload/lettings/validator_spec.rb +++ b/spec/services/bulk_upload/lettings/validator_spec.rb @@ -87,7 +87,7 @@ RSpec.describe BulkUpload::Lettings::Validator do end end - describe "#should_create_logs?" do + describe "#create_logs?" do context "when all logs are valid" do let(:target_path) { file_fixture("2022_23_lettings_bulk_upload.csv") } @@ -111,9 +111,7 @@ RSpec.describe BulkUpload::Lettings::Validator do expect(validator).not_to be_create_logs end end - end - describe "#create_logs?" do context "when a log is not valid?" do let(:log_1) { build(:lettings_log, :completed, created_by: user) } let(:log_2) { build(:lettings_log, :completed, created_by: user) } @@ -146,6 +144,22 @@ RSpec.describe BulkUpload::Lettings::Validator do end end + context "when a single log wants to block log creation" do + let(:unaffiliated_org) { create(:organisation) } + + let(:log_1) { build(:lettings_log, :completed, renttype: 1, created_by: user, owning_organisation: unaffiliated_org) } + + before do + file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.close + end + + it "will not create logs" do + validator.call + expect(validator).not_to be_create_logs + end + end + context "when a log has incomplete setup secion" do let(:log) { build(:lettings_log, :in_progress, created_by: user, startdate: Time.zone.local(2022, 5, 1)) } diff --git a/spec/services/bulk_upload/processor_spec.rb b/spec/services/bulk_upload/processor_spec.rb index b751e7dd7..d56b2ff4e 100644 --- a/spec/services/bulk_upload/processor_spec.rb +++ b/spec/services/bulk_upload/processor_spec.rb @@ -171,6 +171,17 @@ RSpec.describe BulkUpload::Processor do expect(mock_downloader).to have_received(:delete_local_file!) end + it "sends correct and upload again mail" do + mail_double = instance_double("ActionMailer::MessageDelivery", deliver_later: nil) + + allow(BulkUploadMailer).to receive(:send_correct_and_upload_again_mail).and_return(mail_double) + + processor.call + + expect(BulkUploadMailer).to have_received(:send_correct_and_upload_again_mail) + expect(mail_double).to have_received(:deliver_later) + end + it "does not send fix errors email" do allow(BulkUploadMailer).to receive(:send_bulk_upload_with_errors_mail).and_call_original diff --git a/spec/services/exports/lettings_log_export_service_spec.rb b/spec/services/exports/lettings_log_export_service_spec.rb index a8d626b98..55897c354 100644 --- a/spec/services/exports/lettings_log_export_service_spec.rb +++ b/spec/services/exports/lettings_log_export_service_spec.rb @@ -13,8 +13,8 @@ RSpec.describe Exports::LettingsLogExportService do let(:expected_master_manifest_filename) { "Manifest_2022_05_01_0001.csv" } let(:expected_master_manifest_rerun) { "Manifest_2022_05_01_0002.csv" } - let(:expected_zip_filename) { "core_2021_2022_jan_mar_f0001_inc0001.zip" } - let(:expected_data_filename) { "core_2021_2022_jan_mar_f0001_inc0001_pt001.xml" } + let(:expected_zip_filename) { "core_2021_2022_apr_mar_f0001_inc0001.zip" } + let(:expected_data_filename) { "core_2021_2022_apr_mar_f0001_inc0001_pt001.xml" } let(:expected_manifest_filename) { "manifest.xml" } let(:start_time) { Time.zone.local(2022, 5, 1) } @@ -108,7 +108,7 @@ RSpec.describe Exports::LettingsLogExportService do end context "and multiple lettings logs are available for export on different periods" do - let(:expected_zip_filename2) { "core_2022_2023_apr_jun_f0001_inc0001.zip" } + let(:expected_zip_filename2) { "core_2022_2023_apr_mar_f0001_inc0001.zip" } before do FactoryBot.create(:lettings_log, startdate: Time.zone.local(2022, 2, 1)) @@ -206,7 +206,7 @@ RSpec.describe Exports::LettingsLogExportService do end it "generates a ZIP export file with the expected filename" do - expect(storage_service).to receive(:write_file).with("core_2021_2022_jan_mar_f0002_inc0001.zip", any_args) + expect(storage_service).to receive(:write_file).with("core_2021_2022_apr_mar_f0002_inc0001.zip", any_args) export_service.export_xml_lettings_logs(full_update: true) end end @@ -258,6 +258,10 @@ RSpec.describe Exports::LettingsLogExportService do let(:lettings_log) { FactoryBot.create(:lettings_log, :completed, :export, :sh, scheme:, location:, created_by: user, owning_organisation: organisation, startdate: Time.utc(2022, 2, 2, 10, 36, 49), underoccupation_benefitcap: 4, sheltered: 1) } + before do + FactoryBot.create(:location, scheme:, startdate: Time.zone.local(2021, 4, 1), units: nil) + end + it "generates an XML export file with the expected content" do expected_content = replace_entity_ids(lettings_log, export_file.read) expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|