From 462bdab1cad7435e6967b779fd8cb29b9c829d80 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 8 Oct 2025 16:27:08 +0100 Subject: [PATCH 1/4] CLDC-4090: Add validation that the LA is still active for the date of the log --- .../validations/property_validations.rb | 35 +++++++++++++++++++ .../validations/sales/property_validations.rb | 18 ++++++++++ .../lettings/property_information.en.yml | 8 +++++ .../sales/property_information.en.yml | 6 ++++ 4 files changed, 67 insertions(+) diff --git a/app/models/validations/property_validations.rb b/app/models/validations/property_validations.rb index 4b34d2d3c..0ffd2d038 100644 --- a/app/models/validations/property_validations.rb +++ b/app/models/validations/property_validations.rb @@ -76,4 +76,39 @@ module Validations::PropertyValidations record.errors.add :startdate, I18n.t("validations.lettings.property.startdate.location_not_in_england") end end + + def validate_la_is_active(record) + return unless record.form.start_year_2025_or_later? + + 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_confirmation, I18n.t("validations.lettings.property.uprn_confirmation.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..18ae71e55 100644 --- a/app/models/validations/sales/property_validations.rb +++ b/app/models/validations/sales/property_validations.rb @@ -54,4 +54,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 + + def validate_la_is_active(record) + return unless record.form.start_year_2025_or_later? && record.la.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_confirmation, I18n.t("validations.sales.property_information.uprn_confirmation.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..09f1b54f7 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,24 @@ 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." + 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: 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" 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..a2392d44b 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,14 @@ 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." + 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: 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" 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" From 71a0507bef88419075289859c32a965a4cd1ba19 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 8 Oct 2025 16:27:14 +0100 Subject: [PATCH 2/4] CLDC-4090: Add verifying tests --- .../validations/property_validations_spec.rb | 96 +++++++++++++++++++ .../sales/property_validations_spec.rb | 61 ++++++++++++ 2 files changed, 157 insertions(+) diff --git a/spec/models/validations/property_validations_spec.rb b/spec/models/validations/property_validations_spec.rb index f4e5fe6f2..97f5c287c 100644 --- a/spec/models/validations/property_validations_spec.rb +++ b/spec/models/validations/property_validations_spec.rb @@ -280,4 +280,100 @@ RSpec.describe Validations::PropertyValidations do 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_confirmation"]).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_confirmation"]).to include(I18n.t("validations.lettings.property.uprn_confirmation.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_confirmation"]).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_confirmation"]).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_confirmation"]).to be_empty + expect(log.errors["uprn_selection"]).to be_empty + end + 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 ce6585956..6b141b436 100644 --- a/spec/models/validations/sales/property_validations_spec.rb +++ b/spec/models/validations/sales/property_validations_spec.rb @@ -139,4 +139,65 @@ RSpec.describe Validations::Sales::PropertyValidations do 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_confirmation"]).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_confirmation"]).to include(I18n.t("validations.sales.property_information.uprn_confirmation.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_confirmation"]).to be_empty + expect(log.errors["uprn_selection"]).to be_empty + expect(log.errors["saledate"]).to be_empty + end + end + end + end end From 79750fc91b0e5bda26e88efbc82d764e9ae845a0 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Fri, 10 Oct 2025 10:37:21 +0100 Subject: [PATCH 3/4] CLDC-4090: Ensure startdate is not nil for this validation --- app/models/validations/property_validations.rb | 2 +- app/models/validations/sales/property_validations.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/validations/property_validations.rb b/app/models/validations/property_validations.rb index 0ffd2d038..87716428f 100644 --- a/app/models/validations/property_validations.rb +++ b/app/models/validations/property_validations.rb @@ -78,7 +78,7 @@ module Validations::PropertyValidations end def validate_la_is_active(record) - return unless record.form.start_year_2025_or_later? + return unless record.form.start_year_2025_or_later? && record.startdate.present? if record.is_general_needs? return unless record.la diff --git a/app/models/validations/sales/property_validations.rb b/app/models/validations/sales/property_validations.rb index 18ae71e55..d56d47fc3 100644 --- a/app/models/validations/sales/property_validations.rb +++ b/app/models/validations/sales/property_validations.rb @@ -56,7 +56,7 @@ module Validations::Sales::PropertyValidations end def validate_la_is_active(record) - return unless record.form.start_year_2025_or_later? && record.la.present? + return unless record.form.start_year_2025_or_later? && record.la.present? && record.startdate.present? la = LocalAuthority.england.find_by(code: record.la) From 20c5b2c625a6c2ff540bd5daafee4e778fe87751 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 29 Oct 2025 17:39:43 +0000 Subject: [PATCH 4/4] CLDC-4090: Document linked validations --- app/models/validations/property_validations.rb | 4 ++++ app/models/validations/sales/property_validations.rb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/app/models/validations/property_validations.rb b/app/models/validations/property_validations.rb index 87716428f..3c03fb10c 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? @@ -77,6 +80,7 @@ module Validations::PropertyValidations 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? diff --git a/app/models/validations/sales/property_validations.rb b/app/models/validations/sales/property_validations.rb index d56d47fc3..b2a099b97 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)) @@ -55,6 +58,7 @@ module Validations::Sales::PropertyValidations 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?