Browse Source

Merge branch 'refs/heads/main' into CLDC-NONE-update-local-development-docs

# Conflicts:
#	docs/setup.md
CLDC-NONE-update-local-development-docs
samyou-softwire 1 week ago
parent
commit
a2e4481350
  1. 20
      .github/workflows/run_tests.yml
  2. 2
      .nvmrc
  3. 2
      .ruby-version
  4. 15
      Dockerfile
  5. 2
      Gemfile
  6. 2
      Gemfile.lock
  7. 2
      app/frontend/controllers/numeric_question_controller.js
  8. 12
      app/models/form/lettings/pages/no_household_member_likely_to_be_pregnant_check.rb
  9. 4
      app/models/form/lettings/pages/property_local_authority.rb
  10. 2
      app/models/form/lettings/questions/builtype.rb
  11. 2
      app/models/form/lettings/subsections/household_characteristics.rb
  12. 4
      app/models/form/sales/pages/property_local_authority.rb
  13. 11
      app/models/form/sales/questions/buyer_still_serving.rb
  14. 2
      app/models/form/sales/questions/property_building_type.rb
  15. 2
      app/models/form/sales/questions/purchase_price.rb
  16. 21
      app/models/lettings_log.rb
  17. 9
      app/models/validations/financial_validations.rb
  18. 6
      app/views/form/headers/_person_2_known_page.erb
  19. 6
      app/views/form/headers/_person_3_known_page.erb
  20. 6
      app/views/form/headers/_person_4_known_page.erb
  21. 6
      app/views/form/headers/_person_5_known_page.erb
  22. 6
      app/views/form/headers/_person_6_known_page.erb
  23. 8
      aws-devcontainer/.devcontainer/Dockerfile
  24. 3
      config/locales/validations/lettings/financial.en.yml
  25. 4
      docs/Gemfile.lock
  26. 10
      docs/setup.md
  27. 34
      lib/tasks/delete_logs_in_collection_year_and_earlier.rake
  28. 129
      lib/tasks/log_la_fix.rake
  29. 24
      lib/tasks/remap_2025_hhregresstill_values.rake
  30. 11
      lib/tasks/round_value_for_2026_sales_logs.rake
  31. 10
      lib/tasks/update_logs_with_invalid_hb_benefits_2026.rake
  32. 2
      package.json
  33. 9
      spec/features/form/progressive_total_field_spec.rb
  34. 2
      spec/fixtures/files/lettings_log_csv_export_labels_23.csv
  35. 2
      spec/fixtures/files/lettings_log_csv_export_labels_24.csv
  36. 2
      spec/fixtures/files/lettings_log_csv_export_labels_25.csv
  37. 2
      spec/fixtures/files/lettings_log_csv_export_non_support_labels_23.csv
  38. 2
      spec/fixtures/files/lettings_log_csv_export_non_support_labels_24.csv
  39. 2
      spec/fixtures/files/lettings_log_csv_export_non_support_labels_25.csv
  40. 2
      spec/fixtures/files/sales_logs_csv_export_labels_24.csv
  41. 2
      spec/fixtures/files/sales_logs_csv_export_labels_25.csv
  42. 2
      spec/fixtures/files/sales_logs_csv_export_labels_26.csv
  43. 2
      spec/fixtures/files/sales_logs_csv_export_non_support_labels_24.csv
  44. 2
      spec/fixtures/files/sales_logs_csv_export_non_support_labels_25.csv
  45. 2
      spec/fixtures/files/sales_logs_csv_export_non_support_labels_26.csv
  46. 72
      spec/models/form/lettings/pages/property_local_authority_spec.rb
  47. 61
      spec/models/form/sales/pages/property_local_authority_spec.rb
  48. 27
      spec/models/form/sales/questions/buyer_still_serving_spec.rb
  49. 2
      spec/models/form/sales/questions/property_building_type_spec.rb
  50. 101
      spec/models/validations/financial_validations_spec.rb
  51. 2
      spec/requests/check_errors_controller_spec.rb

20
.github/workflows/run_tests.yml

@ -38,7 +38,6 @@ jobs:
env: env:
RAILS_ENV: test RAILS_ENV: test
GEMFILE_RUBY_VERSION: 3.1.1
DB_HOST: localhost DB_HOST: localhost
DB_DATABASE: data_collector DB_DATABASE: data_collector
DB_USERNAME: postgres DB_USERNAME: postgres
@ -59,7 +58,7 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
cache: yarn cache: yarn
node-version: 20 node-version: 24
# This is temporary to fix flaky parallel tests due to `secret_key_base` being read before it's set # This is temporary to fix flaky parallel tests due to `secret_key_base` being read before it's set
- name: Create local secret - name: Create local secret
@ -102,7 +101,6 @@ jobs:
env: env:
RAILS_ENV: test RAILS_ENV: test
GEMFILE_RUBY_VERSION: 3.1.1
DB_HOST: localhost DB_HOST: localhost
DB_DATABASE: data_collector DB_DATABASE: data_collector
DB_USERNAME: postgres DB_USERNAME: postgres
@ -122,7 +120,7 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
cache: yarn cache: yarn
node-version: 20 node-version: 24
- name: Create database - name: Create database
run: | run: |
@ -160,7 +158,6 @@ jobs:
env: env:
RAILS_ENV: test RAILS_ENV: test
GEMFILE_RUBY_VERSION: 3.1.1
DB_HOST: localhost DB_HOST: localhost
DB_DATABASE: data_collector DB_DATABASE: data_collector
DB_USERNAME: postgres DB_USERNAME: postgres
@ -180,7 +177,7 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
cache: yarn cache: yarn
node-version: 20 node-version: 24
- name: Create database - name: Create database
run: | run: |
@ -218,7 +215,6 @@ jobs:
env: env:
RAILS_ENV: test RAILS_ENV: test
GEMFILE_RUBY_VERSION: 3.1.1
DB_HOST: localhost DB_HOST: localhost
DB_DATABASE: data_collector DB_DATABASE: data_collector
DB_USERNAME: postgres DB_USERNAME: postgres
@ -239,7 +235,7 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
cache: yarn cache: yarn
node-version: 20 node-version: 24
- name: Create local secret - name: Create local secret
run: | run: |
@ -281,7 +277,6 @@ jobs:
env: env:
RAILS_ENV: test RAILS_ENV: test
GEMFILE_RUBY_VERSION: 3.1.1
DB_HOST: localhost DB_HOST: localhost
DB_DATABASE: data_collector DB_DATABASE: data_collector
DB_USERNAME: postgres DB_USERNAME: postgres
@ -302,7 +297,7 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
cache: yarn cache: yarn
node-version: 20 node-version: 24
- name: Create local secret - name: Create local secret
run: | run: |
@ -344,7 +339,6 @@ jobs:
env: env:
RAILS_ENV: test RAILS_ENV: test
GEMFILE_RUBY_VERSION: 3.1.1
DB_HOST: localhost DB_HOST: localhost
DB_DATABASE: data_collector DB_DATABASE: data_collector
DB_USERNAME: postgres DB_USERNAME: postgres
@ -365,7 +359,7 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
cache: yarn cache: yarn
node-version: 20 node-version: 24
- name: Create database - name: Create database
run: | run: |
@ -396,7 +390,7 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
cache: yarn cache: yarn
node-version: 20 node-version: 24
- name: Install packages and symlink local dependencies - name: Install packages and symlink local dependencies
run: | run: |

