From 4baf0bcdfc1746a4e9ec65a84255b89d6b41afbc Mon Sep 17 00:00:00 2001 From: Rachael Booth Date: Wed, 27 Mar 2024 11:20:35 +0000 Subject: [PATCH 1/4] CLDC-3350: Add validation for mortgage+deposit for outright sales (#2349) --- .../sales/sale_information_validations.rb | 14 +++ config/locales/en.yml | 1 + .../sale_information_validations_spec.rb | 88 +++++++++++++++++++ 3 files changed, 103 insertions(+) diff --git a/app/models/validations/sales/sale_information_validations.rb b/app/models/validations/sales/sale_information_validations.rb index 024272ac3..86295ceb3 100644 --- a/app/models/validations/sales/sale_information_validations.rb +++ b/app/models/validations/sales/sale_information_validations.rb @@ -54,6 +54,20 @@ module Validations::Sales::SaleInformationValidations end end + def validate_outright_sale_value_matches_mortgage_plus_deposit(record) + return unless record.saledate && record.form.start_year_after_2024? + return unless record.outright_sale? + return unless record.mortgage_used? && record.mortgage + return unless record.deposit && record.value + + if over_tolerance?(record.mortgage_and_deposit_total, record.value, 1) + %i[mortgageused mortgage value deposit].each do |field| + record.errors.add field, I18n.t("validations.sale_information.outright_sale_value", mortgage_and_deposit_total: record.field_formatted_as_currency("mortgage_and_deposit_total"), value: record.field_formatted_as_currency("value")) + end + record.errors.add :ownershipsch, :skip_bu_error, message: I18n.t("validations.sale_information.outright_sale_value", mortgage_and_deposit_total: record.field_formatted_as_currency("mortgage_and_deposit_total"), value: record.field_formatted_as_currency("value")) + end + end + def validate_basic_monthly_rent(record) return unless record.mrent && record.ownershipsch && record.type diff --git a/config/locales/en.yml b/config/locales/en.yml index 6a105b213..48f1168ca 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -633,6 +633,7 @@ en: previous_property_type: property_type_bedsit: "A bedsit cannot have more than 1 bedroom" discounted_ownership_value: "The mortgage, deposit, and grant when added together is %{mortgage_deposit_and_grant_total}, and the purchase purchase price times by the discount is %{value_with_discount}. These figures should be the same" + outright_sale_value: "The mortgage and deposit when added together is %{mortgage_and_deposit_total}, and the purchase price is %{value}. These figures should be the same." monthly_rent: higher_than_expected: "Basic monthly rent must be between £0.00 and £9,999.00" grant: diff --git a/spec/models/validations/sales/sale_information_validations_spec.rb b/spec/models/validations/sales/sale_information_validations_spec.rb index 4859aca2d..969f2c16f 100644 --- a/spec/models/validations/sales/sale_information_validations_spec.rb +++ b/spec/models/validations/sales/sale_information_validations_spec.rb @@ -505,6 +505,94 @@ RSpec.describe Validations::Sales::SaleInformationValidations do end end + describe "#validate_outright_sale_value_matches_mortgage_plus_deposit" do + context "with a 2024 outright sale log" do + let(:record) { FactoryBot.build(:sales_log, value: 300_000, ownershipsch: 3, saledate: Time.zone.local(2024, 5, 1)) } + + context "when a mortgage is used" do + before do + record.mortgageused = 1 + end + + context "and the mortgage plus deposit match the value" do + before do + record.mortgage = 200_000 + record.deposit = 100_000 + end + + it "does not add errors" do + sale_information_validator.validate_outright_sale_value_matches_mortgage_plus_deposit(record) + expect(record.errors).to be_empty + end + end + + context "and the mortgage plus deposit don't match the value" do + before do + record.mortgage = 100_000 + record.deposit = 100_000 + end + + it "adds errors" do + sale_information_validator.validate_outright_sale_value_matches_mortgage_plus_deposit(record) + expect(record.errors["mortgageused"]).to include("The mortgage and deposit when added together is £200,000.00, and the purchase price is £300,000.00. These figures should be the same.") + expect(record.errors["mortgage"]).to include("The mortgage and deposit when added together is £200,000.00, and the purchase price is £300,000.00. These figures should be the same.") + expect(record.errors["deposit"]).to include("The mortgage and deposit when added together is £200,000.00, and the purchase price is £300,000.00. These figures should be the same.") + expect(record.errors["value"]).to include("The mortgage and deposit when added together is £200,000.00, and the purchase price is £300,000.00. These figures should be the same.") + expect(record.errors["ownershipsch"]).to include("The mortgage and deposit when added together is £200,000.00, and the purchase price is £300,000.00. These figures should be the same.") + end + end + + context "and deposit is not provided" do + before do + record.mortgage = 100_000 + record.deposit = nil + end + + it "does not add errors" do + sale_information_validator.validate_outright_sale_value_matches_mortgage_plus_deposit(record) + expect(record.errors).to be_empty + end + end + + context "and mortgage is not provided" do + before do + record.mortgage = nil + record.deposit = 100_000 + end + + it "does not add errors" do + sale_information_validator.validate_outright_sale_value_matches_mortgage_plus_deposit(record) + expect(record.errors).to be_empty + end + end + end + end + + context "with a 2024 log that is not an outright sale" do + let(:record) { FactoryBot.build(:sales_log, value: 300_000, ownershipsch: 2, saledate: Time.zone.local(2024, 5, 1)) } + + it "does not add errors" do + record.mortgageused = 1 + record.mortgage = 100_000 + record.deposit = 100_000 + sale_information_validator.validate_outright_sale_value_matches_mortgage_plus_deposit(record) + expect(record.errors).to be_empty + end + end + + context "with a 2023 outright sale log" do + let(:record) { FactoryBot.build(:sales_log, value: 300_000, ownershipsch: 3, saledate: Time.zone.local(2023, 5, 1)) } + + it "does not add errors" do + record.mortgageused = 1 + record.mortgage = 100_000 + record.deposit = 100_000 + sale_information_validator.validate_outright_sale_value_matches_mortgage_plus_deposit(record) + expect(record.errors).to be_empty + end + end + end + describe "#validate_basic_monthly_rent" do context "when within permitted bounds" do let(:record) { build(:sales_log, mrent: 9998, ownershipsch: 1, type: 2) } From a51abc864b0e14fe4537640abce5725c341d4af8 Mon Sep 17 00:00:00 2001 From: Rachael Booth Date: Wed, 27 Mar 2024 13:00:06 +0000 Subject: [PATCH 2/4] CLDC-3333: Tweak what is a location text (#2337) --- app/views/form/guidance/_finding_location.erb | 2 +- app/views/schemes/changes.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/form/guidance/_finding_location.erb b/app/views/form/guidance/_finding_location.erb index 4107519b3..b50d1802a 100644 --- a/app/views/form/guidance/_finding_location.erb +++ b/app/views/form/guidance/_finding_location.erb @@ -1,4 +1,4 @@ <%= govuk_details(summary_text: "What is a location?") do %> -

A location is a postcode where supported housing is provided under a scheme. A scheme can have multiple locations, and a location can have multiple units at the same postcode.

+

A location is a postcode area where supported housing is provided under a scheme. A scheme can have multiple locations, and a location can have multiple units at the same postcode.

<%= govuk_link_to("Read more about schemes and locations", scheme_changes_path) %>

<% end %> diff --git a/app/views/schemes/changes.html.erb b/app/views/schemes/changes.html.erb index 74c23f0f6..9040d7a05 100644 --- a/app/views/schemes/changes.html.erb +++ b/app/views/schemes/changes.html.erb @@ -9,7 +9,7 @@

A supported housing scheme (also known as a ‘supported housing service’) provides shared or self-contained housing for a particular client group, for example younger or vulnerable people.

What is a location?

-

A location is a postcode where supported housing is provided under a scheme. A scheme can have multiple locations, and a location can have multiple units at the same postcode.

+

A location is a postcode area where supported housing is provided under a scheme. A scheme can have multiple locations, and a location can have multiple units at the same postcode.

How schemes have changed

We have restructured the way we group supported housing scheme data.

From 43c0f457c983bbdfb583f9c0fc9c66f8a9e43c68 Mon Sep 17 00:00:00 2001 From: natdeanlewissoftwire <94526761+natdeanlewissoftwire@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:44:30 +0000 Subject: [PATCH 3/4] CLDC-3363 Uneditable sales log redirect bug fix (#2353) * feat: make check_collection_period log type specific * feat: test check_collection_period routing --- app/controllers/form_controller.rb | 4 +- spec/requests/form_controller_spec.rb | 104 ++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/app/controllers/form_controller.rb b/app/controllers/form_controller.rb index 2bf7ce966..635dc12e4 100644 --- a/app/controllers/form_controller.rb +++ b/app/controllers/form_controller.rb @@ -250,7 +250,9 @@ private def check_collection_period return unless @log - redirect_to lettings_log_path(@log) unless @log.collection_period_open_for_editing? + unless @log.collection_period_open_for_editing? + redirect_to @log.lettings? ? lettings_log_path(@log) : sales_log_path(@log) + end end CONFIRMATION_PAGE_IDS = %w[uprn_confirmation uprn_selection].freeze diff --git a/spec/requests/form_controller_spec.rb b/spec/requests/form_controller_spec.rb index ca558ddc4..bc491a78e 100644 --- a/spec/requests/form_controller_spec.rb +++ b/spec/requests/form_controller_spec.rb @@ -358,6 +358,12 @@ RSpec.describe FormController, type: :request do created_by: user, ) end + let!(:sales_log) do + create( + :sales_log, + created_by: user, + ) + end before do allow(user).to receive(:need_two_factor_authentication?).and_return(false) @@ -612,6 +618,104 @@ RSpec.describe FormController, type: :request do expect(page).to have_content("There is a problem") end end + + context "when allow_future_form_use? is enabled" do + before do + allow(FeatureToggle).to receive(:allow_future_form_use?).and_return(true) + end + + context "when the tenancy start date is out of the editable collection year" do + let(:page_id) { "tenancy_start_date" } + let(:params) do + { + id: lettings_log.id, + lettings_log: { + page: page_id, + "startdate(3i)" => 1, + "startdate(2i)" => 1, + "startdate(1i)" => 1, + }, + } + end + + it "redirects to the review page for the log" do + post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params + follow_redirect! + follow_redirect! + follow_redirect! + expect(page).to have_content("Review lettings log") + end + end + + context "when the sale date is out of the editable collection year" do + let(:page_id) { "completion_date" } + let(:params) do + { + id: sales_log.id, + sales_log: { + page: page_id, + "saledate(3i)" => 1, + "saledate(2i)" => 1, + "saledate(1i)" => 1, + }, + } + end + + it "redirects to the review page for the log" do + post "/sales-logs/#{sales_log.id}/#{page_id.dasherize}", params: params + follow_redirect! + follow_redirect! + follow_redirect! + expect(page).to have_content("Review sales log") + end + end + end + + context "when allow_future_form_use? is disabled" do + before do + allow(FeatureToggle).to receive(:allow_future_form_use?).and_return(false) + end + + context "when the tenancy start date is out of the editable collection year" do + let(:page_id) { "tenancy_start_date" } + let(:params) do + { + id: lettings_log.id, + lettings_log: { + page: page_id, + "startdate(3i)" => 1, + "startdate(2i)" => 1, + "startdate(1i)" => 1, + }, + } + end + + it "validates the date correctly" do + post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params + expect(page).to have_content("There is a problem") + end + end + + context "when the sale date is out of the editable collection year" do + let(:page_id) { "completion_date" } + let(:params) do + { + id: sales_log.id, + sales_log: { + page: page_id, + "saledate(3i)" => 1, + "saledate(2i)" => 1, + "saledate(1i)" => 1, + }, + } + end + + it "validates the date correctly" do + post "/sales-logs/#{sales_log.id}/#{page_id.dasherize}", params: params + expect(page).to have_content("There is a problem") + end + end + end end context "with invalid organisation answers" do From da0d27c8f9f259adee1c95c6c9ccefc5d117f4bb Mon Sep 17 00:00:00 2001 From: natdeanlewissoftwire <94526761+natdeanlewissoftwire@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:43:44 +0000 Subject: [PATCH 4/4] feat: update copy (#2357) --- app/models/form/lettings/questions/reason.rb | 8 ++++---- app/models/form/lettings/questions/reason_renewal.rb | 8 ++++---- config/locales/en.yml | 2 +- .../models/form/lettings/questions/reason_renewal_spec.rb | 8 ++++---- spec/models/form/lettings/questions/reason_spec.rb | 8 ++++---- .../bulk_upload/lettings/year2024/row_parser_spec.rb | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/models/form/lettings/questions/reason.rb b/app/models/form/lettings/questions/reason.rb index 1a7d0a167..223d68634 100644 --- a/app/models/form/lettings/questions/reason.rb +++ b/app/models/form/lettings/questions/reason.rb @@ -18,10 +18,10 @@ class Form::Lettings::Questions::Reason < ::Form::Question def answer_options if form.start_year_after_2024? { - "50" => { "value" => "End of social housing tenancy - no fault" }, - "51" => { "value" => "End of social housing tenancy - evicted due to anti-social behaviour (ASB)" }, - "52" => { "value" => "End of social housing tenancy - evicted due to rent arrears" }, - "53" => { "value" => "End of social housing tenancy - evicted for any other reason" }, + "50" => { "value" => "End of social or private sector tenancy - no fault" }, + "51" => { "value" => "End of social or private sector tenancy - evicted due to anti-social behaviour (ASB)" }, + "52" => { "value" => "End of social or private sector tenancy - evicted due to rent arrears" }, + "53" => { "value" => "End of social or private sector tenancy - evicted for any other reason" }, "1" => { "value" => "Permanently decanted from another property owned by this landlord" }, "2" => { "value" => "Left home country as a refugee" }, "45" => { "value" => "Discharged from prison" }, diff --git a/app/models/form/lettings/questions/reason_renewal.rb b/app/models/form/lettings/questions/reason_renewal.rb index b3e9fc9dc..7f32b0bba 100644 --- a/app/models/form/lettings/questions/reason_renewal.rb +++ b/app/models/form/lettings/questions/reason_renewal.rb @@ -18,10 +18,10 @@ class Form::Lettings::Questions::ReasonRenewal < ::Form::Question def answer_options if form.start_year_after_2024? { - "50" => { "value" => "End of social housing tenancy - no fault" }, - "51" => { "value" => "End of social housing tenancy - evicted due to anti-social behaviour (ASB)" }, - "52" => { "value" => "End of social housing tenancy - evicted due to rent arrears" }, - "53" => { "value" => "End of social housing tenancy - evicted for any other reason" }, + "50" => { "value" => "End of social or private sector tenancy - no fault" }, + "51" => { "value" => "End of social or private sector tenancy - evicted due to anti-social behaviour (ASB)" }, + "52" => { "value" => "End of social or private sector tenancy - evicted due to rent arrears" }, + "53" => { "value" => "End of social or private sector tenancy - evicted for any other reason" }, "20" => { "value" => "Other" }, "47" => { "value" => "Tenant prefers not to say" }, "divider" => { "value" => true }, diff --git a/config/locales/en.yml b/config/locales/en.yml index 48f1168ca..d95187a49 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -554,7 +554,7 @@ en: reason: not_internal_transfer: "Answer cannot be ‘permanently decanted from another property owned by this landlord’ as you told us the source of referral for this tenancy was not an internal transfer" renewal_reason_needed: 'The reason for leaving must be "End of assured shorthold tenancy - no fault" or "End of fixed term tenancy - no fault" if the letting is a renewal' - renewal_reason_needed_2024: 'The reason for leaving must be "End of social housing tenancy - no fault", "End of social housing tenancy - evicted due to anti-social behaviour (ASB)", "End of social housing tenancy - evicted due to rent arrears" or "End of social housing tenancy - evicted for any other reason"' + renewal_reason_needed_2024: '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"' other_not_settled: "Please give the reason for the tenant leaving their last settled home. This is where they were living before they became homeless, were living in temporary accommodation or sleeping rough" condition_effects: no_choices: "You cannot answer this question as you told us nobody in the household has a physical or mental health condition (or other illness) expected to last 12 months or more" diff --git a/spec/models/form/lettings/questions/reason_renewal_spec.rb b/spec/models/form/lettings/questions/reason_renewal_spec.rb index 5575c7e92..d4660d7f4 100644 --- a/spec/models/form/lettings/questions/reason_renewal_spec.rb +++ b/spec/models/form/lettings/questions/reason_renewal_spec.rb @@ -71,10 +71,10 @@ RSpec.describe Form::Lettings::Questions::ReasonRenewal, type: :model do it "has the correct answer_options" do expect(question.answer_options).to eq({ - "50" => { "value" => "End of social housing tenancy - no fault" }, - "51" => { "value" => "End of social housing tenancy - evicted due to anti-social behaviour (ASB)" }, - "52" => { "value" => "End of social housing tenancy - evicted due to rent arrears" }, - "53" => { "value" => "End of social housing tenancy - evicted for any other reason" }, + "50" => { "value" => "End of social or private sector tenancy - no fault" }, + "51" => { "value" => "End of social or private sector tenancy - evicted due to anti-social behaviour (ASB)" }, + "52" => { "value" => "End of social or private sector tenancy - evicted due to rent arrears" }, + "53" => { "value" => "End of social or private sector tenancy - evicted for any other reason" }, "20" => { "value" => "Other" }, "47" => { "value" => "Tenant prefers not to say" }, "divider" => { "value" => true }, diff --git a/spec/models/form/lettings/questions/reason_spec.rb b/spec/models/form/lettings/questions/reason_spec.rb index 68f3fdd36..39724606b 100644 --- a/spec/models/form/lettings/questions/reason_spec.rb +++ b/spec/models/form/lettings/questions/reason_spec.rb @@ -105,10 +105,10 @@ RSpec.describe Form::Lettings::Questions::Reason, type: :model do it "has the correct answer_options" do expect(question.answer_options).to eq({ - "50" => { "value" => "End of social housing tenancy - no fault" }, - "51" => { "value" => "End of social housing tenancy - evicted due to anti-social behaviour (ASB)" }, - "52" => { "value" => "End of social housing tenancy - evicted due to rent arrears" }, - "53" => { "value" => "End of social housing tenancy - evicted for any other reason" }, + "50" => { "value" => "End of social or private sector tenancy - no fault" }, + "51" => { "value" => "End of social or private sector tenancy - evicted due to anti-social behaviour (ASB)" }, + "52" => { "value" => "End of social or private sector tenancy - evicted due to rent arrears" }, + "53" => { "value" => "End of social or private sector tenancy - evicted for any other reason" }, "1" => { "value" => "Permanently decanted from another property owned by this landlord" }, "2" => { "value" => "Left home country as a refugee" }, "45" => { "value" => "Discharged from prison" }, diff --git a/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb index 9549cfe12..d1c2fc761 100644 --- a/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb @@ -1038,7 +1038,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do let(:attributes) { { bulk_upload:, field_98: "1", field_7: "1" } } it "is not permitted" do - expect(parser.errors[:field_98]).to include('The reason for leaving must be "End of social housing tenancy - no fault", "End of social housing tenancy - evicted due to anti-social behaviour (ASB)", "End of social housing tenancy - evicted due to rent arrears" or "End of social housing tenancy - evicted for any other reason"') + expect(parser.errors[:field_98]).to include('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"') end end end