diff --git a/app/services/bulk_upload/lettings/year2026/row_parser.rb b/app/services/bulk_upload/lettings/year2026/row_parser.rb index 3c668cfcc..80f31e0f7 100644 --- a/app/services/bulk_upload/lettings/year2026/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2026/row_parser.rb @@ -466,8 +466,6 @@ class BulkUpload::Lettings::Year2026::RowParser validate :validate_needs_type_present, on: :after_log validate :validate_data_types, on: :after_log validate :validate_relevant_collection_window, on: :after_log - validate :validate_la_with_local_housing_referral, on: :after_log - validate :validate_cannot_be_la_referral_if_general_needs_and_la, on: :after_log validate :validate_leaving_reason_for_renewal, on: :after_log validate :validate_only_one_housing_needs_type, on: :after_log validate :validate_no_disabled_needs_conjunction, on: :after_log @@ -478,6 +476,7 @@ class BulkUpload::Lettings::Year2026::RowParser validate :validate_reasonable_preference_dont_know, on: :after_log validate :validate_condition_effects, on: :after_log validate :validate_if_log_already_exists, on: :after_log, if: -> { FeatureToggle.bulk_upload_duplicate_log_check_enabled? } + validate :validate_referral_fields, on: :after_log validate :validate_owning_org_data_given, on: :after_log validate :validate_owning_org_exists, on: :after_log @@ -738,7 +737,7 @@ private end def validate_prevten_value_when_renewal - return unless field_7 == 1 + return unless renewal? return if field_100.blank? || [6, 30, 31, 32, 33, 34, 35, 38].include?(field_100) errors.add(:field_100, I18n.t("#{ERROR_BASE_KEY}.prevten.invalid")) @@ -845,7 +844,7 @@ private end def validate_leaving_reason_for_renewal - if field_7 == 1 && ![50, 51, 52, 53].include?(field_98) + if renewal? && ![50, 51, 52, 53].include?(field_98) errors.add(:field_98, I18n.t("#{ERROR_BASE_KEY}.reason.renewal_reason_needed")) end end @@ -858,16 +857,8 @@ private field_4 == 2 end - def validate_cannot_be_la_referral_if_general_needs_and_la - if field_116 == 4 && general_needs? && owning_organisation && owning_organisation.la? - errors.add :field_116, I18n.t("#{ERROR_BASE_KEY}.referral.general_needs_prp_referred_by_la") - end - end - - def validate_la_with_local_housing_referral - if field_116 == 3 && owning_organisation && owning_organisation.la? - errors.add(:field_116, I18n.t("#{ERROR_BASE_KEY}.referral.nominated_by_local_ha_but_la")) - end + def renewal? + field_7 == 1 end def validate_relevant_collection_window @@ -1050,6 +1041,57 @@ private end end + def field_referral_register_la_valid? + if owning_organisation&.la? + [1, 2, 3, 4].include?(field_116) + else + field_116.blank? + end + end + + def field_referral_register_prp_valid? + if owning_organisation&.prp? + [5, 6, 7, 8, 9].include?(field_154) + else + field_154.blank? + end + end + + def field_referral_noms_valid? + case field_154 + when 6 + [1, 2, 3, 4].include?(field_155) + when 7 + [5, 6, 7, 8].include?(field_155) + else + field_155.blank? + end + end + + def field_referral_org_valid? + case field_155 + when 1 + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].include?(field_156) + when 7 + [11, 12, 13, 14, 15, 16, 17, 18, 19, 20].include?(field_156) + else + field_156.blank? + end + end + + def referral_fields_valid? + field_referral_register_la_valid? && field_referral_register_prp_valid? && field_referral_noms_valid? && field_referral_org_valid? + end + + def validate_referral_fields + return if renewal? + return if referral_fields_valid? + + %i[field_116 field_154 field_155 field_156].each do |field| + errors.add(field, I18n.t("#{ERROR_BASE_KEY}.referral.invalid_option")) + end + end + def field_mapping_for_errors { lettype: [:field_11], @@ -1780,6 +1822,11 @@ private def referral_register return unless owning_organisation + # by default CORE will ingest all these fields and nil questions that aren't asked. + # here, we specifically want the log to be invalid if any of the referral fields are wrong. + # BU will only consider a log invalid if its incomplete. + # so, nil these fields if any are invalid. + return unless referral_fields_valid? if owning_organisation.la? field_116 @@ -1789,12 +1836,20 @@ private end def referral_noms - field_155 + return unless owning_organisation + return unless referral_fields_valid? + + if owning_organisation.prp? + field_155 + end end def referral_org return unless owning_organisation + return unless referral_fields_valid? - field_156 + if owning_organisation.prp? + field_156 + end end end diff --git a/config/locales/validations/lettings/2026/bulk_upload.en.yml b/config/locales/validations/lettings/2026/bulk_upload.en.yml index 8ac7fc378..bf65ae6bd 100644 --- a/config/locales/validations/lettings/2026/bulk_upload.en.yml +++ b/config/locales/validations/lettings/2026/bulk_upload.en.yml @@ -40,8 +40,7 @@ en: reason: renewal_reason_needed: "The reason for leaving must be \"End of social or private sector tenancy - no fault\", \"End of social or private sector tenancy - evicted due to anti-social behaviour (ASB)\", \"End of social or private sector tenancy - evicted due to rent arrears\" or \"End of social or private sector tenancy - evicted for any other reason\"." referral: - general_needs_prp_referred_by_la: "The source of the referral cannot be referred by local authority housing department for a general needs log." - nominated_by_local_ha_but_la: "The source of the referral cannot be Nominated by local housing authority as your organisation is a local authority." + invalid_option: "Your answers for each part of \"What is the source of referral for this letting?\" are incompatible with each other. Use the bulk upload specification or paper form to see which combinations are valid (available from ‘Collection resources’ on the homepage)." scheme: must_relate_to_org: "This scheme code does not belong to the owning organisation or managing organisation." location: 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 ded0f3f4b..68768d7d9 100644 --- a/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb @@ -225,9 +225,6 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do field_115: "2", field_116: "1", - field_154: "1", - field_155: "1", - field_156: "1", field_117: "1", field_118: "2", @@ -651,12 +648,12 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when an invalid value error has been added" do - let(:attributes) { setup_section_params.merge({ field_116: "100" }) } + let(:attributes) { setup_section_params.merge({ field_115: "100" }) } it "does not add an additional error" do parser.valid? - expect(parser.errors[:field_116].length).to eq(1) - expect(parser.errors[:field_116]).to include(match I18n.t("validations.lettings.2026.bulk_upload.invalid_option", question: "")) + expect(parser.errors[:field_115].length).to eq(1) + expect(parser.errors[:field_115]).to include(match I18n.t("validations.lettings.2026.bulk_upload.invalid_option", question: "")) end end end @@ -1163,10 +1160,239 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - # TODO: CLDC-4191: Add tests for the new referral fields - # describe "#field_116" do # referral - # - # end + describe "#field_116, field_154, field_155, field_156" do # referral + context "when org is LA" do + let(:owning_org) { create(:organisation, :la, :with_old_visible_id) } + + let(:org_attributes) { { bulk_upload:, field_1: owning_org.old_visible_id } } + + context "and not renewal" do + let(:renewal_attributes) { org_attributes.merge({ field_7: nil }) } + + context "and field_116 is valid" do + let(:attributes) { renewal_attributes.merge({ field_116: 1 }) } + + it "does not add an error" do + parser.valid? + expect(parser.errors[:field_116]).to be_blank + expect(parser.errors[:field_154]).to be_blank + expect(parser.errors[:field_155]).to be_blank + expect(parser.errors[:field_156]).to be_blank + end + end + + context "and field_116 is invalid" do + let(:attributes) { renewal_attributes.merge({ field_116: 5 }) } # PRP option + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_154]).to be_present + expect(parser.errors[:field_155]).to be_present + expect(parser.errors[:field_156]).to be_present + end + end + + context "and field_116 is blank" do + let(:attributes) { renewal_attributes.merge({ field_116: nil }) } + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_154]).to be_present + expect(parser.errors[:field_155]).to be_present + expect(parser.errors[:field_156]).to be_present + end + end + + context "and other fields are given" do + let(:attributes) { renewal_attributes.merge({ field_116: 1, field_154: 5, field_155: 1, field_152: 1 }) } + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_154]).to be_present + expect(parser.errors[:field_155]).to be_present + expect(parser.errors[:field_156]).to be_present + end + end + end + + context "and is renewal" do + let(:attributes) { org_attributes.merge({ field_7: 1, field_116: 1, field_154: 5, field_155: 1, field_156: 1 }) } + + it "does not add an error for referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_blank + expect(parser.errors[:field_154]).to be_blank + expect(parser.errors[:field_155]).to be_blank + expect(parser.errors[:field_156]).to be_blank + end + end + end + + context "when org is PRP" do + let(:owning_org) { create(:organisation, :prp, :with_old_visible_id) } + + let(:org_attributes) { { bulk_upload:, field_1: owning_org.old_visible_id } } + + context "and not renewal" do + let(:renewal_attributes) { org_attributes.merge({ field_7: nil }) } + + context "and field_154 is valid and does not expect an answer for field_155" do + let(:attributes) { renewal_attributes.merge({ field_154: 5 }) } + + it "does not add an error" do + parser.valid? + expect(parser.errors[:field_116]).to be_blank + expect(parser.errors[:field_154]).to be_blank + expect(parser.errors[:field_155]).to be_blank + expect(parser.errors[:field_156]).to be_blank + end + + context "and later fields are given" do + let(:attributes) { renewal_attributes.merge({ field_154: 5, field_155: 1, field_156: 1 }) } + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_154]).to be_present + expect(parser.errors[:field_155]).to be_present + expect(parser.errors[:field_156]).to be_present + end + end + end + + context "and field_154 is invalid" do + let(:attributes) { renewal_attributes.merge({ field_154: 1 }) } # LA option + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_154]).to be_present + expect(parser.errors[:field_155]).to be_present + expect(parser.errors[:field_156]).to be_present + end + end + + context "and field_154 is blank" do + let(:attributes) { renewal_attributes.merge({ field_154: nil }) } + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_154]).to be_present + expect(parser.errors[:field_155]).to be_present + expect(parser.errors[:field_156]).to be_present + end + end + + context "and field_154 is valid and expects an answer for field_155" do + let(:field_154_attributes) { renewal_attributes.merge({ field_154: 6 }) } + + context "and field_155 is valid and does not expect an answer for field_156" do + let(:attributes) { field_154_attributes.merge({ field_155: 2 }) } + + it "does not add an error" do + parser.valid? + expect(parser.errors[:field_116]).to be_blank + expect(parser.errors[:field_154]).to be_blank + expect(parser.errors[:field_155]).to be_blank + expect(parser.errors[:field_156]).to be_blank + end + + context "and later fields are given" do + let(:attributes) { field_154_attributes.merge({ field_155: 2, field_156: 1 }) } + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_154]).to be_present + expect(parser.errors[:field_155]).to be_present + expect(parser.errors[:field_156]).to be_present + end + end + end + + context "and field_155 is invalid" do + let(:attributes) { field_154_attributes.merge({ field_155: 5 }) } # needs field_154 to be 7 + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_154]).to be_present + expect(parser.errors[:field_155]).to be_present + expect(parser.errors[:field_156]).to be_present + end + end + + context "and field_155 is blank" do + let(:attributes) { field_154_attributes.merge({ field_155: nil }) } + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_154]).to be_present + expect(parser.errors[:field_155]).to be_present + expect(parser.errors[:field_156]).to be_present + end + end + + context "and field_155 is valid and expects an answer for field_156" do + let(:field_155_attributes) { field_154_attributes.merge({ field_155: 1 }) } + + context "and field_156 is valid" do + let(:attributes) { field_155_attributes.merge({ field_156: 1 }) } + + it "does not add an error" do + parser.valid? + expect(parser.errors[:field_116]).to be_blank + expect(parser.errors[:field_154]).to be_blank + expect(parser.errors[:field_155]).to be_blank + expect(parser.errors[:field_156]).to be_blank + end + end + + context "and field_156 is invalid" do + let(:attributes) { field_155_attributes.merge({ field_156: 11 }) } # needs field_155 to be 7 + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_154]).to be_present + expect(parser.errors[:field_155]).to be_present + expect(parser.errors[:field_156]).to be_present + end + end + + context "and field_156 is blank" do + let(:attributes) { field_155_attributes.merge({ field_156: nil }) } + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_154]).to be_present + expect(parser.errors[:field_155]).to be_present + expect(parser.errors[:field_156]).to be_present + end + end + end + end + end + + context "and is renewal" do + let(:attributes) { org_attributes.merge({ field_7: 1, field_116: 1, field_154: 5, field_155: 1, field_156: 1 }) } + + it "does not add an error for referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_blank + expect(parser.errors[:field_154]).to be_blank + expect(parser.errors[:field_155]).to be_blank + expect(parser.errors[:field_156]).to be_blank + end + end + end + end describe "fields 7, 8, 9 => startdate" do context "when any one of these fields is blank" do