2
.nvmrc

@ -1 +1 @@
20 24

2
.ruby-version

@ -1 +1 @@
3.4.4 3.4.9

15
Dockerfile

@ -1,7 +1,10 @@
FROM ruby:3.4.4-alpine3.20 as base FROM ruby:3.4.9-alpine3.23 as base
WORKDIR /app WORKDIR /app
# Upgrade base packages to pick up latest security patches
RUN apk upgrade --no-cache
# Add the timezone as it's not configured by default in Alpine # Add the timezone as it's not configured by default in Alpine
RUN apk add --update --no-cache tzdata && \ RUN apk add --update --no-cache tzdata && \
cp /usr/share/zoneinfo/Europe/London /etc/localtime && \ cp /usr/share/zoneinfo/Europe/London /etc/localtime && \
@ -10,7 +13,7 @@ RUN apk add --update --no-cache tzdata && \
# build-base: compilation tools for bundle # build-base: compilation tools for bundle
# yarn: node package manager # yarn: node package manager
# postgresql-dev: postgres driver and libraries # postgresql-dev: postgres driver and libraries
RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r29 nodejs=20.15.1-r0 yarn=1.22.22-r0 bash=5.2.26-r0 libpq-dev yaml-dev linux-headers RUN apk add --no-cache build-base busybox nodejs yarn bash libpq-dev yaml-dev linux-headers
# Bundler version should be the same version as what the Gemfile.lock was bundled with # Bundler version should be the same version as what the Gemfile.lock was bundled with
RUN gem install bundler:2.6.4 --no-document RUN gem install bundler:2.6.4 --no-document
@ -40,14 +43,14 @@ RUN bundle config set without ""
RUN bundle install --jobs=4 --no-binstubs --no-cache RUN bundle install --jobs=4 --no-binstubs --no-cache
# Install gecko driver for Capybara tests # Install gecko driver for Capybara tests
RUN apk add firefox RUN apk add firefox=145.0-r0
RUN wget https://github.com/mozilla/geckodriver/releases/download/v0.31.0/geckodriver-v0.31.0-linux64.tar.gz \ RUN wget https://github.com/mozilla/geckodriver/releases/download/v0.31.0/geckodriver-v0.31.0-linux64.tar.gz \
&& tar -xvzf geckodriver-v0.31.0-linux64.tar.gz \ && tar -xvzf geckodriver-v0.31.0-linux64.tar.gz \
&& rm geckodriver-v0.31.0-linux64.tar.gz \ && rm geckodriver-v0.31.0-linux64.tar.gz \
&& chmod +x geckodriver \ && chmod +x geckodriver \
&& mv geckodriver /usr/local/bin/ && mv geckodriver /usr/local/bin/
CMD bundle exec rake parallel:setup && bundle exec rake parallel:spec CMD ["sh", "-c", "bundle exec rake parallel:setup && bundle exec rake parallel:spec"]
FROM base as development FROM base as development
@ -61,7 +64,7 @@ RUN bundle install --jobs=4 --no-binstubs --no-cache
USER nonroot USER nonroot
CMD bundle exec rails s -e ${RAILS_ENV} -p ${PORT} --binding=0.0.0.0 CMD ["sh", "-c", "bundle exec rails s -e ${RAILS_ENV} -p ${PORT} --binding=0.0.0.0"]
FROM base as production FROM base as production
@ -75,4 +78,4 @@ RUN chown -R nonroot performance_test
USER nonroot USER nonroot
CMD bundle exec rails s -e ${RAILS_ENV} -p ${PORT} --binding=0.0.0.0 CMD ["sh", "-c", "bundle exec rails s -e ${RAILS_ENV} -p ${PORT} --binding=0.0.0.0"]

2
Gemfile

@ -3,7 +3,7 @@
source "https://rubygems.org" source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" } git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby "3.4.4" ruby "3.4.9"
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main'
gem "rails", "~> 7.2.2" gem "rails", "~> 7.2.2"

2
Gemfile.lock

@ -648,7 +648,7 @@ DEPENDENCIES
webmock webmock
RUBY VERSION RUBY VERSION
ruby 3.4.4p0 ruby 3.4.9p82
BUNDLED WITH BUNDLED WITH
2.6.4 2.6.4

2
app/frontend/controllers/numeric_question_controller.js

