diff --git a/app/models/derived_variables/lettings_log_variables.rb b/app/models/derived_variables/lettings_log_variables.rb index 9ea820dc0..1c7f161c1 100644 --- a/app/models/derived_variables/lettings_log_variables.rb +++ b/app/models/derived_variables/lettings_log_variables.rb @@ -86,7 +86,7 @@ module DerivedVariables::LettingsLogVariables end set_housingneeds_fields if housingneeds? - if form.start_year_2025_or_later? && is_general_needs? + if form.start_year_2026_or_later? || (form.start_year_2025_or_later? && is_general_needs?) if changed_to_newbuild? && uprn.nil? self.manual_address_entry_selected = true end @@ -170,7 +170,12 @@ module DerivedVariables::LettingsLogVariables self.referral = 7 if referral_type == 6 self.referral = 16 if referral_type == 7 - reset_address_fields! if is_supported_housing? + if !form.start_year_2026_or_later? && is_supported_housing? + reset_address_fields! + elsif form.start_year_2026_or_later? && location_changed? + reset_address_fields! + self.la = nil + end set_checkbox_values! end diff --git a/app/models/form/lettings/pages/address_fallback.rb b/app/models/form/lettings/pages/address_fallback.rb index f7503e3af..8129bf821 100644 --- a/app/models/form/lettings/pages/address_fallback.rb +++ b/app/models/form/lettings/pages/address_fallback.rb @@ -3,7 +3,7 @@ class Form::Lettings::Pages::AddressFallback < ::Form::Page super @id = "address" @copy_key = "lettings.property_information.address" - @depends_on = [{ "is_supported_housing?" => false, "manual_address_entry_selected" => true }] + @depends_on = [{ "is_address_asked?" => true, "manual_address_entry_selected" => true }] @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] end diff --git a/app/models/form/lettings/pages/address_search.rb b/app/models/form/lettings/pages/address_search.rb index 866018d45..59cc40d80 100644 --- a/app/models/form/lettings/pages/address_search.rb +++ b/app/models/form/lettings/pages/address_search.rb @@ -3,7 +3,7 @@ class Form::Lettings::Pages::AddressSearch < ::Form::Page super @id = "address_search" @copy_key = "sales.property_information.address_search" - @depends_on = [{ "is_supported_housing?" => false, "manual_address_entry_selected" => false }] + @depends_on = [{ "is_address_asked?" => true, "manual_address_entry_selected" => false }] @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] end diff --git a/app/models/lettings_log.rb b/app/models/lettings_log.rb index 9c9894d32..f2230c4e5 100644 --- a/app/models/lettings_log.rb +++ b/app/models/lettings_log.rb @@ -184,19 +184,18 @@ class LettingsLog < Log end def la - if location - location.linked_local_authorities.active(form.start_date).first&.code || location.location_code - else - super - end + return super unless location + return super if form.start_year_2026_or_later? && super + + location.linked_local_authorities.active(form.start_date).first&.code || location.location_code end + # TODO: CLDC-4119: Beware! This method may cause issues when testing supported housing log duplicate detection after postcode is added, as it can return `location.postcode` instead of the actual `postcode_full` stored on the log record (`super`). If this happens, investigate why it isn't returning `super`, as it should when `form.start_year_2026_or_later? && super`. def postcode_full - if location - location.postcode - else - super - end + return super unless location + return super if form.start_year_2026_or_later? && super + + location.postcode end def postcode_full=(postcode) @@ -773,6 +772,10 @@ class LettingsLog < Log rsnvac != 15 && rsnvac_was == 15 end + def is_address_asked? + form.start_year_2026_or_later? || !is_supported_housing? + end + private def reset_invalid_unresolved_log_fields! diff --git a/app/models/validations/property_validations.rb b/app/models/validations/property_validations.rb index c5ed9728a..3a9d1d609 100644 --- a/app/models/validations/property_validations.rb +++ b/app/models/validations/property_validations.rb @@ -52,17 +52,34 @@ module Validations::PropertyValidations end end + def validate_property_and_location_postcodes_match(record) + return unless record.form.start_year_2026_or_later? + + postcode = record.postcode_full + return unless postcode + + location_postcode = record.location&.postcode + return unless location_postcode + + if postcode != location_postcode + record.errors.add :location_id, I18n.t("validations.lettings.property.location_id.postcode_does_not_match_scheme_location_postcode") + record.errors.add :uprn, I18n.t("validations.lettings.property.uprn.postcode_does_not_match_scheme_location_postcode") + record.errors.add :postcode_full, I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode") + 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? return unless record.la return if record.la.in?(LocalAuthority.england.pluck(:code)) + 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_selection, I18n.t("validations.lettings.property.uprn_selection.not_in_england") + if record.is_general_needs? - 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_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") else @@ -87,16 +104,15 @@ module Validations::PropertyValidations # only compare end date if it exists return if record.startdate >= la.start_date && (la.end_date.nil? || record.startdate <= la.end_date) - if record.is_general_needs? - 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? + 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) + + if record.is_supported_housing? 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/services/bulk_upload/lettings/year2026/row_parser.rb b/app/services/bulk_upload/lettings/year2026/row_parser.rb index 84f6f57d9..700084724 100644 --- a/app/services/bulk_upload/lettings/year2026/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2026/row_parser.rb @@ -438,8 +438,8 @@ class BulkUpload::Lettings::Year2026::RowParser validate :validate_assigned_to_when_support, on: :after_log validate :validate_all_charges_given, on: :after_log - validate :validate_uprn_exists_if_any_key_address_fields_are_blank, on: :after_log, unless: -> { supported_housing? } - validate :validate_address_fields, on: :after_log, unless: -> { supported_housing? } + validate :validate_uprn_exists_if_any_key_address_fields_are_blank, on: :after_log + validate :validate_address_fields, on: :after_log validate :validate_incomplete_soft_validations, on: :after_log validate :validate_nationality, on: :after_log @@ -536,9 +536,9 @@ class BulkUpload::Lettings::Year2026::RowParser "field_9", # startdate "field_10", # startdate "field_13", # tenancycode - !general_needs? ? :field_6.to_s : nil, # location - !supported_housing? ? "field_23" : nil, # postcode - !supported_housing? ? "field_24" : nil, # postcode + !general_needs? ? :field_6.to_s : nil, # location # TODO: CLDC-4119: remove location from hash + !supported_housing? ? "field_23" : nil, # postcode # TODO: CLDC-4119: add postcode to hash for supported housing + !supported_housing? ? "field_24" : nil, # postcode # TODO: CLDC-4119: add postcode to hash for supported housing "field_42", # age1 "field_43", # sex1 "field_46", # ecstat1 @@ -693,8 +693,8 @@ private "ecstat1", "owning_organisation", "tcharge", - !supported_housing? ? "postcode_full" : nil, - !general_needs? ? "location" : nil, + !supported_housing? ? "postcode_full" : nil, # TODO: CLDC-4119: add postcode to duplicate check fields for supported housing + !general_needs? ? "location" : nil, # TODO: CLDC-4119: remove location from duplicate check fields "tenancycode", log.chcharge.present? ? "chcharge" : nil, ].compact @@ -973,11 +973,11 @@ private errors.add(:field_9, error_message) # startdate errors.add(:field_10, error_message) # startdate errors.add(:field_13, error_message) # tenancycode - errors.add(:field_6, error_message) if !general_needs? && :field_6.present? # location + errors.add(:field_6, error_message) if !general_needs? && :field_6.present? # location # TODO: CLDC-4119: remove location from error fields errors.add(:field_5, error_message) if !general_needs? && :field_6.blank? # add to Scheme field as unclear whether log uses New or Old CORE ids - errors.add(:field_23, error_message) unless supported_housing? # postcode_full - errors.add(:field_24, error_message) unless supported_housing? # postcode_full - errors.add(:field_25, error_message) unless supported_housing? # la + errors.add(:field_23, error_message) unless supported_housing? # postcode_full # TODO: CLDC-4119: add postcode to error fields for supported housing + errors.add(:field_24, error_message) unless supported_housing? # postcode_full # TODO: CLDC-4119: add postcode to error fields for supported housing + errors.add(:field_25, error_message) unless supported_housing? # la # TODO: CLDC-4119: add LA to error fields for supported housing errors.add(:field_42, error_message) # age1 errors.add(:field_43, error_message) # sex1 errors.add(:field_46, error_message) # ecstat1 @@ -1335,29 +1335,26 @@ private attributes["first_time_property_let_as_social_housing"] = first_time_property_let_as_social_housing - if general_needs? - attributes["uprn_known"] = field_18.present? ? 1 : 0 - attributes["uprn_confirmed"] = 1 if field_18.present? - attributes["skip_update_uprn_confirmed"] = true - attributes["uprn"] = field_18 - attributes["address_line1"] = field_19 - attributes["address_line1_as_entered"] = field_19 - attributes["address_line2"] = field_20 - attributes["address_line2_as_entered"] = field_20 - attributes["town_or_city"] = field_21 - attributes["town_or_city_as_entered"] = field_21 - attributes["county"] = field_22 - attributes["county_as_entered"] = field_22 - attributes["postcode_full"] = postcode_full - attributes["postcode_full_as_entered"] = postcode_full - attributes["postcode_known"] = postcode_known - attributes["la"] = field_25 - attributes["la_as_entered"] = field_25 - - attributes["address_line1_input"] = address_line1_input - attributes["postcode_full_input"] = postcode_full - attributes["select_best_address_match"] = true if field_18.blank? - end + attributes["uprn_known"] = field_18.present? ? 1 : 0 + attributes["uprn_confirmed"] = 1 if field_18.present? + attributes["skip_update_uprn_confirmed"] = true + attributes["uprn"] = field_18 + attributes["address_line1"] = field_19 + attributes["address_line1_as_entered"] = field_19 + attributes["address_line2"] = field_20 + attributes["address_line2_as_entered"] = field_20 + attributes["town_or_city"] = field_21 + attributes["town_or_city_as_entered"] = field_21 + attributes["county"] = field_22 + attributes["county_as_entered"] = field_22 + attributes["postcode_full"] = postcode_full + attributes["postcode_full_as_entered"] = postcode_full + attributes["postcode_known"] = postcode_known + attributes["la"] = field_25 + attributes["la_as_entered"] = field_25 + attributes["address_line1_input"] = address_line1_input + attributes["postcode_full_input"] = postcode_full + attributes["select_best_address_match"] = true if field_18.blank? attributes end diff --git a/config/locales/validations/lettings/property_information.en.yml b/config/locales/validations/lettings/property_information.en.yml index 8f706c261..15ea86f0e 100644 --- a/config/locales/validations/lettings/property_information.en.yml +++ b/config/locales/validations/lettings/property_information.en.yml @@ -6,6 +6,7 @@ en: 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" + does_not_match_scheme_location_postcode: "The postcode in the address does not match the postcode for the location in the scheme. Please review your answers to the address or UPRN question (whichever you answered) and the location question." 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." @@ -24,6 +25,7 @@ en: 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" + postcode_does_not_match_scheme_location_postcode: "The postcode in the address does not match the postcode for the location in the scheme. Please review your answers to the address or UPRN question (whichever you answered) and the location question." 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: @@ -38,6 +40,7 @@ en: 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" + postcode_does_not_match_scheme_location_postcode: "The postcode in the address does not match the postcode for the location in the scheme. Please review your answers to the address or UPRN question (whichever you answered) and the location question." 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." diff --git a/spec/models/form/lettings/pages/address_fallback_spec.rb b/spec/models/form/lettings/pages/address_fallback_spec.rb index ffac6238e..c4f720ba3 100644 --- a/spec/models/form/lettings/pages/address_fallback_spec.rb +++ b/spec/models/form/lettings/pages/address_fallback_spec.rb @@ -24,6 +24,6 @@ RSpec.describe Form::Lettings::Pages::AddressFallback, type: :model do end it "has correct depends_on" do - expect(page.depends_on).to eq([{ "manual_address_entry_selected" => true, "is_supported_housing?" => false }]) + expect(page.depends_on).to eq([{ "manual_address_entry_selected" => true, "is_address_asked?" => true }]) end end diff --git a/spec/models/form/lettings/pages/address_search_spec.rb b/spec/models/form/lettings/pages/address_search_spec.rb index 86c6537a1..0bf54bae8 100644 --- a/spec/models/form/lettings/pages/address_search_spec.rb +++ b/spec/models/form/lettings/pages/address_search_spec.rb @@ -25,7 +25,7 @@ RSpec.describe Form::Lettings::Pages::AddressSearch, type: :model do end it "has correct depends_on" do - expect(page.depends_on).to eq([{ "is_supported_housing?" => false, "manual_address_entry_selected" => false }]) + expect(page.depends_on).to eq([{ "is_address_asked?" => true, "manual_address_entry_selected" => false }]) end it "has the correct question number" do diff --git a/spec/models/lettings_log_derived_fields_spec.rb b/spec/models/lettings_log_derived_fields_spec.rb index 5fff41217..9cb6cf7d1 100644 --- a/spec/models/lettings_log_derived_fields_spec.rb +++ b/spec/models/lettings_log_derived_fields_spec.rb @@ -1446,4 +1446,146 @@ RSpec.describe LettingsLog, type: :model do expect { log.set_derived_fields! }.to change(log, :beds).to nil end end + + describe "resetting address fields and LA" do + # log.read_attribute is used in these tests to read the underlying attribute (which is what + # is being reset) rather than the getter method, which may override the underlying attribute. + + let(:uprn) { "123456789" } + let(:uprn_known) { 1 } # A value of 1 is necessary for this test as there is separate logic that resets `uprn` if `uprn_known` is 0. + let(:uprn_confirmed) { 1 } # A value of 1 is necessary for this test as there is separate logic that resets the address fields and LA if `uprn_confirmed` is 0 (and `uprn_known` is 1). + let(:address_line1) { "1 Test Street" } + let(:address_line2) { "Testville" } + let(:town_or_city) { "Testford" } + let(:county) { "Testshire" } + let(:postcode_full) { "SW1 1AA" } + let(:la) { "Test LA" } + let(:manual_address_entry_selected) { false } # A value of `false` is necessary for this test as there is separate logic that resets some of the UPRN fields if `manual_address_entry_selected` is `false`. + + context "when it is 2025", metadata: { year: 25 } do + let(:startdate) { collection_start_date_for_year(2025) } + + around do |example| + Timecop.freeze(collection_start_date_for_year(2025)) do + Singleton.__init__(FormHandler) + + log.save! # This is necessary to prevent `startdate_changed?` returning true; if this happens, some separate logic runs that resets LA to nil. + + log.assign_attributes(uprn:, uprn_known:, uprn_confirmed:, address_line1:, address_line2:, town_or_city:, county:, postcode_full:, la:, manual_address_entry_selected:) + + example.run + end + end + + context "when the log is marked as general needs housing" do + before do + log.needstype = 1 + end + + it "does not reset the address fields" do + expect { log.set_derived_fields! } + .to not_change { log.read_attribute(:uprn) } + .and not_change { log.read_attribute(:uprn_known) } + .and not_change { log.read_attribute(:uprn_confirmed) } + .and not_change { log.read_attribute(:address_line1) } + .and not_change { log.read_attribute(:address_line2) } + .and not_change { log.read_attribute(:town_or_city) } + .and not_change { log.read_attribute(:county) } + .and(not_change { log.read_attribute(:postcode_full) }) + end + + it "does not reset LA" do + expect { log.set_derived_fields! } + .to(not_change { log.read_attribute(:la) }) + end + end + + context "when the log is marked as supported housing" do + before do + log.needstype = 2 + end + + it "resets the address fields to nil" do + expect { log.set_derived_fields! } + .to change { log.read_attribute(:uprn) }.from(uprn).to(nil) + .and change { log.read_attribute(:uprn_known) }.from(uprn_known).to(nil) + .and change { log.read_attribute(:uprn_confirmed) }.from(uprn_confirmed).to(nil) + .and change { log.read_attribute(:address_line1) }.from(address_line1).to(nil) + .and change { log.read_attribute(:address_line2) }.from(address_line2).to(nil) + .and change { log.read_attribute(:town_or_city) }.from(town_or_city).to(nil) + .and change { log.read_attribute(:county) }.from(county).to(nil) + .and change { log.read_attribute(:postcode_full) }.from(postcode_full).to(nil) + end + + it "does not reset LA" do + expect { log.set_derived_fields! } + .to(not_change { log.read_attribute(:la) }) + end + end + end + + context "when it is 2026", metadata: { year: 26 } do + let(:startdate) { collection_start_date_for_year(2026) } + let(:location_a) { create(:location) } + let(:location_b) { create(:location) } + + around do |example| + Timecop.freeze(collection_start_date_for_year(2026)) do + Singleton.__init__(FormHandler) + example.run + end + end + + before do + allow(location_a).to receive(:lookup_postcode!).and_return(nil) + + log.location = location_a + log.save! + + log.assign_attributes(uprn:, uprn_known:, uprn_confirmed:, address_line1:, address_line2:, town_or_city:, county:, postcode_full:, la:, manual_address_entry_selected:) + end + + context "when the location is not changed" do + it "does not reset the address fields" do + expect { log.set_derived_fields! } + .to not_change { log.read_attribute(:uprn) } + .and not_change { log.read_attribute(:uprn_known) } + .and not_change { log.read_attribute(:uprn_confirmed) } + .and not_change { log.read_attribute(:address_line1) } + .and not_change { log.read_attribute(:address_line2) } + .and not_change { log.read_attribute(:town_or_city) } + .and not_change { log.read_attribute(:county) } + .and(not_change { log.read_attribute(:postcode_full) }) + end + + it "does not reset LA" do + expect { log.set_derived_fields! } + .to(not_change { log.read_attribute(:la) }) + end + end + + context "when the location is changed" do + before do + log.location = location_b + end + + it "resets the address fields to nil" do + expect { log.set_derived_fields! } + .to change { log.read_attribute(:uprn) }.from(uprn).to(nil) + .and change { log.read_attribute(:uprn_known) }.from(uprn_known).to(nil) + .and change { log.read_attribute(:uprn_confirmed) }.from(uprn_confirmed).to(nil) + .and change { log.read_attribute(:address_line1) }.from(address_line1).to(nil) + .and change { log.read_attribute(:address_line2) }.from(address_line2).to(nil) + .and change { log.read_attribute(:town_or_city) }.from(town_or_city).to(nil) + .and change { log.read_attribute(:county) }.from(county).to(nil) + .and change { log.read_attribute(:postcode_full) }.from(postcode_full).to(nil) + end + + it "resets LA to nil" do + expect { log.set_derived_fields! } + .to change { log.read_attribute(:la) }.from(la).to(nil) + end + end + end + end end diff --git a/spec/models/lettings_log_spec.rb b/spec/models/lettings_log_spec.rb index 9f0ddec42..d115e09fb 100644 --- a/spec/models/lettings_log_spec.rb +++ b/spec/models/lettings_log_spec.rb @@ -590,6 +590,63 @@ RSpec.describe LettingsLog do end end + context "and the log has different LAs set on the location and the log itself" do + before do + location.update!(location_code: "E07000030") + Timecop.freeze(startdate) + Singleton.__init__(FormHandler) + lettings_log.update!(startdate:, la: "E09000002") + lettings_log.reload + end + + after do + Timecop.unfreeze + Singleton.__init__(FormHandler) + end + + context "with 25/26" do + let(:startdate) { Time.zone.local(2025, 4, 2) } + + it "returns the LA from the location" do + expect(lettings_log["location_id"]).to eq(location.id) + expect(lettings_log.la).to eq("E07000030") + end + end + + context "with 26/27" do + let(:startdate) { Time.zone.local(2026, 4, 2) } + + it "returns the LA from the log itself" do + expect(lettings_log["location_id"]).to eq(location.id) + expect(lettings_log.la).to eq("E09000002") + end + end + end + + context "and the log only has an LA set on the location" do + before do + location.update!(location_code: "E07000030") + Timecop.freeze(startdate) + Singleton.__init__(FormHandler) + lettings_log.update!(startdate:) + lettings_log.reload + end + + after do + Timecop.unfreeze + Singleton.__init__(FormHandler) + end + + context "with 26/27" do + let(:startdate) { Time.zone.local(2026, 4, 2) } + + it "returns the LA from the location" do + expect(lettings_log["location_id"]).to eq(location.id) + expect(lettings_log.la).to eq("E07000030") + end + end + end + context "and the location no local authorities associated with the location_code" do before do Timecop.freeze(Time.zone.local(2022, 4, 2)) @@ -607,6 +664,66 @@ RSpec.describe LettingsLog do expect(lettings_log.la).to eq("E01231231") end end + + context "and the log has different postcodes set on the location and the log itself" do + before do + location.update!(postcode: "AA1 1AA") + Timecop.freeze(startdate) + Singleton.__init__(FormHandler) + lettings_log.update!( + startdate:, + ) + lettings_log.reload + lettings_log.postcode_full = "BB2 2BB" + end + + after do + Timecop.unfreeze + Singleton.__init__(FormHandler) + end + + context "with 25/26" do + let(:startdate) { Time.zone.local(2025, 4, 2) } + + it "returns the postcode from the location" do + expect(lettings_log["location_id"]).to eq(location.id) + expect(lettings_log.postcode_full).to eq("AA1 1AA") + end + end + + context "with 26/27" do + let(:startdate) { Time.zone.local(2026, 4, 2) } + + it "returns the postcode from the log itself" do + expect(lettings_log["location_id"]).to eq(location.id) + expect(lettings_log.postcode_full).to eq("BB2 2BB") + end + end + end + + context "and the log only has a postcode set on the location" do + before do + location.update!(postcode: "AA1 1AA") + Timecop.freeze(startdate) + Singleton.__init__(FormHandler) + lettings_log.update!(startdate:) + lettings_log.reload + end + + after do + Timecop.unfreeze + Singleton.__init__(FormHandler) + end + + context "with 26/27" do + let(:startdate) { Time.zone.local(2026, 4, 2) } + + it "returns the LA from the location" do + expect(lettings_log["location_id"]).to eq(location.id) + expect(lettings_log.postcode_full).to eq("AA1 1AA") + end + end + end end context "and not renewal" do diff --git a/spec/models/validations/property_validations_spec.rb b/spec/models/validations/property_validations_spec.rb index 706abec1d..3cd05bb88 100644 --- a/spec/models/validations/property_validations_spec.rb +++ b/spec/models/validations/property_validations_spec.rb @@ -237,10 +237,10 @@ RSpec.describe Validations::PropertyValidations do expect(log.errors["scheme_id"]).to include(I18n.t("validations.lettings.property.scheme_id.not_in_england")) expect(log.errors["location_id"]).to include(I18n.t("validations.lettings.property.location_id.not_in_england")) expect(log.errors["startdate"]).to include(I18n.t("validations.lettings.property.startdate.location_not_in_england")) - 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["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_selection"]).to include(I18n.t("validations.lettings.property.uprn_selection.not_in_england")) end end @@ -342,10 +342,10 @@ RSpec.describe Validations::PropertyValidations do 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 + 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)) end end diff --git a/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb index 6e37c82b3..958c44a04 100644 --- a/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb @@ -12,7 +12,10 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do let(:owning_org) { create(:organisation, :with_old_visible_id) } let(:managing_org) { create(:organisation, :with_old_visible_id, rent_periods: [4, 1]) } let(:scheme) { create(:scheme, :with_old_visible_id, owning_organisation: owning_org) } - let(:location) { create(:location, :with_old_visible_id, scheme:) } + let(:postcode_first_part) { "AA1".freeze } + let(:postcode_second_part) { "1AA".freeze } + let(:postcode) { "#{postcode_first_part} #{postcode_second_part}" } + let(:location) { create(:location, :with_old_visible_id, scheme:, postcode:) } let(:setup_section_params) do { @@ -88,7 +91,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do .to_return(status: 200, body: "{\"status\":200,\"result\":{\"admin_district\":\"Manchester\", \"codes\":{\"admin_district\": \"E08000003\"}}}", headers: {}) stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/) - .to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.9, BUILDING_NAME: "result address line 1", POST_TOWN: "result town or city", POSTCODE: "AA1 1AA", UPRN: "12345" } }] }.to_json, headers: {}) + .to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.9, BUILDING_NAME: "result address line 1", POST_TOWN: "result town or city", POSTCODE: postcode, UPRN: "12345" } }] }.to_json, headers: {}) stub_request(:get, /api\.os\.uk\/search\/places\/v1\/uprn/) .to_return(status: 200, body: '{"status":200,"results":[{"DPA":{ @@ -101,7 +104,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do "DEPENDENT_THOROUGHFARE_NAME": "data", "THOROUGHFARE_NAME": "thing", "POST_TOWN": "London", - "POSTCODE": "SE2 6RT" + "POSTCODE": "' + postcode + '" }}]}', headers: {}) end @@ -293,7 +296,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do :field_13, # tenancycode :field_23, # postcode_full :field_24, # postcode_full - :field_25, # postcode_full + :field_25, # LA :field_42, # age1 :field_43, # sex1 :field_46, # ecstat1 @@ -324,7 +327,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - context "when a supported housing log already exists in the db" do + context "when a supported housing log already exists in the db" do # TODO: CLDC-4119: Beware! The `postcode_full` method in the `LettingsLog` class may cause issues with these supported housing log duplicate detection tests after postcode is added. See comment on the `postcode_full` method for details. let(:attributes) { valid_attributes.merge({ field_4: "2", field_5: "S#{scheme.id}", field_6: location.old_visible_id, field_36: 3, field_122: 0 }) } before do @@ -1484,183 +1487,251 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end describe "UPRN and address fields" do - context "with a general needs log" do - context "when a valid UPRN is given" do - context "and address fields are not given" do - let(:attributes) { setup_section_params.merge({ field_4: 1, field_18: "123456789012" }) } - - it "does not add errors" do - parser.valid? - %i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field| - expect(parser.errors[field]).to be_empty - end - end + %i[ + general_needs + supported_housing + ].each do |needs_type| + context "with a #{needs_type.to_s.tr('_', ' ')} log" do + let(:base_attributes) do + setup_section_params.merge({ + field_4: needs_type == :supported_housing ? 2 : 1, + field_5: needs_type == :supported_housing ? "S#{scheme.id}" : nil, + field_6: needs_type == :supported_housing ? location.old_visible_id : nil, + }.compact) end - end - - context "when an invalid UPRN is given" do - context "and address fields are not given" do - let(:attributes) { setup_section_params.merge({ field_4: 1, field_18: "1234567890123" }) } - it "adds an appropriate error to the UPRN field" do - parser.valid? - expect(parser.errors[:field_18]).to eql(["UPRN must be 12 digits or less."]) - end + context "when a valid UPRN is given" do + context "and address fields are not given" do + let(:attributes) { base_attributes.merge({ field_18: "123456789012" }) } - it "adds errors to missing key address fields" do - parser.valid? - expect(parser.errors[:field_19]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "address line 1.")]) - expect(parser.errors[:field_21]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "town or city.")]) - expect(parser.errors[:field_23]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "part 1 of postcode.")]) - expect(parser.errors[:field_24]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "part 2 of postcode.")]) - end - end - - context "and address fields are given" do - let(:attributes) { setup_section_params.merge({ field_4: 1, field_18: "1234567890123", field_19: "address line 1", field_21: "town or city", field_23: "AA1", field_24: "1AA" }) } - - it "adds an error to the UPRN field only" do - parser.valid? - expect(parser.errors[:field_18]).to eql(["UPRN must be 12 digits or less."]) - %i[field_19 field_21 field_23 field_24].each do |field| - expect(parser.errors[field]).to be_empty + it "does not add errors" do + parser.valid? + %i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field| + expect(parser.errors[field]).to be_empty + end end - end - - it "does not do an address search" do - parser.valid? - expect(a_request(:any, /api\.os\.uk\/search\/places\/v1\/find/)).not_to have_been_made - end - end - end - - context "when no UPRN is given" do - context "and no address fields are given" do - let(:attributes) { setup_section_params.merge({ field_4: 1 }) } - - it "adds appropriate errors to UPRN and key address fields" do - parser.valid? - expect(parser.errors[:field_18]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")]) - expect(parser.errors[:field_19]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")]) - expect(parser.errors[:field_21]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")]) - expect(parser.errors[:field_23]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")]) - expect(parser.errors[:field_24]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")]) - end - end - context "and some key address field is missing" do - let(:attributes) { setup_section_params.merge({ field_4: 1, field_21: "town or city", field_23: "AA1", field_24: "1AA" }) } - - it "adds errors to UPRN and the missing key address field" do - parser.valid? - expect(parser.errors[:field_18]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")]) - expect(parser.errors[:field_19]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")]) - expect(parser.errors[:field_21]).to be_empty - expect(parser.errors[:field_23]).to be_empty - expect(parser.errors[:field_24]).to be_empty + if needs_type == :supported_housing + context "and the postcode associated with the UPRN does not match the postcode associated with the location" do + before do + stub_request(:get, /api\.os\.uk\/search\/places\/v1\/uprn/) + .to_return(status: 200, body: '{"status":200,"results":[{"DPA":{ + "PO_BOX_NUMBER": "fake", + "ORGANISATION_NAME": "org", + "DEPARTMENT_NAME": "name", + "SUB_BUILDING_NAME": "building", + "BUILDING_NAME": "name", + "BUILDING_NUMBER": "number", + "DEPENDENT_THOROUGHFARE_NAME": "data", + "THOROUGHFARE_NAME": "thing", + "POST_TOWN": "London", + "POSTCODE": "BB2 2BB" + }}]}', headers: {}) + end + + it "adds an appropriate error to the UPRN and location fields" do + parser.valid? + expect(parser.errors[:field_18]).to eql([I18n.t("validations.lettings.property.uprn.postcode_does_not_match_scheme_location_postcode")]) + expect(parser.errors[:field_6]).to eql([I18n.t("validations.lettings.property.location_id.postcode_does_not_match_scheme_location_postcode")]) + end + end + end end end - context "and all key address fields are present" do - let(:attributes) { setup_section_params.merge({ field_4: 1, field_18: nil, field_19: "address line 1", field_21: "town or city", field_23: "AA1", field_24: "1AA" }) } - - context "and an address can be found with a high enough match rating" do - before do - stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/) - .to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.7, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: "AA1 1AA", UPRN: "1" } }] }.to_json, headers: {}) - end + context "when an invalid UPRN is given" do + context "and address fields are not given" do + let(:attributes) { base_attributes.merge({ field_18: "1234567890123" }) } - it "does not add errors" do + it "adds an appropriate error to the UPRN field" do parser.valid? - %i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field| - expect(parser.errors[field]).to be_empty - end + expect(parser.errors[:field_18]).to eql(["UPRN must be 12 digits or less."]) end - it "does not set manual address input" do + it "adds errors to missing key address fields" do parser.valid? - expect(parser.log.manual_address_entry_selected).to be_falsey + expect(parser.errors[:field_19]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "address line 1.")]) + expect(parser.errors[:field_21]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "town or city.")]) + expect(parser.errors[:field_23]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "part 1 of postcode.")]) + expect(parser.errors[:field_24]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "part 2 of postcode.")]) end end - context "when no address can be found" do - before do - stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/) - .to_return(status: 200, body: { results: [] }.to_json, headers: {}) - end + context "and address fields are given" do + let(:attributes) { base_attributes.merge({ field_18: "1234567890123", field_19: "address line 1", field_21: "town or city", field_23: postcode_first_part, field_24: postcode_second_part }) } - it "does not add errors" do + it "adds an error to the UPRN field only" do parser.valid? - %i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field| + expect(parser.errors[:field_18]).to eql(["UPRN must be 12 digits or less."]) + %i[field_19 field_21 field_23 field_24].each do |field| expect(parser.errors[field]).to be_empty end end - it "sets manual address input" do + it "does not do an address search" do parser.valid? - expect(parser.log.manual_address_entry_selected).to be_truthy - expect(parser.log.address_line1).to eq("address line 1") - expect(parser.log.town_or_city).to eq("town or city") - expect(parser.log.postcode_full).to eq("AA1 1AA") + expect(a_request(:any, /api\.os\.uk\/search\/places\/v1\/find/)).not_to have_been_made end end + end - context "when a single address with not a high enough match rating is returned" do - before do - stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/) - .to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.6, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: "AA1 1AA", UPRN: "1" } }] }.to_json, headers: {}) - end + context "when no UPRN is given" do + context "and no address fields are given" do + let(:attributes) { base_attributes.merge({ field_4: 1 }) } - it "does not add errors" do + it "adds appropriate errors to UPRN and key address fields" do parser.valid? - %i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field| - expect(parser.errors[field]).to be_empty - end + expect(parser.errors[:field_18]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_19]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_21]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_23]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_24]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")]) end + end + + context "and some key address field is missing" do + let(:attributes) { base_attributes.merge({ field_21: "town or city", field_23: postcode_first_part, field_24: postcode_second_part }) } - it "sets manual address input" do + it "adds errors to UPRN and the missing key address field" do parser.valid? - expect(parser.log.manual_address_entry_selected).to be_truthy - expect(parser.log.address_line1).to eq("address line 1") - expect(parser.log.town_or_city).to eq("town or city") - expect(parser.log.postcode_full).to eq("AA1 1AA") + expect(parser.errors[:field_18]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_19]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_21]).to be_empty + expect(parser.errors[:field_23]).to be_empty + expect(parser.errors[:field_24]).to be_empty end end - context "when no addresses have a high enough match rating" do - before do - stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/) - .to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.6, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: "AA1 1AA", UPRN: "1" } }, { DPA: { MATCH: 0.8, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: "BB2 2BB", UPRN: "2" } }] }.to_json, headers: {}) + context "and all key address fields are present" do + let(:attributes) { base_attributes.merge({ field_18: nil, field_19: "address line 1", field_21: "town or city", field_23: postcode_first_part, field_24: postcode_second_part }) } + + context "and an address can be found with a high enough match rating" do + before do + stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/) + .to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.7, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: postcode, UPRN: "1" } }] }.to_json, headers: {}) + end + + it "does not add errors" do + parser.valid? + %i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field| + expect(parser.errors[field]).to be_empty + end + end + + it "does not set manual address input" do + parser.valid? + expect(parser.log.manual_address_entry_selected).to be_falsey + end end - it "does not add errors" do - parser.valid? - %i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field| - expect(parser.errors[field]).to be_empty + context "when no address can be found" do + before do + stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/) + .to_return(status: 200, body: { results: [] }.to_json, headers: {}) + end + + it "does not add errors" do + parser.valid? + %i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field| + expect(parser.errors[field]).to be_empty + end + end + + it "sets manual address input" do + parser.valid? + expect(parser.log.manual_address_entry_selected).to be_truthy + expect(parser.log.address_line1).to eq("address line 1") + expect(parser.log.town_or_city).to eq("town or city") + expect(parser.log.postcode_full).to eq(postcode) + end + + if needs_type == :supported_housing + context "and the postcode entered manually does not match the postcode associated with the location" do + let(:attributes) { base_attributes.merge({ field_18: nil, field_19: "address line 1", field_21: "town or city", field_23: "BB2", field_24: "2BB" }) } + + it "adds appropriate errors to the postcode, LA and location fields" do + parser.valid? + expect(parser.errors[:field_23]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")]) + expect(parser.errors[:field_24]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")]) + expect(parser.errors[:field_25]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")]) + expect(parser.errors[:field_6]).to eql([I18n.t("validations.lettings.property.location_id.postcode_does_not_match_scheme_location_postcode")]) + end + end end end - it "sets manual address input" do - parser.valid? - expect(parser.log.manual_address_entry_selected).to be_truthy - expect(parser.log.manual_address_entry_selected).to be_truthy - expect(parser.log.address_line1).to eq("address line 1") - expect(parser.log.town_or_city).to eq("town or city") - expect(parser.log.postcode_full).to eq("AA1 1AA") + context "when a single address with not a high enough match rating is returned" do + before do + stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/) + .to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.6, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: postcode, UPRN: "1" } }] }.to_json, headers: {}) + end + + it "does not add errors" do + parser.valid? + %i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field| + expect(parser.errors[field]).to be_empty + end + end + + it "sets manual address input" do + parser.valid? + expect(parser.log.manual_address_entry_selected).to be_truthy + expect(parser.log.address_line1).to eq("address line 1") + expect(parser.log.town_or_city).to eq("town or city") + expect(parser.log.postcode_full).to eq(postcode) + end + + if needs_type == :supported_housing + context "and the postcode entered manually does not match the postcode associated with the location" do + let(:attributes) { base_attributes.merge({ field_18: nil, field_19: "address line 1", field_21: "town or city", field_23: "BB2", field_24: "2BB" }) } + + it "adds appropriate errors to the postcode, LA and location fields" do + parser.valid? + expect(parser.errors[:field_23]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")]) + expect(parser.errors[:field_24]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")]) + expect(parser.errors[:field_25]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")]) + expect(parser.errors[:field_6]).to eql([I18n.t("validations.lettings.property.location_id.postcode_does_not_match_scheme_location_postcode")]) + end + end + end end - end - end - end - end - context "with a supported housing log" do - context "when neither UPRN nor address fields are provided" do - let(:attributes) { setup_section_params.merge({ field_4: 2, field_5: "S#{scheme.id}", field_6: location.old_visible_id, field_18: nil, field_19: nil, field_21: nil, field_23: nil, field_24: nil }) } + context "when no addresses have a high enough match rating" do + before do + stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/) + .to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.6, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: postcode, UPRN: "1" } }, { DPA: { MATCH: 0.8, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: "BB2 2BB", UPRN: "2" } }] }.to_json, headers: {}) + end + + it "does not add errors" do + parser.valid? + %i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field| + expect(parser.errors[field]).to be_empty + end + end - it "does not add missing field errors" do - parser.valid? - %i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field| - expect(parser.errors[field]).to be_empty + it "sets manual address input" do + parser.valid? + expect(parser.log.manual_address_entry_selected).to be_truthy + expect(parser.log.manual_address_entry_selected).to be_truthy + expect(parser.log.address_line1).to eq("address line 1") + expect(parser.log.town_or_city).to eq("town or city") + expect(parser.log.postcode_full).to eq(postcode) + end + + if needs_type == :supported_housing + context "and the postcode entered manually does not match the postcode associated with the location" do + let(:attributes) { base_attributes.merge({ field_18: nil, field_19: "address line 1", field_21: "town or city", field_23: "BB2", field_24: "2BB" }) } + + it "adds appropriate errors to the postcode, LA and location fields" do + parser.valid? + expect(parser.errors[:field_23]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")]) + expect(parser.errors[:field_24]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")]) + expect(parser.errors[:field_25]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")]) + expect(parser.errors[:field_6]).to eql([I18n.t("validations.lettings.property.location_id.postcode_does_not_match_scheme_location_postcode")]) + end + end + end + end end end end @@ -1869,87 +1940,79 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - describe "#uprn" do - let(:attributes) { { bulk_upload:, field_4: 1, field_18: "12" } } + describe "address-related fields" do + %i[ + general_needs + supported_housing + ].each do |needs_type| + context "with a #{needs_type.to_s.tr('_', ' ')} log" do + let(:base_attributes) do + { + bulk_upload:, + field_4: needs_type == :supported_housing ? 2 : 1, + field_5: needs_type == :supported_housing ? "S#{scheme.id}" : nil, + field_6: needs_type == :supported_housing ? location.old_visible_id : nil, + }.compact + end + + describe "#uprn" do + let(:attributes) { base_attributes.merge({ field_18: "12" }) } - it "sets to given value" do - expect(parser.log.uprn).to eql("12") - end - end + it "sets to given value" do + expect(parser.log.uprn).to eql("12") + end + end - describe "#uprn_known" do - context "when uprn specified" do - let(:attributes) { { bulk_upload:, field_4: 1, field_18: "12" } } + describe "#uprn_known" do + context "when uprn specified" do + let(:attributes) { base_attributes.merge({ field_18: "12" }) } - it "sets to 1" do - expect(parser.log.uprn_known).to be(1) - expect(parser.log.uprn_confirmed).to be(1) - end - end + it "sets to 1" do + expect(parser.log.uprn_known).to be(1) + expect(parser.log.uprn_confirmed).to be(1) + end + end - context "when uprn blank" do - let(:attributes) { { bulk_upload:, field_4: 1, field_18: "" } } + context "when uprn blank" do + let(:attributes) { base_attributes.merge({ field_18: "" }) } - it "sets to 0" do - expect(parser.log.uprn_known).to be(0) - end - end - end + it "sets to 0" do + expect(parser.log.uprn_known).to be(0) + end + end + end - describe "#address_line1" do - let(:attributes) { { bulk_upload:, field_4: 1, field_19: "123 Sesame Street" } } + describe "#address_line1" do + let(:attributes) { base_attributes.merge({ field_19: "123 Sesame Street" }) } - it "sets to given value" do - expect(parser.log.address_line1).to eql("123 Sesame Street") - end - end + it "sets to given value" do + expect(parser.log.address_line1).to eql("123 Sesame Street") + end + end - describe "#address_line2" do - let(:attributes) { { bulk_upload:, field_4: 1, field_20: "Cookie Town" } } + describe "#address_line2" do + let(:attributes) { base_attributes.merge({ field_20: "Cookie Town" }) } - it "sets to given value" do - expect(parser.log.address_line2).to eql("Cookie Town") - end - end + it "sets to given value" do + expect(parser.log.address_line2).to eql("Cookie Town") + end + end - describe "#town_or_city" do - let(:attributes) { { bulk_upload:, field_4: 1, field_21: "London" } } + describe "#town_or_city" do + let(:attributes) { base_attributes.merge({ field_21: "London" }) } - it "sets to given value" do - expect(parser.log.town_or_city).to eql("London") - end - end + it "sets to given value" do + expect(parser.log.town_or_city).to eql("London") + end + end - describe "#county" do - let(:attributes) { { bulk_upload:, field_4: 1, field_22: "Greater London" } } + describe "#county" do + let(:attributes) { base_attributes.merge({ field_22: "Greater London" }) } - it "sets to given value" do - expect(parser.log.county).to eql("Greater London") - end - end - - describe "address related fields for supported housing logs" do - context "when address data is provided for a supported housing log" do - let(:attributes) { { bulk_upload:, field_4: 2, field_18: nil, field_19: "Flat 1", field_20: "Example Place", field_21: "London", field_22: "Greater London", field_23: "SW1A", field_24: "1AA" } } - - it "is not set on the log" do - expect(parser.log.uprn).to be_nil - expect(parser.log.uprn_known).to be_nil - expect(parser.log.address_line1).to be_nil - expect(parser.log.address_line1_as_entered).to be_nil - expect(parser.log.address_line2).to be_nil - expect(parser.log.address_line2_as_entered).to be_nil - expect(parser.log.town_or_city).to be_nil - expect(parser.log.town_or_city_as_entered).to be_nil - expect(parser.log.county).to be_nil - expect(parser.log.county_as_entered).to be_nil - expect(parser.log.postcode_full).to be_nil - expect(parser.log.postcode_full_as_entered).to be_nil - expect(parser.log.la).to be_nil - expect(parser.log.la_as_entered).to be_nil - expect(parser.log.address_line1_input).to be_nil - expect(parser.log.postcode_full_input).to be_nil - expect(parser.log.select_best_address_match).to be_nil + it "sets to given value" do + expect(parser.log.county).to eql("Greater London") + end + end end end end