From 270225f9dce919a3680cd8a0a3a71c879c28bfb8 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 27 Nov 2025 17:23:15 +0000 Subject: [PATCH 1/3] CLDC-4090: BU validation of LA ECode (#3114) * CLDC-4090: Add validation that the LA is still active for the date of the log * CLDC-4090: Add verifying tests * CLDC-4090: Ensure startdate is not nil for this validation * CLDC-4090: Document linked validations * CLDC-4090: Remove errors on uprn_confirmation question this question is no longer asked, so there are no errors needed for it left the existing translations * CLDC-4090: Update tests --- .../validations/property_validations.rb | 39 +++++++- .../validations/sales/property_validations.rb | 22 ++++- .../lettings/property_information.en.yml | 9 +- .../sales/property_information.en.yml | 7 +- .../validations/property_validations_spec.rb | 95 ++++++++++++++++++- .../sales/property_validations_spec.rb | 61 +++++++++++- 6 files changed, 222 insertions(+), 11 deletions(-) diff --git a/app/models/validations/property_validations.rb b/app/models/validations/property_validations.rb index 4b34d2d3c..ed155da95 100644 --- a/app/models/validations/property_validations.rb +++ b/app/models/validations/property_validations.rb @@ -32,6 +32,7 @@ module Validations::PropertyValidations end end + # see also: this validation in sales/property_validations.rb def validate_uprn(record) return unless record.uprn @@ -40,6 +41,7 @@ module Validations::PropertyValidations record.errors.add :uprn, I18n.t("validations.lettings.property.uprn.invalid") end + # see also: this validation in sales/property_validations.rb def validate_property_postcode(record) postcode = record.postcode_full return unless postcode @@ -50,6 +52,7 @@ module Validations::PropertyValidations end end + # see also: this validation in sales/property_validations.rb def validate_la_in_england(record) return unless record.form.start_year_2025_or_later? @@ -60,7 +63,6 @@ module Validations::PropertyValidations record.errors.add :la, I18n.t("validations.lettings.property.la.not_in_england") record.errors.add :postcode_full, I18n.t("validations.lettings.property.postcode_full.not_in_england") record.errors.add :uprn, I18n.t("validations.lettings.property.uprn.not_in_england") - record.errors.add :uprn_confirmation, I18n.t("validations.lettings.property.uprn_confirmation.not_in_england") record.errors.add :uprn_selection, I18n.t("validations.lettings.property.uprn_selection.not_in_england") if record.uprn.present? record.errors.add :startdate, I18n.t("validations.lettings.property.startdate.address_not_in_england") @@ -76,4 +78,39 @@ module Validations::PropertyValidations record.errors.add :startdate, I18n.t("validations.lettings.property.startdate.location_not_in_england") end end + + # see also: this validation in sales/property_validations.rb + def validate_la_is_active(record) + return unless record.form.start_year_2025_or_later? && record.startdate.present? + + if record.is_general_needs? + return unless record.la + + la = LocalAuthority.england.find_by(code: record.la) + + # will be caught by the not in england validation + return if la.nil? + # only compare end date if it exists + return if record.startdate >= la.start_date && (la.end_date.nil? || record.startdate <= la.end_date) + + record.errors.add :la, I18n.t("validations.lettings.property.la.la_not_valid_for_date", la: la.name) + record.errors.add :postcode_full, I18n.t("validations.lettings.property.postcode_full.la_not_valid_for_date", la: la.name) + record.errors.add :uprn, I18n.t("validations.lettings.property.uprn.la_not_valid_for_date", la: la.name) + record.errors.add :uprn_selection, I18n.t("validations.lettings.property.uprn_selection.la_not_valid_for_date", la: la.name) + record.errors.add :startdate, I18n.t("validations.lettings.property.startdate.la_not_valid_for_date", la: la.name) + elsif record.is_supported_housing? + return unless record.location + + la = LocalAuthority.england.find_by(code: record.location.location_code) + + # will be caught by the not in england validation + return if la.nil? + # only compare end date if it exists + return if record.startdate >= la.start_date && (la.end_date.nil? || record.startdate <= la.end_date) + + record.errors.add :location_id, I18n.t("validations.lettings.property.location_id.la_not_valid_for_date", la: la.name) + record.errors.add :scheme_id, I18n.t("validations.lettings.property.scheme_id.la_not_valid_for_date", la: la.name) + record.errors.add :startdate, I18n.t("validations.lettings.property.startdate.la_not_valid_for_date", la: la.name) + end + end end diff --git a/app/models/validations/sales/property_validations.rb b/app/models/validations/sales/property_validations.rb index 56a09c36f..16d4c93f7 100644 --- a/app/models/validations/sales/property_validations.rb +++ b/app/models/validations/sales/property_validations.rb @@ -21,6 +21,7 @@ module Validations::Sales::PropertyValidations end end + # see also: this validation in validations/property_validations.rb def validate_uprn(record) return unless record.uprn @@ -29,6 +30,7 @@ module Validations::Sales::PropertyValidations record.errors.add :uprn, I18n.t("validations.sales.property_information.uprn.invalid") end + # see also: this validation in validations/property_validations.rb def validate_property_postcode(record) postcode = record.postcode_full return unless postcode @@ -39,6 +41,7 @@ module Validations::Sales::PropertyValidations end end + # see also: this validation in validations/property_validations.rb def validate_la_in_england(record) return unless record.form.start_year_2025_or_later? && record.la.present? return if record.la.in?(LocalAuthority.england.pluck(:code)) @@ -46,7 +49,6 @@ module Validations::Sales::PropertyValidations record.errors.add :la, I18n.t("validations.sales.property_information.la.not_in_england") record.errors.add :postcode_full, I18n.t("validations.sales.property_information.postcode_full.not_in_england") record.errors.add :uprn, I18n.t("validations.sales.property_information.uprn.not_in_england") - record.errors.add :uprn_confirmation, I18n.t("validations.sales.property_information.uprn_confirmation.not_in_england") record.errors.add :uprn_selection, I18n.t("validations.sales.property_information.uprn_selection.not_in_england") if record.uprn.present? record.errors.add :saledate, :skip_bu_error, message: I18n.t("validations.sales.property_information.saledate.address_not_in_england") @@ -54,4 +56,22 @@ module Validations::Sales::PropertyValidations record.errors.add :saledate, :skip_bu_error, message: I18n.t("validations.sales.property_information.saledate.postcode_not_in_england") end end + + # see also: this validation in validations/property_validations.rb + def validate_la_is_active(record) + return unless record.form.start_year_2025_or_later? && record.la.present? && record.startdate.present? + + la = LocalAuthority.england.find_by(code: record.la) + + # will be caught by the not in england validation + return if la.nil? + # only compare end date if it exists + return if record.startdate >= la.start_date && (la.end_date.nil? || record.startdate <= la.end_date) + + record.errors.add :la, I18n.t("validations.sales.property_information.la.la_not_valid_for_date", la: la.name) + record.errors.add :postcode_full, I18n.t("validations.sales.property_information.postcode_full.la_not_valid_for_date", la: la.name) + record.errors.add :uprn, I18n.t("validations.sales.property_information.uprn.la_not_valid_for_date", la: la.name) + record.errors.add :uprn_selection, I18n.t("validations.sales.property_information.uprn_selection.la_not_valid_for_date", la: la.name) + record.errors.add :saledate, :skip_bu_error, message: I18n.t("validations.sales.property_information.saledate.la_not_valid_for_date", la: la.name) + end end diff --git a/config/locales/validations/lettings/property_information.en.yml b/config/locales/validations/lettings/property_information.en.yml index 20fdcba94..8f706c261 100644 --- a/config/locales/validations/lettings/property_information.en.yml +++ b/config/locales/validations/lettings/property_information.en.yml @@ -5,6 +5,7 @@ en: postcode_full: invalid: "Enter a postcode in the correct format, for example AA1 1AA." not_in_england: "It looks like you have an entered a postcode outside of England. Only create logs for lettings in England." + la_not_valid_for_date: "%{la} does not exist on the tenancy start date, due to a change in local authority names and boundaries. Please enter the local authority name in use on the tenancy start date" rsnvac: non_temp_accommodation: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as this accommodation is not temporary." referral_invalid: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as a different source of referral for this letting." @@ -22,17 +23,23 @@ en: uprn: invalid: "UPRN must be 12 digits or less." not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England." - uprn_confirmation: + la_not_valid_for_date: "%{la} does not exist on the tenancy start date, due to a change in local authority names and boundaries. Please enter the local authority name in use on the tenancy start date" + uprn_confirmation: # legacy question not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England." uprn_selection: not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England." + la_not_valid_for_date: "%{la} does not exist on the tenancy start date, due to a change in local authority names and boundaries. Please enter the local authority name in use on the tenancy start date" la: not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England." + la_not_valid_for_date: "%{la} does not exist on the tenancy start date, due to a change in local authority names and boundaries. Please enter the local authority name in use on the tenancy start date" scheme_id: not_in_england: "This scheme’s only location is outside of England. Only create logs for lettings in England." + la_not_valid_for_date: "%{la} does not exist on the tenancy start date, due to a change in local authority names and boundaries. Please enter the local authority name in use on the tenancy start date" location_id: not_in_england: "It looks like you have selected a location outside of England. Only create logs for lettings in England." + la_not_valid_for_date: "%{la} does not exist on the tenancy start date, due to a change in local authority names and boundaries. Please enter the local authority name in use on the tenancy start date" startdate: postcode_not_in_england: "It looks like you have an entered a postcode outside of England. Only create logs for lettings in England." address_not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England." location_not_in_england: "It looks like you have selected a location outside of England. Only create logs for lettings in England." + la_not_valid_for_date: "%{la} does not exist on the tenancy start date, due to a change in local authority names and boundaries. Please enter the local authority name in use on the tenancy start date" diff --git a/config/locales/validations/sales/property_information.en.yml b/config/locales/validations/sales/property_information.en.yml index f421177af..ec107a833 100644 --- a/config/locales/validations/sales/property_information.en.yml +++ b/config/locales/validations/sales/property_information.en.yml @@ -8,6 +8,7 @@ en: not_joint_purchase: "Buyer’s last accommodation and discounted ownership postcodes must match." invalid: "Enter a postcode in the correct format, for example AA1 1AA." not_in_england: "It looks like you have entered a postcode outside of England. Only create logs for sales in England." + la_not_valid_for_date: "%{la} does not exist on the property sale date, due to a change in local authority names and boundaries. Please enter the local authority name in use on the sale date" ppostcode_full: postcode_must_match_previous: joint_purchase: "Buyers’ last accommodation and discounted ownership postcodes must match." @@ -22,6 +23,7 @@ en: not_joint_purchase: "Buyer’s last accommodation and discounted ownership postcodes must match." invalid: "UPRN must be 12 digits or less." not_in_england: "It looks like you have entered an address outside of England. Only create logs for sales in England." + la_not_valid_for_date: "%{la} does not exist on the property sale date, due to a change in local authority names and boundaries. Please enter the local authority name in use on the sale date" beds: bedsits_have_max_one_bedroom: "Number of bedrooms must be 1 if the property is a bedsit." proptype: @@ -30,10 +32,13 @@ en: invalid: "You must answer UPRN known?" la: not_in_england: "It looks like you have entered an address outside of England. Only create logs for sales in England." - uprn_confirmation: + la_not_valid_for_date: "%{la} does not exist on the property sale date, due to a change in local authority names and boundaries. Please enter the local authority name in use on the sale date" + uprn_confirmation: # legacy question not_in_england: "It looks like you have entered an address outside of England. Only create logs for sales in England." uprn_selection: not_in_england: "It looks like you have entered an address outside of England. Only create logs for sales in England." + la_not_valid_for_date: "%{la} does not exist on the property sale date, due to a change in local authority names and boundaries. Please enter the local authority name in use on the sale date" saledate: postcode_not_in_england: "It looks like you have entered a postcode outside of England. Only create logs for sales in England." address_not_in_england: "It looks like you have entered an address outside of England. Only create logs for sales in England." + la_not_valid_for_date: "%{la} does not exist on the property sale date, due to a change in local authority names and boundaries. Please enter the local authority name in use on the sale date" diff --git a/spec/models/validations/property_validations_spec.rb b/spec/models/validations/property_validations_spec.rb index f4e5fe6f2..1def796dc 100644 --- a/spec/models/validations/property_validations_spec.rb +++ b/spec/models/validations/property_validations_spec.rb @@ -221,7 +221,6 @@ RSpec.describe Validations::PropertyValidations do expect(log.errors["la"]).to include(I18n.t("validations.lettings.property.la.not_in_england")) expect(log.errors["postcode_full"]).to include(I18n.t("validations.lettings.property.postcode_full.not_in_england")) expect(log.errors["uprn"]).to include(I18n.t("validations.lettings.property.uprn.not_in_england")) - expect(log.errors["uprn_confirmation"]).to include(I18n.t("validations.lettings.property.uprn_confirmation.not_in_england")) expect(log.errors["uprn_selection"]).to include(I18n.t("validations.lettings.property.uprn_selection.not_in_england")) expect(log.errors["startdate"]).to include(I18n.t("validations.lettings.property.startdate.postcode_not_in_england")) expect(log.errors["scheme_id"]).to be_empty @@ -241,7 +240,6 @@ RSpec.describe Validations::PropertyValidations do expect(log.errors["la"]).to be_empty expect(log.errors["postcode_full"]).to be_empty expect(log.errors["uprn"]).to be_empty - expect(log.errors["uprn_confirmation"]).to be_empty expect(log.errors["uprn_selection"]).to be_empty end end @@ -254,7 +252,6 @@ RSpec.describe Validations::PropertyValidations do expect(log.errors["la"]).to be_empty expect(log.errors["postcode_full"]).to be_empty expect(log.errors["uprn"]).to be_empty - expect(log.errors["uprn_confirmation"]).to be_empty expect(log.errors["uprn_selection"]).to be_empty expect(log.errors["startdate"]).to be_empty end @@ -274,7 +271,97 @@ RSpec.describe Validations::PropertyValidations do expect(log.errors["la"]).to be_empty expect(log.errors["postcode_full"]).to be_empty expect(log.errors["uprn"]).to be_empty - expect(log.errors["uprn_confirmation"]).to be_empty + expect(log.errors["uprn_selection"]).to be_empty + end + end + end + end + + describe "#validate_la_is_active" do + let(:la_ecode_active) { "E09000033" } + let(:la_ecode_inactive) { "E07000156" } + let(:local_authority_active) { LocalAuthority.find_by(code: la_ecode_active) } + let(:local_authority_inactive) { LocalAuthority.find_by(code: la_ecode_inactive) } + + context "with a log on or after 2025" do + before do + allow(log.form).to receive(:start_year_2025_or_later?).and_return true + end + + context "and the local authority is active for general needs log" do + let(:log) { build(:lettings_log, :completed, la: la_ecode_active, needstype: 1) } + + it "does not add an error" do + property_validator.validate_la_is_active(log) + expect(log.errors["la"]).to be_empty + expect(log.errors["postcode_full"]).to be_empty + expect(log.errors["uprn"]).to be_empty + expect(log.errors["uprn_selection"]).to be_empty + expect(log.errors["startdate"]).to be_empty + end + end + + context "and the local authority is inactive for general needs log" do + let(:log) { build(:lettings_log, :completed, la: la_ecode_inactive, needstype: 1) } + + it "adds an error" do + property_validator.validate_la_is_active(log) + expect(log.errors["la"]).to include(I18n.t("validations.lettings.property.la.la_not_valid_for_date", la: local_authority_inactive.name)) + expect(log.errors["postcode_full"]).to include(I18n.t("validations.lettings.property.postcode_full.la_not_valid_for_date", la: local_authority_inactive.name)) + expect(log.errors["uprn"]).to include(I18n.t("validations.lettings.property.uprn.la_not_valid_for_date", la: local_authority_inactive.name)) + expect(log.errors["uprn_selection"]).to include(I18n.t("validations.lettings.property.uprn_selection.la_not_valid_for_date", la: local_authority_inactive.name)) + expect(log.errors["startdate"]).to include(I18n.t("validations.lettings.property.startdate.la_not_valid_for_date", la: local_authority_inactive.name)) + expect(log.errors["scheme_id"]).to be_empty + expect(log.errors["location_id"]).to be_empty + end + end + + context "and the local authority is active for supported housing log" do + let(:location) { create(:location, location_code: la_ecode_active) } + let(:log) { build(:lettings_log, :completed, needstype: 2, location:) } + + it "does not add an error" do + property_validator.validate_la_is_active(log) + expect(log.errors["scheme_id"]).to be_empty + expect(log.errors["location_id"]).to be_empty + expect(log.errors["startdate"]).to be_empty + expect(log.errors["la"]).to be_empty + expect(log.errors["postcode_full"]).to be_empty + expect(log.errors["uprn"]).to be_empty + expect(log.errors["uprn_selection"]).to be_empty + end + end + + context "and the local authority is inactive for supported housing log" do + let(:location) { create(:location, location_code: la_ecode_inactive) } + let(:log) { build(:lettings_log, :completed, needstype: 2, location:) } + + it "adds an error" do + property_validator.validate_la_is_active(log) + expect(log.errors["scheme_id"]).to include(I18n.t("validations.lettings.property.scheme_id.la_not_valid_for_date", la: local_authority_inactive.name)) + expect(log.errors["location_id"]).to include(I18n.t("validations.lettings.property.location_id.la_not_valid_for_date", la: local_authority_inactive.name)) + expect(log.errors["startdate"]).to include(I18n.t("validations.lettings.property.startdate.la_not_valid_for_date", la: local_authority_inactive.name)) + expect(log.errors["la"]).to be_empty + expect(log.errors["postcode_full"]).to be_empty + expect(log.errors["uprn"]).to be_empty + expect(log.errors["uprn_selection"]).to be_empty + end + end + end + + context "with a log before 2025" do + before do + allow(log.form).to receive(:start_year_2025_or_later?).and_return false + end + + context "and the local authority is inactive" do + let(:log) { build(:lettings_log, :completed, la: la_ecode_inactive) } + + it "does not add an error" do + property_validator.validate_la_is_active(log) + expect(log.errors["la"]).to be_empty + expect(log.errors["postcode_full"]).to be_empty + expect(log.errors["uprn"]).to be_empty expect(log.errors["uprn_selection"]).to be_empty end end diff --git a/spec/models/validations/sales/property_validations_spec.rb b/spec/models/validations/sales/property_validations_spec.rb index ce6585956..e46f58f84 100644 --- a/spec/models/validations/sales/property_validations_spec.rb +++ b/spec/models/validations/sales/property_validations_spec.rb @@ -98,7 +98,6 @@ RSpec.describe Validations::Sales::PropertyValidations do expect(log.errors["la"]).to include(I18n.t("validations.sales.property_information.la.not_in_england")) expect(log.errors["postcode_full"]).to include(I18n.t("validations.sales.property_information.postcode_full.not_in_england")) expect(log.errors["uprn"]).to include(I18n.t("validations.sales.property_information.uprn.not_in_england")) - expect(log.errors["uprn_confirmation"]).to include(I18n.t("validations.sales.property_information.uprn_confirmation.not_in_england")) expect(log.errors["uprn_selection"]).to include(I18n.t("validations.sales.property_information.uprn_selection.not_in_england")) expect(log.errors["saledate"]).to include(I18n.t("validations.sales.property_information.saledate.postcode_not_in_england")) end @@ -112,7 +111,6 @@ RSpec.describe Validations::Sales::PropertyValidations do expect(log.errors["la"]).to be_empty expect(log.errors["postcode_full"]).to be_empty expect(log.errors["uprn"]).to be_empty - expect(log.errors["uprn_confirmation"]).to be_empty expect(log.errors["uprn_selection"]).to be_empty expect(log.errors["saledate"]).to be_empty end @@ -132,7 +130,64 @@ RSpec.describe Validations::Sales::PropertyValidations do expect(log.errors["la"]).to be_empty expect(log.errors["postcode_full"]).to be_empty expect(log.errors["uprn"]).to be_empty - expect(log.errors["uprn_confirmation"]).to be_empty + expect(log.errors["uprn_selection"]).to be_empty + expect(log.errors["saledate"]).to be_empty + end + end + end + end + + describe "#validate_la_is_active" do + let(:la_ecode_active) { "E09000033" } + let(:la_ecode_inactive) { "E07000156" } + let(:local_authority_active) { LocalAuthority.find_by(code: la_ecode_active) } + let(:local_authority_inactive) { LocalAuthority.find_by(code: la_ecode_inactive) } + + context "with a log on or after 2025" do + before do + allow(log.form).to receive(:start_year_2025_or_later?).and_return true + end + + context "and the local authority is active" do + let(:log) { build(:sales_log, :completed, la: la_ecode_active) } + + it "adds an error" do + property_validator.validate_la_is_active(log) + expect(log.errors["la"]).to be_empty + expect(log.errors["postcode_full"]).to be_empty + expect(log.errors["uprn"]).to be_empty + expect(log.errors["uprn_selection"]).to be_empty + expect(log.errors["saledate"]).to be_empty + end + end + + context "and the local authority is inactive" do + let(:log) { build(:sales_log, :completed, la: la_ecode_inactive) } + + it "does not add an error" do + property_validator.validate_la_is_active(log) + expect(log.errors["la"]).to include(I18n.t("validations.sales.property_information.la.la_not_valid_for_date", la: local_authority_inactive.name)) + expect(log.errors["postcode_full"]).to include(I18n.t("validations.sales.property_information.postcode_full.la_not_valid_for_date", la: local_authority_inactive.name)) + expect(log.errors["uprn"]).to include(I18n.t("validations.sales.property_information.uprn.la_not_valid_for_date", la: local_authority_inactive.name)) + expect(log.errors["uprn_selection"]).to include(I18n.t("validations.sales.property_information.uprn_selection.la_not_valid_for_date", la: local_authority_inactive.name)) + expect(log.errors["saledate"]).to include(I18n.t("validations.sales.property_information.saledate.la_not_valid_for_date", la: local_authority_inactive.name)) + end + end + end + + context "with a log before 2025" do + before do + allow(log.form).to receive(:start_year_2025_or_later?).and_return false + end + + context "and the local authority is inactive" do + let(:log) { build(:sales_log, :completed, la: la_ecode_inactive) } + + it "does not add an error" do + property_validator.validate_la_is_active(log) + expect(log.errors["la"]).to be_empty + expect(log.errors["postcode_full"]).to be_empty + expect(log.errors["uprn"]).to be_empty expect(log.errors["uprn_selection"]).to be_empty expect(log.errors["saledate"]).to be_empty end From d61fa39c00b12d5cecaa544de445ade9e3569d1a Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 27 Nov 2025 17:30:59 +0000 Subject: [PATCH 2/3] CLDC-4095: Add errors on all net income fields (#3116) * CLDC-4095: Ensure errors are added to Q89 dependent fields this ensures that the user is always shown an error if they complete the form in a different order to the question set not doing this leads to the user submitting an answer and nothing happening * CLDC-4095: Update existing tests to check these new fields --- app/models/validations/financial_validations.rb | 2 ++ config/locales/validations/lettings/financial.en.yml | 3 +++ spec/models/validations/financial_validations_spec.rb | 7 +++++++ 3 files changed, 12 insertions(+) diff --git a/app/models/validations/financial_validations.rb b/app/models/validations/financial_validations.rb index 7827fbb16..6f135aa39 100644 --- a/app/models/validations/financial_validations.rb +++ b/app/models/validations/financial_validations.rb @@ -20,6 +20,8 @@ module Validations::FinancialValidations is_partner_or_main = relationship == "P" || n == 1 if is_employed && is_partner_or_main && record.benefits == 1 record.errors.add :benefits, I18n.t("validations.lettings.financial.benefits.part_or_full_time") + record.errors.add "ecstat#{n}", I18n.t("validations.lettings.financial.ecstat.part_or_full_time") + record.errors.add "relat#{n}", I18n.t("validations.lettings.financial.relat.part_or_full_time", person_num: n) if n > 1 end end end diff --git a/config/locales/validations/lettings/financial.en.yml b/config/locales/validations/lettings/financial.en.yml index f77e23e17..d91610602 100644 --- a/config/locales/validations/lettings/financial.en.yml +++ b/config/locales/validations/lettings/financial.en.yml @@ -24,6 +24,9 @@ en: ecstat: earnings_over_hard_max: "The household’s income of %{earnings} %{frequency} is too high given the household’s working situation." earnings_under_hard_min: "The household’s income of %{earnings} %{frequency} is too low given the household’s working situation." + part_or_full_time: "Answer cannot be ‘full-time’ or ‘part-time’ if ‘all’ household income is from Universal Credit, state pensions or benefits." + relat: + part_or_full_time: "Tenant %{person_num} cannot be the partner of tenant 1 if ‘all’ household income is from Universal Credit, state pensions or benefits and tenant %{person_num} works full-time or part-time." age: earnings_over_hard_max: "The household’s income of %{earnings} %{frequency} is too high for the number of adults. Change either the household income or the age of the tenants." incfreq: diff --git a/spec/models/validations/financial_validations_spec.rb b/spec/models/validations/financial_validations_spec.rb index e701f12da..7e458ee28 100644 --- a/spec/models/validations/financial_validations_spec.rb +++ b/spec/models/validations/financial_validations_spec.rb @@ -40,6 +40,7 @@ RSpec.describe Validations::FinancialValidations do record.ecstat1 = 1 financial_validator.validate_net_income_uc_proportion(record) expect(record.errors["benefits"]).to include(match I18n.t("validations.lettings.financial.benefits.part_or_full_time")) + expect(record.errors["ecstat1"]).to include(match I18n.t("validations.lettings.financial.ecstat.part_or_full_time")) end it "validates that the lead tenant is not in part time employment" do @@ -47,6 +48,7 @@ RSpec.describe Validations::FinancialValidations do record.ecstat1 = 2 financial_validator.validate_net_income_uc_proportion(record) expect(record.errors["benefits"]).to include(match I18n.t("validations.lettings.financial.benefits.part_or_full_time")) + expect(record.errors["ecstat1"]).to include(match I18n.t("validations.lettings.financial.ecstat.part_or_full_time")) end it "expects that the lead tenant is not in full-time or part-time employment" do @@ -54,6 +56,7 @@ RSpec.describe Validations::FinancialValidations do record.ecstat1 = 4 financial_validator.validate_net_income_uc_proportion(record) expect(record.errors["benefits"]).to be_empty + expect(record.errors["ecstat1"]).to be_empty end it "validates that the tenant’s partner is not in full time employment" do @@ -62,6 +65,8 @@ RSpec.describe Validations::FinancialValidations do record.relat2 = "P" financial_validator.validate_net_income_uc_proportion(record) expect(record.errors["benefits"]).to include(match I18n.t("validations.lettings.financial.benefits.part_or_full_time")) + expect(record.errors["ecstat2"]).to include(match I18n.t("validations.lettings.financial.ecstat.part_or_full_time")) + expect(record.errors["relat2"]).to include(match I18n.t("validations.lettings.financial.relat.part_or_full_time", person_num: 2)) end it "expects that the tenant’s partner is not in full-time or part-time employment" do @@ -70,6 +75,8 @@ RSpec.describe Validations::FinancialValidations do record.relat2 = "P" financial_validator.validate_net_income_uc_proportion(record) expect(record.errors["benefits"]).to be_empty + expect(record.errors["ecstat2"]).to be_empty + expect(record.errors["relat2"]).to be_empty end end end From 14c8fa63da1eebc94680e9caeec42f5de0436796 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 27 Nov 2025 17:49:50 +0000 Subject: [PATCH 3/3] CLDC-4066: Increase our resilience to an OS Places outage (#3104) * CLDC-4066: Implement a debounce on address search disappointingly the underlying library https://github.com/alphagov/accessible-autocomplete doesn't have this as an option, though we can implement one ourselves in the fetch code * CLDC-4066: Reduce read timeout for OS Places APIs 30 -> 15 in practice waiting up to 1 min 30 for requests could cause too many threads to be blocked under periods of slow OS Places API this reduces the max wait time to 45 seconds, as requested by CORE * CLDC-4066: Lint * CLDC-4066: Fix uprn timeout to match address search --- .../controllers/address_search_controller.js | 18 ++++++++++++++++++ app/services/address_client.rb | 2 +- app/services/uprn_client.rb | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/frontend/controllers/address_search_controller.js b/app/frontend/controllers/address_search_controller.js index de54090ec..b3410e49d 100644 --- a/app/frontend/controllers/address_search_controller.js +++ b/app/frontend/controllers/address_search_controller.js @@ -4,10 +4,28 @@ import 'accessible-autocomplete/dist/accessible-autocomplete.min.css' const options = [] +let latestQueryId = 0 + +const sleep = (ms) => { + return new Promise(resolve => setTimeout(resolve, ms)) +} + const fetchOptions = async (query, searchUrl) => { if (query.length < 2) { throw new Error('Query must be at least 2 characters long.') } + + // implement a debounce + // this is because this API has periods of high latency if OS Places has an outage + // making too many requests can overwhelm the number of threads available on the server + // which can in turn cause a site wide outage + latestQueryId++ + const myQueryId = latestQueryId + await sleep(500) + if (myQueryId !== latestQueryId) { + throw new Error('Outdated query, ignoring result.') + } + try { const response = await fetch(`${searchUrl}?query=${encodeURIComponent(query.trim())}`) return await response.json() diff --git a/app/services/address_client.rb b/app/services/address_client.rb index 20cf603fe..3fca0a0d0 100644 --- a/app/services/address_client.rb +++ b/app/services/address_client.rb @@ -35,7 +35,7 @@ private client.use_ssl = true client.verify_mode = OpenSSL::SSL::VERIFY_PEER client.max_retries = 3 - client.read_timeout = 30 # seconds + client.read_timeout = 15 # seconds client end diff --git a/app/services/uprn_client.rb b/app/services/uprn_client.rb index f847c7da5..8dcd2e7a0 100644 --- a/app/services/uprn_client.rb +++ b/app/services/uprn_client.rb @@ -39,7 +39,7 @@ private client.use_ssl = true client.verify_mode = OpenSSL::SSL::VERIFY_PEER client.max_retries = 3 - client.read_timeout = 30 # seconds + client.read_timeout = 15 # seconds client end