@ -11,7 +11,7 @@ export default class extends Controller {
calculateFields () { calculateFields () {
const affectedField = this.element.dataset.target const affectedField = this.element.dataset.target
const fieldsToAdd = JSON.parse(this.element.dataset.calculated).map(x => `lettings-log-${x.replaceAll('_', '-')}-field`) const fieldsToAdd = JSON.parse(this.element.dataset.calculated).map(x => `lettings-log-${x.replaceAll('_', '-')}-field`)
const valuesToAdd = fieldsToAdd.map(x => getFieldValue(x)).filter(x => x) const valuesToAdd = fieldsToAdd.map(x => getFieldValue(x)).filter(x => x && !isNaN(parseFloat(x)))
const newValue = valuesToAdd.map(x => parseFloat(x)).reduce((a, b) => a + b, 0).toFixed(2) const newValue = valuesToAdd.map(x => parseFloat(x)).reduce((a, b) => a + b, 0).toFixed(2)
const elementToUpdate = document.getElementById(affectedField) const elementToUpdate = document.getElementById(affectedField)
elementToUpdate.value = newValue elementToUpdate.value = newValue

12
app/models/form/lettings/pages/no_household_member_likely_to_be_pregnant_check.rb

@ -2,7 +2,8 @@ class Form::Lettings::Pages::NoHouseholdMemberLikelyToBePregnantCheck < ::Form::
def initialize(id, hsh, subsection, person_index: 0) def initialize(id, hsh, subsection, person_index: 0)
super(id, hsh, subsection) super(id, hsh, subsection)
@copy_key = "lettings.soft_validations.pregnancy_value_check.no_household_member_likely_to_be_pregnant_check" @copy_key = "lettings.soft_validations.pregnancy_value_check.no_household_member_likely_to_be_pregnant_check"
@depends_on = [{ "no_household_member_likely_to_be_pregnant?" => true }] @person_index = person_index
@depends_on = depends_on
@title_text = { @title_text = {
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text", "translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text",
"arguments" => [], "arguments" => [],
@ -11,7 +12,14 @@ class Form::Lettings::Pages::NoHouseholdMemberLikelyToBePregnantCheck < ::Form::
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.informative_text", "translation" => "forms.#{form.start_date.year}.#{@copy_key}.informative_text",
"arguments" => [], "arguments" => [],
} }
@person_index = person_index end
def depends_on
if @person_index >= 2
[{ "no_household_member_likely_to_be_pregnant?" => true, "details_known_#{@person_index}" => 0 }]
else
[{ "no_household_member_likely_to_be_pregnant?" => true }]
end
end end
def questions def questions

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

@ -3,8 +3,8 @@ class Form::Lettings::Pages::PropertyLocalAuthority < ::Form::Page
super super
@id = "property_local_authority" @id = "property_local_authority"
@depends_on = [ @depends_on = [
{ "is_la_inferred" => false, "is_general_needs?" => true, "form.start_year_2024_or_later?" => false }, { "is_la_inferred" => false, "is_general_needs?" => true, "form.start_year_2025_or_later?" => false, "address_search_given?" => true },
{ "is_la_inferred" => false, "is_general_needs?" => true, "form.start_year_2024_or_later?" => true, "address_search_given?" => true }, { "is_la_inferred" => false, "is_general_needs?" => true, "form.start_year_2025_or_later?" => true },
] ]
end end

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

@ -9,7 +9,7 @@ class Form::Lettings::Questions::Builtype < ::Form::Question
ANSWER_OPTIONS = { ANSWER_OPTIONS = {
"2" => { "value" => "Converted from previous residential or non-residential property" }, "2" => { "value" => "Converted from previous residential or non-residential property" },
"1" => { "value" => "Purpose built" }, "1" => { "value" => "Purpose-built" },
}.freeze }.freeze
QUESTION_NUMBER_FROM_YEAR = { 2023 => 20, 2024 => 20, 2025 => 20 }.freeze QUESTION_NUMBER_FROM_YEAR = { 2023 => 20, 2024 => 20, 2025 => 20 }.freeze

2
app/models/form/lettings/subsections/household_characteristics.rb

@ -16,7 +16,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::LeadTenantAge.new(nil, nil, self), Form::Lettings::Pages::LeadTenantAge.new(nil, nil, self),
(Form::Lettings::Pages::NoFemalesPregnantHouseholdLeadAgeValueCheck.new(nil, nil, self) unless form.start_year_2026_or_later?), (Form::Lettings::Pages::NoFemalesPregnantHouseholdLeadAgeValueCheck.new(nil, nil, self) unless form.start_year_2026_or_later?),
(Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdLeadAgeValueCheck.new(nil, nil, self) unless form.start_year_2026_or_later?), (Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdLeadAgeValueCheck.new(nil, nil, self) unless form.start_year_2026_or_later?),
(Form::Lettings::Pages::NoHouseholdMemberLikelyToBePregnantCheck.new("no_household_member_likely_to_be_pregnant_lead_age_check", nil, self) if form.start_year_2026_or_later?), (Form::Lettings::Pages::NoHouseholdMemberLikelyToBePregnantCheck.new("no_household_member_likely_to_be_pregnant_lead_age_check", nil, self, person_index: 1) if form.start_year_2026_or_later?),
Form::Lettings::Pages::LeadTenantUnderRetirementValueCheck.new("age_lead_tenant_under_retirement_value_check", nil, self), Form::Lettings::Pages::LeadTenantUnderRetirementValueCheck.new("age_lead_tenant_under_retirement_value_check", nil, self),
Form::Lettings::Pages::LeadTenantOverRetirementValueCheck.new("age_lead_tenant_over_retirement_value_check", nil, self), Form::Lettings::Pages::LeadTenantOverRetirementValueCheck.new("age_lead_tenant_over_retirement_value_check", nil, self),
(Form::Lettings::Pages::LeadTenantSexRegisteredAtBirth.new(nil, nil, self) if form.start_year_2026_or_later?), (Form::Lettings::Pages::LeadTenantSexRegisteredAtBirth.new(nil, nil, self) if form.start_year_2026_or_later?),

4
app/models/form/sales/pages/property_local_authority.rb

@ -3,8 +3,8 @@ class Form::Sales::Pages::PropertyLocalAuthority < ::Form::Page
super super
@id = "property_local_authority" @id = "property_local_authority"
@depends_on = [ @depends_on = [
{ "is_la_inferred" => false, "form.start_year_2024_or_later?" => false }, { "is_la_inferred" => false, "form.start_year_2025_or_later?" => false, "address_search_given?" => true },
{ "is_la_inferred" => false, "form.start_year_2024_or_later?" => true, "address_search_given?" => true }, { "is_la_inferred" => false, "form.start_year_2025_or_later?" => true },
] ]
end end

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

@ -19,13 +19,18 @@ class Form::Sales::Questions::BuyerStillServing < ::Form::Question
else else
{ {
"4" => { "value" => "Yes" }, "4" => { "value" => "Yes" },
"5" => { "value" => "No" }, "5" => { "value" => "No - they left up to and including 2 years ago" },
"6" => { "value" => "Buyer prefers not to say" }, "6" => { "value" => "No - they left more than 2 years ago" },
"divider" => { "value" => true }, "divider" => { "value" => true },
"7" => { "value" => "Don’t know" }, "9" => { "value" => "Don’t know" },
"10" => { "value" => "No" },
}.freeze }.freeze
end end
end end
def displayed_answer_options(_log, _user = nil)
answer_options.reject { |key, _v| key == "10" }
end
QUESTION_NUMBER_FROM_YEAR = { 2023 => 63, 2024 => 65, 2025 => 62, 2026 => 70 }.freeze QUESTION_NUMBER_FROM_YEAR = { 2023 => 63, 2024 => 65, 2025 => 62, 2026 => 70 }.freeze
end end

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

@ -9,7 +9,7 @@ class Form::Sales::Questions::PropertyBuildingType < ::Form::Question
end end
ANSWER_OPTIONS = { ANSWER_OPTIONS = {
"1" => { "value" => "Purpose built" }, "1" => { "value" => "Purpose-built" },
"2" => { "value" => "Converted from previous residential or non-residential property" }, "2" => { "value" => "Converted from previous residential or non-residential property" },
}.freeze }.freeze

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

@ -4,7 +4,7 @@ class Form::Sales::Questions::PurchasePrice < ::Form::Question
@id = "value" @id = "value"
@type = "numeric" @type = "numeric"
@min = form.start_year_2026_or_later? ? 15_000 : 0 @min = form.start_year_2026_or_later? ? 15_000 : 0
@step = 0.01 @step = form.start_year_2026_or_later? ? 1 : 0.01 # 0.01 was a mistake that was fixed in 2026
@width = 5 @width = 5
@prefix = "£" @prefix = "£"
@ownership_sch = ownershipsch @ownership_sch = ownershipsch

21
app/models/lettings_log.rb

@ -542,7 +542,7 @@ class LettingsLog < Log
reason == 1 reason == 1
end end
def receives_housing_benefit_only? def receives_housing_benefit?
# 1: Housing benefit # 1: Housing benefit
hb == 1 hb == 1
end end
@ -551,13 +551,7 @@ class LettingsLog < Log
hb == 3 hb == 3
end end
# Option 8 has been removed starting from 22/23 def receives_universal_credit
def receives_housing_benefit_and_universal_credit?
# 8: Housing benefit and Universal Credit (without housing element)
hb == 8
end
def receives_uc_with_housing_element_excl_housing_benefit?
# 6: Universal Credit with housing element (excluding housing benefit) # 6: Universal Credit with housing element (excluding housing benefit)
hb == 6 hb == 6
end end
@ -572,12 +566,11 @@ class LettingsLog < Log
end end
def receives_housing_related_benefits? def receives_housing_related_benefits?
if collection_start_year <= 2021 receives_housing_benefit? || receives_universal_credit
receives_housing_benefit_only? || receives_uc_with_housing_element_excl_housing_benefit? || end
receives_housing_benefit_and_universal_credit?
else def no_household_income_comes_from_benefits?
receives_housing_benefit_only? || receives_uc_with_housing_element_excl_housing_benefit? benefits == 3
end
end end
def local_housing_referral? def local_housing_referral?

9
app/models/validations/financial_validations.rb

@ -175,6 +175,15 @@ module Validations::FinancialValidations
end end
end end
def validate_housing_benefits_matches_income_proportion(record)
return unless record.hb && record.benefits && record.form.start_year_2026_or_later?
if (record.receives_universal_credit || record.receives_housing_benefit?) && record.no_household_income_comes_from_benefits?
record.errors.add :hb, I18n.t("validations.lettings.financial.hb.housing_benefits_not_match_income_source")
record.errors.add :benefits, I18n.t("validations.lettings.financial.benefits.housing_benefits_not_match_income_source")
end
end
private private
def validate_charges(record) def validate_charges(record)

6
app/views/form/headers/_person_2_known_page.erb

@ -1 +1,5 @@
You have given us the details for 0 of the <%= log.hholdcount %> other people in the household <% if log.form.start_year_2026_or_later? %>
You have given us the details for 1 of the <%= log.hholdcount %> people in the household
<% else %>
You have given us the details for 0 of the <%= log.hholdcount %> other people in the household
<% end %>

6
app/views/form/headers/_person_3_known_page.erb

@ -1 +1,5 @@
You have given us the details for <%= log.joint_purchase? ? 0 : 1 %> of the <%= log.hholdcount %> other people in the household <% if log.form.start_year_2026_or_later? %>
You have given us the details for 2 of the <%= log.hholdcount %> people in the household
<% else %>
You have given us the details for <%= log.joint_purchase? ? 0 : 1 %> of the <%= log.hholdcount %> other people in the household
<% end %>

6
app/views/form/headers/_person_4_known_page.erb

@ -1 +1,5 @@
You have given us the details for <%= log.joint_purchase? ? 1 : 2 %> of the <%= log.hholdcount %> other people in the household <% if log.form.start_year_2026_or_later? %>
You have given us the details for 3 of the <%= log.hholdcount %> people in the household
<% else %>
You have given us the details for <%= log.joint_purchase? ? 1 : 2 %> of the <%= log.hholdcount %> other people in the household
<% end %>

6
app/views/form/headers/_person_5_known_page.erb

@ -1 +1,5 @@
You have given us the details for <%= log.joint_purchase? ? 2 : 3 %> of the <%= log.hholdcount %> other people in the household <% if log.form.start_year_2026_or_later? %>
You have given us the details for 4 of the <%= log.hholdcount %> people in the household
<% else %>
You have given us the details for <%= log.joint_purchase? ? 2 : 3 %> of the <%= log.hholdcount %> other people in the household
<% end %>

6
app/views/form/headers/_person_6_known_page.erb

@ -1 +1,5 @@
You have given us the details for <%= log.joint_purchase? ? 3 : 4 %> of the <%= log.hholdcount %> other people in the household <% if log.form.start_year_2026_or_later? %>
You have given us the details for 5 of the <%= log.hholdcount %> people in the household
<% else %>
You have given us the details for <%= log.joint_purchase? ? 3 : 4 %> of the <%= log.hholdcount %> other people in the household
<% end %>

8
aws-devcontainer/.devcontainer/Dockerfile

@ -1,7 +1,13 @@
FROM homebrew/brew FROM homebrew/brew
RUN brew install aws-vault && brew install awscli RUN brew install aws-vault && brew install awscli
RUN curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_64bit/session-manager-plugin.deb" -o "session-manager-plugin.deb" && sudo dpkg -i session-manager-plugin.deb RUN if [ "$(dpkg --print-architecture)" = "arm64" ]; then \
ARCH="ubuntu_arm64"; \
else \
ARCH="ubuntu_64bit"; \
fi && \
curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/${ARCH}/session-manager-plugin.deb" -o "session-manager-plugin.deb" && \
sudo dpkg -i session-manager-plugin.deb
ENV AWS_VAULT_BACKEND=file ENV AWS_VAULT_BACKEND=file
ENV AWS_VAULT_FILE_DIR=./vault ENV AWS_VAULT_FILE_DIR=./vault

3
config/locales/validations/lettings/financial.en.yml

@ -12,6 +12,7 @@ en:
outstanding_amount_not_expected: "Answer must be ‘yes’ as you have answered the outstanding amount question." outstanding_amount_not_expected: "Answer must be ‘yes’ as you have answered the outstanding amount question."
benefits: benefits:
part_or_full_time: "Answer cannot be ‘all’ for income from Universal Credit, state pensions or benefits if the tenant or their partner works part-time or full-time." part_or_full_time: "Answer cannot be ‘all’ for income from Universal Credit, state pensions or benefits if the tenant or their partner works part-time or full-time."
housing_benefits_not_match_income_source: "You answered that none of the household’s income is from Universal Credit, state pensions or benefits, but also that the tenant is likely to be receiving Universal Credit or housing benefit."
earnings: earnings:
over_hard_max: "The household’s income cannot be greater than %{hard_max} per week given the household’s working situation." over_hard_max: "The household’s income cannot be greater than %{hard_max} per week given the household’s working situation."
under_hard_min: "The household’s income cannot be less than %{hard_min} per week given the household’s working situation." under_hard_min: "The household’s income cannot be less than %{hard_min} per week given the household’s working situation."
@ -87,3 +88,5 @@ en:
needstype: needstype:
rent_below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this lettings type." rent_below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this lettings type."
rent_above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this lettings type." rent_above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this lettings type."
hb:
housing_benefits_not_match_income_source: "You answered that none of the household’s income is from Universal Credit, state pensions or benefits, but also that the tenant is likely to be receiving Universal Credit or housing benefit."

4
docs/Gemfile.lock

@ -13,8 +13,8 @@ GEM
minitest (>= 5.1, < 6) minitest (>= 5.1, < 6)
securerandom (>= 0.3) securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5) tzinfo (~> 2.0, >= 2.0.5)
addressable (2.8.1) addressable (2.9.0)
public_suffix (>= 2.0.2, < 6.0) public_suffix (>= 2.0.2, < 8.0)
base64 (0.3.0) base64 (0.3.0)
benchmark (0.5.0) benchmark (0.5.0)
bigdecimal (4.0.1) bigdecimal (4.0.1)

10
docs/setup.md

@ -78,21 +78,19 @@ Some windows IDEs, such as [VSCode](https://code.visualstudio.com/docs/remote/ws
4. Install Ruby and Bundler 4. Install Ruby and Bundler
```bash ```bash
rbenv install 3.4.4 rbenv install 3.4.9
rbenv global 3.4.4 rbenv global 3.4.9
source ~/.bashrc source ~/.bashrc
gem install bundler gem install bundler
``` ```
5. Install JavaScript dependencies 5. Install JavaScript dependencies
Note that we currently use node v20, which is no longer the latest LTS version so you will need to specify the version number when installing
macOS (using nvm): macOS (using nvm):
```bash ```bash
nvm install 20 nvm install 24
nvm use 20 nvm use 24
brew install yarn brew install yarn
``` ```

34
lib/tasks/delete_logs_in_collection_year_and_earlier.rake

@ -0,0 +1,34 @@
desc "Deletes all logs in a given collection year and earlier. Note that this operation is PERMANENT and this will bypass callbacks/paper trail. Use only as instructed in a yearly cleanup task."
task :delete_logs_in_collection_year_and_earlier, %i[year] => :environment do |_task, args|
year = args[:year].to_i
if year < 2020
raise ArgumentError, "Year must be above 2020. Make sure you've written out the entire year"
end
if year > Time.zone.now.year - 3
raise ArgumentError, "Year cannot be the last 3 years, as these may contain visible logs"
end
puts "Deleting Logs before #{year}"
puts "Deleting Sales Logs in batches of 10000"
logs = SalesLog.filter_by_year_or_earlier(year)
logs.in_batches(of: 10_000).each_with_index do |logs, i|
puts "Deleting batch #{i + 1}"
logs.delete_all
end
puts "Done deleting Sales Logs"
puts "Deleting Lettings Logs in batches of 10000"
logs = LettingsLog.filter_by_year_or_earlier(year)
logs.in_batches(of: 10_000).each_with_index do |logs, i|
puts "Deleting batch #{i + 1}"
logs.delete_all
end
puts "Done deleting Lettings Logs"
puts "Done deleting Logs before #{year}"
end

129
lib/tasks/log_la_fix.rake

@ -0,0 +1,129 @@
namespace :log_la_fix do
desc "For all logs missing an LA that could have one, call the postcode changed method to request a new one from postcodes API. For logs where an LA still cannot be found, this will set them back to in progress."
task :search_for_la_on_logs_with_nil_la, [:year] => :environment do |_task, args|
include CollectionTimeHelper
year = args[:year]&.to_i || current_collection_start_year
lettings_logs = LettingsLog.filter_by_year(year).where(la: nil, needstype: 1, status: "completed")
sales_logs = SalesLog.filter_by_year(year).where(la: nil, status: "completed")
lettings_logs_count = lettings_logs.count
sales_logs_count = sales_logs.count
puts "Checking LA on #{lettings_logs_count} lettings logs in #{year}"
i = 0
lettings_logs.find_each do |log|
next unless log.valid?
log.process_postcode_changes!
unless log.save
puts "Failed to save lettings log #{log.id}"
puts "Errors: #{log.errors.full_messages}"
end
if log.la.nil? && log.status == "in_progress"
puts "#lettings##{log.id},\"#{log.tenancycode}\",\"#{log.propcode}\",#{log.owning_organisation_id},\"#{log.owning_organisation&.name}\",#{log.managing_organisation_id},\"#{log.managing_organisation&.name}\",#{log.assigned_to_id},\"#{log.assigned_to&.name}\",#{log.startdate},\"#{log.address_line1}\",\"#{log.address_line2}\",\"#{log.town_or_city}\",\"#{log.county}\",\"#{log.postcode_full}\",\"\",\"\""
end
i += 1
if (i % 100).zero?
puts "Processed #{i} lettings logs"
end
end
puts "Done #{lettings_logs_count} lettings logs"
puts "Checking LA on #{sales_logs_count} sales logs in #{year}"
i = 0
sales_logs.find_each do |log|
next unless log.valid?
log.process_postcode_changes!
unless log.save
puts "Failed to save sales log #{log.id}"
puts "Errors: #{log.errors.full_messages}"
end
if log.la.nil? && log.status == "in_progress"
puts "#sales##{log.id},\"#{log.purchid}\",#{log.owning_organisation_id},\"#{log.owning_organisation&.name}\",#{log.managing_organisation_id},\"#{log.managing_organisation&.name}\",#{log.assigned_to_id},\"#{log.assigned_to&.name}\",#{log.saledate},\"#{log.address_line1}\",\"#{log.address_line2}\",\"#{log.town_or_city}\",\"#{log.county}\",\"#{log.postcode_full}\",\"\",\"\""
end
i += 1
if (i % 100).zero?
puts "Processed #{i} sales logs"
end
end
puts "Done #{sales_logs_count} sales logs"
puts "Done"
end
desc "Parse the output of search_for_la_on_logs_with_nil_la into separate lettings and sales CSV files"
task parse_logs_moved_to_incomplete_with_no_la: :environment do
require "csv"
file = "output.txt"
lettings_headers = %w[id tenancycode propcode owning_organisation_id owning_organisation managing_organisation_id managing_organisation assigned_to_id assigned_to startdate address_line1 address_line2 town_or_city county postcode_full la_ecode la_name]
sales_headers = %w[id purchid owning_organisation_id owning_organisation managing_organisation_id managing_organisation assigned_to_id assigned_to saledate address_line1 address_line2 town_or_city county postcode_full la_ecode la_name]
lettings_csv = CSV.open("lettings_logs_moved_to_incomplete_with_no_la.csv", "w")
sales_csv = CSV.open("sales_logs_moved_to_incomplete_with_no_la.csv", "w")
lettings_csv << lettings_headers
sales_csv << sales_headers
File.readlines(file).each do |line|
line = line.strip
if line.start_with?("#lettings#")
row = CSV.parse_line(line.delete_prefix("#lettings#"), liberal_parsing: true)
lettings_csv << row
elsif line.start_with?("#sales#")
row = CSV.parse_line(line.delete_prefix("#sales#"), liberal_parsing: true)
sales_csv << row
end
end
lettings_csv.close
sales_csv.close
puts "Written lettings_logs_moved_to_incomplete_with_no_la.csv"
puts "Written sales_logs_moved_to_incomplete_with_no_la.csv"
end
desc "Split lettings and sales CSVs by managing organisation into separate files per org"
task split_logs_by_managing_org: :environment do
require "csv"
%w[lettings sales].each do |log_type|
input_file = "#{log_type}_logs_moved_to_incomplete_with_no_la.csv"
rows_by_org = Hash.new { |h, k| h[k] = [] }
table = CSV.read(input_file, headers: true)
table.each do |row|
org_name = row["managing_organisation"]
rows_by_org[org_name] << row
end
rows_by_org.each do |org_name, rows|
if rows.size < 30
puts "Skipping #{org_name} (#{rows.size} rows)"
next
end
FileUtils.mkdir_p("log_output")
sanitised_name = org_name.parameterize(separator: "_")
output_file = "log_output/#{sanitised_name}_#{log_type}_logs_moved_to_incomplete_with_no_la.csv"
CSV.open(output_file, "w") do |csv|
csv << table.headers
rows.each { |row| csv << row }
end
puts "Written #{output_file} (#{rows.size} rows)"
end
end
end
end

24
lib/tasks/remap_2025_hhregresstill_values.rake

@ -0,0 +1,24 @@
desc "Remaps hhregresstill values for manually created 2025/26 sales logs"
task :remap_2025_hhregresstill_values, %i[before_datetime] => :environment do |_task, args|
usage_message = "Usage: rake remap_2025_hhregresstill_values['before_datetime']. before_datetime must be in format YYYY-MM-DDTHH:MM:SS"
raise usage_message if args[:before_datetime].blank?
before_datetime = Time.zone.parse(args[:before_datetime])
raise usage_message if before_datetime.nil?
logs = SalesLog.filter_by_year(2025).where(bulk_upload_id: nil).where(hhregresstill: [5, 6, 7]).where("created_at < ?", before_datetime)
puts "Updating #{logs.count} sales logs"
updated_ids = []
logs.find_each do |log|
new_value = case log.hhregresstill
when 5 then 10
when 6, 7 then 9
end
log.update!(hhregresstill: new_value)
updated_ids << log.id
end
puts "Updated log IDs: #{updated_ids.join(', ')}"
puts "Done"
end

11
lib/tasks/round_value_for_2026_sales_logs.rake

@ -0,0 +1,11 @@
desc "Rounds purchase price (the 'value' field) for sales logs in the database if not a whole number"
task round_value_for_2026_sales_logs: :environment do
logs = SalesLog.filter_by_year(2026).where("value % 1 != 0")
puts "Correcting #{logs.count} sales logs, #{logs.map(&:id)}"
logs.find_each do |log|
log.update(value: log.value.round)
end
puts "Done"
end

10
lib/tasks/update_logs_with_invalid_hb_benefits_2026.rake

@ -0,0 +1,10 @@
desc "For logs that fail the validate_housing_universal_credit_matches_income_proportion check created before we released it, clear the answer to the question"
task update_logs_with_invalid_hb_benefits_2026: :environment do
impacted_logs = LettingsLog.filter_by_year(2026).where(hb: [1, 6], benefits: 3)
puts "#{impacted_logs.count} logs will be updated #{impacted_logs.map(&:id)}"
impacted_logs.update!(benefits: nil, hb: nil)
puts "Done"
end

2
package.json

@ -2,7 +2,7 @@
"name": "data-collector", "name": "data-collector",
"private": true, "private": true,
"engines": { "engines": {
"node": "^20.0.0" "node": "^24.0.0"
}, },
"dependencies": { "dependencies": {
"@babel/core": "^7.17.7", "@babel/core": "^7.17.7",

9
spec/features/form/progressive_total_field_spec.rb

@ -58,4 +58,13 @@ RSpec.describe "Accessible Autocomplete" do
fill_in("lettings-log-supcharg-field-error", with: 50) fill_in("lettings-log-supcharg-field-error", with: 50)
expect(find("#lettings-log-tcharge-field").value).to eq("550.00") expect(find("#lettings-log-tcharge-field").value).to eq("550.00")
end end
it "does not show 'NaN' if one of the inputs is not a number", :js do
visit("/lettings-logs/#{lettings_log.id}/rent")
expect(page).to have_selector("#tcharge_div")
fill_in("lettings-log-brent-field", with: 5)
expect(find("#lettings-log-tcharge-field").value).to eq("5.00")
fill_in("lettings-log-pscharge-field", with: "something else")
expect(find("#lettings-log-tcharge-field").value).to eq("5.00")
end
end end

2
spec/fixtures/files/lettings_log_csv_export_labels_23.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/lettings_log_csv_export_labels_24.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/lettings_log_csv_export_labels_25.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/lettings_log_csv_export_non_support_labels_23.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/lettings_log_csv_export_non_support_labels_24.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/lettings_log_csv_export_non_support_labels_25.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_labels_24.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_labels_25.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_labels_26.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_non_support_labels_24.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_non_support_labels_25.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_non_support_labels_26.csv vendored

File diff suppressed because one or more lines are too long

72
spec/models/form/lettings/pages/property_local_authority_spec.rb

@ -35,66 +35,26 @@ RSpec.describe Form::Lettings::Pages::PropertyLocalAuthority, type: :model do
context "when routing to the page" do context "when routing to the page" do
let(:log) { build(:lettings_log) } let(:log) { build(:lettings_log) }
context "with form before 2024" do before do
before do allow(form).to receive(:start_year_2025_or_later?).and_return(true)
allow(form).to receive(:start_year_2024_or_later?).and_return(false)
end
it "is routed to when la is not inferred and it is general needs log" do
log.needstype = 1
log.is_la_inferred = false
expect(page).to be_routed_to(log, nil)
end
it "is not routed to when la is inferred" do
log.needstype = 1
log.is_la_inferred = true
expect(page).not_to be_routed_to(log, nil)
end
it "is not routed to when it's a supported housing log" do
log.needstype = 2
log.is_la_inferred = false
expect(page).not_to be_routed_to(log, nil)
end
end end
context "with form after 2024" do it "is routed to when la is not inferred and it is general needs log" do
before do log.needstype = 1
allow(form).to receive(:start_year_2024_or_later?).and_return(true) log.is_la_inferred = false
end expect(page).to be_routed_to(log, nil)
end
it "is routed to when la is not inferred, it is general needs log and address search has been given" do
log.needstype = 1
log.is_la_inferred = false
log.address_line1_input = "1"
log.postcode_full_input = "A11AA"
expect(page).to be_routed_to(log, nil)
end
it "is not routed to when la is inferred" do
log.needstype = 1
log.is_la_inferred = true
log.address_line1_input = "1"
log.postcode_full_input = "A11AA"
expect(page).not_to be_routed_to(log, nil)
end
it "is not routed to when it's a supported housing log" do it "is not routed to when la is inferred" do
log.needstype = 2 log.needstype = 1
log.is_la_inferred = false log.is_la_inferred = true
log.address_line1_input = "1" expect(page).not_to be_routed_to(log, nil)
log.postcode_full_input = "A11AA" end
expect(page).not_to be_routed_to(log, nil)
end
it "is not routed to when address search is not given" do it "is not routed to when it's a supported housing log" do
log.needstype = 1 log.needstype = 2
log.is_la_inferred = false log.is_la_inferred = false
log.address_line1_input = nil expect(page).not_to be_routed_to(log, nil)
log.postcode_full_input = "A11AA"
expect(page).not_to be_routed_to(log, nil)
end
end end
end end
end end

61
spec/models/form/sales/pages/property_local_authority_spec.rb

@ -17,18 +17,8 @@ RSpec.describe Form::Sales::Pages::PropertyLocalAuthority, type: :model do
expect(page.subsection).to eq(subsection) expect(page.subsection).to eq(subsection)
end end
describe "has correct questions" do it "has correct questions" do
context "when 2023" do expect(page.questions.map(&:id)).to eq(%w[la])
let(:start_date) { Time.utc(2023, 2, 8) }
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(
%w[
la
],
)
end
end
end end
it "has the correct id" do it "has the correct id" do
@ -42,47 +32,18 @@ RSpec.describe Form::Sales::Pages::PropertyLocalAuthority, type: :model do
context "when routing to the page" do context "when routing to the page" do
let(:log) { build(:sales_log) } let(:log) { build(:sales_log) }
context "with form before 2024" do before do
before do allow(form).to receive(:start_year_2025_or_later?).and_return(true)
allow(form).to receive(:start_year_2024_or_later?).and_return(false)
end
it "is routed to when la is not inferred" do
log.is_la_inferred = false
expect(page).to be_routed_to(log, nil)
end
it "is not routed to when la is inferred" do
log.is_la_inferred = true
expect(page).not_to be_routed_to(log, nil)
end
end end
context "with form after 2024" do it "is routed to when la is not inferred" do
before do log.is_la_inferred = false
allow(form).to receive(:start_year_2024_or_later?).and_return(true) expect(page).to be_routed_to(log, nil)
end end
it "is routed to when la is not inferred and address search has been given" do
log.is_la_inferred = false
log.address_line1_input = "1"
log.postcode_full_input = "A11AA"
expect(page).to be_routed_to(log, nil)
end
it "is not routed to when la is inferred" do
log.is_la_inferred = true
log.address_line1_input = "1"
log.postcode_full_input = "A11AA"
expect(page).not_to be_routed_to(log, nil)
end
it "is not routed to when address search is not given" do it "is not routed to when la is inferred" do
log.is_la_inferred = false log.is_la_inferred = true
log.address_line1_input = nil expect(page).not_to be_routed_to(log, nil)
log.postcode_full_input = "A11AA"
expect(page).not_to be_routed_to(log, nil)
end
end end
end end
end end

27
spec/models/form/sales/questions/buyer_still_serving_spec.rb

@ -32,10 +32,21 @@ RSpec.describe Form::Sales::Questions::BuyerStillServing, type: :model do
it "has the correct answer_options" do it "has the correct answer_options" do
expect(question.answer_options).to eq({ expect(question.answer_options).to eq({
"4" => { "value" => "Yes" }, "4" => { "value" => "Yes" },
"5" => { "value" => "No" }, "5" => { "value" => "No - they left up to and including 2 years ago" },
"6" => { "value" => "Buyer prefers not to say" }, "6" => { "value" => "No - they left more than 2 years ago" },
"divider" => { "value" => true }, "divider" => { "value" => true },
"7" => { "value" => "Don’t know" }, "9" => { "value" => "Don’t know" },
"10" => { "value" => "No" },
})
end
it "has the correct displayed_answer_options" do
expect(question.displayed_answer_options(nil)).to eq({
"4" => { "value" => "Yes" },
"5" => { "value" => "No - they left up to and including 2 years ago" },
"6" => { "value" => "No - they left more than 2 years ago" },
"divider" => { "value" => true },
"9" => { "value" => "Don’t know" },
}) })
end end
end end
@ -52,5 +63,15 @@ RSpec.describe Form::Sales::Questions::BuyerStillServing, type: :model do
"9" => { "value" => "Don’t know" }, "9" => { "value" => "Don’t know" },
}) })
end end
it "has the correct displayed_answer_options" do
expect(question.displayed_answer_options(nil)).to eq({
"4" => { "value" => "Yes" },
"5" => { "value" => "No - they left up to and including 2 years ago" },
"6" => { "value" => "No - they left more than 2 years ago" },
"divider" => { "value" => true },
"9" => { "value" => "Don’t know" },
})
end
end end
end end

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

@ -25,7 +25,7 @@ RSpec.describe Form::Sales::Questions::PropertyBuildingType, type: :model do
it "has the correct answer_options" do it "has the correct answer_options" do
expect(question.answer_options).to eq({ expect(question.answer_options).to eq({
"1" => { "value" => "Purpose built" }, "1" => { "value" => "Purpose-built" },
"2" => { "value" => "Converted from previous residential or non-residential property" }, "2" => { "value" => "Converted from previous residential or non-residential property" },
}) })
end end

101
spec/models/validations/financial_validations_spec.rb

@ -5,11 +5,6 @@ RSpec.describe Validations::FinancialValidations do
let(:validator_class) { Class.new { include Validations::FinancialValidations } } let(:validator_class) { Class.new { include Validations::FinancialValidations } }
let(:record) { FactoryBot.create(:lettings_log) } let(:record) { FactoryBot.create(:lettings_log) }
let(:fake_2021_2022_form) { Form.new("spec/fixtures/forms/2021_2022.json") }
before do
allow(FormHandler.instance).to receive(:current_lettings_form).and_return(fake_2021_2022_form)
end
describe "earnings and income frequency" do describe "earnings and income frequency" do
it "when earnings are provided it validates that income frequency must be provided" do it "when earnings are provided it validates that income frequency must be provided" do
@ -1234,4 +1229,100 @@ RSpec.describe Validations::FinancialValidations do
end end
end end
end end
describe "universal credit and income sources validations" do
before do
record.hb = hb
record.benefits = benefits
end
context "with a 2025 form", metadata: { year: 25 } do
before do
allow(record.form).to receive(:start_year_2026_or_later?).and_return(false)
end
context "when tenant receives universal credit and no household income comes from benefits" do
let(:hb) { 6 }
let(:benefits) { 3 }
it "does not add errors" do
financial_validator.validate_housing_benefits_matches_income_proportion(record)
expect(record.errors["hb"]).to be_empty
expect(record.errors["benefits"]).to be_empty
end
end
end
context "with a 2026 form", metadata: { year: 26 } do
before do
allow(record.form).to receive(:start_year_2026_or_later?).and_return(true)
end
context "when tenant receives universal credit and no household income comes from benefits" do
let(:hb) { 6 }
let(:benefits) { 3 }
it "adds errors to hb and benefits" do
financial_validator.validate_housing_benefits_matches_income_proportion(record)
expect(record.errors["hb"]).to include(match I18n.t("validations.lettings.financial.hb.housing_benefits_not_match_income_source"))
expect(record.errors["benefits"]).to include(match I18n.t("validations.lettings.financial.benefits.housing_benefits_not_match_income_source"))
end
end
context "when tenant receives universal credit and some household income comes from benefits" do
let(:hb) { 6 }
let(:benefits) { 2 }
it "does not add errors" do
financial_validator.validate_housing_benefits_matches_income_proportion(record)
expect(record.errors["hb"]).to be_empty
expect(record.errors["benefits"]).to be_empty
end
end
context "when tenant receives housing benefit and no household income comes from benefits" do
let(:hb) { 1 }
let(:benefits) { 3 }
it "adds errors to hb and benefits" do
financial_validator.validate_housing_benefits_matches_income_proportion(record)
expect(record.errors["hb"]).to include(match I18n.t("validations.lettings.financial.hb.housing_benefits_not_match_income_source"))
expect(record.errors["benefits"]).to include(match I18n.t("validations.lettings.financial.benefits.housing_benefits_not_match_income_source"))
end
end
context "when tenant receives housing benefit and some household income comes from benefits" do
let(:hb) { 1 }
let(:benefits) { 2 }
it "does not add errors" do
financial_validator.validate_housing_benefits_matches_income_proportion(record)
expect(record.errors["hb"]).to be_empty
expect(record.errors["benefits"]).to be_empty
end
end
context "when hb is not set" do
let(:hb) { nil }
let(:benefits) { 3 }
it "does not add errors" do
financial_validator.validate_housing_benefits_matches_income_proportion(record)
expect(record.errors["hb"]).to be_empty
expect(record.errors["benefits"]).to be_empty
end
end
context "when benefits is not set" do
let(:hb) { 6 }
let(:benefits) { nil }
it "does not add errors" do
financial_validator.validate_housing_benefits_matches_income_proportion(record)
expect(record.errors["hb"]).to be_empty
expect(record.errors["benefits"]).to be_empty
end
end
end
end
end end

2
spec/requests/check_errors_controller_spec.rb

@ -84,7 +84,7 @@ RSpec.describe CheckErrorsController, type: :request do
end end
it "displays correct clear and change links" do it "displays correct clear and change links" do
expect(page.all(:button, value: "Clear").count).to eq(1) expect(page.all(:button, value: "Clear").count).to eq(2)
expect(page).to have_link("Change", count: 1) expect(page).to have_link("Change", count: 1)
expect(page).to have_button("Clear all") expect(page).to have_button("Clear all")
end end

Loading…
Cancel
Save