From 9391d5e6493b581400590fbdc73005a2813cc72c Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Fri, 23 Jan 2026 14:56:30 +0000 Subject: [PATCH 01/40] CLDC-4151: Note deprecations of existing referral questions --- app/models/form/lettings/pages/referral_direct.rb | 2 ++ app/models/form/lettings/pages/referral_general_needs.rb | 1 + app/models/form/lettings/pages/referral_general_needs_prp.rb | 1 + app/models/form/lettings/pages/referral_hsc.rb | 2 ++ app/models/form/lettings/pages/referral_justice.rb | 2 ++ app/models/form/lettings/pages/referral_la.rb | 2 ++ app/models/form/lettings/pages/referral_prp.rb | 2 ++ app/models/form/lettings/pages/referral_supported_housing.rb | 1 + .../form/lettings/pages/referral_supported_housing_prp.rb | 1 + app/models/form/lettings/pages/referral_type.rb | 2 ++ app/models/form/lettings/questions/referral_direct.rb | 2 ++ app/models/form/lettings/questions/referral_general_needs.rb | 1 + .../form/lettings/questions/referral_general_needs_prp.rb | 1 + app/models/form/lettings/questions/referral_hsc.rb | 2 ++ app/models/form/lettings/questions/referral_justice.rb | 2 ++ app/models/form/lettings/questions/referral_la.rb | 2 ++ app/models/form/lettings/questions/referral_prp.rb | 2 ++ .../form/lettings/questions/referral_supported_housing.rb | 1 + .../form/lettings/questions/referral_supported_housing_prp.rb | 1 + app/models/form/lettings/questions/referral_type.rb | 2 ++ 20 files changed, 32 insertions(+) diff --git a/app/models/form/lettings/pages/referral_direct.rb b/app/models/form/lettings/pages/referral_direct.rb index df05aa997..79a8d4044 100644 --- a/app/models/form/lettings/pages/referral_direct.rb +++ b/app/models/form/lettings/pages/referral_direct.rb @@ -1,3 +1,5 @@ +# added in 2025 +# removed in 2026 class Form::Lettings::Pages::ReferralDirect < ::Form::Page def initialize(id, hsh, subsection) super diff --git a/app/models/form/lettings/pages/referral_general_needs.rb b/app/models/form/lettings/pages/referral_general_needs.rb index 5522d1f23..a55ed5553 100644 --- a/app/models/form/lettings/pages/referral_general_needs.rb +++ b/app/models/form/lettings/pages/referral_general_needs.rb @@ -1,3 +1,4 @@ +# removed in 2025 class Form::Lettings::Pages::ReferralGeneralNeeds < ::Form::Page def initialize(id, hsh, subsection) super diff --git a/app/models/form/lettings/pages/referral_general_needs_prp.rb b/app/models/form/lettings/pages/referral_general_needs_prp.rb index e3206ebdb..99e033ab6 100644 --- a/app/models/form/lettings/pages/referral_general_needs_prp.rb +++ b/app/models/form/lettings/pages/referral_general_needs_prp.rb @@ -1,3 +1,4 @@ +# removed in 2025 class Form::Lettings::Pages::ReferralGeneralNeedsPrp < ::Form::Page def initialize(id, hsh, subsection) super diff --git a/app/models/form/lettings/pages/referral_hsc.rb b/app/models/form/lettings/pages/referral_hsc.rb index 596852947..c7c16ba58 100644 --- a/app/models/form/lettings/pages/referral_hsc.rb +++ b/app/models/form/lettings/pages/referral_hsc.rb @@ -1,3 +1,5 @@ +# added in 2025 +# removed in 2026 class Form::Lettings::Pages::ReferralHsc < ::Form::Page def initialize(id, hsh, subsection) super diff --git a/app/models/form/lettings/pages/referral_justice.rb b/app/models/form/lettings/pages/referral_justice.rb index fa10bb727..564f9f98c 100644 --- a/app/models/form/lettings/pages/referral_justice.rb +++ b/app/models/form/lettings/pages/referral_justice.rb @@ -1,3 +1,5 @@ +# added in 2025 +# removed in 2026 class Form::Lettings::Pages::ReferralJustice < ::Form::Page def initialize(id, hsh, subsection) super diff --git a/app/models/form/lettings/pages/referral_la.rb b/app/models/form/lettings/pages/referral_la.rb index 3f04f3aaf..5e3adbf62 100644 --- a/app/models/form/lettings/pages/referral_la.rb +++ b/app/models/form/lettings/pages/referral_la.rb @@ -1,3 +1,5 @@ +# added in 2025 +# removed in 2026 class Form::Lettings::Pages::ReferralLa < ::Form::Page def initialize(id, hsh, subsection) super diff --git a/app/models/form/lettings/pages/referral_prp.rb b/app/models/form/lettings/pages/referral_prp.rb index 8d25edc44..492941e52 100644 --- a/app/models/form/lettings/pages/referral_prp.rb +++ b/app/models/form/lettings/pages/referral_prp.rb @@ -1,3 +1,5 @@ +# added in 2025 +# removed in 2026 class Form::Lettings::Pages::ReferralPrp < ::Form::Page def initialize(id, hsh, subsection) super diff --git a/app/models/form/lettings/pages/referral_supported_housing.rb b/app/models/form/lettings/pages/referral_supported_housing.rb index a3e915e26..b20a0a825 100644 --- a/app/models/form/lettings/pages/referral_supported_housing.rb +++ b/app/models/form/lettings/pages/referral_supported_housing.rb @@ -1,3 +1,4 @@ +# removed in 2025 class Form::Lettings::Pages::ReferralSupportedHousing < ::Form::Page def initialize(id, hsh, subsection) super diff --git a/app/models/form/lettings/pages/referral_supported_housing_prp.rb b/app/models/form/lettings/pages/referral_supported_housing_prp.rb index 66b6f370e..0f546b400 100644 --- a/app/models/form/lettings/pages/referral_supported_housing_prp.rb +++ b/app/models/form/lettings/pages/referral_supported_housing_prp.rb @@ -1,3 +1,4 @@ +# removed in 2025 class Form::Lettings::Pages::ReferralSupportedHousingPrp < ::Form::Page def initialize(id, hsh, subsection) super diff --git a/app/models/form/lettings/pages/referral_type.rb b/app/models/form/lettings/pages/referral_type.rb index 3cca2ca2b..08b19d14c 100644 --- a/app/models/form/lettings/pages/referral_type.rb +++ b/app/models/form/lettings/pages/referral_type.rb @@ -1,3 +1,5 @@ +# added in 2025 +# removed in 2026 class Form::Lettings::Pages::ReferralType < ::Form::Page def initialize(id, hsh, subsection) super diff --git a/app/models/form/lettings/questions/referral_direct.rb b/app/models/form/lettings/questions/referral_direct.rb index ddadcc8b7..d79065df7 100644 --- a/app/models/form/lettings/questions/referral_direct.rb +++ b/app/models/form/lettings/questions/referral_direct.rb @@ -1,3 +1,5 @@ +# added in 2025 +# removed in 2026 class Form::Lettings::Questions::ReferralDirect < ::Form::Question def initialize(id, hsh, page) super diff --git a/app/models/form/lettings/questions/referral_general_needs.rb b/app/models/form/lettings/questions/referral_general_needs.rb index 6efdfc1f0..6c9f159e4 100644 --- a/app/models/form/lettings/questions/referral_general_needs.rb +++ b/app/models/form/lettings/questions/referral_general_needs.rb @@ -1,3 +1,4 @@ +# removed in 2025 class Form::Lettings::Questions::ReferralGeneralNeeds < ::Form::Question def initialize(id, hsh, page) super diff --git a/app/models/form/lettings/questions/referral_general_needs_prp.rb b/app/models/form/lettings/questions/referral_general_needs_prp.rb index afd26117b..492f32c8f 100644 --- a/app/models/form/lettings/questions/referral_general_needs_prp.rb +++ b/app/models/form/lettings/questions/referral_general_needs_prp.rb @@ -1,3 +1,4 @@ +# removed in 2025 class Form::Lettings::Questions::ReferralGeneralNeedsPrp < ::Form::Question def initialize(id, hsh, page) super diff --git a/app/models/form/lettings/questions/referral_hsc.rb b/app/models/form/lettings/questions/referral_hsc.rb index a5b9c32f0..f049b6359 100644 --- a/app/models/form/lettings/questions/referral_hsc.rb +++ b/app/models/form/lettings/questions/referral_hsc.rb @@ -1,3 +1,5 @@ +# added in 2025 +# removed in 2026 class Form::Lettings::Questions::ReferralHsc < ::Form::Question def initialize(id, hsh, page) super diff --git a/app/models/form/lettings/questions/referral_justice.rb b/app/models/form/lettings/questions/referral_justice.rb index 0e02e0c42..f222f5fe8 100644 --- a/app/models/form/lettings/questions/referral_justice.rb +++ b/app/models/form/lettings/questions/referral_justice.rb @@ -1,3 +1,5 @@ +# added in 2025 +# removed in 2026 class Form::Lettings::Questions::ReferralJustice < ::Form::Question def initialize(id, hsh, page) super diff --git a/app/models/form/lettings/questions/referral_la.rb b/app/models/form/lettings/questions/referral_la.rb index 7a654df88..352457964 100644 --- a/app/models/form/lettings/questions/referral_la.rb +++ b/app/models/form/lettings/questions/referral_la.rb @@ -1,3 +1,5 @@ +# added in 2025 +# removed in 2026 class Form::Lettings::Questions::ReferralLa < ::Form::Question def initialize(id, hsh, page) super diff --git a/app/models/form/lettings/questions/referral_prp.rb b/app/models/form/lettings/questions/referral_prp.rb index 44799bb8c..825967a3f 100644 --- a/app/models/form/lettings/questions/referral_prp.rb +++ b/app/models/form/lettings/questions/referral_prp.rb @@ -1,3 +1,5 @@ +# added in 2025 +# removed in 2026 class Form::Lettings::Questions::ReferralPrp < ::Form::Question def initialize(id, hsh, page) super diff --git a/app/models/form/lettings/questions/referral_supported_housing.rb b/app/models/form/lettings/questions/referral_supported_housing.rb index d8d05fade..3a7ba04ac 100644 --- a/app/models/form/lettings/questions/referral_supported_housing.rb +++ b/app/models/form/lettings/questions/referral_supported_housing.rb @@ -1,3 +1,4 @@ +# removed in 2025 class Form::Lettings::Questions::ReferralSupportedHousing < ::Form::Question def initialize(id, hsh, page) super diff --git a/app/models/form/lettings/questions/referral_supported_housing_prp.rb b/app/models/form/lettings/questions/referral_supported_housing_prp.rb index 75cc218fe..d0236f4af 100644 --- a/app/models/form/lettings/questions/referral_supported_housing_prp.rb +++ b/app/models/form/lettings/questions/referral_supported_housing_prp.rb @@ -1,3 +1,4 @@ +# removed in 2025 class Form::Lettings::Questions::ReferralSupportedHousingPrp < ::Form::Question def initialize(id, hsh, page) super diff --git a/app/models/form/lettings/questions/referral_type.rb b/app/models/form/lettings/questions/referral_type.rb index 5ff0f411e..b4bb87515 100644 --- a/app/models/form/lettings/questions/referral_type.rb +++ b/app/models/form/lettings/questions/referral_type.rb @@ -1,3 +1,5 @@ +# added in 2025 +# removed in 2026 class Form::Lettings::Questions::ReferralType < ::Form::Question def initialize(id, hsh, page) super From bf2c647b1843a7026bb3cd6795d863f02632465f Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 26 Jan 2026 11:12:00 +0000 Subject: [PATCH 02/40] CLDC-4151: Add new cols to lettings log model --- db/migrate/20260123150201_add2026_referral_fields.rb | 9 +++++++++ db/schema.rb | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20260123150201_add2026_referral_fields.rb diff --git a/db/migrate/20260123150201_add2026_referral_fields.rb b/db/migrate/20260123150201_add2026_referral_fields.rb new file mode 100644 index 000000000..32279fad5 --- /dev/null +++ b/db/migrate/20260123150201_add2026_referral_fields.rb @@ -0,0 +1,9 @@ +class Add2026ReferralFields < ActiveRecord::Migration[7.2] + def change + change_table :lettings_logs, bulk: true do |t| + t.integer :referral_register + t.integer :referral_noms + t.integer :referral_org + end + end +end diff --git a/db/schema.rb b/db/schema.rb index f781ed899..9eebf92a7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2025_04_16_111741) do +ActiveRecord::Schema[7.2].define(version: 2026_01_23_150201) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -375,6 +375,9 @@ ActiveRecord::Schema[7.2].define(version: 2025_04_16_111741) do t.bigint "created_by_id" t.boolean "manual_address_entry_selected", default: false t.integer "referral_type" + t.integer "referral_register" + t.integer "referral_noms" + t.integer "referral_org" t.index ["assigned_to_id"], name: "index_lettings_logs_on_assigned_to_id" t.index ["bulk_upload_id"], name: "index_lettings_logs_on_bulk_upload_id" t.index ["created_by_id"], name: "index_lettings_logs_on_created_by_id" From e24a77b9ca9221929a69cc14e9f7f89dcc86ca45 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 26 Jan 2026 12:23:46 +0000 Subject: [PATCH 03/40] CLDC-4151: Add new referrals questions --- .../form/lettings/pages/referral_noms.rb | 11 +++++++++ .../form/lettings/pages/referral_org.rb | 11 +++++++++ .../form/lettings/pages/referral_register.rb | 11 +++++++++ .../form/lettings/questions/referral_noms.rb | 24 +++++++++++++++++++ .../form/lettings/questions/referral_org.rb | 24 +++++++++++++++++++ .../lettings/questions/referral_register.rb | 24 +++++++++++++++++++ .../subsections/household_situation.rb | 8 ++++++- .../2026/lettings/household_situation.en.yml | 24 +++---------------- 8 files changed, 115 insertions(+), 22 deletions(-) create mode 100644 app/models/form/lettings/pages/referral_noms.rb create mode 100644 app/models/form/lettings/pages/referral_org.rb create mode 100644 app/models/form/lettings/pages/referral_register.rb create mode 100644 app/models/form/lettings/questions/referral_noms.rb create mode 100644 app/models/form/lettings/questions/referral_org.rb create mode 100644 app/models/form/lettings/questions/referral_register.rb diff --git a/app/models/form/lettings/pages/referral_noms.rb b/app/models/form/lettings/pages/referral_noms.rb new file mode 100644 index 000000000..af1590676 --- /dev/null +++ b/app/models/form/lettings/pages/referral_noms.rb @@ -0,0 +1,11 @@ +# added in 2026 +class Form::Lettings::Pages::ReferralNoms < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "referral_noms" + end + + def questions + @questions ||= [Form::Lettings::Questions::ReferralNoms.new(nil, nil, self)] + end +end diff --git a/app/models/form/lettings/pages/referral_org.rb b/app/models/form/lettings/pages/referral_org.rb new file mode 100644 index 000000000..8d3aa2b7d --- /dev/null +++ b/app/models/form/lettings/pages/referral_org.rb @@ -0,0 +1,11 @@ +# added in 2026 +class Form::Lettings::Pages::ReferralOrg < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "referral_org" + end + + def questions + @questions ||= [Form::Lettings::Questions::ReferralOrg.new(nil, nil, self)] + end +end diff --git a/app/models/form/lettings/pages/referral_register.rb b/app/models/form/lettings/pages/referral_register.rb new file mode 100644 index 000000000..e835b3c1c --- /dev/null +++ b/app/models/form/lettings/pages/referral_register.rb @@ -0,0 +1,11 @@ +# added in 2026 +class Form::Lettings::Pages::ReferralRegister < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "referral_register" + end + + def questions + @questions ||= [Form::Lettings::Questions::ReferralRegister.new(nil, nil, self)] + end +end diff --git a/app/models/form/lettings/questions/referral_noms.rb b/app/models/form/lettings/questions/referral_noms.rb new file mode 100644 index 000000000..00d2581dc --- /dev/null +++ b/app/models/form/lettings/questions/referral_noms.rb @@ -0,0 +1,24 @@ +# added in 2026 +class Form::Lettings::Questions::ReferralNoms < ::Form::Question + def initialize(id, hsh, page) + super + @id = "referral_noms" + @copy_key = "lettings.household_situation.referral.noms" + @type = "radio" + @check_answers_card_number = 0 + @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + end + + def answer_options + { + "1" => { + "value" => "Answer A", + }, + "2" => { + "value" => "Answer B", + }, + }.freeze + end + + QUESTION_NUMBER_FROM_YEAR = { 2025 => 84 }.freeze +end diff --git a/app/models/form/lettings/questions/referral_org.rb b/app/models/form/lettings/questions/referral_org.rb new file mode 100644 index 000000000..5425fce56 --- /dev/null +++ b/app/models/form/lettings/questions/referral_org.rb @@ -0,0 +1,24 @@ +# added in 2026 +class Form::Lettings::Questions::ReferralOrg < ::Form::Question + def initialize(id, hsh, page) + super + @id = "referral_org" + @copy_key = "lettings.household_situation.referral.org" + @type = "radio" + @check_answers_card_number = 0 + @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + end + + def answer_options + { + "1" => { + "value" => "Answer A", + }, + "2" => { + "value" => "Answer B", + }, + }.freeze + end + + QUESTION_NUMBER_FROM_YEAR = { 2025 => 84 }.freeze +end diff --git a/app/models/form/lettings/questions/referral_register.rb b/app/models/form/lettings/questions/referral_register.rb new file mode 100644 index 000000000..4769fcdbe --- /dev/null +++ b/app/models/form/lettings/questions/referral_register.rb @@ -0,0 +1,24 @@ +# added in 2026 +class Form::Lettings::Questions::ReferralRegister < ::Form::Question + def initialize(id, hsh, page) + super + @id = "referral_register" + @copy_key = "lettings.household_situation.referral.register" + @type = "radio" + @check_answers_card_number = 0 + @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + end + + def answer_options + { + "1" => { + "value" => "Answer A", + }, + "2" => { + "value" => "Answer B", + }, + }.freeze + end + + QUESTION_NUMBER_FROM_YEAR = { 2025 => 84 }.freeze +end diff --git a/app/models/form/lettings/subsections/household_situation.rb b/app/models/form/lettings/subsections/household_situation.rb index 8bf747f01..3f4133233 100644 --- a/app/models/form/lettings/subsections/household_situation.rb +++ b/app/models/form/lettings/subsections/household_situation.rb @@ -27,7 +27,13 @@ class Form::Lettings::Subsections::HouseholdSituation < ::Form::Subsection end def referral_questions - if form.start_year_2025_or_later? + if form.start_year_2026_or_later? + [ + Form::Lettings::Pages::ReferralRegister.new(nil, nil, self), + Form::Lettings::Pages::ReferralNoms.new(nil, nil, self), + Form::Lettings::Pages::ReferralOrg.new(nil, nil, self), + ] + elsif form.start_year_2025_or_later? [ Form::Lettings::Pages::ReferralType.new(nil, nil, self), Form::Lettings::Pages::ReferralDirect.new(nil, nil, self), diff --git a/config/locales/forms/2026/lettings/household_situation.en.yml b/config/locales/forms/2026/lettings/household_situation.en.yml index 1d3e135c7..279ab53c2 100644 --- a/config/locales/forms/2026/lettings/household_situation.en.yml +++ b/config/locales/forms/2026/lettings/household_situation.en.yml @@ -112,37 +112,19 @@ en: question_text: "How was this letting allocated?" referral: - type: + register: page_header: "" check_answer_label: "Source of referral for letting" check_answer_prompt: "Select source of referral" hint_text: "" question_text: "What was the source of referral for this letting?" - direct: + noms: page_header: "" check_answer_label: "Source of referral for letting" check_answer_prompt: "Select source of referral" hint_text: "" question_text: "What was the source of referral for this letting?" - la: - page_header: "" - check_answer_label: "Source of referral for letting" - check_answer_prompt: "Select source of referral" - hint_text: "" - question_text: "What was the source of referral for this letting?" - prp: - page_header: "" - check_answer_label: "Source of referral for letting" - check_answer_prompt: "Select source of referral" - hint_text: "" - question_text: "What was the source of referral for this letting?" - hsc: - page_header: "" - check_answer_label: "Source of referral for letting" - check_answer_prompt: "Select source of referral" - hint_text: "" - question_text: "What was the source of referral for this letting?" - justice: + org: page_header: "" check_answer_label: "Source of referral for letting" check_answer_prompt: "Select source of referral" From 4e6a5bec4aab2fa4d81c13c368ce2fea127d354d Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 26 Jan 2026 13:50:17 +0000 Subject: [PATCH 04/40] CLDC-4151: Remove referral value check --- app/models/form/lettings/pages/referral_value_check.rb | 1 + .../form/lettings/subsections/household_situation.rb | 3 ++- .../locales/forms/2026/lettings/soft_validations.en.yml | 9 --------- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/app/models/form/lettings/pages/referral_value_check.rb b/app/models/form/lettings/pages/referral_value_check.rb index aa3a34200..265ba5312 100644 --- a/app/models/form/lettings/pages/referral_value_check.rb +++ b/app/models/form/lettings/pages/referral_value_check.rb @@ -1,3 +1,4 @@ +# removed in 2026 class Form::Lettings::Pages::ReferralValueCheck < ::Form::Page def initialize(id, hsh, subsection) super diff --git a/app/models/form/lettings/subsections/household_situation.rb b/app/models/form/lettings/subsections/household_situation.rb index 3f4133233..948ab7611 100644 --- a/app/models/form/lettings/subsections/household_situation.rb +++ b/app/models/form/lettings/subsections/household_situation.rb @@ -22,7 +22,6 @@ class Form::Lettings::Subsections::HouseholdSituation < ::Form::Subsection Form::Lettings::Pages::ReasonablePreferenceReason.new(nil, nil, self), Form::Lettings::Pages::AllocationSystem.new("allocation_system", nil, self), referral_questions, - Form::Lettings::Pages::ReferralValueCheck.new(nil, nil, self), ].flatten.compact end @@ -41,6 +40,7 @@ class Form::Lettings::Subsections::HouseholdSituation < ::Form::Subsection Form::Lettings::Pages::ReferralPrp.new(nil, nil, self), Form::Lettings::Pages::ReferralHsc.new(nil, nil, self), Form::Lettings::Pages::ReferralJustice.new(nil, nil, self), + Form::Lettings::Pages::ReferralValueCheck.new(nil, nil, self), ] else [ @@ -48,6 +48,7 @@ class Form::Lettings::Subsections::HouseholdSituation < ::Form::Subsection Form::Lettings::Pages::ReferralGeneralNeedsPrp.new(nil, nil, self), Form::Lettings::Pages::ReferralSupportedHousing.new(nil, nil, self), Form::Lettings::Pages::ReferralSupportedHousingPrp.new(nil, nil, self), + Form::Lettings::Pages::ReferralValueCheck.new(nil, nil, self), ] end end diff --git a/config/locales/forms/2026/lettings/soft_validations.en.yml b/config/locales/forms/2026/lettings/soft_validations.en.yml index 638bb4403..bc94dbcdf 100644 --- a/config/locales/forms/2026/lettings/soft_validations.en.yml +++ b/config/locales/forms/2026/lettings/soft_validations.en.yml @@ -57,15 +57,6 @@ en: title_text: "You told us that the tenant’s main reason for leaving their last settled home was %{reasonother}." informative_text: "The reason you have entered looks very similar to one of the existing response categories. Please check the categories and select the appropriate one. If the existing categories are not suitable, please confirm here to move onto the next question." - referral_value_check: - page_header: "" - check_answer_label: "Referral confirmation" - check_answer_prompt: "Confirm referral type" - hint_text: "" - question_text: "Are you sure?" - title_text: "Are you sure?" - informative_text: "This is a general needs log, and this referral type is for supported housing." - net_income_value_check: page_header: "" check_answer_label: "Net income confirmation" From 23aa1efb4e0df10602b9ec7287bdfb6778c66ac6 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 26 Jan 2026 16:27:17 +0000 Subject: [PATCH 05/40] CLDC-4151: Add new fields to bulk upload use new fields as presented in bulk upload --- .../bulk_upload/lettings_log_to_csv.rb | 154 +++++++++++++++++- app/models/organisation.rb | 1 + .../lettings/year2026/csv_parser.rb | 6 +- .../lettings/year2026/row_parser.rb | 52 ++++-- 4 files changed, 190 insertions(+), 23 deletions(-) diff --git a/app/helpers/bulk_upload/lettings_log_to_csv.rb b/app/helpers/bulk_upload/lettings_log_to_csv.rb index 891331e40..5ddd8d6a9 100644 --- a/app/helpers/bulk_upload/lettings_log_to_csv.rb +++ b/app/helpers/bulk_upload/lettings_log_to_csv.rb @@ -93,12 +93,162 @@ class BulkUpload::LettingsLogToCsv def default_2026_field_numbers # TODO: CLDC-4162 Replace with actual field numbers when 2026 format is known - (1..129).to_a + (1..132).to_a end def to_2026_row # TODO: CLDC-4162: Implement when 2026 format is known - to_2025_row + owning_organisation = if overrides[:organisation_id] + then Organisation.find(overrides[:organisation_id]) + else + log.owning_organisation + end + [ + owning_organisation&.id, # 1 + overrides[:managing_organisation_id] || log.managing_organisation&.old_visible_id, + log.assigned_to&.email, + log.needstype, + log.scheme&.id ? "S#{log.scheme&.id}" : "", + log.location&.id, + renewal, + log.startdate&.day, + log.startdate&.month, + log.startdate&.strftime("%y"), # 10 + + rent_type, + log.irproduct_other, + log.tenancycode, + log.propcode, + log.declaration, + log.rsnvac, + log.unitletas, + log.uprn, + log.address_line1&.tr(",", " "), + log.address_line2&.tr(",", " "), # 20 + + log.town_or_city&.tr(",", " "), + log.county&.tr(",", " "), + ((log.postcode_full || "").split(" ") || [""]).first, + ((log.postcode_full || "").split(" ") || [""]).last, + log.la, + log.unittype_gn, + log.builtype, + log.wchair, + log.beds, + log.voiddate&.day, # 30 + + log.voiddate&.month, + log.voiddate&.strftime("%y"), + log.mrcdate&.day, + log.mrcdate&.month, + log.mrcdate&.strftime("%y"), + log.sheltered, + log.joint, + log.startertenancy, + log.tenancy, + log.tenancyother, # 40 + + log.tenancylength, + log.age1 || overrides[:age1], + log.sex1, + log.ethnic, + log.nationality_all_group, + log.ecstat1, + relat_number(log.relat2), + log.age2 || overrides[:age2], + log.sex2, + log.ecstat2, # 50 + + relat_number(log.relat3), + log.age3 || overrides[:age3], + log.sex3, + log.ecstat3, + relat_number(log.relat4), + log.age4 || overrides[:age4], + log.sex4, + log.ecstat4, + relat_number(log.relat5), + log.age5 || overrides[:age5], # 60 + + log.sex5, + log.ecstat5, + relat_number(log.relat6), + log.age6 || overrides[:age6], + log.sex6, + log.ecstat6, + relat_number(log.relat7), + log.age7 || overrides[:age7], + log.sex7, + log.ecstat7, # 70 + + relat_number(log.relat8), + log.age8 || overrides[:age8], + log.sex8, + log.ecstat8, + log.armedforces, + log.leftreg, + log.reservist, + log.preg_occ, + log.housingneeds_a, + log.housingneeds_b, # 80 + + log.housingneeds_c, + log.housingneeds_f, + log.housingneeds_g, + log.housingneeds_h, + overrides[:illness] || log.illness, + log.illness_type_1, + log.illness_type_2, + log.illness_type_3, + log.illness_type_4, + log.illness_type_5, # 90 + + log.illness_type_6, + log.illness_type_7, + log.illness_type_8, + log.illness_type_9, + log.illness_type_10, + log.layear, + log.waityear, + log.reason, + log.reasonother, + log.prevten, # 100 + + homeless, + previous_postcode_known, + ((log.ppostcode_full || "").split(" ") || [""]).first, + ((log.ppostcode_full || "").split(" ") || [""]).last, + log.prevloc, + log.reasonpref, + log.rp_homeless, + log.rp_insan_unsat, + log.rp_medwel, + log.rp_hardship, # 110 + + log.rp_dontknow, + cbl, + chr, + cap, + accessible_register, + owning_organisation&.la? ? log.referral_register : nil, + net_income_known, + log.incfreq, + log.earnings, + log.hb, # 120 + + log.benefits, + log.household_charge, + log.period, + log.brent, + log.scharge, + log.pscharge, + log.supcharg, + log.hbrentshortfall, + log.tshortfall, + owning_organisation&.prp? ? log.referral_register : nil, + log.referral_noms, + log.referral_org, # 132 + ] end def to_2025_row diff --git a/app/models/organisation.rb b/app/models/organisation.rb index 94390c3f9..b33d896c4 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -74,6 +74,7 @@ class Organisation < ApplicationRecord before_save :clear_group_member_fields_if_not_group_member alias_method :la?, :LA? + alias_method :prp?, :PRP? validates :name, presence: { message: I18n.t("validations.organisation.name_missing") } validates :name, uniqueness: { case_sensitive: false, message: I18n.t("validations.organisation.name_not_unique") } diff --git a/app/services/bulk_upload/lettings/year2026/csv_parser.rb b/app/services/bulk_upload/lettings/year2026/csv_parser.rb index 2484cbc4b..d74b191e5 100644 --- a/app/services/bulk_upload/lettings/year2026/csv_parser.rb +++ b/app/services/bulk_upload/lettings/year2026/csv_parser.rb @@ -4,8 +4,8 @@ class BulkUpload::Lettings::Year2026::CsvParser include CollectionTimeHelper # TODO: CLDC-4162: Update when 2026 format is known - FIELDS = 129 - MAX_COLUMNS = 130 + FIELDS = 132 + MAX_COLUMNS = 133 FORM_YEAR = 2026 attr_reader :path @@ -28,7 +28,7 @@ class BulkUpload::Lettings::Year2026::CsvParser def cols # TODO: CLDC-4162: Update when 2026 format is known - @cols ||= ("A".."DZ").to_a + @cols ||= ("A".."EC").to_a end def row_parsers diff --git a/app/services/bulk_upload/lettings/year2026/row_parser.rb b/app/services/bulk_upload/lettings/year2026/row_parser.rb index 84f6f57d9..6d07cca64 100644 --- a/app/services/bulk_upload/lettings/year2026/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2026/row_parser.rb @@ -120,7 +120,7 @@ class BulkUpload::Lettings::Year2026::RowParser field_113: "Was the letting made under the Common Allocation Policy (CAP)?", field_114: "Was the letting made under the Common Housing Register (CHR)?", field_115: "Was the letting made under the Accessible Register?", - field_116: "What was the source of referral for this letting?", + field_116: "What was the source of referral for this letting? - LA properties", field_117: "Do you know the household’s combined total income after tax?", field_118: "How often does the household receive income?", field_119: "How much income does the household have in total?", @@ -134,6 +134,9 @@ class BulkUpload::Lettings::Year2026::RowParser field_127: "What is the support charge?", field_128: "After the household has received any housing-related benefits, will they still need to pay for rent and charges?", field_129: "What do you expect the outstanding amount to be?", + field_130: "What was the source of referral for this letting? - PRP properties part 1", + field_131: "What was the source of referral for this letting? - PRP properties part 2", + field_132: "What was the source of referral for this letting? - PRP properties part 3", }.freeze RENT_TYPE_BU_MAPPING = { @@ -282,6 +285,9 @@ class BulkUpload::Lettings::Year2026::RowParser attribute :field_127, :decimal attribute :field_128, :integer attribute :field_129, :decimal + attribute :field_130, :integer + attribute :field_131, :integer + attribute :field_132, :integer validate :validate_valid_radio_option, on: :before_log @@ -1101,8 +1107,9 @@ private accessible_register: %i[field_115], letting_allocation: %i[field_112 field_113 field_114 field_115], - referral_type: %i[field_116], - referral: %i[field_116], + referral_register: %i[field_116 field_130], + referral_noms: %i[field_131], + referral_org: %i[field_132], net_income_known: %i[field_117], incfreq: %i[field_118], @@ -1287,8 +1294,9 @@ private attributes["accessible_register"] = accessible_register attributes["letting_allocation_unknown"] = letting_allocation_unknown - attributes["referral_type"] = referral_type - attributes["referral"] = field_116 + attributes["referral_register"] = referral_register + attributes["referral_noms"] = referral_noms + attributes["referral_org"] = referral_org attributes["net_income_known"] = net_income_known attributes["earnings"] = earnings @@ -1684,21 +1692,29 @@ private false end - def referral_type - mapping = { - 1 => [20, 2, 8], - 2 => [21, 3, 4, 22], - 3 => [1, 10, 23], - 4 => [15, 9, 14, 24, 17], - 5 => [18, 19], - 6 => [7], - 7 => [16], - } + def referral_register + return unless owning_organisation - mapping.each do |key, values| - return key if values.include?(field_116) + if owning_organisation.la? + field_116 + else + field_130 end + end - 0 + def referral_noms + return unless owning_organisation + + if owning_organisation.la? + field_131 + end + end + + def referral_org + return unless owning_organisation + + if owning_organisation.la? + field_132 + end end end From 45aa7214384e69dd52c63681d3efd3f2e105e48e Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 26 Jan 2026 14:09:53 +0000 Subject: [PATCH 06/40] CLDC-4151: Update tests also add bulk upload file to test with --- .../files/2026_27_lettings_bulk_upload.csv | 18 +++++----- .../lettings_download_26_27.csv | 3 ++ .../tasks/log_variable_definitions_spec.rb | 4 +-- .../subsections/household_situation_spec.rb | 34 ++++++++++--------- .../bulk_upload/lettings/validator_spec.rb | 6 ++-- .../lettings/year2026/csv_parser_spec.rb | 2 +- .../lettings/year2026/row_parser_spec.rb | 5 ++- .../csv/lettings_log_csv_service_spec.rb | 4 ++- 8 files changed, 43 insertions(+), 33 deletions(-) create mode 100644 spec/fixtures/variable_definitions/lettings_download_26_27.csv diff --git a/spec/fixtures/files/2026_27_lettings_bulk_upload.csv b/spec/fixtures/files/2026_27_lettings_bulk_upload.csv index a3ee5cfc1..e3d774068 100644 --- a/spec/fixtures/files/2026_27_lettings_bulk_upload.csv +++ b/spec/fixtures/files/2026_27_lettings_bulk_upload.csv @@ -1,4 +1,4 @@ -Section,Setting up this lettings log,,,,,,,,,,,,,,,Property information,,,,,,,,,,,,,,,,,,,,,Tenancy information,,,,,Household characteristics,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Household needs,,,,,,,,,,,,,,,,,,,,,Household situation,,,,,,,,,,,,,,,,,,,,,"Income, benefits and outgoings",,,,,,,,,,,, +Section,Setting up this lettings log,,,,,,,,,,,,,,,Property information,,,,,,,,,,,,,,,,,,,,,Tenancy information,,,,,Household characteristics,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Household needs,,,,,,,,,,,,,,,,,,,,,Household situation,,,,,,,,,,,,,,,,,,,,,"Income, benefits and outgoings",,,,,,,,,,,,,,, Question,Which organisation owns this property?,Which organisation manages this letting?,What is the CORE username of the account this letting log should be assigned to? ,What is the needs type?,What scheme does this letting belong to?,Which location is this letting for?,Is this letting a renewal of social housing to the same tenant in the same property?,What is the tenancy start date? - day DD,What is the tenancy start date? - month MM,What is the tenancy start date? - year YY,What is the rent type?,Which 'Other' type of Intermediate Rent is this letting?,What is the tenant code?,What is the property reference?,Has the tenant seen or been given access to the MHCLG privacy notice?,What is the reason for the property being vacant?,What type was the property most recently let as?,"If known, provide this property’s UPRN",Address Line 1,Address Line 2,Town or city,County,Part 1 of the property's postcode,Part 2 of the property's postcode,What is the property's local authority?,What type of unit is the property?,Which type of building is the property?,Is the property built or adapted to wheelchair-user standards?,How many bedrooms does the property have?,What is the void date? - day DD,What is the void date? - month MM,What is the void date? - year YY,What date were any major repairs completed on? - day DD,What date were any major repairs completed on? - month MM,What date were any major repairs completed on? - year YY,Is this property older people's housing?,Is this a joint tenancy?,Is this a starter tenancy?,What is the type of tenancy?,"If 'Other', what is the type of tenancy?",What is the length of the fixed-term tenancy to the nearest year?,What is the lead tenant’s age?,Which of these best describes the lead tenant’s gender identity? ,Which of these best describes the lead tenant's ethnic background?,What is the lead tenant’s nationality?,Which of these best describes the lead tenant’s working situation?,Is person 2 the partner of the lead tenant?,What is person 2's age?,Which of these best describes person 2's gender identity?,Which of these best describes person 2's working situation?,Is person 3 the partner of the lead tenant?,What is person 3's age?,Which of these best describes person 3's gender identity?,Which of these best describes person 3's working situation?,Is person 4 the partner of the lead tenant?,What is person 4's age?,Which of these best describes person 4's gender identity?,Which of these best describes person 4's working situation?,Is person 5 the partner of the lead tenant?,What is person 5's age?,Which of these best describes person 5's gender identity?,Which of these best describes person 5's working situation?,Is person 6 the partner of the lead tenant?,What is person 6's age?,Which of these best describes person 6's gender identity?,Which of these best describes person 6's working situation?,Is person 7 the partner of the lead tenant?,What is person 7's age?,Which of these best describes person 7's gender identity?,Which of these best describes person 7's working situation?,Is person 8 the partner of the lead tenant?,What is person 8's age?,Which of these best describes person 8's gender identity?,Which of these best describes person 8's working situation?,Does anybody in the household have links to the UK armed forces?,Is this person still serving in the UK armed forces?,Was this person seriously injured or ill as a result of serving in the UK armed forces?,Is anybody in the household pregnant?,"Disabled access needs a) Fully wheelchair-accessible housing","Disabled access needs @@ -29,7 +29,7 @@ Common Allocations Policy (CAP)","How was this letting allocated? Common Housing Register (CHR)","How was this letting allocated? -Accessible Housing Register",What was the source of referral for this letting?,Do you know the household's combined total income after tax?,How often does the household receive income?,How much income does the household have in total?,Is the tenant likely to be receiving any of these housing-related benefits?,"How much of the household's income is from Universal Credit, state pensions or benefits?",Does the household pay rent or other charges for the accommodation?,How often does the household pay rent and other charges?,What is the basic rent?,What is the service charge?,What is the personal service charge?,What is the support charge?,"After the household has received any housing-related benefits, will they still need to pay for rent and charges?",What do you expect the outstanding amount to be? +Accessible Housing Register",What was the source of referral for this letting?,Do you know the household's combined total income after tax?,How often does the household receive income?,How much income does the household have in total?,Is the tenant likely to be receiving any of these housing-related benefits?,"How much of the household's income is from Universal Credit, state pensions or benefits?",Does the household pay rent or other charges for the accommodation?,How often does the household pay rent and other charges?,What is the basic rent?,What is the service charge?,What is the personal service charge?,What is the support charge?,"After the household has received any housing-related benefits, will they still need to pay for rent and charges?",What do you expect the outstanding amount to be?,,, Additional info,"You can find the org ID on the CORE service under 'Stock owners' or, if your organisation is the stock owner, under 'About your organisation'","You can find the org ID on the CORE service under 'Managing agents' or, if your organisation is the managing agent, under 'About your organisation'","If left empty, the letting log will be assigned to the account used to upload the log.","General needs housing includes both self-contained and shared housing without support or specific adaptations. Supported housing includes direct access hostels, group homes, residential care and nursing homes.","Scheme code. Include the 'S' at the beginning if it has one. You can find the scheme code on the CORE service under 'Schemes', either by searching for the specific scheme or downloading a csv.","Location code. @@ -45,7 +45,7 @@ The UPRN may not be the same as the property reference assigned by your organisa Extra care housing is for tenants with medium to high care and support needs, often with 24 hour access to support staff provided by an agency registered with the Care Quality Commission.",This is where two or more people are named on the tenancy agreement.,"If the tenancy has an ‘introductory period’ answer ‘yes’. You should submit a CORE log at the beginning of the starter tenancy or introductory period, with the best information you have at the time. You do not need to submit a log when a tenant later rolls onto the main tenancy.",This is about the main tenancy after any starter or introductory period. See specification for definitions.,,Do not include the starter or introductory period. The minimum period is 2 years for social or affordable rent general needs logs. You do not need to submit CORE logs for these types of tenancies if they are shorter than 2 years.,"This is the household member who does the most paid work. If several people do the same amount of paid work, it's the oldest household member.",This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,"If the lead tenant is a dual national of the United Kingdom and another country, enter United Kingdom. If they are a dual national of two other countries, the tenant should decide which country to enter.","This is the household member who does the most paid work. If several people do the same amount of paid work, it's the oldest household member.",,Answer 1 for children aged under 1 year old,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,Answer 1 for children aged under 1 year old,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,Answer 1 for children aged under 1 year old,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,Answer 1 for children aged under 1 year old,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,Answer 1 for children aged under 1 year old,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,Answer 1 for children aged under 1 year old,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,Answer 1 for children aged under 1 year old,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,"This excludes national service. -If several household members have these links, answer for regular first. If no regular, answer for reserve. If no reserve, answer for spouses or civil partners.",,,,,,,,,,,"For example, lifting and carrying objects, or using a keyboard",,"For example, deafness or partial hearing",,"For example, depression or anxiety","For example, walking short distances or climbing stairs","For example, anything associated with autism spectrum disorder (ASD), including Asperger’s or attention deficit hyperactivity disorder (ADHD)",,"For example, blindness or partial sight",,,,"The tenant's ‘last settled home' is their last long-standing home. For tenants who had temporary accommodation, sleeping rough or otherwise homeless, their last settled home is where they were living previously.",,,,"This is the tenant’s last long-standing home. It is where the tenant was living before any period in temporary accommodation, sleeping rough or otherwise homeless.","Combined with field 104, it should be a postcode which lies within the local authority given in field 105.","Combined with field 103, it should be a postcode which lies within the local authority given in field 105.","This is the tenant’s last long-standing home. It is where the tenant was living before any period in temporary accommodation, sleeping rough or otherwise homeless.",Households may be given ‘reasonable preference’ for social housing under one or more specific category by the local authority. This is also known as ‘priority need’.,,,,,,Where available vacant properties are advertised and applicants are able to bid for specific properties.,Where a common system agreed between a group of housing providers is used to determine applicants' priority for housing.,Where a single waiting list is used by a group of housing providers to receive and process housing applications. Providers may use different approaches to determine priority.,Where the 'access category' or another descriptor of whether an available vacant property meets a range of access needs is displayed to applicants during the allocations process.,,,,"Include any income after tax from employment, pensions, and Universal Credit. Don't include National Insurance (NI) contributions and tax, housing benefit, child benefit, or council tax support.","This is about when the tenant is in their new let. If they are unsure about the situation for their new let and their financial and working situation hasn’t changed significantly, answer based on what housing-related benefits they currently receive.",,"If rent is charged on the property then answer Yes, even if tenants do not pay it themselves.",,"This is the amount paid before any charges are added for services (for example, hot water or cleaning). Households may receive housing benefit or Universal Credit towards basic rent.","For example, cleaning. Households may get household benefits towards the service charge.",For example heating or hot water. This doesn’t include housing benefit or Universal Credit.,Any charges made to fund support services included in the tenancy agreement.,Also known as the 'outstanding amount',You only need to give an approximate figure. +If several household members have these links, answer for regular first. If no regular, answer for reserve. If no reserve, answer for spouses or civil partners.",,,,,,,,,,,"For example, lifting and carrying objects, or using a keyboard",,"For example, deafness or partial hearing",,"For example, depression or anxiety","For example, walking short distances or climbing stairs","For example, anything associated with autism spectrum disorder (ASD), including Asperger’s or attention deficit hyperactivity disorder (ADHD)",,"For example, blindness or partial sight",,,,"The tenant's ‘last settled home' is their last long-standing home. For tenants who had temporary accommodation, sleeping rough or otherwise homeless, their last settled home is where they were living previously.",,,,"This is the tenant’s last long-standing home. It is where the tenant was living before any period in temporary accommodation, sleeping rough or otherwise homeless.","Combined with field 104, it should be a postcode which lies within the local authority given in field 105.","Combined with field 103, it should be a postcode which lies within the local authority given in field 105.","This is the tenant’s last long-standing home. It is where the tenant was living before any period in temporary accommodation, sleeping rough or otherwise homeless.",Households may be given ‘reasonable preference’ for social housing under one or more specific category by the local authority. This is also known as ‘priority need’.,,,,,,Where available vacant properties are advertised and applicants are able to bid for specific properties.,Where a common system agreed between a group of housing providers is used to determine applicants' priority for housing.,Where a single waiting list is used by a group of housing providers to receive and process housing applications. Providers may use different approaches to determine priority.,Where the 'access category' or another descriptor of whether an available vacant property meets a range of access needs is displayed to applicants during the allocations process.,,,,"Include any income after tax from employment, pensions, and Universal Credit. Don't include National Insurance (NI) contributions and tax, housing benefit, child benefit, or council tax support.","This is about when the tenant is in their new let. If they are unsure about the situation for their new let and their financial and working situation hasn’t changed significantly, answer based on what housing-related benefits they currently receive.",,"If rent is charged on the property then answer Yes, even if tenants do not pay it themselves.",,"This is the amount paid before any charges are added for services (for example, hot water or cleaning). Households may receive housing benefit or Universal Credit towards basic rent.","For example, cleaning. Households may get household benefits towards the service charge.",For example heating or hot water. This doesn’t include housing benefit or Universal Credit.,Any charges made to fund support services included in the tenancy agreement.,Also known as the 'outstanding amount',You only need to give an approximate figure.,,, Values,Alphanumeric,,Email format,01-Feb,Alphanumeric,Numeric,01-Feb,Jan-31,01-Dec,25 - 26,01-Jul,Text,"Alphanumeric, max 13 characters","Alphanumeric, max 12 characters",1,"5 - 6, or 8 - 22",1 - 3 or 5 - 9,Numeric,Alphanumeric,,Text,,"Alphanumeric, 2 - 4 characters","Alphanumeric, 3 characters","9 character ONS code, beginning with 'E' (https://www.get-information-schools.service.gov.uk/Guidance/LaNameCodes) ","1 - 2, 4 or 6 - 10",01-Feb,01-Feb,01-Jul,Jan-31,01-Dec,Jun-26,Jan-31,01-Dec,Jun-26,"2 - 4, 7 - 8",01-Mar,01-Feb,02-Aug,Text,"1 - 99, see specification for more detail",16 - 120 or R,"F, M, X or R",Jan-20,"3 digit ISO country code, see specification",0 - 10,01-Mar,"Numeric, range 1 - 120 or text (upper case 'R') @@ -77,11 +77,11 @@ Must be >= 16 if working situation = 1 - 8 or 0 Must be <16 if working situation = 9","F, M, X or R","0 - 10 Must be 9 if age <16",01-Jun,03-Jun,01-Mar,,1 or empty,,,,,,01-Mar,1 or empty,,,,,,,,,,1 - 2 or 6 - 12,2 or 6 - 13,"1 - 2, 4, 8 - 14, 16 - 20, 28 - 31, 34 or 44 - 55",Text,"3 - 4, 6 - 7, 9 - 10, 13 - 14, 18 - 19, 21, 23 - 33, 35, 37 - 39 ",1 or 11,01-Feb,"Alphanumeric, 2 - 4 characters","Alphanumeric, -3 characters","9 character ONS code, beginning with 'E' (https://www.get-information-schools.service.gov.uk/Guidance/LaNameCodes) ",01-Mar,1 or empty,,,,,01-Feb,,,,"1 - 4, 7 - 10, 14 - 24",01-Mar,01-Mar,0 - 99999,"1, 3, 6, 9 or 10",01-Apr,0 - 1,01-Oct,xxxx.xx,,,,01-Mar,xxxx.xx +3 characters","9 character ONS code, beginning with 'E' (https://www.get-information-schools.service.gov.uk/Guidance/LaNameCodes) ",01-Mar,1 or empty,,,,,01-Feb,,,,"1 - 4, 7 - 10, 14 - 24",01-Mar,01-Mar,0 - 99999,"1, 3, 6, 9 or 10",01-Apr,0 - 1,01-Oct,xxxx.xx,,,,01-Mar,xxxx.xx,,, Can be empty?,No,,Yes,No,"Yes, if letting is general needs (if field 4 = 1)","Yes, if letting is general needs (if field 4 = 1)",No,,,,,"Yes, if letting is not 'Other intermediate rent product' (if field 11 is not 6)",Yes,,No,"Yes, if letting is a renewal (if field 7 = 1)","Yes, if letting is a renewal (if field 7 = 1) or a first-time let (if field 16 = 15 - 17)","Yes, if letting is supported housing (if field 4 = 2) or if the property's postcode is not empty (if fields 23 and 24 contain full and valid entries)","Yes, if letting is supported housing (if field 4 = 2) or if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",Yes,"Yes, if letting is supported housing (if field 4 = 2) or if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",Yes,"Yes, if letting is supported housing (if field 4 = 2) or if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",,"Yes, if letting is supported housing (if field 4 = 2)",,,,,"Yes, if letting is a renewal (if field 7 = 1)",,,Yes,,,"Yes, if letting is general needs (if field 4 = 1)",No,,,"Yes, if 'Other' is not selected for tenancy type (if field 39 is not 3)","Yes, if letting is not a fixed-term tenancy (if field 39 is not 4 or 6)",No,,,,,"Yes, if all fields about person 2 are empty (fields 47 - 50)",,,,"Yes, if all fields about person 3 are empty (fields 51 - 54)",,,,"Yes, if all fields about person 4 are empty (fields 55 - 58)",,,,"Yes, if all fields about person 5 are empty (fields 59 - 62)",,,,"Yes, if all fields about person 6 are empty (fields 63 - 66)",,,,"Yes, if all fields about person 7 are empty (fields 67 - 70)",,,,"Yes, if all fields about person 8 are empty (fields 71 - 74)",,,,No,"Yes, if no one in the household is a current or former regular (if field 75 is not 1)","Yes, if no one in the household is a current or former regular or reserve (if field 75 is not 1 or 4)",No,"Yes, if no household members have access needs or if it is unknown (if field 83 or 84 = 1)",,,,"Yes, if a household member has an access need (if at least one of fields 79 to 82 = 1)",,No,"Yes, if a household member has an access need (if at least one of fields 79 to 82 = 1) If someone in the household does have such a condition (if field 89 = 1), then at least 1 of these fields must be 1.",,,,,,,,,,No,"Yes, if letting is a renewal (if field 7 = 1)",No,"Yes, if 'Other' is not selected for reason for leaving last settled home (if field 98 is not 20)",No,No,,"Yes, if postcode of household's last settled home is not known (if 102 = 2)",,Yes,No,"If household was given 'reasonable preference' (if field 107 = 1), at least one of these fields must be 1 -If household was not given 'reasonable preference' (if field 106 = 2 or 3), these fields will be ignored.",,,,,No,,,,"Yes, if letting is a renewal (if field 7 = 1)",No,"Yes, if household's income is unknown (if field 117 = 2 or 3)",,No,,"Yes, if letting is supported housing (if field 4 = 2)",No,"Yes, if the household does not pay rent (if field 122 = 1)",,,,"Yes, if the household doesn't receive housing benefits, or if it is unknown (if field 120 = 3, 9 or 10)","Yes, if the household does not need to pay rent or charges after receiving housing benefits (if field 128 is not 1)" -Type of letting the question applies to,,,,,Supported housing only,,,,,,,Other Intermediate Rent only,,,,,,General needs only,,,,,,,,,,,,,,,,,,Supported housing only,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Supported housing only,,,,,,, -Duplicate check field?,Yes,,,,Yes,,,Yes,,,,,Yes,,,,,,,,,,Yes,,,,,,,,,,,,,,,,,,,Yes,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -Field number,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129 -,ORG1,ORG1,support@example.com,1,,,2,1,4,26,1,,1,1,1,5,1,,a,a,a,a,a1,1aa,E09000001,1,1,1,1,1,4,25,,,,,3,1,2,,,20,F,1,GBR,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,6,3,1,1,,,,,,1,1,,,,,,,,,,1,2,50,,30,1,2,,,,1,1,,,,,1,1,1,1,7,2,,,1,2,1,1,50,0,0,0,3, +If household was not given 'reasonable preference' (if field 106 = 2 or 3), these fields will be ignored.",,,,,No,,,,"Yes, if letting is a renewal (if field 7 = 1)",No,"Yes, if household's income is unknown (if field 117 = 2 or 3)",,No,,"Yes, if letting is supported housing (if field 4 = 2)",No,"Yes, if the household does not pay rent (if field 122 = 1)",,,,"Yes, if the household doesn't receive housing benefits, or if it is unknown (if field 120 = 3, 9 or 10)","Yes, if the household does not need to pay rent or charges after receiving housing benefits (if field 128 is not 1)",,, +Type of letting the question applies to,,,,,Supported housing only,,,,,,,Other Intermediate Rent only,,,,,,General needs only,,,,,,,,,,,,,,,,,,Supported housing only,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Supported housing only,,,,,,,,,, +Duplicate check field?,Yes,,,,Yes,,,Yes,,,,,Yes,,,,,,,,,,Yes,,,,,,,,,,,,,,,,,,,Yes,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +Field number,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132 +,ORG1,ORG1,support@example.com,1,,,2,1,4,26,1,,1,1,1,5,1,,a,a,a,a,a1,1aa,E09000001,1,1,1,1,1,4,25,,,,,3,1,2,,,20,F,1,GBR,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,6,3,1,1,,,,,,1,1,,,,,,,,,,1,2,50,,30,1,2,,,,1,1,,,,,1,1,1,1,1,2,,,1,2,1,1,50,0,0,0,3,,1,1,1 diff --git a/spec/fixtures/variable_definitions/lettings_download_26_27.csv b/spec/fixtures/variable_definitions/lettings_download_26_27.csv new file mode 100644 index 000000000..4e564bde7 --- /dev/null +++ b/spec/fixtures/variable_definitions/lettings_download_26_27.csv @@ -0,0 +1,3 @@ +referral_register,What was the source of referral for this letting? +referral_noms,What was the source of referral for this letting? +referral_org,What was the source of referral for this letting? diff --git a/spec/lib/tasks/log_variable_definitions_spec.rb b/spec/lib/tasks/log_variable_definitions_spec.rb index 7fdeb66e4..57b379b1f 100644 --- a/spec/lib/tasks/log_variable_definitions_spec.rb +++ b/spec/lib/tasks/log_variable_definitions_spec.rb @@ -14,7 +14,7 @@ RSpec.describe "log_variable_definitions" do end it "adds CsvVariableDefinition records from each file in the specified directory" do - expect { task.invoke(path) }.to change(CsvVariableDefinition, :count).by(416) + expect { task.invoke(path) }.to change(CsvVariableDefinition, :count).by(419) end it "handles an empty directory without errors" do @@ -34,7 +34,7 @@ RSpec.describe "log_variable_definitions" do task.invoke(path) second_run_count = CsvVariableDefinition.count - expect(first_run_count).to eq(initial_count + 416) + expect(first_run_count).to eq(initial_count + 419) expect(second_run_count).to eq(first_run_count) end end diff --git a/spec/models/form/lettings/subsections/household_situation_spec.rb b/spec/models/form/lettings/subsections/household_situation_spec.rb index eb056ca7a..985ac1783 100644 --- a/spec/models/form/lettings/subsections/household_situation_spec.rb +++ b/spec/models/form/lettings/subsections/household_situation_spec.rb @@ -10,16 +10,18 @@ RSpec.describe Form::Lettings::Subsections::HouseholdSituation, type: :model do before do allow(section).to receive(:form).and_return(form) + allow(form).to receive(:start_year_2024_or_later?).and_return(false) + allow(form).to receive(:start_year_2025_or_later?).and_return(false) + allow(form).to receive(:start_year_2026_or_later?).and_return(false) end it "has correct section" do expect(household_situation.section).to eq(section) end - context "with form year before 2024" do + context "with form year is 2024", metadata: { year: 24 } do before do - allow(form).to receive(:start_year_2024_or_later?).and_return(false) - allow(form).to receive(:start_year_2025_or_later?).and_return(false) + allow(form).to receive(:start_year_2024_or_later?).and_return(true) end it "has correct pages" do @@ -29,6 +31,7 @@ RSpec.describe Form::Lettings::Subsections::HouseholdSituation, type: :model do time_on_waiting_list reason_for_leaving_last_settled_home reason_for_leaving_last_settled_home_renewal + reasonother_value_check previous_housing_situation previous_housing_situation_renewal homelessness @@ -47,10 +50,10 @@ RSpec.describe Form::Lettings::Subsections::HouseholdSituation, type: :model do end end - context "with form year is 2024" do + context "with form year is 2025", metadata: { year: 25 } do before do allow(form).to receive(:start_year_2024_or_later?).and_return(true) - allow(form).to receive(:start_year_2025_or_later?).and_return(false) + allow(form).to receive(:start_year_2025_or_later?).and_return(true) end it "has correct pages" do @@ -69,20 +72,23 @@ RSpec.describe Form::Lettings::Subsections::HouseholdSituation, type: :model do reasonable_preference reasonable_preference_reason allocation_system - referral + referral_type + referral_direct + referral_la referral_prp - referral_supported_housing - referral_supported_housing_prp + referral_hsc + referral_justice referral_value_check ], ) end end - context "with form year is 2025" do + context "with form year is 2026", metadata: { year: 26 } do before do allow(form).to receive(:start_year_2024_or_later?).and_return(true) allow(form).to receive(:start_year_2025_or_later?).and_return(true) + allow(form).to receive(:start_year_2026_or_later?).and_return(true) end it "has correct pages" do @@ -101,13 +107,9 @@ RSpec.describe Form::Lettings::Subsections::HouseholdSituation, type: :model do reasonable_preference reasonable_preference_reason allocation_system - referral_type - referral_direct - referral_la - referral_prp - referral_hsc - referral_justice - referral_value_check + referral_register + referral_noms + referral_org ], ) end diff --git a/spec/services/bulk_upload/lettings/validator_spec.rb b/spec/services/bulk_upload/lettings/validator_spec.rb index 0658d95f0..9b1361f5f 100644 --- a/spec/services/bulk_upload/lettings/validator_spec.rb +++ b/spec/services/bulk_upload/lettings/validator_spec.rb @@ -11,7 +11,7 @@ RSpec.describe BulkUpload::Lettings::Validator do let(:user) { create(:user, organisation:) } let(:log) { build(:lettings_log, :completed, period: 2, assigned_to: user) } let(:log_to_csv) { BulkUpload::LettingsLogToCsv.new(log:) } - let(:bulk_upload) { create(:bulk_upload, user:, year: log.collection_start_year) } + let(:bulk_upload) { create(:bulk_upload, user:, year:) } let(:path) { file.path } let(:file) { Tempfile.new } @@ -190,8 +190,8 @@ RSpec.describe BulkUpload::Lettings::Validator do expect(error.tenant_code).to eql(log.tenancycode) expect(error.property_ref).to eql(log.propcode) expect(error.row).to eql("2") - expect(error.cell).to eql("CX2") - expect(error.col).to eql("CX") + expect(error.cell).to eql("DA2") + expect(error.col).to eql("DA") end end end diff --git a/spec/services/bulk_upload/lettings/year2026/csv_parser_spec.rb b/spec/services/bulk_upload/lettings/year2026/csv_parser_spec.rb index 91648d729..30574c73c 100644 --- a/spec/services/bulk_upload/lettings/year2026/csv_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2026/csv_parser_spec.rb @@ -246,7 +246,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::CsvParser do it "returns correct column" do expect(service.column_for_field("field_5")).to eql("B") expect(service.column_for_field("field_22")).to eql("AS") - expect(service.column_for_field("field_26")).to eql("DG") + expect(service.column_for_field("field_26")).to eql("DI") expect(service.column_for_field("field_25")).to eql("I") 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..dfdc71552 100644 --- a/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb @@ -221,7 +221,10 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do field_114: "2", field_115: "2", - field_116: "2", + field_116: "1", + field_130: "1", + field_131: "1", + field_132: "1", field_117: "1", field_118: "2", diff --git a/spec/services/csv/lettings_log_csv_service_spec.rb b/spec/services/csv/lettings_log_csv_service_spec.rb index 1579fb499..984e4edb1 100644 --- a/spec/services/csv/lettings_log_csv_service_spec.rb +++ b/spec/services/csv/lettings_log_csv_service_spec.rb @@ -301,7 +301,9 @@ RSpec.describe Csv::LettingsLogCsvService do chr: 1, cap: 0, accessible_register: 0, - referral: 2, + referral_register: 1, + referral_noms: 1, + referral_org: 1, net_income_known: 0, incref: 0, incfreq: 1, From a3f49e80383c98458659420430bed890a84da4da Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 26 Jan 2026 18:09:10 +0000 Subject: [PATCH 07/40] CLDC-4151: Ignore tests failing for later tickets --- spec/services/csv/lettings_log_csv_service_spec.rb | 12 ++++++++---- .../exports/lettings_log_export_service_spec.rb | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/spec/services/csv/lettings_log_csv_service_spec.rb b/spec/services/csv/lettings_log_csv_service_spec.rb index 984e4edb1..9b426922e 100644 --- a/spec/services/csv/lettings_log_csv_service_spec.rb +++ b/spec/services/csv/lettings_log_csv_service_spec.rb @@ -329,7 +329,8 @@ RSpec.describe Csv::LettingsLogCsvService do context "when the current user is a support user" do let(:user) { create(:user, :support, organisation:, email: "s.port@jeemayle.com") } - it "exports the CSV with all values correct" do + # TODO: CLDC-4191 Reinstate this test when we update log export + xit "exports the CSV with all values correct" do expected_content = CSV.read("spec/fixtures/files/lettings_log_csv_export_labels_26.csv") values_to_delete = %w[id] values_to_delete.each do |attribute| @@ -343,7 +344,8 @@ RSpec.describe Csv::LettingsLogCsvService do context "when the current user is not a support user" do let(:user) { create(:user, :data_provider, organisation:, email: "choreographer@owtluk.com") } - it "exports the CSV with all values correct" do + # TODO: CLDC-4191 Reinstate this test when we update log export + xit "exports the CSV with all values correct" do expected_content = CSV.read("spec/fixtures/files/lettings_log_csv_export_non_support_labels_26.csv") values_to_delete = %w[id] values_to_delete.each do |attribute| @@ -361,7 +363,8 @@ RSpec.describe Csv::LettingsLogCsvService do context "when the current user is a support user" do let(:user) { create(:user, :support, organisation:, email: "s.port@jeemayle.com") } - it "exports the CSV with all values correct" do + # TODO: CLDC-4191 Reinstate this test when we update log export + xit "exports the CSV with all values correct" do expected_content = CSV.read("spec/fixtures/files/lettings_log_csv_export_codes_26.csv") values_to_delete = %w[id] values_to_delete.each do |attribute| @@ -375,7 +378,8 @@ RSpec.describe Csv::LettingsLogCsvService do context "when the current user is not a support user" do let(:user) { create(:user, :data_provider, organisation:, email: "choreographer@owtluk.com") } - it "exports the CSV with all values correct" do + # TODO: CLDC-4191 Reinstate this test when we update log export + xit "exports the CSV with all values correct" do expected_content = CSV.read("spec/fixtures/files/lettings_log_csv_export_non_support_codes_26.csv") values_to_delete = %w[id] values_to_delete.each do |attribute| diff --git a/spec/services/exports/lettings_log_export_service_spec.rb b/spec/services/exports/lettings_log_export_service_spec.rb index 613d64dd0..c39f0d515 100644 --- a/spec/services/exports/lettings_log_export_service_spec.rb +++ b/spec/services/exports/lettings_log_export_service_spec.rb @@ -502,7 +502,8 @@ RSpec.describe Exports::LettingsLogExportService do let(:expected_data_filename) { "core_2026_2027_apr_mar_f0001_inc0001_pt001.xml" } let(:xml_export_file) { File.open("spec/fixtures/exports/general_needs_log_26_27.xml", "r:UTF-8") } - it "generates an XML export file with the expected content within the ZIP file" do + # TODO: CLDC-4191 Reinstate this test when we update log export + xit "generates an XML export file with the expected content within the ZIP file" do expected_content = replace_entity_ids(lettings_log, xml_export_file.read) expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| entry = Zip::File.open_buffer(content).find_entry(expected_data_filename) From 659a147c61082005c3a93f93f8c5a10f07d3ea1e Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 29 Jan 2026 17:19:09 +0000 Subject: [PATCH 08/40] fixup! CLDC-4151: Add new referrals questions start q nums from 2026 --- app/models/form/lettings/questions/referral_noms.rb | 2 +- app/models/form/lettings/questions/referral_org.rb | 2 +- app/models/form/lettings/questions/referral_register.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/form/lettings/questions/referral_noms.rb b/app/models/form/lettings/questions/referral_noms.rb index 00d2581dc..8c1ea88db 100644 --- a/app/models/form/lettings/questions/referral_noms.rb +++ b/app/models/form/lettings/questions/referral_noms.rb @@ -20,5 +20,5 @@ class Form::Lettings::Questions::ReferralNoms < ::Form::Question }.freeze end - QUESTION_NUMBER_FROM_YEAR = { 2025 => 84 }.freeze + QUESTION_NUMBER_FROM_YEAR = { 2026 => 84 }.freeze end diff --git a/app/models/form/lettings/questions/referral_org.rb b/app/models/form/lettings/questions/referral_org.rb index 5425fce56..2a8614970 100644 --- a/app/models/form/lettings/questions/referral_org.rb +++ b/app/models/form/lettings/questions/referral_org.rb @@ -20,5 +20,5 @@ class Form::Lettings::Questions::ReferralOrg < ::Form::Question }.freeze end - QUESTION_NUMBER_FROM_YEAR = { 2025 => 84 }.freeze + QUESTION_NUMBER_FROM_YEAR = { 2026 => 84 }.freeze end diff --git a/app/models/form/lettings/questions/referral_register.rb b/app/models/form/lettings/questions/referral_register.rb index 4769fcdbe..d7c653181 100644 --- a/app/models/form/lettings/questions/referral_register.rb +++ b/app/models/form/lettings/questions/referral_register.rb @@ -20,5 +20,5 @@ class Form::Lettings::Questions::ReferralRegister < ::Form::Question }.freeze end - QUESTION_NUMBER_FROM_YEAR = { 2025 => 84 }.freeze + QUESTION_NUMBER_FROM_YEAR = { 2026 => 84 }.freeze end From aa1ddb7c10e9a7b58468b4212126583f6afd9aac Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 29 Jan 2026 17:19:44 +0000 Subject: [PATCH 09/40] CLDC-4151: Add model tests for new questions --- .../form/lettings/pages/referral_noms_spec.rb | 34 +++++++++++++ .../form/lettings/pages/referral_org_spec.rb | 34 +++++++++++++ .../lettings/pages/referral_register_spec.rb | 34 +++++++++++++ .../lettings/questions/referral_noms_spec.rb | 49 +++++++++++++++++++ .../lettings/questions/referral_org_spec.rb | 49 +++++++++++++++++++ .../questions/referral_register_spec.rb | 49 +++++++++++++++++++ 6 files changed, 249 insertions(+) create mode 100644 spec/models/form/lettings/pages/referral_noms_spec.rb create mode 100644 spec/models/form/lettings/pages/referral_org_spec.rb create mode 100644 spec/models/form/lettings/pages/referral_register_spec.rb create mode 100644 spec/models/form/lettings/questions/referral_noms_spec.rb create mode 100644 spec/models/form/lettings/questions/referral_org_spec.rb create mode 100644 spec/models/form/lettings/questions/referral_register_spec.rb diff --git a/spec/models/form/lettings/pages/referral_noms_spec.rb b/spec/models/form/lettings/pages/referral_noms_spec.rb new file mode 100644 index 000000000..256e4fb4a --- /dev/null +++ b/spec/models/form/lettings/pages/referral_noms_spec.rb @@ -0,0 +1,34 @@ +require "rails_helper" + +RSpec.describe Form::Lettings::Pages::ReferralNoms, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form:) } + let(:form) { instance_double(Form, start_date: Time.zone.today) } + + before do + allow(subsection).to receive(:form).and_return(form) + end + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[referral_noms]) + end + + it "has the correct id" do + expect(page.id).to eq("referral_noms") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has the correct depends_on" do + expect(page.depends_on).to be nil + end +end diff --git a/spec/models/form/lettings/pages/referral_org_spec.rb b/spec/models/form/lettings/pages/referral_org_spec.rb new file mode 100644 index 000000000..cb39c95ca --- /dev/null +++ b/spec/models/form/lettings/pages/referral_org_spec.rb @@ -0,0 +1,34 @@ +require "rails_helper" + +RSpec.describe Form::Lettings::Pages::ReferralOrg, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form:) } + let(:form) { instance_double(Form, start_date: Time.zone.today) } + + before do + allow(subsection).to receive(:form).and_return(form) + end + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[referral_org]) + end + + it "has the correct id" do + expect(page.id).to eq("referral_org") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has the correct depends_on" do + expect(page.depends_on).to be nil + end +end diff --git a/spec/models/form/lettings/pages/referral_register_spec.rb b/spec/models/form/lettings/pages/referral_register_spec.rb new file mode 100644 index 000000000..28981e03d --- /dev/null +++ b/spec/models/form/lettings/pages/referral_register_spec.rb @@ -0,0 +1,34 @@ +require "rails_helper" + +RSpec.describe Form::Lettings::Pages::ReferralRegister, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form:) } + let(:form) { instance_double(Form, start_date: Time.zone.today) } + + before do + allow(subsection).to receive(:form).and_return(form) + end + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[referral_register]) + end + + it "has the correct id" do + expect(page.id).to eq("referral_register") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has the correct depends_on" do + expect(page.depends_on).to be nil + end +end diff --git a/spec/models/form/lettings/questions/referral_noms_spec.rb b/spec/models/form/lettings/questions/referral_noms_spec.rb new file mode 100644 index 000000000..064c4fbe7 --- /dev/null +++ b/spec/models/form/lettings/questions/referral_noms_spec.rb @@ -0,0 +1,49 @@ +require "rails_helper" + +RSpec.describe Form::Lettings::Questions::ReferralNoms, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page) } + let(:subsection) { instance_double(Form::Subsection) } + let(:form) { instance_double(Form, start_date: Time.zone.today) } + + before do + allow(page).to receive(:subsection).and_return(subsection) + allow(subsection).to receive(:form).and_return(form) + end + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("referral_noms") + end + + it "has the correct type" do + expect(question.type).to eq("radio") + end + + it "is not marked as derived" do + expect(question.derived?(nil)).to be false + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq( + { + "1" => { + "value" => "Answer A", + }, + "2" => { + "value" => "Answer B", + }, + }.freeze, + ) + end + + it "has the correct question_number" do + expect(question.question_number).to eq(84) + end +end diff --git a/spec/models/form/lettings/questions/referral_org_spec.rb b/spec/models/form/lettings/questions/referral_org_spec.rb new file mode 100644 index 000000000..0c01d972b --- /dev/null +++ b/spec/models/form/lettings/questions/referral_org_spec.rb @@ -0,0 +1,49 @@ +require "rails_helper" + +RSpec.describe Form::Lettings::Questions::ReferralOrg, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page) } + let(:subsection) { instance_double(Form::Subsection) } + let(:form) { instance_double(Form, start_date: Time.zone.today) } + + before do + allow(page).to receive(:subsection).and_return(subsection) + allow(subsection).to receive(:form).and_return(form) + end + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("referral_org") + end + + it "has the correct type" do + expect(question.type).to eq("radio") + end + + it "is not marked as derived" do + expect(question.derived?(nil)).to be false + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq( + { + "1" => { + "value" => "Answer A", + }, + "2" => { + "value" => "Answer B", + }, + }.freeze, + ) + end + + it "has the correct question_number" do + expect(question.question_number).to eq(84) + end +end diff --git a/spec/models/form/lettings/questions/referral_register_spec.rb b/spec/models/form/lettings/questions/referral_register_spec.rb new file mode 100644 index 000000000..6d5ad3ffd --- /dev/null +++ b/spec/models/form/lettings/questions/referral_register_spec.rb @@ -0,0 +1,49 @@ +require "rails_helper" + +RSpec.describe Form::Lettings::Questions::ReferralRegister, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page) } + let(:subsection) { instance_double(Form::Subsection) } + let(:form) { instance_double(Form, start_date: Time.zone.today) } + + before do + allow(page).to receive(:subsection).and_return(subsection) + allow(subsection).to receive(:form).and_return(form) + end + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("referral_register") + end + + it "has the correct type" do + expect(question.type).to eq("radio") + end + + it "is not marked as derived" do + expect(question.derived?(nil)).to be false + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq( + { + "1" => { + "value" => "Answer A", + }, + "2" => { + "value" => "Answer B", + }, + }.freeze, + ) + end + + it "has the correct question_number" do + expect(question.question_number).to eq(84) + end +end From 580599a0b9eec4cd60f1148ac219670e67b2e03b Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 2 Feb 2026 13:40:59 +0000 Subject: [PATCH 10/40] fixup! CLDC-4151: Add new fields to bulk upload export old_visible_id set MAX_COLUMNS correctly check .prp? for prp cols --- app/helpers/bulk_upload/lettings_log_to_csv.rb | 2 +- app/services/bulk_upload/lettings/year2026/csv_parser.rb | 2 +- app/services/bulk_upload/lettings/year2026/row_parser.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/helpers/bulk_upload/lettings_log_to_csv.rb b/app/helpers/bulk_upload/lettings_log_to_csv.rb index 5ddd8d6a9..d8a6d9b82 100644 --- a/app/helpers/bulk_upload/lettings_log_to_csv.rb +++ b/app/helpers/bulk_upload/lettings_log_to_csv.rb @@ -104,7 +104,7 @@ class BulkUpload::LettingsLogToCsv log.owning_organisation end [ - owning_organisation&.id, # 1 + owning_organisation&.old_visible_id, # 1 overrides[:managing_organisation_id] || log.managing_organisation&.old_visible_id, log.assigned_to&.email, log.needstype, diff --git a/app/services/bulk_upload/lettings/year2026/csv_parser.rb b/app/services/bulk_upload/lettings/year2026/csv_parser.rb index d74b191e5..c3e1948de 100644 --- a/app/services/bulk_upload/lettings/year2026/csv_parser.rb +++ b/app/services/bulk_upload/lettings/year2026/csv_parser.rb @@ -5,7 +5,7 @@ class BulkUpload::Lettings::Year2026::CsvParser # TODO: CLDC-4162: Update when 2026 format is known FIELDS = 132 - MAX_COLUMNS = 133 + MAX_COLUMNS = 132 FORM_YEAR = 2026 attr_reader :path diff --git a/app/services/bulk_upload/lettings/year2026/row_parser.rb b/app/services/bulk_upload/lettings/year2026/row_parser.rb index 6d07cca64..782521698 100644 --- a/app/services/bulk_upload/lettings/year2026/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2026/row_parser.rb @@ -1705,7 +1705,7 @@ private def referral_noms return unless owning_organisation - if owning_organisation.la? + if owning_organisation.prp? field_131 end end @@ -1713,7 +1713,7 @@ private def referral_org return unless owning_organisation - if owning_organisation.la? + if owning_organisation.prp? field_132 end end From 40fca2865ed135820df38f66971708c902de7a7c Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 2 Feb 2026 13:57:37 +0000 Subject: [PATCH 11/40] fixup! CLDC-4151: Update tests clarify tests that may update --- spec/services/bulk_upload/lettings/validator_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/services/bulk_upload/lettings/validator_spec.rb b/spec/services/bulk_upload/lettings/validator_spec.rb index 9b1361f5f..54e61c76a 100644 --- a/spec/services/bulk_upload/lettings/validator_spec.rb +++ b/spec/services/bulk_upload/lettings/validator_spec.rb @@ -172,8 +172,8 @@ RSpec.describe BulkUpload::Lettings::Validator do expect(error.tenant_code).to eql(log.tenancycode) expect(error.property_ref).to eql(log.propcode) expect(error.row).to eql("2") - expect(error.cell).to eql("CX2") - expect(error.col).to eql("CX") + expect(error.cell).to eql("CX2") # this may change when adding a new field as the cols are in a random order + expect(error.col).to eql("CX") # this may change when adding a new field as the cols are in a random order end end @@ -190,8 +190,8 @@ RSpec.describe BulkUpload::Lettings::Validator do expect(error.tenant_code).to eql(log.tenancycode) expect(error.property_ref).to eql(log.propcode) expect(error.row).to eql("2") - expect(error.cell).to eql("DA2") - expect(error.col).to eql("DA") + expect(error.cell).to eql("DA2") # this may change when adding a new field as the cols are in a random order + expect(error.col).to eql("DA") # this may change when adding a new field as the cols are in a random order end end end From 60524b82a9d94510e59855fab81d07a072271fc5 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 2 Feb 2026 14:55:51 +0000 Subject: [PATCH 12/40] fixup! CLDC-4151: Add new fields to bulk upload handle the old_visible_id not existing use override org ID only for the output owning ID. it's only passed as eg ORG1 --- app/helpers/bulk_upload/lettings_log_to_csv.rb | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/app/helpers/bulk_upload/lettings_log_to_csv.rb b/app/helpers/bulk_upload/lettings_log_to_csv.rb index d8a6d9b82..a1b5c29e3 100644 --- a/app/helpers/bulk_upload/lettings_log_to_csv.rb +++ b/app/helpers/bulk_upload/lettings_log_to_csv.rb @@ -98,13 +98,8 @@ class BulkUpload::LettingsLogToCsv def to_2026_row # TODO: CLDC-4162: Implement when 2026 format is known - owning_organisation = if overrides[:organisation_id] - then Organisation.find(overrides[:organisation_id]) - else - log.owning_organisation - end [ - owning_organisation&.old_visible_id, # 1 + overrides[:organisation_id] || log.owning_organisation&.old_visible_id, # 1 overrides[:managing_organisation_id] || log.managing_organisation&.old_visible_id, log.assigned_to&.email, log.needstype, @@ -230,7 +225,7 @@ class BulkUpload::LettingsLogToCsv chr, cap, accessible_register, - owning_organisation&.la? ? log.referral_register : nil, + log.owning_organisation.la? ? log.referral_register : nil, net_income_known, log.incfreq, log.earnings, @@ -245,7 +240,7 @@ class BulkUpload::LettingsLogToCsv log.supcharg, log.hbrentshortfall, log.tshortfall, - owning_organisation&.prp? ? log.referral_register : nil, + log.owning_organisation.prp? ? log.referral_register : nil, log.referral_noms, log.referral_org, # 132 ] From 4e6a8c203c85b665b8eaefe2d76dff767b4fc367 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 2 Feb 2026 15:06:48 +0000 Subject: [PATCH 13/40] CLDC-4151: Add new questions to log factory --- spec/factories/lettings_log.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/factories/lettings_log.rb b/spec/factories/lettings_log.rb index 62cc94a14..c5aa46a52 100644 --- a/spec/factories/lettings_log.rb +++ b/spec/factories/lettings_log.rb @@ -162,6 +162,9 @@ FactoryBot.define do first_time_property_let_as_social_housing { 0 } referral_type { 1 } referral { 2 } + referral_register { 1 } + referral_noms { 1 } + referral_org { 1 } uprn_known { 0 } joint { 3 } address_line1 { "Address line 1" } From 3b743cde79d11415b2c689721d9fcc596be4e1ba Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 2 Feb 2026 15:22:46 +0000 Subject: [PATCH 14/40] fixup! CLDC-4151: Add new fields to bulk upload leave field_131 validations till later --- .../bulk_upload/lettings/year2026/row_parser.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/services/bulk_upload/lettings/year2026/row_parser.rb b/app/services/bulk_upload/lettings/year2026/row_parser.rb index 782521698..61ab00152 100644 --- a/app/services/bulk_upload/lettings/year2026/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2026/row_parser.rb @@ -1703,18 +1703,12 @@ private end def referral_noms - return unless owning_organisation - - if owning_organisation.prp? - field_131 - end + field_131 end def referral_org return unless owning_organisation - if owning_organisation.prp? - field_132 - end + field_132 end end From 3289539c561a7c7d423ff22836ee279e5d7d2f8f Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 29 Jan 2026 10:53:17 +0000 Subject: [PATCH 15/40] CLDC-4188: Add LA flow splits the referral_register question and pages into two, as its not easy to have the answers be dependent if the owning organisation changes type, reset the referral register question. the other questions do not need to be reset as they are no longer routed to --- .../form/lettings/pages/referral_noms.rb | 4 ++ .../form/lettings/pages/referral_org.rb | 4 ++ .../form/lettings/pages/referral_register.rb | 11 ------ .../lettings/pages/referral_register_la.rb | 15 ++++++++ .../lettings/pages/referral_register_prp.rb | 15 ++++++++ .../lettings/questions/referral_register.rb | 38 ++++++++++++++----- .../subsections/household_situation.rb | 3 +- app/models/lettings_log.rb | 13 +++++++ 8 files changed, 81 insertions(+), 22 deletions(-) delete mode 100644 app/models/form/lettings/pages/referral_register.rb create mode 100644 app/models/form/lettings/pages/referral_register_la.rb create mode 100644 app/models/form/lettings/pages/referral_register_prp.rb diff --git a/app/models/form/lettings/pages/referral_noms.rb b/app/models/form/lettings/pages/referral_noms.rb index af1590676..fa811e961 100644 --- a/app/models/form/lettings/pages/referral_noms.rb +++ b/app/models/form/lettings/pages/referral_noms.rb @@ -8,4 +8,8 @@ class Form::Lettings::Pages::ReferralNoms < ::Form::Page def questions @questions ||= [Form::Lettings::Questions::ReferralNoms.new(nil, nil, self)] end + + def routed_to?(log, _current_user) + log.owning_organisation&.prp? + end end diff --git a/app/models/form/lettings/pages/referral_org.rb b/app/models/form/lettings/pages/referral_org.rb index 8d3aa2b7d..fe23140d8 100644 --- a/app/models/form/lettings/pages/referral_org.rb +++ b/app/models/form/lettings/pages/referral_org.rb @@ -8,4 +8,8 @@ class Form::Lettings::Pages::ReferralOrg < ::Form::Page def questions @questions ||= [Form::Lettings::Questions::ReferralOrg.new(nil, nil, self)] end + + def routed_to?(log, _current_user) + log.owning_organisation&.prp? + end end diff --git a/app/models/form/lettings/pages/referral_register.rb b/app/models/form/lettings/pages/referral_register.rb deleted file mode 100644 index e835b3c1c..000000000 --- a/app/models/form/lettings/pages/referral_register.rb +++ /dev/null @@ -1,11 +0,0 @@ -# added in 2026 -class Form::Lettings::Pages::ReferralRegister < ::Form::Page - def initialize(id, hsh, subsection) - super - @id = "referral_register" - end - - def questions - @questions ||= [Form::Lettings::Questions::ReferralRegister.new(nil, nil, self)] - end -end diff --git a/app/models/form/lettings/pages/referral_register_la.rb b/app/models/form/lettings/pages/referral_register_la.rb new file mode 100644 index 000000000..ae1266b8e --- /dev/null +++ b/app/models/form/lettings/pages/referral_register_la.rb @@ -0,0 +1,15 @@ +# added in 2026 +class Form::Lettings::Pages::ReferralRegisterLa < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "referral_register_la" + end + + def questions + @questions ||= [Form::Lettings::Questions::ReferralRegister.new(nil, nil, self, :la)] + end + + def routed_to?(log, _current_user) + log.owning_organisation&.la? + end +end diff --git a/app/models/form/lettings/pages/referral_register_prp.rb b/app/models/form/lettings/pages/referral_register_prp.rb new file mode 100644 index 000000000..7191937c8 --- /dev/null +++ b/app/models/form/lettings/pages/referral_register_prp.rb @@ -0,0 +1,15 @@ +# added in 2026 +class Form::Lettings::Pages::ReferralRegisterPrp < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "referral_register_prp" + end + + def questions + @questions ||= [Form::Lettings::Questions::ReferralRegister.new(nil, nil, self, :prp)] + end + + def routed_to?(log, _current_user) + log.owning_organisation&.prp? + end +end diff --git a/app/models/form/lettings/questions/referral_register.rb b/app/models/form/lettings/questions/referral_register.rb index d7c653181..214748323 100644 --- a/app/models/form/lettings/questions/referral_register.rb +++ b/app/models/form/lettings/questions/referral_register.rb @@ -1,23 +1,41 @@ # added in 2026 class Form::Lettings::Questions::ReferralRegister < ::Form::Question - def initialize(id, hsh, page) - super + def initialize(id, hsh, page, provider_type) + super(id, hsh, page) @id = "referral_register" @copy_key = "lettings.household_situation.referral.register" @type = "radio" @check_answers_card_number = 0 @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + @provider_type = provider_type end def answer_options - { - "1" => { - "value" => "Answer A", - }, - "2" => { - "value" => "Answer B", - }, - }.freeze + if @provider_type == :la + { + "1" => { + "value" => "Renewal to the same tenant in the same property", + }, + "2" => { + "value" => "Internal transfer from another property owned by the same local authority - for existing social tenants only", + }, + "3" => { + "value" => "From a housing register (waiting list)", + }, + "4" => { + "value" => "Tenant applied directly (not via a nomination or housing register)", + }, + }.freeze + else + { + "1" => { + "value" => "Answer A", + }, + "2" => { + "value" => "Answer B", + }, + }.freeze + end end QUESTION_NUMBER_FROM_YEAR = { 2026 => 84 }.freeze diff --git a/app/models/form/lettings/subsections/household_situation.rb b/app/models/form/lettings/subsections/household_situation.rb index 948ab7611..be99907a0 100644 --- a/app/models/form/lettings/subsections/household_situation.rb +++ b/app/models/form/lettings/subsections/household_situation.rb @@ -28,7 +28,8 @@ class Form::Lettings::Subsections::HouseholdSituation < ::Form::Subsection def referral_questions if form.start_year_2026_or_later? [ - Form::Lettings::Pages::ReferralRegister.new(nil, nil, self), + Form::Lettings::Pages::ReferralRegisterLa.new(nil, nil, self), + Form::Lettings::Pages::ReferralRegisterPrp.new(nil, nil, self), Form::Lettings::Pages::ReferralNoms.new(nil, nil, self), Form::Lettings::Pages::ReferralOrg.new(nil, nil, self), ] diff --git a/app/models/lettings_log.rb b/app/models/lettings_log.rb index 9c9894d32..307b801bd 100644 --- a/app/models/lettings_log.rb +++ b/app/models/lettings_log.rb @@ -35,6 +35,7 @@ class LettingsLog < Log before_validation :set_derived_fields! before_validation :process_uprn_change!, if: :should_process_uprn_change? before_validation :process_address_change!, if: :should_process_address_change? + before_validation :reset_referral_register!, if: :should_reset_referral_register? belongs_to :scheme, optional: true belongs_to :location, optional: true @@ -944,4 +945,16 @@ private uprn_selection_changed? || startdate_changed? end end + + def reset_referral_register! + self.referral_register = nil + end + + def should_reset_referral_register? + return unless owning_organisation_id_changed? && owning_organisation_id && owning_organisation_id_was + + old_owning_organisation = Organisation.find(owning_organisation_id_was) + + old_owning_organisation.provider_type != owning_organisation.provider_type + end end From f7eef8a5fd51ef8332144bc1465507f213de5406 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 29 Jan 2026 12:20:42 +0000 Subject: [PATCH 16/40] CLDC-4188: Infer referral_register for renewals needs a new dependencies array for 2026 this handles inferring as well as clearing inferred answers if the prior answer changes --- .../lettings_log_variables.rb | 53 +++++++++++++++++-- .../lettings/pages/referral_register_la.rb | 2 +- .../lettings/questions/referral_register.rb | 4 ++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/app/models/derived_variables/lettings_log_variables.rb b/app/models/derived_variables/lettings_log_variables.rb index 9ea820dc0..360dbf03b 100644 --- a/app/models/derived_variables/lettings_log_variables.rb +++ b/app/models/derived_variables/lettings_log_variables.rb @@ -33,7 +33,7 @@ module DerivedVariables::LettingsLogVariables def set_derived_fields! clear_inapplicable_derived_values! - set_encoded_derived_values!(DEPENDENCIES) + set_encoded_derived_values!(dependencies) if rsnvac.present? self.newprop = has_first_let_vacancy_reason? ? 1 : 2 @@ -177,7 +177,54 @@ module DerivedVariables::LettingsLogVariables private - DEPENDENCIES = [ + def dependencies + if form.start_year_2026_or_later? + DEPENDENCIES_2026 + else + DEPENDENCIES_PRE_2026 + end + end + + DEPENDENCIES_2026 = [ + { + conditions: { + renewal: 1, + }, + derived_values: { + referral_register: 1, # new in 2026 + waityear: 2, + offered: 0, + rsnvac: 14, + first_time_property_let_as_social_housing: 0, + }, + }, + { + conditions: { + net_income_known: 2, + }, + derived_values: { + incref: 1, + }, + }, + { + conditions: { + net_income_known: 0, + }, + derived_values: { + incref: 0, + }, + }, + { + conditions: { + net_income_known: 1, + }, + derived_values: { + incref: 2, + }, + }, + ].freeze + + DEPENDENCIES_PRE_2026 = [ { conditions: { renewal: 1, @@ -218,7 +265,7 @@ private ].freeze def clear_inapplicable_derived_values! - reset_invalidated_derived_values!(DEPENDENCIES) + reset_invalidated_derived_values!(dependencies) if (startdate_changed? || renewal_changed?) && (renewal_was == 1 && startdate_was&.between?(Time.zone.local(2021, 4, 1), Time.zone.local(2022, 3, 31))) self.underoccupation_benefitcap = nil end diff --git a/app/models/form/lettings/pages/referral_register_la.rb b/app/models/form/lettings/pages/referral_register_la.rb index ae1266b8e..ee570f1cb 100644 --- a/app/models/form/lettings/pages/referral_register_la.rb +++ b/app/models/form/lettings/pages/referral_register_la.rb @@ -10,6 +10,6 @@ class Form::Lettings::Pages::ReferralRegisterLa < ::Form::Page end def routed_to?(log, _current_user) - log.owning_organisation&.la? + log.owning_organisation&.la? && !log.is_renewal? end end diff --git a/app/models/form/lettings/questions/referral_register.rb b/app/models/form/lettings/questions/referral_register.rb index 214748323..d6e184758 100644 --- a/app/models/form/lettings/questions/referral_register.rb +++ b/app/models/form/lettings/questions/referral_register.rb @@ -38,5 +38,9 @@ class Form::Lettings::Questions::ReferralRegister < ::Form::Question end end + def derived?(log) + log.is_renewal? + end + QUESTION_NUMBER_FROM_YEAR = { 2026 => 84 }.freeze end From 6fb22c1ecb6d3054c4543d30313d264e6d5c8719 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 29 Jan 2026 15:44:18 +0000 Subject: [PATCH 17/40] CLDC-4188: Add validation between prevten and referral_register block two other validations from previous years --- app/models/lettings_log.rb | 8 ++++++-- app/models/validations/household_validations.rb | 10 ++++++++-- config/locales/validations/lettings/household.en.yml | 4 ++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/models/lettings_log.rb b/app/models/lettings_log.rb index 307b801bd..5b7d4966b 100644 --- a/app/models/lettings_log.rb +++ b/app/models/lettings_log.rb @@ -378,8 +378,12 @@ class LettingsLog < Log end def is_internal_transfer? - # 1: Internal Transfer - referral == 1 + if form.start_year_2026_or_later? + referral_register == 2 + else + # 1: Internal Transfer + referral == 1 + end end def is_from_prp_only_housing_register_or_waiting_list? diff --git a/app/models/validations/household_validations.rb b/app/models/validations/household_validations.rb index 8ba58ba2b..f29ad7877 100644 --- a/app/models/validations/household_validations.rb +++ b/app/models/validations/household_validations.rb @@ -162,7 +162,7 @@ module Validations::HouseholdValidations # 27 Owner occupation (low-cost home ownership) # 28 Living with Friends or Family # 29 Prison / Approved Probation Hostel - if record.is_internal_transfer? && [3, 4, 7, 10, 13, 14, 19, 23, 24, 25, 26, 27, 28, 29].include?(record.prevten) + if record.is_internal_transfer? && [3, 4, 7, 10, 13, 14, 19, 23, 24, 25, 26, 27, 28, 29].include?(record.prevten) && !record.form.start_year_2026_or_later? label = record.form.get_question("prevten", record).present? ? record.form.get_question("prevten", record).label_from_value(record.prevten) : "" record.errors.add :prevten, :internal_transfer_non_social_housing, message: I18n.t("validations.lettings.household.prevten.internal_transfer", prevten: label) record.errors.add :referral, :internal_transfer_non_social_housing, message: I18n.t("validations.lettings.household.referral.prevten_invalid", prevten: label) @@ -172,7 +172,13 @@ module Validations::HouseholdValidations def validate_referral(record) return unless record.owning_organisation - if record.is_internal_transfer? && record.owning_organisation.provider_type == "PRP" && record.is_prevten_la_general_needs? + if record.form.start_year_2026_or_later? + if record.is_internal_transfer? && record.owning_organisation.la? && ![30, 31, 32, 33, 35, 38, 6].include?(record.prevten) + label = record.form.get_question("prevten", record).present? ? record.form.get_question("prevten", record).label_from_value(record.prevten) : "" + record.errors.add :prevten, message: I18n.t("validations.lettings.household.prevten.general_needs.internal_transfer", prevten: label) + record.errors.add :referral_register, message: I18n.t("validations.lettings.household.referral.general_needs.internal_transfer", prevten: label) + end + elsif record.is_internal_transfer? && record.owning_organisation.provider_type == "PRP" && record.is_prevten_la_general_needs? record.errors.add :prevten, :internal_transfer_fixed_or_lifetime, message: I18n.t("validations.lettings.household.prevten.la_general_needs.internal_transfer") record.errors.add :referral, :internal_transfer_fixed_or_lifetime, message: I18n.t("validations.lettings.household.referral.la_general_needs.internal_transfer") end diff --git a/config/locales/validations/lettings/household.en.yml b/config/locales/validations/lettings/household.en.yml index 297cbed02..2b159a1c4 100644 --- a/config/locales/validations/lettings/household.en.yml +++ b/config/locales/validations/lettings/household.en.yml @@ -98,8 +98,12 @@ en: internal_transfer: "Answer cannot be %{prevten} as this tenancy is an internal transfer." la_general_needs: internal_transfer: "Answer cannot be a fixed-term or lifetime local authority general needs tenancy as it’s an internal transfer and a private registered provider is on the tenancy agreement." + general_needs: + internal_transfer: "Answer cannot be %{prevten} as this tenancy is an internal transfer. Internal transfers are for existing social tenants only, your answer to where the household was immediately before this letting shows the tenant was not in the social sector immediately prior to this letting." referral: prevten_invalid: "Answer cannot be internal transfer as the household situation immediately before this letting was %{prevten}." la_general_needs: internal_transfer: "Answer cannot be internal transfer as it’s the same landlord on the tenancy agreement and the household had either a fixed-term or lifetime local authority general needs tenancy immediately before this letting." + general_needs: + internal_transfer: "Answer cannot be internal transfer as the household situation immediately before this letting was %{prevten}. Internal transfers are for existing social tenants only, your answer to where the household was immediately before this letting shows the tenant was not in the social sector immediately prior to this letting." From b75c290a1689c835903a8bd47899d6cef749c345 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 29 Jan 2026 17:19:44 +0000 Subject: [PATCH 18/40] CLDC-4188: Update tests --- .../pages/referral_register_la_spec.rb | 78 ++++++++++++++++ ..._spec.rb => referral_register_prp_spec.rb} | 4 +- .../questions/referral_register_spec.rb | 70 +++++++++++--- .../subsections/household_situation_spec.rb | 3 +- .../validations/household_validations_spec.rb | 93 +++++++++++++++++-- 5 files changed, 221 insertions(+), 27 deletions(-) create mode 100644 spec/models/form/lettings/pages/referral_register_la_spec.rb rename spec/models/form/lettings/pages/{referral_register_spec.rb => referral_register_prp_spec.rb} (85%) diff --git a/spec/models/form/lettings/pages/referral_register_la_spec.rb b/spec/models/form/lettings/pages/referral_register_la_spec.rb new file mode 100644 index 000000000..1f2459cbd --- /dev/null +++ b/spec/models/form/lettings/pages/referral_register_la_spec.rb @@ -0,0 +1,78 @@ +require "rails_helper" + +RSpec.describe Form::Lettings::Pages::ReferralRegisterLa, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form:) } + let(:form) { instance_double(Form, start_date: Time.zone.today) } + let(:la?) { nil } + let(:organisation) { instance_double(Organisation, la?: la?) } + let(:is_renewal?) { nil } + let(:log) { instance_double(LettingsLog, is_renewal?: is_renewal?, owning_organisation: organisation) } + + before do + allow(subsection).to receive(:form).and_return(form) + end + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[referral_register]) + end + + it "has the correct id" do + expect(page.id).to eq("referral_register_la") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has the correct depends_on" do + expect(page.depends_on).to be nil + end + + context "when log is a renewal" do + let(:is_renewal?) { true } + + context "and log owning organisation is la" do + let(:la?) { true } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + + context "and log owning organisation is not an la" do + let(:la?) { false } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + end + + context "when log is not a renewal" do + let(:is_renewal?) { false } + + context "and log owning organisation is la" do + let(:la?) { true } + + it "is routed to" do + expect(page.routed_to?(log, nil)).to be true + end + end + + context "and log owning organisation is not an la" do + let(:la?) { false } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + end +end diff --git a/spec/models/form/lettings/pages/referral_register_spec.rb b/spec/models/form/lettings/pages/referral_register_prp_spec.rb similarity index 85% rename from spec/models/form/lettings/pages/referral_register_spec.rb rename to spec/models/form/lettings/pages/referral_register_prp_spec.rb index 28981e03d..e62d54e47 100644 --- a/spec/models/form/lettings/pages/referral_register_spec.rb +++ b/spec/models/form/lettings/pages/referral_register_prp_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe Form::Lettings::Pages::ReferralRegister, type: :model do +RSpec.describe Form::Lettings::Pages::ReferralRegisterPrp, type: :model do subject(:page) { described_class.new(page_id, page_definition, subsection) } let(:page_id) { nil } @@ -21,7 +21,7 @@ RSpec.describe Form::Lettings::Pages::ReferralRegister, type: :model do end it "has the correct id" do - expect(page.id).to eq("referral_register") + expect(page.id).to eq("referral_register_prp") end it "has the correct description" do diff --git a/spec/models/form/lettings/questions/referral_register_spec.rb b/spec/models/form/lettings/questions/referral_register_spec.rb index 6d5ad3ffd..112de43e0 100644 --- a/spec/models/form/lettings/questions/referral_register_spec.rb +++ b/spec/models/form/lettings/questions/referral_register_spec.rb @@ -1,13 +1,16 @@ require "rails_helper" RSpec.describe Form::Lettings::Questions::ReferralRegister, type: :model do - subject(:question) { described_class.new(question_id, question_definition, page) } + subject(:question) { described_class.new(question_id, question_definition, page, provider_type) } let(:question_id) { nil } let(:question_definition) { nil } let(:page) { instance_double(Form::Page) } + let(:provider_type) { nil } let(:subsection) { instance_double(Form::Subsection) } let(:form) { instance_double(Form, start_date: Time.zone.today) } + let(:is_renewal?) { nil } + let(:log) { instance_double(LettingsLog, is_renewal?: is_renewal?) } before do allow(page).to receive(:subsection).and_return(subsection) @@ -26,21 +29,60 @@ RSpec.describe Form::Lettings::Questions::ReferralRegister, type: :model do expect(question.type).to eq("radio") end - it "is not marked as derived" do - expect(question.derived?(nil)).to be false + context "when log is a renewal" do + let(:is_renewal?) { true } + + it "is marked as derived" do + expect(question.derived?(log)).to be true + end + end + + context "when log is not a renewal" do + let(:is_renewal?) { false } + + it "is not marked as derived" do + expect(question.derived?(log)).to be false + end end - it "has the correct answer_options" do - expect(question.answer_options).to eq( - { - "1" => { - "value" => "Answer A", - }, - "2" => { - "value" => "Answer B", - }, - }.freeze, - ) + context "when log is owned by an LA" do + let(:provider_type) { :la } + + it "has the correct answer_options" do + expect(question.answer_options).to eq( + { + "1" => { + "value" => "Renewal to the same tenant in the same property", + }, + "2" => { + "value" => "Internal transfer from another property owned by the same local authority - for existing social tenants only", + }, + "3" => { + "value" => "From a housing register (waiting list)", + }, + "4" => { + "value" => "Tenant applied directly (not via a nomination or housing register)", + }, + }.freeze, + ) + end + end + + context "when log is owned by an PRP" do + let(:provider_type) { :prp } + + it "has the correct answer_options" do + expect(question.answer_options).to eq( + { + "1" => { + "value" => "Answer A", + }, + "2" => { + "value" => "Answer B", + }, + }.freeze, + ) + end end it "has the correct question_number" do diff --git a/spec/models/form/lettings/subsections/household_situation_spec.rb b/spec/models/form/lettings/subsections/household_situation_spec.rb index 985ac1783..909046719 100644 --- a/spec/models/form/lettings/subsections/household_situation_spec.rb +++ b/spec/models/form/lettings/subsections/household_situation_spec.rb @@ -107,7 +107,8 @@ RSpec.describe Form::Lettings::Subsections::HouseholdSituation, type: :model do reasonable_preference reasonable_preference_reason allocation_system - referral_register + referral_register_la + referral_register_prp referral_noms referral_org ], diff --git a/spec/models/validations/household_validations_spec.rb b/spec/models/validations/household_validations_spec.rb index f79f2c6e4..7ae866800 100644 --- a/spec/models/validations/household_validations_spec.rb +++ b/spec/models/validations/household_validations_spec.rb @@ -1,6 +1,8 @@ require "rails_helper" RSpec.describe Validations::HouseholdValidations do + include CollectionTimeHelper + subject(:household_validator) { validator_class.new } let(:validator_class) { Class.new { include Validations::HouseholdValidations } } @@ -488,6 +490,60 @@ RSpec.describe Validations::HouseholdValidations do expect(record.errors["homeless"]).to be_empty end end + + context "when start year is 2026 and record is internal transfer and owning organisation is LA" do + let(:startdate) { collection_start_date_for_year(2026) } + + [ + { code: 3, label: "Private sector tenancy" }, + { code: 27, label: "Owner occupation (low-cost home ownership)" }, + { code: 26, label: "Owner occupation (private)" }, + { code: 28, label: "Living with friends or family (long-term)" }, + { code: 39, label: "Sofa surfing (moving regularly between family or friends, no permanent bed)" }, + { code: 14, label: "Bed and breakfast" }, + { code: 7, label: "Direct access hostel" }, + { code: 10, label: "Hospital" }, + { code: 29, label: "Prison or approved probation hostel" }, + { code: 19, label: "Rough sleeping" }, + { code: 18, label: "Any other temporary accommodation" }, + { code: 13, label: "Children’s home or foster care" }, + { code: 24, label: "Home Office Asylum Support" }, + { code: 37, label: "Host family or similar refugee accommodation" }, + { code: 23, label: "Mobile home or caravan" }, + { code: 21, label: "Refuge" }, + { code: 9, label: "Residential care home" }, + { code: 4, label: "Tied housing or rented with job" }, + { code: 25, label: "Any other accommodation" }, + ].each do |prevten| + it "prevten cannot be #{prevten[:code]}" do + record.referral_register = 2 + record.prevten = prevten[:code] + household_validator.validate_referral(record) + expect(record.errors["prevten"]) + .to include(match I18n.t("validations.lettings.household.prevten.general_needs.internal_transfer", prevten: prevten[:label])) + expect(record.errors["referral_register"]) + .to include(match I18n.t("validations.lettings.household.referral.general_needs.internal_transfer", prevten: prevten[:label])) + end + end + + [ + { code: 30, label: "Fixed-term local authority general needs tenancy" }, + { code: 31, label: "Lifetime local authority general needs tenancy" }, + { code: 32, label: "Fixed-term private registered provider (PRP) general needs tenancy" }, + { code: 33, label: "Lifetime private registered provider (PRP) general needs tenancy" }, + { code: 35, label: "Extra care housing" }, + { code: 38, label: "Older people’s housing for tenants with low support needs" }, + { code: 6, label: "Other supported housing" }, + ].each do |prevten| + it "prevten can be #{prevten[:code]}" do + record.referral_register = 2 + record.prevten = prevten[:code] + household_validator.validate_referral(record) + expect(record.errors["prevten"]).to be_empty + expect(record.errors["referral"]).to be_empty + end + end + end end describe "la validations" do @@ -593,16 +649,33 @@ RSpec.describe Validations::HouseholdValidations do { code: 28, label: "Living with friends or family" }, { code: 29, label: "Prison or approved probation hostel" }, ].each do |prevten| - it "prevten cannot be #{prevten[:code]}" do - record.referral_type = 3 - record.referral = 1 - record.prevten = prevten[:code] - household_validator.validate_previous_housing_situation(record) - label = record.form.start_year_2025_or_later? && prevten[:code] == 28 ? "Living with friends or family (long-term)" : prevten[:label] - expect(record.errors["prevten"]) - .to include(match I18n.t("validations.lettings.household.prevten.internal_transfer", prevten: label)) - expect(record.errors["referral"]) - .to include(match I18n.t("validations.lettings.household.referral.prevten_invalid", prevten: "")) + context "when year is 2025" do + let(:startdate) { collection_start_date_for_year(2025) } + + it "prevten cannot be #{prevten[:code]}" do + record.referral_type = 3 + record.referral = 1 + record.prevten = prevten[:code] + household_validator.validate_previous_housing_situation(record) + label = record.form.start_year_2025_or_later? && prevten[:code] == 28 ? "Living with friends or family (long-term)" : prevten[:label] + expect(record.errors["prevten"]) + .to include(match I18n.t("validations.lettings.household.prevten.internal_transfer", prevten: label)) + expect(record.errors["referral"]) + .to include(match I18n.t("validations.lettings.household.referral.prevten_invalid", prevten: "")) + end + end + + context "when year is 2026" do + let(:startdate) { collection_start_date_for_year(2026) } + + it "prevten can be #{prevten[:code]}" do + record.referral_type = 3 + record.referral = 1 + record.prevten = prevten[:code] + household_validator.validate_previous_housing_situation(record) + expect(record.errors["prevten"]).to be_empty + expect(record.errors["referral"]).to be_empty + end end end end From 7174d0f722a5d467181c87b302e77b283e08afa8 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Fri, 30 Jan 2026 11:28:53 +0000 Subject: [PATCH 19/40] CLDC-4188: Ignore tests failing for future tickets --- .../lettings/year2026/row_parser_spec.rb | 44 ++----------------- 1 file changed, 4 insertions(+), 40 deletions(-) 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 dfdc71552..ff5df6bc4 100644 --- a/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb @@ -1135,46 +1135,10 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - describe "#field_116" do # referral - context "when 3 ie PRP nominated by LA and owning org is LA" do - let(:attributes) { { bulk_upload:, field_116: "3", field_1: owning_org.old_visible_id, field_2: owning_org.old_visible_id } } - - it "is not permitted" do - parser.valid? - expect(parser.errors[:field_116]).to be_present - end - end - - context "when 4 ie referred by LA and is general needs and owning org is LA" do - let(:attributes) { { bulk_upload:, field_116: "4", field_1: owning_org.old_visible_id, field_2: owning_org.old_visible_id, field_4: "1" } } - - it "is not permitted" do - parser.valid? - expect(parser.errors[:field_116]).to be_present - end - end - - context "when 4 ie referred by LA and is general needs and owning org is PRP" do - let(:owning_org) { create(:organisation, :prp, :with_old_visible_id) } - - let(:attributes) { { bulk_upload:, field_116: "4", field_1: owning_org.old_visible_id, field_2: owning_org.old_visible_id } } - - it "is permitted" do - parser.valid? - expect(parser.errors[:field_116]).to be_blank - end - end - - context "when 4 ie referred by LA and is not general needs" do - let(:bulk_upload) { create(:bulk_upload, :lettings, user:) } - let(:attributes) { { bulk_upload:, field_116: "4", field_4: "2" } } - - it "is permitted" do - parser.valid? - expect(parser.errors[:field_116]).to be_blank - end - end - end + # TODO: CLDC-4191: Add tests for the new referral fields + # describe "#field_116" do # referral + # + # end describe "fields 7, 8, 9 => startdate" do context "when any one of these fields is blank" do From f986fc485d5014ef599d2696fe32e22bffd3f25a Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Fri, 30 Jan 2026 12:12:21 +0000 Subject: [PATCH 20/40] CLDC-4188: Update existing tests needed as should_reset_referral_register? calls a .find() --- .../merge/merge_organisations_service_spec.rb | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/services/merge/merge_organisations_service_spec.rb b/spec/services/merge/merge_organisations_service_spec.rb index 3f0ee4b1c..5e47daba6 100644 --- a/spec/services/merge/merge_organisations_service_spec.rb +++ b/spec/services/merge/merge_organisations_service_spec.rb @@ -56,6 +56,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(absorbing_organisation.id).and_return(absorbing_organisation) allow(absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -100,6 +101,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(absorbing_organisation.id).and_return(absorbing_organisation) allow(absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -137,6 +139,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(absorbing_organisation.id).and_return(absorbing_organisation) allow(absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -557,6 +560,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(absorbing_organisation.id).and_return(absorbing_organisation) allow(absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -590,6 +594,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(absorbing_organisation.id).and_return(absorbing_organisation) allow(absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -652,6 +657,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(absorbing_organisation.id).and_return(absorbing_organisation) allow(absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -688,6 +694,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(absorbing_organisation.id).and_return(absorbing_organisation) allow(absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -877,6 +884,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(absorbing_organisation.id).and_return(absorbing_organisation) allow(absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -996,6 +1004,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(absorbing_organisation.id).and_return(absorbing_organisation) allow(absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -1121,6 +1130,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(absorbing_organisation.id).and_return(absorbing_organisation) allow(absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -1191,6 +1201,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(new_absorbing_organisation.id).and_return(new_absorbing_organisation) allow(new_absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -1235,6 +1246,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(new_absorbing_organisation.id).and_return(new_absorbing_organisation) allow(new_absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -1272,6 +1284,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(new_absorbing_organisation.id).and_return(new_absorbing_organisation) allow(new_absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -1377,6 +1390,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(new_absorbing_organisation.id).and_return(new_absorbing_organisation) allow(new_absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -1409,6 +1423,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(new_absorbing_organisation.id).and_return(new_absorbing_organisation) allow(new_absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -1448,6 +1463,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(new_absorbing_organisation.id).and_return(new_absorbing_organisation) allow(new_absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -1484,6 +1500,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(new_absorbing_organisation.id).and_return(new_absorbing_organisation) allow(new_absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -1590,6 +1607,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(new_absorbing_organisation.id).and_return(new_absorbing_organisation) allow(new_absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -1680,6 +1698,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(new_absorbing_organisation.id).and_return(new_absorbing_organisation) allow(new_absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") @@ -1722,6 +1741,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "rolls back if there's an error" do allow(Organisation).to receive(:find).with(merging_organisation_ids).and_return(Organisation.find(merging_organisation_ids)) + allow(Organisation).to receive(:find).with(merging_organisation.id).and_return(merging_organisation) allow(Organisation).to receive(:find).with(new_absorbing_organisation.id).and_return(new_absorbing_organisation) allow(new_absorbing_organisation).to receive(:save!).and_raise(ActiveRecord::RecordInvalid) expect(Rails.logger).to receive(:error).with("Organisation merge failed with: Record invalid") From c3bfc33f5b05abb7d37c5e88823cdca80bde2f74 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 2 Feb 2026 14:17:39 +0000 Subject: [PATCH 21/40] fixup! CLDC-4188: Add validation between prevten and referral_register use .prp? improve grammar --- app/models/validations/household_validations.rb | 2 +- config/locales/validations/lettings/household.en.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/validations/household_validations.rb b/app/models/validations/household_validations.rb index f29ad7877..2c2b46831 100644 --- a/app/models/validations/household_validations.rb +++ b/app/models/validations/household_validations.rb @@ -178,7 +178,7 @@ module Validations::HouseholdValidations record.errors.add :prevten, message: I18n.t("validations.lettings.household.prevten.general_needs.internal_transfer", prevten: label) record.errors.add :referral_register, message: I18n.t("validations.lettings.household.referral.general_needs.internal_transfer", prevten: label) end - elsif record.is_internal_transfer? && record.owning_organisation.provider_type == "PRP" && record.is_prevten_la_general_needs? + elsif record.is_internal_transfer? && record.owning_organisation.prp? && record.is_prevten_la_general_needs? record.errors.add :prevten, :internal_transfer_fixed_or_lifetime, message: I18n.t("validations.lettings.household.prevten.la_general_needs.internal_transfer") record.errors.add :referral, :internal_transfer_fixed_or_lifetime, message: I18n.t("validations.lettings.household.referral.la_general_needs.internal_transfer") end diff --git a/config/locales/validations/lettings/household.en.yml b/config/locales/validations/lettings/household.en.yml index 2b159a1c4..3fe1d244f 100644 --- a/config/locales/validations/lettings/household.en.yml +++ b/config/locales/validations/lettings/household.en.yml @@ -99,11 +99,11 @@ en: la_general_needs: internal_transfer: "Answer cannot be a fixed-term or lifetime local authority general needs tenancy as it’s an internal transfer and a private registered provider is on the tenancy agreement." general_needs: - internal_transfer: "Answer cannot be %{prevten} as this tenancy is an internal transfer. Internal transfers are for existing social tenants only, your answer to where the household was immediately before this letting shows the tenant was not in the social sector immediately prior to this letting." + internal_transfer: "Answer cannot be %{prevten} as this tenancy is an internal transfer. Internal transfers are for existing social tenants only; your answer to where the household was immediately before this letting shows the tenant was not in the social sector immediately prior to this letting." referral: prevten_invalid: "Answer cannot be internal transfer as the household situation immediately before this letting was %{prevten}." la_general_needs: internal_transfer: "Answer cannot be internal transfer as it’s the same landlord on the tenancy agreement and the household had either a fixed-term or lifetime local authority general needs tenancy immediately before this letting." general_needs: - internal_transfer: "Answer cannot be internal transfer as the household situation immediately before this letting was %{prevten}. Internal transfers are for existing social tenants only, your answer to where the household was immediately before this letting shows the tenant was not in the social sector immediately prior to this letting." + internal_transfer: "Answer cannot be internal transfer as the household situation immediately before this letting was %{prevten}. Internal transfers are for existing social tenants only; your answer to where the household was immediately before this letting shows the tenant was not in the social sector immediately prior to this letting." From e4dc36fb6672edf5c03a8bd93bebb3a6caa09b48 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 2 Feb 2026 14:26:02 +0000 Subject: [PATCH 22/40] fixup! CLDC-4188: Infer referral_register for renewals extract dependencies to common list --- .../lettings_log_variables.rb | 51 ++++++------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/app/models/derived_variables/lettings_log_variables.rb b/app/models/derived_variables/lettings_log_variables.rb index 360dbf03b..14861226f 100644 --- a/app/models/derived_variables/lettings_log_variables.rb +++ b/app/models/derived_variables/lettings_log_variables.rb @@ -181,23 +181,11 @@ private if form.start_year_2026_or_later? DEPENDENCIES_2026 else - DEPENDENCIES_PRE_2026 + DEPENDENCIES_2025_2024 end end - DEPENDENCIES_2026 = [ - { - conditions: { - renewal: 1, - }, - derived_values: { - referral_register: 1, # new in 2026 - waityear: 2, - offered: 0, - rsnvac: 14, - first_time_property_let_as_social_housing: 0, - }, - }, + COMMON_DEPENDENCIES = [ { conditions: { net_income_known: 2, @@ -224,44 +212,37 @@ private }, ].freeze - DEPENDENCIES_PRE_2026 = [ + DEPENDENCIES_2026 = [ { conditions: { renewal: 1, }, derived_values: { - referral: 1, - referral_type: 3, + referral_register: 1, waityear: 2, offered: 0, rsnvac: 14, first_time_property_let_as_social_housing: 0, }, }, + *COMMON_DEPENDENCIES, + ].freeze + + DEPENDENCIES_2025_2024 = [ { conditions: { - net_income_known: 2, - }, - derived_values: { - incref: 1, - }, - }, - { - conditions: { - net_income_known: 0, - }, - derived_values: { - incref: 0, - }, - }, - { - conditions: { - net_income_known: 1, + renewal: 1, }, derived_values: { - incref: 2, + referral: 1, + referral_type: 3, + waityear: 2, + offered: 0, + rsnvac: 14, + first_time_property_let_as_social_housing: 0, }, }, + *COMMON_DEPENDENCIES, ].freeze def clear_inapplicable_derived_values! From eb1b02f8f811195817f6ca5f6c06b47a840761d0 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 2 Feb 2026 17:07:58 +0000 Subject: [PATCH 23/40] fixup! CLDC-4188: Update tests use before and context blocks better --- .../validations/household_validations_spec.rb | 91 +++++++++++++------ 1 file changed, 65 insertions(+), 26 deletions(-) diff --git a/spec/models/validations/household_validations_spec.rb b/spec/models/validations/household_validations_spec.rb index 7ae866800..aacb98592 100644 --- a/spec/models/validations/household_validations_spec.rb +++ b/spec/models/validations/household_validations_spec.rb @@ -494,6 +494,11 @@ RSpec.describe Validations::HouseholdValidations do context "when start year is 2026 and record is internal transfer and owning organisation is LA" do let(:startdate) { collection_start_date_for_year(2026) } + before do + record.owning_organisation.provider_type = "LA" + record.referral_register = 2 + end + [ { code: 3, label: "Private sector tenancy" }, { code: 27, label: "Owner occupation (low-cost home ownership)" }, @@ -515,14 +520,18 @@ RSpec.describe Validations::HouseholdValidations do { code: 4, label: "Tied housing or rented with job" }, { code: 25, label: "Any other accommodation" }, ].each do |prevten| - it "prevten cannot be #{prevten[:code]}" do - record.referral_register = 2 - record.prevten = prevten[:code] - household_validator.validate_referral(record) - expect(record.errors["prevten"]) - .to include(match I18n.t("validations.lettings.household.prevten.general_needs.internal_transfer", prevten: prevten[:label])) - expect(record.errors["referral_register"]) - .to include(match I18n.t("validations.lettings.household.referral.general_needs.internal_transfer", prevten: prevten[:label])) + context "and prevten is #{prevten[:code]}" do + before do + record.prevten = prevten[:code] + end + + it "adds an error" do + household_validator.validate_referral(record) + expect(record.errors["prevten"]) + .to include(match I18n.t("validations.lettings.household.prevten.general_needs.internal_transfer", prevten: prevten[:label])) + expect(record.errors["referral_register"]) + .to include(match I18n.t("validations.lettings.household.referral.general_needs.internal_transfer", prevten: prevten[:label])) + end end end @@ -535,12 +544,16 @@ RSpec.describe Validations::HouseholdValidations do { code: 38, label: "Older people’s housing for tenants with low support needs" }, { code: 6, label: "Other supported housing" }, ].each do |prevten| - it "prevten can be #{prevten[:code]}" do - record.referral_register = 2 - record.prevten = prevten[:code] - household_validator.validate_referral(record) - expect(record.errors["prevten"]).to be_empty - expect(record.errors["referral"]).to be_empty + context "and prevten is #{prevten[:code]}" do + before do + record.prevten = prevten[:code] + end + + it "does not add an error" do + household_validator.validate_referral(record) + expect(record.errors["prevten"]).to be_empty + expect(record.errors["referral"]).to be_empty + end end end end @@ -620,10 +633,15 @@ RSpec.describe Validations::HouseholdValidations do end end - context "when the referral is internal transfer" do - it "prevten can be 9" do + context "when the referral is internal transfer for 2025" do + let(:startdate) { collection_start_date_for_year(2025) } + + before do record.referral_type = 3 record.referral = 1 + end + + it "prevten can be 9" do record.prevten = 9 household_validator.validate_previous_housing_situation(record) expect(record.errors["prevten"]) @@ -649,12 +667,9 @@ RSpec.describe Validations::HouseholdValidations do { code: 28, label: "Living with friends or family" }, { code: 29, label: "Prison or approved probation hostel" }, ].each do |prevten| - context "when year is 2025" do - let(:startdate) { collection_start_date_for_year(2025) } + context "and prevten is #{prevten}" do - it "prevten cannot be #{prevten[:code]}" do - record.referral_type = 3 - record.referral = 1 + it "adds an error" do record.prevten = prevten[:code] household_validator.validate_previous_housing_situation(record) label = record.form.start_year_2025_or_later? && prevten[:code] == 28 ? "Living with friends or family (long-term)" : prevten[:label] @@ -664,14 +679,38 @@ RSpec.describe Validations::HouseholdValidations do .to include(match I18n.t("validations.lettings.household.referral.prevten_invalid", prevten: "")) end end + end + end - context "when year is 2026" do - let(:startdate) { collection_start_date_for_year(2026) } + context "when the referral is internal transfer for 2026" do + let(:startdate) { collection_start_date_for_year(2026) } + + before do + record.owning_organisation.provider_type = "LA" + record.referral_register = 2 + end - it "prevten can be #{prevten[:code]}" do - record.referral_type = 3 - record.referral = 1 + [ + { code: 3, label: "Private sector tenancy" }, + { code: 4, label: "Tied housing or rented with job" }, + { code: 7, label: "Direct access hostel" }, + { code: 10, label: "Hospital" }, + { code: 13, label: "Children’s home or foster care" }, + { code: 14, label: "Bed and breakfast" }, + { code: 19, label: "Rough sleeping" }, + { code: 23, label: "Mobile home or caravan" }, + { code: 24, label: "Home Office Asylum Support" }, + { code: 25, label: "Any other accommodation" }, + { code: 26, label: "Owner occupation (private)" }, + { code: 28, label: "Living with friends or family" }, + { code: 29, label: "Prison or approved probation hostel" }, + ].each do |prevten| + context "and prevten is #{prevten}" do + before do record.prevten = prevten[:code] + end + + it "does not add an error" do household_validator.validate_previous_housing_situation(record) expect(record.errors["prevten"]).to be_empty expect(record.errors["referral"]).to be_empty From c0797108dd1663e15623ece7522e3dafd3697b2b Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 2 Feb 2026 17:33:37 +0000 Subject: [PATCH 24/40] fixup! CLDC-4188: Update tests lint --- spec/models/validations/household_validations_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/models/validations/household_validations_spec.rb b/spec/models/validations/household_validations_spec.rb index aacb98592..2aa95b297 100644 --- a/spec/models/validations/household_validations_spec.rb +++ b/spec/models/validations/household_validations_spec.rb @@ -668,7 +668,6 @@ RSpec.describe Validations::HouseholdValidations do { code: 29, label: "Prison or approved probation hostel" }, ].each do |prevten| context "and prevten is #{prevten}" do - it "adds an error" do record.prevten = prevten[:code] household_validator.validate_previous_housing_situation(record) From 5b4b83bdada6e52813943ed670bdd31d2c96ce41 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 3 Feb 2026 09:45:30 +0000 Subject: [PATCH 25/40] fixup! CLDC-4188: Update tests remove unneeded referral_type set --- spec/models/validations/household_validations_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/models/validations/household_validations_spec.rb b/spec/models/validations/household_validations_spec.rb index 2aa95b297..78f27ed3f 100644 --- a/spec/models/validations/household_validations_spec.rb +++ b/spec/models/validations/household_validations_spec.rb @@ -637,7 +637,6 @@ RSpec.describe Validations::HouseholdValidations do let(:startdate) { collection_start_date_for_year(2025) } before do - record.referral_type = 3 record.referral = 1 end From b4e18dcf661e5d45b80f45b61469f82ac0be1ed8 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 29 Jan 2026 16:57:58 +0000 Subject: [PATCH 26/40] CLDC-4189: Add PRP flow --- .../{referral_noms.rb => referral_noms_hr.rb} | 8 +- .../lettings/pages/referral_noms_la_hr.rb | 15 ++++ .../form/lettings/pages/referral_org.rb | 15 ---- .../pages/referral_org_directly_referred.rb | 15 ++++ .../lettings/pages/referral_org_nominated.rb | 15 ++++ .../lettings/pages/referral_register_prp.rb | 2 +- .../form/lettings/questions/referral_noms.rb | 45 ++++++++--- .../form/lettings/questions/referral_org.rb | 81 ++++++++++++++++--- .../lettings/questions/referral_register.rb | 17 +++- .../subsections/household_situation.rb | 6 +- app/models/lettings_log.rb | 16 ++++ 11 files changed, 189 insertions(+), 46 deletions(-) rename app/models/form/lettings/pages/{referral_noms.rb => referral_noms_hr.rb} (50%) create mode 100644 app/models/form/lettings/pages/referral_noms_la_hr.rb delete mode 100644 app/models/form/lettings/pages/referral_org.rb create mode 100644 app/models/form/lettings/pages/referral_org_directly_referred.rb create mode 100644 app/models/form/lettings/pages/referral_org_nominated.rb diff --git a/app/models/form/lettings/pages/referral_noms.rb b/app/models/form/lettings/pages/referral_noms_hr.rb similarity index 50% rename from app/models/form/lettings/pages/referral_noms.rb rename to app/models/form/lettings/pages/referral_noms_hr.rb index fa811e961..d1120bffd 100644 --- a/app/models/form/lettings/pages/referral_noms.rb +++ b/app/models/form/lettings/pages/referral_noms_hr.rb @@ -1,15 +1,15 @@ # added in 2026 -class Form::Lettings::Pages::ReferralNoms < ::Form::Page +class Form::Lettings::Pages::ReferralNomsHr < ::Form::Page def initialize(id, hsh, subsection) super - @id = "referral_noms" + @id = "referral_noms_hr" end def questions - @questions ||= [Form::Lettings::Questions::ReferralNoms.new(nil, nil, self)] + @questions ||= [Form::Lettings::Questions::ReferralNoms.new(nil, nil, self, 7)] end def routed_to?(log, _current_user) - log.owning_organisation&.prp? + log.owning_organisation&.prp? && !log.is_renewal? && log.referral_is_from_housing_register? end end diff --git a/app/models/form/lettings/pages/referral_noms_la_hr.rb b/app/models/form/lettings/pages/referral_noms_la_hr.rb new file mode 100644 index 000000000..cc0e27371 --- /dev/null +++ b/app/models/form/lettings/pages/referral_noms_la_hr.rb @@ -0,0 +1,15 @@ +# added in 2026 +class Form::Lettings::Pages::ReferralNomsLaHr < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "referral_noms_la_hr" + end + + def questions + @questions ||= [Form::Lettings::Questions::ReferralNoms.new(nil, nil, self, 6)] + end + + def routed_to?(log, _current_user) + log.owning_organisation&.prp? && !log.is_renewal? && log.referral_is_from_local_authority_housing_register? + end +end diff --git a/app/models/form/lettings/pages/referral_org.rb b/app/models/form/lettings/pages/referral_org.rb deleted file mode 100644 index fe23140d8..000000000 --- a/app/models/form/lettings/pages/referral_org.rb +++ /dev/null @@ -1,15 +0,0 @@ -# added in 2026 -class Form::Lettings::Pages::ReferralOrg < ::Form::Page - def initialize(id, hsh, subsection) - super - @id = "referral_org" - end - - def questions - @questions ||= [Form::Lettings::Questions::ReferralOrg.new(nil, nil, self)] - end - - def routed_to?(log, _current_user) - log.owning_organisation&.prp? - end -end diff --git a/app/models/form/lettings/pages/referral_org_directly_referred.rb b/app/models/form/lettings/pages/referral_org_directly_referred.rb new file mode 100644 index 000000000..baf4c2747 --- /dev/null +++ b/app/models/form/lettings/pages/referral_org_directly_referred.rb @@ -0,0 +1,15 @@ +# added in 2026 +class Form::Lettings::Pages::ReferralOrgDirectlyReferred < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "referral_org_directly_referred" + end + + def questions + @questions ||= [Form::Lettings::Questions::ReferralOrg.new(nil, nil, self, 7)] + end + + def routed_to?(log, _current_user) + log.owning_organisation&.prp? && !log.is_renewal? && log.referral_is_directly_referred? + end +end diff --git a/app/models/form/lettings/pages/referral_org_nominated.rb b/app/models/form/lettings/pages/referral_org_nominated.rb new file mode 100644 index 000000000..8be5f8edc --- /dev/null +++ b/app/models/form/lettings/pages/referral_org_nominated.rb @@ -0,0 +1,15 @@ +# added in 2026 +class Form::Lettings::Pages::ReferralOrgNominated < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "referral_org_nominated" + end + + def questions + @questions ||= [Form::Lettings::Questions::ReferralOrg.new(nil, nil, self, 1)] + end + + def routed_to?(log, _current_user) + log.owning_organisation&.prp? && !log.is_renewal? && log.referral_is_nominated_by_local_authority? + end +end diff --git a/app/models/form/lettings/pages/referral_register_prp.rb b/app/models/form/lettings/pages/referral_register_prp.rb index 7191937c8..d0c5cdaf1 100644 --- a/app/models/form/lettings/pages/referral_register_prp.rb +++ b/app/models/form/lettings/pages/referral_register_prp.rb @@ -10,6 +10,6 @@ class Form::Lettings::Pages::ReferralRegisterPrp < ::Form::Page end def routed_to?(log, _current_user) - log.owning_organisation&.prp? + log.owning_organisation&.prp? && !log.is_renewal? end end diff --git a/app/models/form/lettings/questions/referral_noms.rb b/app/models/form/lettings/questions/referral_noms.rb index 8c1ea88db..87083c148 100644 --- a/app/models/form/lettings/questions/referral_noms.rb +++ b/app/models/form/lettings/questions/referral_noms.rb @@ -1,23 +1,48 @@ # added in 2026 class Form::Lettings::Questions::ReferralNoms < ::Form::Question - def initialize(id, hsh, page) - super + def initialize(id, hsh, page, referral_register) + super(id, hsh, page) @id = "referral_noms" @copy_key = "lettings.household_situation.referral.noms" @type = "radio" @check_answers_card_number = 0 @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + @referral_register = referral_register end def answer_options - { - "1" => { - "value" => "Answer A", - }, - "2" => { - "value" => "Answer B", - }, - }.freeze + case @referral_register + when 6 + { + "1" => { + "value" => "Nominated by a local authority to a PRP", + }, + "2" => { + "value" => "Supported housing only - referred by a local authority to a PRP", + }, + "3" => { + "value" => "Internal transfer from another property owned by the same PRP landlord - for existing social tenants only", + }, + "4" => { + "value" => "Other", + }, + }.freeze + when 7 + { + "5" => { + "value" => "Internal transfer from another property owned by the same PRP landlord - for existing social tenants only", + }, + "6" => { + "value" => " A different PRP landlord - for existing socail tenants only", + }, + "7" => { + "value" => "Directly referred by a third party", + }, + "8" => { + "value" => "Other", + }, + }.freeze + end end QUESTION_NUMBER_FROM_YEAR = { 2026 => 84 }.freeze diff --git a/app/models/form/lettings/questions/referral_org.rb b/app/models/form/lettings/questions/referral_org.rb index 2a8614970..e8bc730d8 100644 --- a/app/models/form/lettings/questions/referral_org.rb +++ b/app/models/form/lettings/questions/referral_org.rb @@ -1,23 +1,84 @@ # added in 2026 class Form::Lettings::Questions::ReferralOrg < ::Form::Question - def initialize(id, hsh, page) - super + def initialize(id, hsh, page, referral_noms) + super(id, hsh, page) @id = "referral_org" @copy_key = "lettings.household_situation.referral.org" @type = "radio" @check_answers_card_number = 0 @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + @referral_noms = referral_noms end def answer_options - { - "1" => { - "value" => "Answer A", - }, - "2" => { - "value" => "Answer B", - }, - }.freeze + case @referral_noms + when 1 + { + "1" => { + "value" => "Referred to LA by health service", + }, + "2" => { + "value" => "Referred to LA by community learning disability team", + }, + "3" => { + "value" => "Referred to LA by community mental health team", + }, + "4" => { + "value" => "Referred to LA by adult social services", + }, + "5" => { + "value" => "Referred to LA by children's social care", + }, + "6" => { + "value" => "Referred to LA by police, probation, prison or youth offending team following a custodial sentence", + }, + "7" => { + "value" => "Referred to LA by police, probation, prison or youth offending team without a custodial sentence", + }, + "8" => { + "value" => "Referred to LA by a voluntary agency", + }, + "9" => { + "value" => "Other referral", + }, + "10" => { + "value" => "Don't know", + }, + }.freeze + when 7 + { + "11" => { + "value" => "Health service", + }, + "12" => { + "value" => "Community learning disability team", + }, + "13" => { + "value" => "Community mental health team", + }, + "14" => { + "value" => "Adult social services", + }, + "15" => { + "value" => "Children's social care", + }, + "16" => { + "value" => "Police, probation, prison or youth offending team following a custodial sentence", + }, + "17" => { + "value" => "Police, probation, prison or youth offending team without a custodial sentence", + }, + "18" => { + "value" => "Voluntary agency", + }, + "19" => { + "value" => "Other third party", + }, + "20" => { + "value" => "Don't know", + }, + }.freeze + end end QUESTION_NUMBER_FROM_YEAR = { 2026 => 84 }.freeze diff --git a/app/models/form/lettings/questions/referral_register.rb b/app/models/form/lettings/questions/referral_register.rb index d6e184758..1e0964be0 100644 --- a/app/models/form/lettings/questions/referral_register.rb +++ b/app/models/form/lettings/questions/referral_register.rb @@ -28,11 +28,20 @@ class Form::Lettings::Questions::ReferralRegister < ::Form::Question }.freeze else { - "1" => { - "value" => "Answer A", + "5" => { + "value" => "Renewal to the same tenant in the same property", }, - "2" => { - "value" => "Answer B", + "6" => { + "value" => "From a local authority housing register (waiting list) or a register with local authority involvement", + }, + "7" => { + "value" => "From a housing register (waiting list) with no local authority involvement", + }, + "8" => { + "value" => "Tenant applied directly (not via a nomination or waiting list)", + }, + "9" => { + "value" => "Don't know", }, }.freeze end diff --git a/app/models/form/lettings/subsections/household_situation.rb b/app/models/form/lettings/subsections/household_situation.rb index be99907a0..cc6ec59de 100644 --- a/app/models/form/lettings/subsections/household_situation.rb +++ b/app/models/form/lettings/subsections/household_situation.rb @@ -30,8 +30,10 @@ class Form::Lettings::Subsections::HouseholdSituation < ::Form::Subsection [ Form::Lettings::Pages::ReferralRegisterLa.new(nil, nil, self), Form::Lettings::Pages::ReferralRegisterPrp.new(nil, nil, self), - Form::Lettings::Pages::ReferralNoms.new(nil, nil, self), - Form::Lettings::Pages::ReferralOrg.new(nil, nil, self), + Form::Lettings::Pages::ReferralNomsLaHr.new(nil, nil, self), + Form::Lettings::Pages::ReferralNomsHr.new(nil, nil, self), + Form::Lettings::Pages::ReferralOrgNominated.new(nil, nil, self), + Form::Lettings::Pages::ReferralOrgDirectlyReferred.new(nil, nil, self), ] elsif form.start_year_2025_or_later? [ diff --git a/app/models/lettings_log.rb b/app/models/lettings_log.rb index 5b7d4966b..d3cacb956 100644 --- a/app/models/lettings_log.rb +++ b/app/models/lettings_log.rb @@ -778,6 +778,22 @@ class LettingsLog < Log rsnvac != 15 && rsnvac_was == 15 end + def referral_is_from_local_authority_housing_register? + referral_register == 6 + end + + def referral_is_from_housing_register? + referral_register == 7 + end + + def referral_is_nominated_by_local_authority? + referral_is_from_local_authority_housing_register? && referral_noms == 1 + end + + def referral_is_directly_referred? + referral_is_from_housing_register? && referral_noms == 7 + end + private def reset_invalid_unresolved_log_fields! From 8f7e12899fe60a0ed1bd9850ed1577cdce2467c6 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Fri, 30 Jan 2026 10:47:12 +0000 Subject: [PATCH 27/40] CLDC-4189: Update validation between prevten and referral_register --- app/models/lettings_log.rb | 6 +++++- app/models/validations/household_validations.rb | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/models/lettings_log.rb b/app/models/lettings_log.rb index d3cacb956..3b593d590 100644 --- a/app/models/lettings_log.rb +++ b/app/models/lettings_log.rb @@ -379,7 +379,7 @@ class LettingsLog < Log def is_internal_transfer? if form.start_year_2026_or_later? - referral_register == 2 + referral_register == 2 || (referral_register == 6 && referral_noms == 3) || (referral_register == 7 && referral_noms == 5) else # 1: Internal Transfer referral == 1 @@ -550,6 +550,10 @@ class LettingsLog < Log [30, 31].any?(prevten) end + def is_prevten_general_needs? + ![30, 31, 32, 33, 35, 38, 6].include?(prevten) + end + def owning_organisation_name owning_organisation&.name end diff --git a/app/models/validations/household_validations.rb b/app/models/validations/household_validations.rb index 2c2b46831..cf1962be0 100644 --- a/app/models/validations/household_validations.rb +++ b/app/models/validations/household_validations.rb @@ -173,10 +173,11 @@ module Validations::HouseholdValidations return unless record.owning_organisation if record.form.start_year_2026_or_later? - if record.is_internal_transfer? && record.owning_organisation.la? && ![30, 31, 32, 33, 35, 38, 6].include?(record.prevten) + if record.is_internal_transfer? && record.is_prevten_general_needs? label = record.form.get_question("prevten", record).present? ? record.form.get_question("prevten", record).label_from_value(record.prevten) : "" record.errors.add :prevten, message: I18n.t("validations.lettings.household.prevten.general_needs.internal_transfer", prevten: label) record.errors.add :referral_register, message: I18n.t("validations.lettings.household.referral.general_needs.internal_transfer", prevten: label) + record.errors.add :referral_noms, message: I18n.t("validations.lettings.household.referral.general_needs.internal_transfer", prevten: label) end elsif record.is_internal_transfer? && record.owning_organisation.prp? && record.is_prevten_la_general_needs? record.errors.add :prevten, :internal_transfer_fixed_or_lifetime, message: I18n.t("validations.lettings.household.prevten.la_general_needs.internal_transfer") From bc706582cd15fd9ddb121ca674d392f8ee470485 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 29 Jan 2026 17:20:12 +0000 Subject: [PATCH 28/40] CLDC-4189: Add tests --- .../lettings/pages/referral_noms_hr_spec.rb | 79 +++++++++ .../pages/referral_noms_la_hr_spec.rb | 79 +++++++++ .../form/lettings/pages/referral_noms_spec.rb | 34 ---- .../referral_org_directly_referred_spec.rb | 71 ++++++++ .../pages/referral_org_nominated_spec.rb | 71 ++++++++ .../form/lettings/pages/referral_org_spec.rb | 34 ---- .../pages/referral_register_prp_spec.rb | 44 +++++ .../lettings/questions/referral_noms_spec.rb | 58 ++++-- .../lettings/questions/referral_org_spec.rb | 94 ++++++++-- .../questions/referral_register_spec.rb | 17 +- .../subsections/household_situation_spec.rb | 6 +- .../validations/household_validations_spec.rb | 166 ++++++++++++------ 12 files changed, 604 insertions(+), 149 deletions(-) create mode 100644 spec/models/form/lettings/pages/referral_noms_hr_spec.rb create mode 100644 spec/models/form/lettings/pages/referral_noms_la_hr_spec.rb delete mode 100644 spec/models/form/lettings/pages/referral_noms_spec.rb create mode 100644 spec/models/form/lettings/pages/referral_org_directly_referred_spec.rb create mode 100644 spec/models/form/lettings/pages/referral_org_nominated_spec.rb delete mode 100644 spec/models/form/lettings/pages/referral_org_spec.rb diff --git a/spec/models/form/lettings/pages/referral_noms_hr_spec.rb b/spec/models/form/lettings/pages/referral_noms_hr_spec.rb new file mode 100644 index 000000000..b3cd1e113 --- /dev/null +++ b/spec/models/form/lettings/pages/referral_noms_hr_spec.rb @@ -0,0 +1,79 @@ +require "rails_helper" + +RSpec.describe Form::Lettings::Pages::ReferralNomsHr, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form:) } + let(:form) { instance_double(Form, start_date: Time.zone.today) } + let(:prp?) { nil } + let(:organisation) { instance_double(Organisation, prp?: prp?) } + let(:is_renewal?) { nil } + let(:referral_is_from_housing_register?) { nil } + let(:log) { instance_double(LettingsLog, is_renewal?: is_renewal?, owning_organisation: organisation, referral_is_from_housing_register?: referral_is_from_housing_register?) } + + before do + allow(subsection).to receive(:form).and_return(form) + end + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[referral_noms]) + end + + it "has the correct id" do + expect(page.id).to eq("referral_noms_hr") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has the correct depends_on" do + expect(page.depends_on).to be nil + end + + context "and log owning organisation is not prp" do + let(:prp?) { false } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + + context "and log owning organisation is prp" do + let(:prp?) { true } + + context "when log is a renewal" do + let(:is_renewal?) { true } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + + context "and log is not a renewal" do + let(:is_renewal?) { false } + + context "and log referral is not from housing register" do + let(:referral_is_from_housing_register?) { false } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + + context "and log referral is from housing register" do + let(:referral_is_from_housing_register?) { true } + + it "is routed to" do + expect(page.routed_to?(log, nil)).to be true + end + end + end + end +end diff --git a/spec/models/form/lettings/pages/referral_noms_la_hr_spec.rb b/spec/models/form/lettings/pages/referral_noms_la_hr_spec.rb new file mode 100644 index 000000000..91fb8cadb --- /dev/null +++ b/spec/models/form/lettings/pages/referral_noms_la_hr_spec.rb @@ -0,0 +1,79 @@ +require "rails_helper" + +RSpec.describe Form::Lettings::Pages::ReferralNomsLaHr, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form:) } + let(:form) { instance_double(Form, start_date: Time.zone.today) } + let(:prp?) { nil } + let(:organisation) { instance_double(Organisation, prp?: prp?) } + let(:is_renewal?) { nil } + let(:referral_is_from_local_authority_housing_register?) { nil } + let(:log) { instance_double(LettingsLog, is_renewal?: is_renewal?, owning_organisation: organisation, referral_is_from_local_authority_housing_register?: referral_is_from_local_authority_housing_register?) } + + before do + allow(subsection).to receive(:form).and_return(form) + end + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[referral_noms]) + end + + it "has the correct id" do + expect(page.id).to eq("referral_noms_la_hr") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has the correct depends_on" do + expect(page.depends_on).to be nil + end + + context "and log owning organisation is not prp" do + let(:prp?) { false } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + + context "and log owning organisation is prp" do + let(:prp?) { true } + + context "when log is a renewal" do + let(:is_renewal?) { true } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + + context "and log is not a renewal" do + let(:is_renewal?) { false } + + context "and log referral is not from local authority housing register" do + let(:referral_is_from_local_authority_housing_register?) { false } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + + context "and log referral is from local authority housing register" do + let(:referral_is_from_local_authority_housing_register?) { true } + + it "is routed to" do + expect(page.routed_to?(log, nil)).to be true + end + end + end + end +end diff --git a/spec/models/form/lettings/pages/referral_noms_spec.rb b/spec/models/form/lettings/pages/referral_noms_spec.rb deleted file mode 100644 index 256e4fb4a..000000000 --- a/spec/models/form/lettings/pages/referral_noms_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require "rails_helper" - -RSpec.describe Form::Lettings::Pages::ReferralNoms, type: :model do - subject(:page) { described_class.new(page_id, page_definition, subsection) } - - let(:page_id) { nil } - let(:page_definition) { nil } - let(:subsection) { instance_double(Form::Subsection, form:) } - let(:form) { instance_double(Form, start_date: Time.zone.today) } - - before do - allow(subsection).to receive(:form).and_return(form) - end - - it "has correct subsection" do - expect(page.subsection).to eq(subsection) - end - - it "has correct questions" do - expect(page.questions.map(&:id)).to eq(%w[referral_noms]) - end - - it "has the correct id" do - expect(page.id).to eq("referral_noms") - end - - it "has the correct description" do - expect(page.description).to be_nil - end - - it "has the correct depends_on" do - expect(page.depends_on).to be nil - end -end diff --git a/spec/models/form/lettings/pages/referral_org_directly_referred_spec.rb b/spec/models/form/lettings/pages/referral_org_directly_referred_spec.rb new file mode 100644 index 000000000..fc308aceb --- /dev/null +++ b/spec/models/form/lettings/pages/referral_org_directly_referred_spec.rb @@ -0,0 +1,71 @@ +require "rails_helper" + +RSpec.describe Form::Lettings::Pages::ReferralOrgDirectlyReferred, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form:) } + let(:form) { instance_double(Form, start_date: Time.zone.today) } + let(:prp?) { nil } + let(:organisation) { instance_double(Organisation, prp?: prp?) } + let(:is_renewal?) { nil } + let(:referral_is_directly_referred?) { nil } + let(:log) { instance_double(LettingsLog, is_renewal?: is_renewal?, owning_organisation: organisation, referral_is_directly_referred?: referral_is_directly_referred?) } + + before do + allow(subsection).to receive(:form).and_return(form) + end + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[referral_org]) + end + + it "has the correct id" do + expect(page.id).to eq("referral_org_directly_referred") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has the correct depends_on" do + expect(page.depends_on).to be nil + end + + context "and log owning organisation is prp" do + let(:prp?) { true } + + context "when log is a renewal" do + let(:is_renewal?) { true } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + + context "and log is not a renewal" do + let(:is_renewal?) { false } + + context "and log referral is not nominated by local authority" do + let(:referral_is_directly_referred?) { false } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + + context "and log referral is nominated by local authority" do + let(:referral_is_directly_referred?) { true } + + it "is routed to" do + expect(page.routed_to?(log, nil)).to be true + end + end + end + end +end diff --git a/spec/models/form/lettings/pages/referral_org_nominated_spec.rb b/spec/models/form/lettings/pages/referral_org_nominated_spec.rb new file mode 100644 index 000000000..1f0c0aab5 --- /dev/null +++ b/spec/models/form/lettings/pages/referral_org_nominated_spec.rb @@ -0,0 +1,71 @@ +require "rails_helper" + +RSpec.describe Form::Lettings::Pages::ReferralOrgNominated, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form:) } + let(:form) { instance_double(Form, start_date: Time.zone.today) } + let(:prp?) { nil } + let(:organisation) { instance_double(Organisation, prp?: prp?) } + let(:is_renewal?) { nil } + let(:referral_is_nominated_by_local_authority?) { nil } + let(:log) { instance_double(LettingsLog, is_renewal?: is_renewal?, owning_organisation: organisation, referral_is_nominated_by_local_authority?: referral_is_nominated_by_local_authority?) } + + before do + allow(subsection).to receive(:form).and_return(form) + end + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[referral_org]) + end + + it "has the correct id" do + expect(page.id).to eq("referral_org_nominated") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has the correct depends_on" do + expect(page.depends_on).to be nil + end + + context "and log owning organisation is prp" do + let(:prp?) { true } + + context "when log is a renewal" do + let(:is_renewal?) { true } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + + context "and log is not a renewal" do + let(:is_renewal?) { false } + + context "and log referral is not nominated by local authority" do + let(:referral_is_nominated_by_local_authority?) { false } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + + context "and log referral is nominated by local authority" do + let(:referral_is_nominated_by_local_authority?) { true } + + it "is routed to" do + expect(page.routed_to?(log, nil)).to be true + end + end + end + end +end diff --git a/spec/models/form/lettings/pages/referral_org_spec.rb b/spec/models/form/lettings/pages/referral_org_spec.rb deleted file mode 100644 index cb39c95ca..000000000 --- a/spec/models/form/lettings/pages/referral_org_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require "rails_helper" - -RSpec.describe Form::Lettings::Pages::ReferralOrg, type: :model do - subject(:page) { described_class.new(page_id, page_definition, subsection) } - - let(:page_id) { nil } - let(:page_definition) { nil } - let(:subsection) { instance_double(Form::Subsection, form:) } - let(:form) { instance_double(Form, start_date: Time.zone.today) } - - before do - allow(subsection).to receive(:form).and_return(form) - end - - it "has correct subsection" do - expect(page.subsection).to eq(subsection) - end - - it "has correct questions" do - expect(page.questions.map(&:id)).to eq(%w[referral_org]) - end - - it "has the correct id" do - expect(page.id).to eq("referral_org") - end - - it "has the correct description" do - expect(page.description).to be_nil - end - - it "has the correct depends_on" do - expect(page.depends_on).to be nil - end -end diff --git a/spec/models/form/lettings/pages/referral_register_prp_spec.rb b/spec/models/form/lettings/pages/referral_register_prp_spec.rb index e62d54e47..bcb48562a 100644 --- a/spec/models/form/lettings/pages/referral_register_prp_spec.rb +++ b/spec/models/form/lettings/pages/referral_register_prp_spec.rb @@ -7,6 +7,10 @@ RSpec.describe Form::Lettings::Pages::ReferralRegisterPrp, type: :model do let(:page_definition) { nil } let(:subsection) { instance_double(Form::Subsection, form:) } let(:form) { instance_double(Form, start_date: Time.zone.today) } + let(:prp?) { nil } + let(:organisation) { instance_double(Organisation, prp?: prp?) } + let(:is_renewal?) { nil } + let(:log) { instance_double(LettingsLog, is_renewal?: is_renewal?, owning_organisation: organisation) } before do allow(subsection).to receive(:form).and_return(form) @@ -31,4 +35,44 @@ RSpec.describe Form::Lettings::Pages::ReferralRegisterPrp, type: :model do it "has the correct depends_on" do expect(page.depends_on).to be nil end + + context "when log is a renewal" do + let(:is_renewal?) { true } + + context "and log owning organisation is prp" do + let(:prp?) { true } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + + context "and log owning organisation is not prp" do + let(:prp?) { false } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + end + + context "when log is not a renewal" do + let(:is_renewal?) { false } + + context "and log owning organisation is prp" do + let(:prp?) { true } + + it "is routed to" do + expect(page.routed_to?(log, nil)).to be true + end + end + + context "and log owning organisation is not prp" do + let(:prp?) { false } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + end end diff --git a/spec/models/form/lettings/questions/referral_noms_spec.rb b/spec/models/form/lettings/questions/referral_noms_spec.rb index 064c4fbe7..23dd55b16 100644 --- a/spec/models/form/lettings/questions/referral_noms_spec.rb +++ b/spec/models/form/lettings/questions/referral_noms_spec.rb @@ -1,11 +1,12 @@ require "rails_helper" RSpec.describe Form::Lettings::Questions::ReferralNoms, type: :model do - subject(:question) { described_class.new(question_id, question_definition, page) } + subject(:question) { described_class.new(question_id, question_definition, page, referral_register) } let(:question_id) { nil } let(:question_definition) { nil } let(:page) { instance_double(Form::Page) } + let(:referral_register) { nil } let(:subsection) { instance_double(Form::Subsection) } let(:form) { instance_double(Form, start_date: Time.zone.today) } @@ -30,17 +31,50 @@ RSpec.describe Form::Lettings::Questions::ReferralNoms, type: :model do expect(question.derived?(nil)).to be false end - it "has the correct answer_options" do - expect(question.answer_options).to eq( - { - "1" => { - "value" => "Answer A", - }, - "2" => { - "value" => "Answer B", - }, - }.freeze, - ) + context "when referral_register is 6" do + let(:referral_register) { 6 } + + it "has the correct answer_options" do + expect(question.answer_options).to eq( + { + "1" => { + "value" => "Nominated by a local authority to a PRP", + }, + "2" => { + "value" => "Supported housing only - referred by a local authority to a PRP", + }, + "3" => { + "value" => "Internal transfer from another property owned by the same PRP landlord - for existing social tenants only", + }, + "4" => { + "value" => "Other", + }, + }.freeze, + ) + end + end + + context "when referral_register is 7" do + let(:referral_register) { 7 } + + it "has the correct answer_options" do + expect(question.answer_options).to eq( + { + "5" => { + "value" => "Internal transfer from another property owned by the same PRP landlord - for existing social tenants only", + }, + "6" => { + "value" => " A different PRP landlord - for existing socail tenants only", + }, + "7" => { + "value" => "Directly referred by a third party", + }, + "8" => { + "value" => "Other", + }, + }.freeze, + ) + end end it "has the correct question_number" do diff --git a/spec/models/form/lettings/questions/referral_org_spec.rb b/spec/models/form/lettings/questions/referral_org_spec.rb index 0c01d972b..b407ea796 100644 --- a/spec/models/form/lettings/questions/referral_org_spec.rb +++ b/spec/models/form/lettings/questions/referral_org_spec.rb @@ -1,11 +1,12 @@ require "rails_helper" RSpec.describe Form::Lettings::Questions::ReferralOrg, type: :model do - subject(:question) { described_class.new(question_id, question_definition, page) } + subject(:question) { described_class.new(question_id, question_definition, page, referral_noms) } let(:question_id) { nil } let(:question_definition) { nil } let(:page) { instance_double(Form::Page) } + let(:referral_noms) { nil } let(:subsection) { instance_double(Form::Subsection) } let(:form) { instance_double(Form, start_date: Time.zone.today) } @@ -30,17 +31,86 @@ RSpec.describe Form::Lettings::Questions::ReferralOrg, type: :model do expect(question.derived?(nil)).to be false end - it "has the correct answer_options" do - expect(question.answer_options).to eq( - { - "1" => { - "value" => "Answer A", - }, - "2" => { - "value" => "Answer B", - }, - }.freeze, - ) + context "when referral_noms is 1" do + let(:referral_noms) { 1 } + + it "has the correct answer_options" do + expect(question.answer_options).to eq( + { + "1" => { + "value" => "Referred to LA by health service", + }, + "2" => { + "value" => "Referred to LA by community learning disability team", + }, + "3" => { + "value" => "Referred to LA by community mental health team", + }, + "4" => { + "value" => "Referred to LA by adult social services", + }, + "5" => { + "value" => "Referred to LA by children's social care", + }, + "6" => { + "value" => "Referred to LA by police, probation, prison or youth offending team following a custodial sentence", + }, + "7" => { + "value" => "Referred to LA by police, probation, prison or youth offending team without a custodial sentence", + }, + "8" => { + "value" => "Referred to LA by a voluntary agency", + }, + "9" => { + "value" => "Other referral", + }, + "10" => { + "value" => "Don't know", + }, + }.freeze, + ) + end + end + + context "when referral_noms is 7" do + let(:referral_noms) { 7 } + + it "has the correct answer_options" do + expect(question.answer_options).to eq( + { + "11" => { + "value" => "Health service", + }, + "12" => { + "value" => "Community learning disability team", + }, + "13" => { + "value" => "Community mental health team", + }, + "14" => { + "value" => "Adult social services", + }, + "15" => { + "value" => "Children's social care", + }, + "16" => { + "value" => "Police, probation, prison or youth offending team following a custodial sentence", + }, + "17" => { + "value" => "Police, probation, prison or youth offending team without a custodial sentence", + }, + "18" => { + "value" => "Voluntary agency", + }, + "19" => { + "value" => "Other third party", + }, + "20" => { + "value" => "Don't know", + }, + }.freeze, + ) + end end it "has the correct question_number" do diff --git a/spec/models/form/lettings/questions/referral_register_spec.rb b/spec/models/form/lettings/questions/referral_register_spec.rb index 112de43e0..4cf53cd23 100644 --- a/spec/models/form/lettings/questions/referral_register_spec.rb +++ b/spec/models/form/lettings/questions/referral_register_spec.rb @@ -74,11 +74,20 @@ RSpec.describe Form::Lettings::Questions::ReferralRegister, type: :model do it "has the correct answer_options" do expect(question.answer_options).to eq( { - "1" => { - "value" => "Answer A", + "5" => { + "value" => "Renewal to the same tenant in the same property", }, - "2" => { - "value" => "Answer B", + "6" => { + "value" => "From a local authority housing register (waiting list) or a register with local authority involvement", + }, + "7" => { + "value" => "From a housing register (waiting list) with no local authority involvement", + }, + "8" => { + "value" => "Tenant applied directly (not via a nomination or waiting list)", + }, + "9" => { + "value" => "Don't know", }, }.freeze, ) diff --git a/spec/models/form/lettings/subsections/household_situation_spec.rb b/spec/models/form/lettings/subsections/household_situation_spec.rb index 909046719..3da63a140 100644 --- a/spec/models/form/lettings/subsections/household_situation_spec.rb +++ b/spec/models/form/lettings/subsections/household_situation_spec.rb @@ -109,8 +109,10 @@ RSpec.describe Form::Lettings::Subsections::HouseholdSituation, type: :model do allocation_system referral_register_la referral_register_prp - referral_noms - referral_org + referral_noms_la_hr + referral_noms_hr + referral_org_nominated + referral_org_directly_referred ], ) end diff --git a/spec/models/validations/household_validations_spec.rb b/spec/models/validations/household_validations_spec.rb index 78f27ed3f..2ccf89af6 100644 --- a/spec/models/validations/household_validations_spec.rb +++ b/spec/models/validations/household_validations_spec.rb @@ -491,68 +491,132 @@ RSpec.describe Validations::HouseholdValidations do end end - context "when start year is 2026 and record is internal transfer and owning organisation is LA" do + context "when start year is 2026" do let(:startdate) { collection_start_date_for_year(2026) } - before do - record.owning_organisation.provider_type = "LA" - record.referral_register = 2 - end + context "and record is internal transfer via LA path" do + before do + record.owning_organisation.provider_type = "LA" + record.referral_register = 2 + end - [ - { code: 3, label: "Private sector tenancy" }, - { code: 27, label: "Owner occupation (low-cost home ownership)" }, - { code: 26, label: "Owner occupation (private)" }, - { code: 28, label: "Living with friends or family (long-term)" }, - { code: 39, label: "Sofa surfing (moving regularly between family or friends, no permanent bed)" }, - { code: 14, label: "Bed and breakfast" }, - { code: 7, label: "Direct access hostel" }, - { code: 10, label: "Hospital" }, - { code: 29, label: "Prison or approved probation hostel" }, - { code: 19, label: "Rough sleeping" }, - { code: 18, label: "Any other temporary accommodation" }, - { code: 13, label: "Children’s home or foster care" }, - { code: 24, label: "Home Office Asylum Support" }, - { code: 37, label: "Host family or similar refugee accommodation" }, - { code: 23, label: "Mobile home or caravan" }, - { code: 21, label: "Refuge" }, - { code: 9, label: "Residential care home" }, - { code: 4, label: "Tied housing or rented with job" }, - { code: 25, label: "Any other accommodation" }, - ].each do |prevten| - context "and prevten is #{prevten[:code]}" do - before do - record.prevten = prevten[:code] + [ + { code: 3, label: "Private sector tenancy" }, + { code: 27, label: "Owner occupation (low-cost home ownership)" }, + { code: 26, label: "Owner occupation (private)" }, + { code: 28, label: "Living with friends or family (long-term)" }, + { code: 39, label: "Sofa surfing (moving regularly between family or friends, no permanent bed)" }, + { code: 14, label: "Bed and breakfast" }, + { code: 7, label: "Direct access hostel" }, + { code: 10, label: "Hospital" }, + { code: 29, label: "Prison or approved probation hostel" }, + { code: 19, label: "Rough sleeping" }, + { code: 18, label: "Any other temporary accommodation" }, + { code: 13, label: "Children’s home or foster care" }, + { code: 24, label: "Home Office Asylum Support" }, + { code: 37, label: "Host family or similar refugee accommodation" }, + { code: 23, label: "Mobile home or caravan" }, + { code: 21, label: "Refuge" }, + { code: 9, label: "Residential care home" }, + { code: 4, label: "Tied housing or rented with job" }, + { code: 25, label: "Any other accommodation" }, + ].each do |prevten| + context "and prevten is #{prevten[:code]}" do + it "adds an error" do + record.referral_register = 2 + record.prevten = prevten[:code] + household_validator.validate_referral(record) + expect(record.errors["prevten"]) + .to include(match I18n.t("validations.lettings.household.prevten.general_needs.internal_transfer", prevten: prevten[:label])) + expect(record.errors["referral_register"]) + .to include(match I18n.t("validations.lettings.household.referral.general_needs.internal_transfer", prevten: prevten[:label])) + end end + end - it "adds an error" do - household_validator.validate_referral(record) - expect(record.errors["prevten"]) - .to include(match I18n.t("validations.lettings.household.prevten.general_needs.internal_transfer", prevten: prevten[:label])) - expect(record.errors["referral_register"]) - .to include(match I18n.t("validations.lettings.household.referral.general_needs.internal_transfer", prevten: prevten[:label])) + [ + { code: 30, label: "Fixed-term local authority general needs tenancy" }, + { code: 31, label: "Lifetime local authority general needs tenancy" }, + { code: 32, label: "Fixed-term private registered provider (PRP) general needs tenancy" }, + { code: 33, label: "Lifetime private registered provider (PRP) general needs tenancy" }, + { code: 35, label: "Extra care housing" }, + { code: 38, label: "Older people’s housing for tenants with low support needs" }, + { code: 6, label: "Other supported housing" }, + ].each do |prevten| + context "and prevten is #{prevten[:code]}" do + before do + record.prevten = prevten[:code] + end + + it "does not add an error" do + household_validator.validate_referral(record) + expect(record.errors["prevten"]).to be_empty + expect(record.errors["referral"]).to be_empty + end end end end - [ - { code: 30, label: "Fixed-term local authority general needs tenancy" }, - { code: 31, label: "Lifetime local authority general needs tenancy" }, - { code: 32, label: "Fixed-term private registered provider (PRP) general needs tenancy" }, - { code: 33, label: "Lifetime private registered provider (PRP) general needs tenancy" }, - { code: 35, label: "Extra care housing" }, - { code: 38, label: "Older people’s housing for tenants with low support needs" }, - { code: 6, label: "Other supported housing" }, - ].each do |prevten| - context "and prevten is #{prevten[:code]}" do - before do - record.prevten = prevten[:code] + context "and record is internal transfer via PRP path" do + before do + record.owning_organisation.provider_type = "PRP" + record.referral_register = 6 + record.referral_noms = 3 + end + + [ + { code: 3, label: "Private sector tenancy" }, + { code: 27, label: "Owner occupation (low-cost home ownership)" }, + { code: 26, label: "Owner occupation (private)" }, + { code: 28, label: "Living with friends or family (long-term)" }, + { code: 39, label: "Sofa surfing (moving regularly between family or friends, no permanent bed)" }, + { code: 14, label: "Bed and breakfast" }, + { code: 7, label: "Direct access hostel" }, + { code: 10, label: "Hospital" }, + { code: 29, label: "Prison or approved probation hostel" }, + { code: 19, label: "Rough sleeping" }, + { code: 18, label: "Any other temporary accommodation" }, + { code: 13, label: "Children’s home or foster care" }, + { code: 24, label: "Home Office Asylum Support" }, + { code: 37, label: "Host family or similar refugee accommodation" }, + { code: 23, label: "Mobile home or caravan" }, + { code: 21, label: "Refuge" }, + { code: 9, label: "Residential care home" }, + { code: 4, label: "Tied housing or rented with job" }, + { code: 25, label: "Any other accommodation" }, + ].each do |prevten| + context "and prevten is #{prevten[:code]}" do + it "adds an error" do + record.referral_register = 2 + record.prevten = prevten[:code] + household_validator.validate_referral(record) + expect(record.errors["prevten"]) + .to include(match I18n.t("validations.lettings.household.prevten.general_needs.internal_transfer", prevten: prevten[:label])) + expect(record.errors["referral_register"]) + .to include(match I18n.t("validations.lettings.household.referral.general_needs.internal_transfer", prevten: prevten[:label])) + end end + end - it "does not add an error" do - household_validator.validate_referral(record) - expect(record.errors["prevten"]).to be_empty - expect(record.errors["referral"]).to be_empty + [ + { code: 30, label: "Fixed-term local authority general needs tenancy" }, + { code: 31, label: "Lifetime local authority general needs tenancy" }, + { code: 32, label: "Fixed-term private registered provider (PRP) general needs tenancy" }, + { code: 33, label: "Lifetime private registered provider (PRP) general needs tenancy" }, + { code: 35, label: "Extra care housing" }, + { code: 38, label: "Older people’s housing for tenants with low support needs" }, + { code: 6, label: "Other supported housing" }, + ].each do |prevten| + context "and prevten is #{prevten[:code]}" do + before do + record.prevten = prevten[:code] + end + + it "does not add an error" do + household_validator.validate_referral(record) + expect(record.errors["prevten"]).to be_empty + expect(record.errors["referral"]).to be_empty + end end end end From 327390e50913ffa1450f565261703e5b6cb006ee Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 2 Feb 2026 18:02:57 +0000 Subject: [PATCH 29/40] fixup! CLDC-4189: Add PRP flow fix typo Co-authored-by: Oscar Richardson <116292912+oscar-richardson-softwire@users.noreply.github.com> --- app/models/form/lettings/questions/referral_noms.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/form/lettings/questions/referral_noms.rb b/app/models/form/lettings/questions/referral_noms.rb index 87083c148..b1675f773 100644 --- a/app/models/form/lettings/questions/referral_noms.rb +++ b/app/models/form/lettings/questions/referral_noms.rb @@ -33,7 +33,7 @@ class Form::Lettings::Questions::ReferralNoms < ::Form::Question "value" => "Internal transfer from another property owned by the same PRP landlord - for existing social tenants only", }, "6" => { - "value" => " A different PRP landlord - for existing socail tenants only", + "value" => " A different PRP landlord - for existing social tenants only", }, "7" => { "value" => "Directly referred by a third party", From 03e70f405c7a2f1ce0c84e6b72c62d668abb4fac Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 3 Feb 2026 09:27:44 +0000 Subject: [PATCH 30/40] fixup! CLDC-4189: Add tests fix typo --- spec/models/form/lettings/questions/referral_noms_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/form/lettings/questions/referral_noms_spec.rb b/spec/models/form/lettings/questions/referral_noms_spec.rb index 23dd55b16..809a722cc 100644 --- a/spec/models/form/lettings/questions/referral_noms_spec.rb +++ b/spec/models/form/lettings/questions/referral_noms_spec.rb @@ -64,7 +64,7 @@ RSpec.describe Form::Lettings::Questions::ReferralNoms, type: :model do "value" => "Internal transfer from another property owned by the same PRP landlord - for existing social tenants only", }, "6" => { - "value" => " A different PRP landlord - for existing socail tenants only", + "value" => " A different PRP landlord - for existing social tenants only", }, "7" => { "value" => "Directly referred by a third party", From 73be38242eec8a6651135a095a53779030d14640 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 3 Feb 2026 12:20:52 +0000 Subject: [PATCH 31/40] fixup! CLDC-4189: Add tests fix missing cases in page specs use a loop and before blocks for household validation tests --- .../referral_org_directly_referred_spec.rb | 8 + .../pages/referral_org_nominated_spec.rb | 8 + .../validations/household_validations_spec.rb | 213 ++++++++---------- 3 files changed, 112 insertions(+), 117 deletions(-) diff --git a/spec/models/form/lettings/pages/referral_org_directly_referred_spec.rb b/spec/models/form/lettings/pages/referral_org_directly_referred_spec.rb index fc308aceb..27e1cfa50 100644 --- a/spec/models/form/lettings/pages/referral_org_directly_referred_spec.rb +++ b/spec/models/form/lettings/pages/referral_org_directly_referred_spec.rb @@ -37,6 +37,14 @@ RSpec.describe Form::Lettings::Pages::ReferralOrgDirectlyReferred, type: :model expect(page.depends_on).to be nil end + context "and log owning organisation is not prp" do + let(:prp?) { false } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + context "and log owning organisation is prp" do let(:prp?) { true } diff --git a/spec/models/form/lettings/pages/referral_org_nominated_spec.rb b/spec/models/form/lettings/pages/referral_org_nominated_spec.rb index 1f0c0aab5..68a0a17f1 100644 --- a/spec/models/form/lettings/pages/referral_org_nominated_spec.rb +++ b/spec/models/form/lettings/pages/referral_org_nominated_spec.rb @@ -37,6 +37,14 @@ RSpec.describe Form::Lettings::Pages::ReferralOrgNominated, type: :model do expect(page.depends_on).to be nil end + context "and log owning organisation is not prp" do + let(:prp?) { false } + + it "is not routed to" do + expect(page.routed_to?(log, nil)).to be false + end + end + context "and log owning organisation is prp" do let(:prp?) { true } diff --git a/spec/models/validations/household_validations_spec.rb b/spec/models/validations/household_validations_spec.rb index 2ccf89af6..03a99aa9a 100644 --- a/spec/models/validations/household_validations_spec.rb +++ b/spec/models/validations/household_validations_spec.rb @@ -494,128 +494,107 @@ RSpec.describe Validations::HouseholdValidations do context "when start year is 2026" do let(:startdate) { collection_start_date_for_year(2026) } - context "and record is internal transfer via LA path" do - before do - record.owning_organisation.provider_type = "LA" - record.referral_register = 2 - end - - [ - { code: 3, label: "Private sector tenancy" }, - { code: 27, label: "Owner occupation (low-cost home ownership)" }, - { code: 26, label: "Owner occupation (private)" }, - { code: 28, label: "Living with friends or family (long-term)" }, - { code: 39, label: "Sofa surfing (moving regularly between family or friends, no permanent bed)" }, - { code: 14, label: "Bed and breakfast" }, - { code: 7, label: "Direct access hostel" }, - { code: 10, label: "Hospital" }, - { code: 29, label: "Prison or approved probation hostel" }, - { code: 19, label: "Rough sleeping" }, - { code: 18, label: "Any other temporary accommodation" }, - { code: 13, label: "Children’s home or foster care" }, - { code: 24, label: "Home Office Asylum Support" }, - { code: 37, label: "Host family or similar refugee accommodation" }, - { code: 23, label: "Mobile home or caravan" }, - { code: 21, label: "Refuge" }, - { code: 9, label: "Residential care home" }, - { code: 4, label: "Tied housing or rented with job" }, - { code: 25, label: "Any other accommodation" }, - ].each do |prevten| - context "and prevten is #{prevten[:code]}" do - it "adds an error" do - record.referral_register = 2 - record.prevten = prevten[:code] - household_validator.validate_referral(record) - expect(record.errors["prevten"]) - .to include(match I18n.t("validations.lettings.household.prevten.general_needs.internal_transfer", prevten: prevten[:label])) - expect(record.errors["referral_register"]) - .to include(match I18n.t("validations.lettings.household.referral.general_needs.internal_transfer", prevten: prevten[:label])) - end - end - end - - [ - { code: 30, label: "Fixed-term local authority general needs tenancy" }, - { code: 31, label: "Lifetime local authority general needs tenancy" }, - { code: 32, label: "Fixed-term private registered provider (PRP) general needs tenancy" }, - { code: 33, label: "Lifetime private registered provider (PRP) general needs tenancy" }, - { code: 35, label: "Extra care housing" }, - { code: 38, label: "Older people’s housing for tenants with low support needs" }, - { code: 6, label: "Other supported housing" }, - ].each do |prevten| - context "and prevten is #{prevten[:code]}" do - before do - record.prevten = prevten[:code] - end - - it "does not add an error" do - household_validator.validate_referral(record) - expect(record.errors["prevten"]).to be_empty - expect(record.errors["referral"]).to be_empty - end + [ + { + internal_transfer: true, + label: "LA", + referral_register: 2, + referral_noms: nil, + }, + { + internal_transfer: true, + label: "PRP", + referral_register: 6, + referral_noms: 3, + }, + { + internal_transfer: false, + label: "LA", + referral_register: 1, + referral_noms: nil, + }, + { + internal_transfer: false, + label: "PRP", + referral_register: 6, + referral_noms: 2, + }, + ] + .each do |scenario| + context "and record #{scenario[:internal_transfer] ? 'is ' : ''}internal transfer via #{scenario[:label]} path" do + before do + record.owning_organisation.provider_type = scenario[:label] + record.referral_register = scenario[:referral_register] + record.referral_noms = scenario[:referral_noms] end - end - end - - context "and record is internal transfer via PRP path" do - before do - record.owning_organisation.provider_type = "PRP" - record.referral_register = 6 - record.referral_noms = 3 - end - [ - { code: 3, label: "Private sector tenancy" }, - { code: 27, label: "Owner occupation (low-cost home ownership)" }, - { code: 26, label: "Owner occupation (private)" }, - { code: 28, label: "Living with friends or family (long-term)" }, - { code: 39, label: "Sofa surfing (moving regularly between family or friends, no permanent bed)" }, - { code: 14, label: "Bed and breakfast" }, - { code: 7, label: "Direct access hostel" }, - { code: 10, label: "Hospital" }, - { code: 29, label: "Prison or approved probation hostel" }, - { code: 19, label: "Rough sleeping" }, - { code: 18, label: "Any other temporary accommodation" }, - { code: 13, label: "Children’s home or foster care" }, - { code: 24, label: "Home Office Asylum Support" }, - { code: 37, label: "Host family or similar refugee accommodation" }, - { code: 23, label: "Mobile home or caravan" }, - { code: 21, label: "Refuge" }, - { code: 9, label: "Residential care home" }, - { code: 4, label: "Tied housing or rented with job" }, - { code: 25, label: "Any other accommodation" }, - ].each do |prevten| - context "and prevten is #{prevten[:code]}" do - it "adds an error" do - record.referral_register = 2 - record.prevten = prevten[:code] - household_validator.validate_referral(record) - expect(record.errors["prevten"]) - .to include(match I18n.t("validations.lettings.household.prevten.general_needs.internal_transfer", prevten: prevten[:label])) - expect(record.errors["referral_register"]) - .to include(match I18n.t("validations.lettings.household.referral.general_needs.internal_transfer", prevten: prevten[:label])) + [ + { code: 3, label: "Private sector tenancy" }, + { code: 27, label: "Owner occupation (low-cost home ownership)" }, + { code: 26, label: "Owner occupation (private)" }, + { code: 28, label: "Living with friends or family (long-term)" }, + { code: 39, label: "Sofa surfing (moving regularly between family or friends, no permanent bed)" }, + { code: 14, label: "Bed and breakfast" }, + { code: 7, label: "Direct access hostel" }, + { code: 10, label: "Hospital" }, + { code: 29, label: "Prison or approved probation hostel" }, + { code: 19, label: "Rough sleeping" }, + { code: 18, label: "Any other temporary accommodation" }, + { code: 13, label: "Children’s home or foster care" }, + { code: 24, label: "Home Office Asylum Support" }, + { code: 37, label: "Host family or similar refugee accommodation" }, + { code: 23, label: "Mobile home or caravan" }, + { code: 21, label: "Refuge" }, + { code: 9, label: "Residential care home" }, + { code: 4, label: "Tied housing or rented with job" }, + { code: 25, label: "Any other accommodation" }, + ].each do |prevten| + context "and prevten is #{prevten[:code]}" do + before do + record.prevten = prevten[:code] + end + + if scenario[:internal_transfer] + it "adds an error" do + household_validator.validate_referral(record) + expect(record.errors["prevten"]) + .to include(match I18n.t("validations.lettings.household.prevten.general_needs.internal_transfer", prevten: prevten[:label])) + expect(record.errors["referral_register"]) + .to include(match I18n.t("validations.lettings.household.referral.general_needs.internal_transfer", prevten: prevten[:label])) + expect(record.errors["referral_noms"]) + .to include(match I18n.t("validations.lettings.household.referral.general_needs.internal_transfer", prevten: prevten[:label])) + end + else + it "does not add an error" do + household_validator.validate_referral(record) + expect(record.errors["prevten"]).to be_empty + expect(record.errors["referral_register"]).to be_empty + expect(record.errors["referral_noms"]).to be_empty + end + end end end - end - - [ - { code: 30, label: "Fixed-term local authority general needs tenancy" }, - { code: 31, label: "Lifetime local authority general needs tenancy" }, - { code: 32, label: "Fixed-term private registered provider (PRP) general needs tenancy" }, - { code: 33, label: "Lifetime private registered provider (PRP) general needs tenancy" }, - { code: 35, label: "Extra care housing" }, - { code: 38, label: "Older people’s housing for tenants with low support needs" }, - { code: 6, label: "Other supported housing" }, - ].each do |prevten| - context "and prevten is #{prevten[:code]}" do - before do - record.prevten = prevten[:code] - end - it "does not add an error" do - household_validator.validate_referral(record) - expect(record.errors["prevten"]).to be_empty - expect(record.errors["referral"]).to be_empty + [ + { code: 30, label: "Fixed-term local authority general needs tenancy" }, + { code: 31, label: "Lifetime local authority general needs tenancy" }, + { code: 32, label: "Fixed-term private registered provider (PRP) general needs tenancy" }, + { code: 33, label: "Lifetime private registered provider (PRP) general needs tenancy" }, + { code: 35, label: "Extra care housing" }, + { code: 38, label: "Older people’s housing for tenants with low support needs" }, + { code: 6, label: "Other supported housing" }, + ].each do |prevten| + context "and prevten is #{prevten[:code]}" do + before do + record.prevten = prevten[:code] + end + + it "does not add an error" do + household_validator.validate_referral(record) + expect(record.errors["prevten"]).to be_empty + expect(record.errors["referral_register"]).to be_empty + expect(record.errors["referral_noms"]).to be_empty + end end end end From b1a6a30dcd64bc557e9c336ce541e4d5425d3702 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 3 Feb 2026 15:27:52 +0000 Subject: [PATCH 32/40] CLDC-4189: Ensure log is not classed as generan needs if prevten isnt answered --- app/models/lettings_log.rb | 2 ++ .../validations/household_validations_spec.rb | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/app/models/lettings_log.rb b/app/models/lettings_log.rb index 3b593d590..602ae6e68 100644 --- a/app/models/lettings_log.rb +++ b/app/models/lettings_log.rb @@ -551,6 +551,8 @@ class LettingsLog < Log end def is_prevten_general_needs? + return false unless prevten + ![30, 31, 32, 33, 35, 38, 6].include?(prevten) end diff --git a/spec/models/validations/household_validations_spec.rb b/spec/models/validations/household_validations_spec.rb index 03a99aa9a..872900317 100644 --- a/spec/models/validations/household_validations_spec.rb +++ b/spec/models/validations/household_validations_spec.rb @@ -528,6 +528,19 @@ RSpec.describe Validations::HouseholdValidations do record.referral_noms = scenario[:referral_noms] end + context "and prevten is nil" do + before do + record.prevten = nil + end + + it "does not add an error" do + household_validator.validate_referral(record) + expect(record.errors["prevten"]).to be_empty + expect(record.errors["referral_register"]).to be_empty + expect(record.errors["referral_noms"]).to be_empty + end + end + [ { code: 3, label: "Private sector tenancy" }, { code: 27, label: "Owner occupation (low-cost home ownership)" }, From ef4c7165c3867a1769cb986869b9f773a7b4eb5b Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 3 Feb 2026 16:17:26 +0000 Subject: [PATCH 33/40] fixup! CLDC-4189: Add tests add cases for other internal transfer flow --- .../validations/household_validations_spec.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/spec/models/validations/household_validations_spec.rb b/spec/models/validations/household_validations_spec.rb index 872900317..5d2d971e5 100644 --- a/spec/models/validations/household_validations_spec.rb +++ b/spec/models/validations/household_validations_spec.rb @@ -507,6 +507,12 @@ RSpec.describe Validations::HouseholdValidations do referral_register: 6, referral_noms: 3, }, + { + internal_transfer: true, + label: "PRP", + referral_register: 7, + referral_noms: 5, + }, { internal_transfer: false, label: "LA", @@ -519,9 +525,15 @@ RSpec.describe Validations::HouseholdValidations do referral_register: 6, referral_noms: 2, }, + { + internal_transfer: false, + label: "PRP", + referral_register: 7, + referral_noms: 6, + }, ] .each do |scenario| - context "and record #{scenario[:internal_transfer] ? 'is ' : ''}internal transfer via #{scenario[:label]} path" do + context "and record #{scenario[:internal_transfer] ? 'is ' : 'is not '}internal transfer via #{scenario[:label]} path" do before do record.owning_organisation.provider_type = scenario[:label] record.referral_register = scenario[:referral_register] From bd210f77a7ab98696a6aed90cea4d9f2faeee0df Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 2 Feb 2026 10:04:02 +0000 Subject: [PATCH 34/40] CLDC-4190: Invalidate all referral fields if any are wrong --- .../lettings/year2026/row_parser.rb | 68 ++++++++++++++++++- .../lettings/2026/bulk_upload.en.yml | 1 + 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/app/services/bulk_upload/lettings/year2026/row_parser.rb b/app/services/bulk_upload/lettings/year2026/row_parser.rb index 61ab00152..97da93d47 100644 --- a/app/services/bulk_upload/lettings/year2026/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2026/row_parser.rb @@ -426,6 +426,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 @@ -995,6 +996,56 @@ private end end + def field_116_valid? + if owning_organisation&.la? + [1, 2, 3, 4].include?(field_116) + else + field_116.blank? + end + end + + def field_130_valid? + if owning_organisation&.prp? + [5, 6, 7, 8, 9].include?(field_130) + else + field_130.blank? + end + end + + def field_131_valid? + case field_130 + when 6 + [1, 2, 3, 4].include?(field_131) + when 7 + [5, 6, 7, 8].include?(field_131) + else + field_131.blank? + end + end + + def field_132_valid? + case field_131 + when 1 + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].include?(field_132) + when 7 + [11, 12, 13, 14, 15, 16, 17, 18, 19, 20].include?(field_132) + else + field_132.blank? + end + end + + def referral_fields_valid? + field_116_valid? && field_130_valid? && field_131_valid? && field_132_valid? + end + + def validate_referral_fields + return if referral_fields_valid? + + %i[field_116 field_130 field_131 field_132].each do |field| + errors.add(field, I18n.t("#{ERROR_BASE_KEY}.referral.invalid_option")) + end + end + def field_mapping_for_errors { lettype: [:field_11], @@ -1694,6 +1745,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 @@ -1703,12 +1759,20 @@ private end def referral_noms - field_131 + return unless owning_organisation + return unless referral_fields_valid? + + if owning_organisation.prp? + field_131 + end end def referral_org return unless owning_organisation + return unless referral_fields_valid? - field_132 + if owning_organisation.prp? + field_132 + 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..b8257c8fb 100644 --- a/config/locales/validations/lettings/2026/bulk_upload.en.yml +++ b/config/locales/validations/lettings/2026/bulk_upload.en.yml @@ -42,6 +42,7 @@ en: 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: From 7c575098d5c86d5f3580a3c4f89588aca31bc07f Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 2 Feb 2026 10:10:45 +0000 Subject: [PATCH 35/40] CLDC-4190: Remove other validations on referral fields --- .../bulk_upload/lettings/year2026/row_parser.rb | 14 -------------- .../validations/lettings/2026/bulk_upload.en.yml | 2 -- 2 files changed, 16 deletions(-) diff --git a/app/services/bulk_upload/lettings/year2026/row_parser.rb b/app/services/bulk_upload/lettings/year2026/row_parser.rb index 97da93d47..177a69d30 100644 --- a/app/services/bulk_upload/lettings/year2026/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2026/row_parser.rb @@ -414,8 +414,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 @@ -805,18 +803,6 @@ 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 - end - def validate_relevant_collection_window return if startdate.blank? || bulk_upload.form.blank? diff --git a/config/locales/validations/lettings/2026/bulk_upload.en.yml b/config/locales/validations/lettings/2026/bulk_upload.en.yml index b8257c8fb..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,6 @@ 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." From 1d2775d27c9598c57fd94a4dc4de26f3398e153f Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 2 Feb 2026 11:39:58 +0000 Subject: [PATCH 36/40] CLDC-4190: Ignore referral validation if BU is renewal --- app/services/bulk_upload/lettings/year2026/row_parser.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/services/bulk_upload/lettings/year2026/row_parser.rb b/app/services/bulk_upload/lettings/year2026/row_parser.rb index 177a69d30..58312d7bf 100644 --- a/app/services/bulk_upload/lettings/year2026/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2026/row_parser.rb @@ -684,7 +684,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")) @@ -790,7 +790,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 @@ -803,6 +803,10 @@ private field_4 == 2 end + def renewal? + field_7 == 1 + end + def validate_relevant_collection_window return if startdate.blank? || bulk_upload.form.blank? @@ -1025,6 +1029,7 @@ private end def validate_referral_fields + return if renewal? return if referral_fields_valid? %i[field_116 field_130 field_131 field_132].each do |field| From 6b394d835db81b33ae94300f6cf94d2143e9f73b Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 2 Feb 2026 11:49:53 +0000 Subject: [PATCH 37/40] CLDC-4190: Add tests --- .../lettings/year2026/row_parser_spec.rb | 246 +++++++++++++++++- 1 file changed, 236 insertions(+), 10 deletions(-) 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 ff5df6bc4..c2f54c18c 100644 --- a/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb @@ -222,9 +222,6 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do field_115: "2", field_116: "1", - field_130: "1", - field_131: "1", - field_132: "1", field_117: "1", field_118: "2", @@ -623,12 +620,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 @@ -1135,10 +1132,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_130, field_131, field_132" 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_130]).to be_blank + expect(parser.errors[:field_131]).to be_blank + expect(parser.errors[:field_132]).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_130]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).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_130]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + end + end + + context "and other fields are given" do + let(:attributes) { renewal_attributes.merge({ field_116: 1, field_130: 5, field_131: 1, field_132: 1 }) } + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_130]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + end + end + end + + context "and is renewal" do + let(:attributes) { org_attributes.merge({ field_7: 1, field_116: 1, field_130: 5, field_131: 1, field_132: 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_130]).to be_blank + expect(parser.errors[:field_131]).to be_blank + expect(parser.errors[:field_132]).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_130 is valid" do + let(:attributes) { renewal_attributes.merge({ field_130: 5 }) } + + it "does not add an error" do + parser.valid? + expect(parser.errors[:field_116]).to be_blank + expect(parser.errors[:field_130]).to be_blank + expect(parser.errors[:field_131]).to be_blank + expect(parser.errors[:field_132]).to be_blank + end + + context "and later fields are given" do + let(:attributes) { renewal_attributes.merge({ field_130: 5, field_131: 1, field_132: 1 }) } + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_130]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + end + end + end + + context "and field_130 is invalid" do + let(:attributes) { renewal_attributes.merge({ field_130: 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_130]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + end + end + + context "and field_130 is blank" do + let(:attributes) { renewal_attributes.merge({ field_130: nil }) } + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_130]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + end + end + + context "and field_130 expects an answer for field_131" do + let(:field_130_attributes) { renewal_attributes.merge({ field_130: 6 }) } + + context "and field_131 is valid" do + let(:attributes) { field_130_attributes.merge({ field_131: 2 }) } + + it "does not add an error" do + parser.valid? + expect(parser.errors[:field_116]).to be_blank + expect(parser.errors[:field_130]).to be_blank + expect(parser.errors[:field_131]).to be_blank + expect(parser.errors[:field_132]).to be_blank + end + + context "and later fields are given" do + let(:attributes) { field_130_attributes.merge({ field_131: 2, field_132: 1 }) } + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_130]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + end + end + end + + context "and field_131 is invalid" do + let(:attributes) { field_130_attributes.merge({ field_131: 5 }) } # needs field_130 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_130]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + end + end + + context "and field_131 is blank" do + let(:attributes) { field_130_attributes.merge({ field_131: nil }) } + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_130]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + end + end + + context "and field_131 expects an answer for field_132" do + let(:field_131_attributes) { field_130_attributes.merge({ field_131: 1 }) } + + context "and field_132 is valid" do + let(:attributes) { field_131_attributes.merge({ field_132: 1 }) } + + it "does not add an error" do + parser.valid? + expect(parser.errors[:field_116]).to be_blank + expect(parser.errors[:field_130]).to be_blank + expect(parser.errors[:field_131]).to be_blank + expect(parser.errors[:field_132]).to be_blank + end + end + + context "and field_132 is invalid" do + let(:attributes) { field_131_attributes.merge({ field_132: 11 }) } # needs field_131 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_130]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + end + end + + context "and field_132 is blank" do + let(:attributes) { field_131_attributes.merge({ field_132: nil }) } + + it "adds errors to all referral fields" do + parser.valid? + expect(parser.errors[:field_116]).to be_present + expect(parser.errors[:field_130]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).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_130: 5, field_131: 1, field_132: 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_130]).to be_blank + expect(parser.errors[:field_131]).to be_blank + expect(parser.errors[:field_132]).to be_blank + end + end + end + end describe "fields 7, 8, 9 => startdate" do context "when any one of these fields is blank" do From 001bc58d69127102957cfab9f6ccbdf877eef6e1 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 3 Feb 2026 16:45:08 +0000 Subject: [PATCH 38/40] fixup! CLDC-4190: Invalidate all referral fields if any are wrong add punctuation to comment name validation fields better --- .../lettings/year2026/row_parser.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/services/bulk_upload/lettings/year2026/row_parser.rb b/app/services/bulk_upload/lettings/year2026/row_parser.rb index 58312d7bf..2bab36ae5 100644 --- a/app/services/bulk_upload/lettings/year2026/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2026/row_parser.rb @@ -986,7 +986,7 @@ private end end - def field_116_valid? + def field_referral_register_la_valid? if owning_organisation&.la? [1, 2, 3, 4].include?(field_116) else @@ -994,7 +994,7 @@ private end end - def field_130_valid? + def field_referral_register_prp_valid? if owning_organisation&.prp? [5, 6, 7, 8, 9].include?(field_130) else @@ -1002,7 +1002,7 @@ private end end - def field_131_valid? + def field_referral_noms_valid? case field_130 when 6 [1, 2, 3, 4].include?(field_131) @@ -1013,7 +1013,7 @@ private end end - def field_132_valid? + def field_referral_org_valid? case field_131 when 1 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].include?(field_132) @@ -1025,7 +1025,7 @@ private end def referral_fields_valid? - field_116_valid? && field_130_valid? && field_131_valid? && field_132_valid? + field_referral_register_la_valid? && field_referral_register_prp_valid? && field_referral_noms_valid? && field_referral_org_valid? end def validate_referral_fields @@ -1736,10 +1736,10 @@ 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 + # 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? From 5b71132e38a958db9d94684a843ac91ab67bd366 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 3 Feb 2026 16:57:21 +0000 Subject: [PATCH 39/40] fixup! CLDC-4190: Add tests make test names clearer --- .../bulk_upload/lettings/year2026/row_parser_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 c2f54c18c..bcc31bb40 100644 --- a/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb @@ -1211,7 +1211,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do context "and not renewal" do let(:renewal_attributes) { org_attributes.merge({ field_7: nil }) } - context "and field_130 is valid" do + context "and field_130 is valid and does not expect an answer for field_131" do let(:attributes) { renewal_attributes.merge({ field_130: 5 }) } it "does not add an error" do @@ -1259,10 +1259,10 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - context "and field_130 expects an answer for field_131" do + context "and field_130 is valid and expects an answer for field_131" do let(:field_130_attributes) { renewal_attributes.merge({ field_130: 6 }) } - context "and field_131 is valid" do + context "and field_131 is valid and does not expect an answer for field_132" do let(:attributes) { field_130_attributes.merge({ field_131: 2 }) } it "does not add an error" do @@ -1310,7 +1310,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - context "and field_131 expects an answer for field_132" do + context "and field_131 is valid and expects an answer for field_132" do let(:field_131_attributes) { field_130_attributes.merge({ field_131: 1 }) } context "and field_132 is valid" do From 9637209d68bc3f68dbd5911c4d4f06d646235ea6 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 16 Feb 2026 17:15:23 +0000 Subject: [PATCH 40/40] CLDC-4190: Final field fixes --- app/services/bulk_upload/lettings/year2026/row_parser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/bulk_upload/lettings/year2026/row_parser.rb b/app/services/bulk_upload/lettings/year2026/row_parser.rb index 215238d59..80f31e0f7 100644 --- a/app/services/bulk_upload/lettings/year2026/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2026/row_parser.rb @@ -1087,7 +1087,7 @@ private return if renewal? return if referral_fields_valid? - %i[field_116 field_130 field_131 field_132].each do |field| + %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