From eb8d1c2adef89ab7a4c6e7aab53b0f60bdfbcd23 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Tue, 18 Apr 2023 09:16:55 +0100 Subject: [PATCH 01/23] Add error to main field when validating other field (#1554) --- app/models/validations/shared_validations.rb | 1 + .../lettings_logs_import_service_spec.rb | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/app/models/validations/shared_validations.rb b/app/models/validations/shared_validations.rb index 0a0c3f63b..6a32563a7 100644 --- a/app/models/validations/shared_validations.rb +++ b/app/models/validations/shared_validations.rb @@ -7,6 +7,7 @@ module Validations::SharedValidations main_field_label = main_label || main_field.to_s.humanize(capitalize: false) other_field_label = other_label || other_field.to_s.humanize(capitalize: false) if record[main_field] == value_other && record[other_field].blank? + record.errors.add main_field.to_sym, I18n.t("validations.other_field_missing", main_field_label:, other_field_label:) record.errors.add other_field.to_sym, I18n.t("validations.other_field_missing", main_field_label:, other_field_label:) end diff --git a/spec/services/imports/lettings_logs_import_service_spec.rb b/spec/services/imports/lettings_logs_import_service_spec.rb index f144a798d..11a25ff22 100644 --- a/spec/services/imports/lettings_logs_import_service_spec.rb +++ b/spec/services/imports/lettings_logs_import_service_spec.rb @@ -323,6 +323,34 @@ RSpec.describe Imports::LettingsLogsImportService do end end + context "and is a other tenancy but missing tenancyother" do + let(:lettings_log_id) { "0b4a68df-30cc-474a-93c0-a56ce8fdad3b" } + + before do + lettings_log_xml.at_xpath("//meta:status").content = "saved" + lettings_log_xml.at_xpath("//xmlns:Q2b").content = "3" + lettings_log_xml.at_xpath("//xmlns:Q2ba").content = "" + end + + it "intercepts the relevant validation error" do + allow(logger).to receive(:warn) + + expect { lettings_log_service.send(:create_log, lettings_log_xml) } + .not_to raise_error + end + + it "clears out the invalid answers" do + allow(logger).to receive(:warn) + + lettings_log_service.send(:create_log, lettings_log_xml) + lettings_log = LettingsLog.find_by(old_id: lettings_log_id) + + expect(lettings_log).not_to be_nil + expect(lettings_log.tenancy).to be_nil + expect(lettings_log.tenancyother).to be_nil + end + end + context "and this is an internal transfer from a non social housing" do before do lettings_log_xml.at_xpath("//xmlns:Q11").content = "9 Residential care home" From f57b93045d4e8cc947b0ee144d1949712aacca0e Mon Sep 17 00:00:00 2001 From: Arthur Campbell <51094020+arfacamble@users.noreply.github.com> Date: Tue, 18 Apr 2023 09:30:49 +0100 Subject: [PATCH 02/23] Revert "CLDC-1945 numeric questions decimal letter entry (#1442)" (#1555) This reverts commit c78f2ef5f97b79f3ad94f12ab6b0dab0ea65a28b. --- app/models/form/lettings/questions/beds.rb | 4 +- app/models/form/lettings/questions/offered.rb | 2 +- .../lettings/questions/offered_social_let.rb | 2 +- app/models/form/sales/questions/age1.rb | 1 - app/models/form/sales/questions/age2.rb | 1 - .../form/sales/questions/deposit_amount.rb | 3 +- .../form/sales/questions/deposit_discount.rb | 1 - app/models/form/sales/questions/discount.rb | 1 - app/models/form/sales/questions/equity.rb | 1 - app/models/form/sales/questions/grant.rb | 1 - .../form/sales/questions/leasehold_charges.rb | 1 - .../form/sales/questions/monthly_rent.rb | 1 - .../form/sales/questions/mortgage_amount.rb | 1 - .../form/sales/questions/mortgage_length.rb | 1 - .../questions/number_of_others_in_property.rb | 1 - app/models/form/sales/questions/person_age.rb | 1 - .../form/sales/questions/previous_bedrooms.rb | 1 - .../questions/property_number_of_bedrooms.rb | 1 - .../form/sales/questions/purchase_price.rb | 1 - app/models/form/sales/questions/savings.rb | 4 +- .../form/sales/questions/staircase_bought.rb | 1 - .../form/sales/questions/staircase_owned.rb | 1 - app/models/form/sales/questions/value.rb | 1 - .../validations/property_validations.rb | 26 +++ .../sales/sale_information_validations.rb | 2 +- app/models/validations/shared_validations.rb | 21 --- .../imports/lettings_logs_import_service.rb | 4 +- config/forms/2021_2022.json | 8 +- config/forms/2022_2023.json | 8 +- config/locales/en.yml | 9 +- spec/fixtures/forms/2021_2022.json | 26 ++- .../questions/offered_social_let_spec.rb | 8 +- .../form/sales/questions/savings_spec.rb | 4 +- spec/models/lettings_log_spec.rb | 8 +- spec/models/sales_log_spec.rb | 4 +- .../validations/property_validations_spec.rb | 49 ++++++ .../validations/shared_validations_spec.rb | 158 ++++++------------ .../requests/lettings_logs_controller_spec.rb | 2 +- .../lettings_logs_import_service_spec.rb | 4 +- 39 files changed, 174 insertions(+), 200 deletions(-) diff --git a/app/models/form/lettings/questions/beds.rb b/app/models/form/lettings/questions/beds.rb index 6fa6c7c2b..75d2a835d 100644 --- a/app/models/form/lettings/questions/beds.rb +++ b/app/models/form/lettings/questions/beds.rb @@ -7,8 +7,8 @@ class Form::Lettings::Questions::Beds < ::Form::Question @type = "numeric" @width = 2 @check_answers_card_number = 0 - @max = 12 - @min = 1 + @max = 150 + @min = 0 @hint_text = "If shared accommodation, enter the number of bedrooms occupied by this household. A bedsit has 1 bedroom." @step = 1 @question_number = 22 diff --git a/app/models/form/lettings/questions/offered.rb b/app/models/form/lettings/questions/offered.rb index 2c9828d31..e41def301 100644 --- a/app/models/form/lettings/questions/offered.rb +++ b/app/models/form/lettings/questions/offered.rb @@ -7,7 +7,7 @@ class Form::Lettings::Questions::Offered < ::Form::Question @type = "numeric" @width = 2 @check_answers_card_number = 0 - @max = 20 + @max = 150 @min = 0 @hint_text = I18n.t("hints.offered") @step = 1 diff --git a/app/models/form/lettings/questions/offered_social_let.rb b/app/models/form/lettings/questions/offered_social_let.rb index dd57acbfe..d3ac29516 100644 --- a/app/models/form/lettings/questions/offered_social_let.rb +++ b/app/models/form/lettings/questions/offered_social_let.rb @@ -7,7 +7,7 @@ class Form::Lettings::Questions::OfferedSocialLet < ::Form::Question @type = "numeric" @width = 2 @check_answers_card_number = 0 - @max = 20 + @max = 150 @min = 0 @hint_text = I18n.t("hints.offered") @step = 1 diff --git a/app/models/form/sales/questions/age1.rb b/app/models/form/sales/questions/age1.rb index 0ae3bf682..d32083b53 100644 --- a/app/models/form/sales/questions/age1.rb +++ b/app/models/form/sales/questions/age1.rb @@ -19,7 +19,6 @@ class Form::Sales::Questions::Age1 < ::Form::Question @check_answers_card_number = 1 @min = 16 @max = 110 - @step = 1 @question_number = 20 end end diff --git a/app/models/form/sales/questions/age2.rb b/app/models/form/sales/questions/age2.rb index c003eb02e..c0dc567a1 100644 --- a/app/models/form/sales/questions/age2.rb +++ b/app/models/form/sales/questions/age2.rb @@ -13,7 +13,6 @@ class Form::Sales::Questions::Age2 < ::Form::Question @check_answers_card_number = 2 @max = 110 @min = 0 - @step = 1 @question_number = 28 end end diff --git a/app/models/form/sales/questions/deposit_amount.rb b/app/models/form/sales/questions/deposit_amount.rb index 784bb56a1..fb6c0e563 100644 --- a/app/models/form/sales/questions/deposit_amount.rb +++ b/app/models/form/sales/questions/deposit_amount.rb @@ -6,9 +6,8 @@ class Form::Sales::Questions::DepositAmount < ::Form::Question @header = "How much cash deposit was paid on the property?" @type = "numeric" @min = 0 - @max = 999_999 - @step = 1 @width = 5 + @max = 999_999 @prefix = "£" @hint_text = "Enter the total cash sum paid by the buyer towards the property that was not funded by the mortgage" @derived = true diff --git a/app/models/form/sales/questions/deposit_discount.rb b/app/models/form/sales/questions/deposit_discount.rb index ae6521392..577da5a91 100644 --- a/app/models/form/sales/questions/deposit_discount.rb +++ b/app/models/form/sales/questions/deposit_discount.rb @@ -7,7 +7,6 @@ class Form::Sales::Questions::DepositDiscount < ::Form::Question @type = "numeric" @min = 0 @max = 999_999 - @step = 1 @width = 5 @prefix = "£" @hint_text = "Enter the total cash discount given on the property being purchased through the Social HomeBuy scheme" diff --git a/app/models/form/sales/questions/discount.rb b/app/models/form/sales/questions/discount.rb index ee39b8916..8c5cf7132 100644 --- a/app/models/form/sales/questions/discount.rb +++ b/app/models/form/sales/questions/discount.rb @@ -7,7 +7,6 @@ class Form::Sales::Questions::Discount < ::Form::Question @type = "numeric" @min = 0 @max = 100 - @step = 1 @width = 5 @suffix = "%" @hint_text = "For Right to Buy (RTB), Preserved Right to Buy (PRTB), and Voluntary Right to Buy (VRTB)

diff --git a/app/models/form/sales/questions/equity.rb b/app/models/form/sales/questions/equity.rb index 4db09a31f..0119fea69 100644 --- a/app/models/form/sales/questions/equity.rb +++ b/app/models/form/sales/questions/equity.rb @@ -7,7 +7,6 @@ class Form::Sales::Questions::Equity < ::Form::Question @type = "numeric" @min = 0 @max = 100 - @step = 1 @width = 5 @suffix = "%" @hint_text = "Enter the amount of initial equity held by the purchaser (for example, 25% or 50%)" diff --git a/app/models/form/sales/questions/grant.rb b/app/models/form/sales/questions/grant.rb index e113be536..9b0fd3091 100644 --- a/app/models/form/sales/questions/grant.rb +++ b/app/models/form/sales/questions/grant.rb @@ -7,7 +7,6 @@ class Form::Sales::Questions::Grant < ::Form::Question @type = "numeric" @min = 0 @max = 999_999 - @step = 1 @width = 5 @prefix = "£" @hint_text = "For all schemes except Right to Buy (RTB), Preserved Right to Buy (PRTB), Voluntary Right to Buy (VRTB) and Rent to Buy" diff --git a/app/models/form/sales/questions/leasehold_charges.rb b/app/models/form/sales/questions/leasehold_charges.rb index 2b2afb3e4..22ed7246e 100644 --- a/app/models/form/sales/questions/leasehold_charges.rb +++ b/app/models/form/sales/questions/leasehold_charges.rb @@ -6,7 +6,6 @@ class Form::Sales::Questions::LeaseholdCharges < ::Form::Question @header = "Enter the total monthly charge" @type = "numeric" @min = 1 - @step = 0.01 @width = 5 @prefix = "£" @ownershipsch = ownershipsch diff --git a/app/models/form/sales/questions/monthly_rent.rb b/app/models/form/sales/questions/monthly_rent.rb index 75c4a7ce5..2a28bc691 100644 --- a/app/models/form/sales/questions/monthly_rent.rb +++ b/app/models/form/sales/questions/monthly_rent.rb @@ -6,7 +6,6 @@ class Form::Sales::Questions::MonthlyRent < ::Form::Question @header = "What is the basic monthly rent?" @type = "numeric" @min = 0 - @step = 0.01 @width = 5 @prefix = "£" @hint_text = "Amount paid before any charges" diff --git a/app/models/form/sales/questions/mortgage_amount.rb b/app/models/form/sales/questions/mortgage_amount.rb index ce4b548e1..9b199b2aa 100644 --- a/app/models/form/sales/questions/mortgage_amount.rb +++ b/app/models/form/sales/questions/mortgage_amount.rb @@ -6,7 +6,6 @@ class Form::Sales::Questions::MortgageAmount < ::Form::Question @header = "What is the mortgage amount?" @type = "numeric" @min = 1 - @step = 1 @width = 5 @prefix = "£" @hint_text = "Enter the amount of mortgage agreed with the mortgage lender. Exclude any deposits or cash payments. Numeric in pounds. Rounded to the nearest pound." diff --git a/app/models/form/sales/questions/mortgage_length.rb b/app/models/form/sales/questions/mortgage_length.rb index 218ea03a1..adaa94d01 100644 --- a/app/models/form/sales/questions/mortgage_length.rb +++ b/app/models/form/sales/questions/mortgage_length.rb @@ -7,7 +7,6 @@ class Form::Sales::Questions::MortgageLength < ::Form::Question @type = "numeric" @min = 0 @max = 60 - @step = 1 @width = 5 @suffix = " years" @hint_text = "You should round up to the nearest year. Value should not exceed 60 years." diff --git a/app/models/form/sales/questions/number_of_others_in_property.rb b/app/models/form/sales/questions/number_of_others_in_property.rb index 450b12eb0..556ddf837 100644 --- a/app/models/form/sales/questions/number_of_others_in_property.rb +++ b/app/models/form/sales/questions/number_of_others_in_property.rb @@ -9,7 +9,6 @@ class Form::Sales::Questions::NumberOfOthersInProperty < ::Form::Question @width = 2 @min = 0 @max = 15 - @step = 1 @question_number = 35 end diff --git a/app/models/form/sales/questions/person_age.rb b/app/models/form/sales/questions/person_age.rb index cdbc9f80b..779e09669 100644 --- a/app/models/form/sales/questions/person_age.rb +++ b/app/models/form/sales/questions/person_age.rb @@ -12,7 +12,6 @@ class Form::Sales::Questions::PersonAge < ::Form::Question @check_answers_card_number = person_index @min = 0 @max = 110 - @step = 1 @question_number = 29 + (4 * person_index) end end diff --git a/app/models/form/sales/questions/previous_bedrooms.rb b/app/models/form/sales/questions/previous_bedrooms.rb index 8f36a8485..d9f9aaddb 100644 --- a/app/models/form/sales/questions/previous_bedrooms.rb +++ b/app/models/form/sales/questions/previous_bedrooms.rb @@ -8,7 +8,6 @@ class Form::Sales::Questions::PreviousBedrooms < ::Form::Question @width = 5 @min = 1 @max = 6 - @step = 1 @hint_text = "For bedsits enter 1" @question_number = 85 end diff --git a/app/models/form/sales/questions/property_number_of_bedrooms.rb b/app/models/form/sales/questions/property_number_of_bedrooms.rb index b37253244..9dbdbf3e8 100644 --- a/app/models/form/sales/questions/property_number_of_bedrooms.rb +++ b/app/models/form/sales/questions/property_number_of_bedrooms.rb @@ -9,7 +9,6 @@ class Form::Sales::Questions::PropertyNumberOfBedrooms < ::Form::Question @width = 10 @min = 1 @max = 9 - @step = 1 @question_number = 11 end end diff --git a/app/models/form/sales/questions/purchase_price.rb b/app/models/form/sales/questions/purchase_price.rb index f88938777..c98a39ef3 100644 --- a/app/models/form/sales/questions/purchase_price.rb +++ b/app/models/form/sales/questions/purchase_price.rb @@ -6,7 +6,6 @@ class Form::Sales::Questions::PurchasePrice < ::Form::Question @header = "What is the full purchase price?" @type = "numeric" @min = 0 - @step = 0.01 @width = 5 @prefix = "£" @hint_text = "For all schemes, including Right to Acquire (RTA), Right to Buy (RTB), Voluntary Right to Buy (VRTB) or Preserved Right to Buy (PRTB) sales, enter the full price of the property without any discount" diff --git a/app/models/form/sales/questions/savings.rb b/app/models/form/sales/questions/savings.rb index 6c01ed2fd..086a0ec8d 100644 --- a/app/models/form/sales/questions/savings.rb +++ b/app/models/form/sales/questions/savings.rb @@ -2,12 +2,12 @@ class Form::Sales::Questions::Savings < ::Form::Question def initialize(id, hsh, page) super @id = "savings" - @check_answer_label = "Buyer’s total savings before any deposit paid" + @check_answer_label = "Buyer’s total savings (to nearest £10) before any deposit paid" @header = "Enter their total savings to the nearest £10" @type = "numeric" @width = 5 @prefix = "£" - @step = 10 + @step = 1 @min = 0 @question_number = 72 end diff --git a/app/models/form/sales/questions/staircase_bought.rb b/app/models/form/sales/questions/staircase_bought.rb index dfdb273f5..9e54e92d3 100644 --- a/app/models/form/sales/questions/staircase_bought.rb +++ b/app/models/form/sales/questions/staircase_bought.rb @@ -8,7 +8,6 @@ class Form::Sales::Questions::StaircaseBought < ::Form::Question @width = 5 @min = 0 @max = 100 - @step = 1 @suffix = "%" @question_number = 77 end diff --git a/app/models/form/sales/questions/staircase_owned.rb b/app/models/form/sales/questions/staircase_owned.rb index 2b4d89861..b8d5a65ec 100644 --- a/app/models/form/sales/questions/staircase_owned.rb +++ b/app/models/form/sales/questions/staircase_owned.rb @@ -8,7 +8,6 @@ class Form::Sales::Questions::StaircaseOwned < ::Form::Question @width = 5 @min = 0 @max = 100 - @step = 1 @suffix = "%" @question_number = 78 end diff --git a/app/models/form/sales/questions/value.rb b/app/models/form/sales/questions/value.rb index d349a99df..4736f37af 100644 --- a/app/models/form/sales/questions/value.rb +++ b/app/models/form/sales/questions/value.rb @@ -6,7 +6,6 @@ class Form::Sales::Questions::Value < ::Form::Question @header = "What was the full purchase price?" @type = "numeric" @min = 0 - @step = 1 @width = 5 @prefix = "£" @hint_text = "Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser)" diff --git a/app/models/validations/property_validations.rb b/app/models/validations/property_validations.rb index e9680facf..d2b6d2a0b 100644 --- a/app/models/validations/property_validations.rb +++ b/app/models/validations/property_validations.rb @@ -2,6 +2,24 @@ module Validations::PropertyValidations # Validations methods need to be called 'validate_' to run on model save # or 'validate_' to run on submit as well + def validate_property_number_of_times_relet(record) + return unless record.offered + + # Since offered is an integer type ActiveRecord will automatically cast that for us + # but it's type casting is a little lax so "random" becomes 0. To make sure that doesn't pass + # validation and then get silently dropped we attempt strict type casting on the original value + # as part of our validation. + begin + Integer(record.offered_before_type_cast) + rescue ArgumentError + record.errors.add :offered, I18n.t("validations.property.offered.relet_number") + end + + if record.offered.negative? || record.offered > 20 + record.errors.add :offered, :over_20, message: I18n.t("validations.property.offered.relet_number") + end + end + REFERRAL_INVALID_TMP = [8, 10, 12, 13, 14, 15].freeze def validate_rsnvac(record) if !record.first_time_property_let_as_social_housing? && record.has_first_let_vacancy_reason? @@ -33,6 +51,10 @@ module Validations::PropertyValidations end def validate_shared_housing_rooms(record) + if record.beds.present? && record.beds <= 0 + record.errors.add :beds, I18n.t("validations.property.beds.non_positive") + end + unless record.unittype_gn.nil? if record.is_bedsit? && record.beds != 1 && record.beds.present? record.errors.add :unittype_gn, I18n.t("validations.property.unittype_gn.one_bedroom_bedsit") @@ -48,6 +70,10 @@ module Validations::PropertyValidations record.errors.add :beds, I18n.t("validations.property.unittype_gn.one_seven_bedroom_shared") end end + + if record.beds.present? && record.beds > 12 + record.errors.add :beds, :over_max, message: I18n.t("validations.property.beds.over_max") + end end def validate_uprn(record) diff --git a/app/models/validations/sales/sale_information_validations.rb b/app/models/validations/sales/sale_information_validations.rb index bf3c2af86..42dbde2a5 100644 --- a/app/models/validations/sales/sale_information_validations.rb +++ b/app/models/validations/sales/sale_information_validations.rb @@ -12,7 +12,7 @@ module Validations::Sales::SaleInformationValidations end def validate_years_living_in_property_before_purchase(record) - return unless record.proplen&.nonzero? + return unless record.proplen && record.proplen.nonzero? case record.type when 18 diff --git a/app/models/validations/shared_validations.rb b/app/models/validations/shared_validations.rb index 6a32563a7..f032a089c 100644 --- a/app/models/validations/shared_validations.rb +++ b/app/models/validations/shared_validations.rb @@ -35,27 +35,6 @@ module Validations::SharedValidations end end - def validate_numeric_step(record) - record.form.numeric_questions.each do |question| - next unless question.step - next unless record[question.id] && question.page.routed_to?(record, nil) - - value = record.public_send("#{question.id}_before_type_cast") - field = question.check_answer_label || question.id - incorrect_accuracy = (value.to_d * 100) % (question.step * 100) != 0 - - if question.step < 1 && incorrect_accuracy - record.errors.add question.id.to_sym, I18n.t("validations.numeric.nearest_hundredth", field:) - elsif incorrect_accuracy || value.to_d != value.to_i # if the user enters a value in exponent notation (eg '4e1') the to_i method does not convert this to the correct value - field = question.check_answer_label || question.id - case question.step - when 1 then record.errors.add question.id.to_sym, I18n.t("validations.numeric.whole_number", field:) - when 10 then record.errors.add question.id.to_sym, I18n.t("validations.numeric.nearest_ten", field:) - end - end - end - end - def validate_property_postcode(record) postcode = record.postcode_full if record.postcode_known? && (postcode.blank? || !postcode.match(POSTCODE_REGEXP)) diff --git a/app/services/imports/lettings_logs_import_service.rb b/app/services/imports/lettings_logs_import_service.rb index 56fb0c0d2..01c71faef 100644 --- a/app/services/imports/lettings_logs_import_service.rb +++ b/app/services/imports/lettings_logs_import_service.rb @@ -290,10 +290,10 @@ module Imports %i[prevten over_20_foster_care] => %w[prevten age1], %i[prevten non_temp_accommodation] => %w[prevten rsnvac], %i[joint not_joint_tenancy] => %w[joint], - %i[offered outside_the_range] => %w[offered], + %i[offered over_20] => %w[offered], %i[earnings over_hard_max] => %w[ecstat1], %i[tshortfall no_outstanding_charges] => %w[tshortfall hbrentshortfall], - %i[beds outside_the_range] => %w[beds], + %i[beds over_max] => %w[beds], %i[tcharge complete_1_of_3] => %w[brent scharge pscharge supcharg tcharge], %i[scharge under_min] => %w[brent scharge pscharge supcharg tcharge], %i[tshortfall must_be_positive] => %w[tshortfall tshortfall_known], diff --git a/config/forms/2021_2022.json b/config/forms/2021_2022.json index cf8ac4cd9..741195f01 100644 --- a/config/forms/2021_2022.json +++ b/config/forms/2021_2022.json @@ -566,7 +566,7 @@ "hint_text": "This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0.", "type": "numeric", "min": 0, - "max": 20, + "max": 150, "step": 1, "width": 2 } @@ -588,7 +588,7 @@ "hint_text": "If the property is being offered for let for the first time, enter 0.", "type": "numeric", "min": 0, - "max": 20, + "max": 150, "step": 1, "width": 2 } @@ -702,8 +702,8 @@ "header": "How many bedrooms does the property have?", "hint_text": "If shared accommodation, enter the number of bedrooms occupied by this household. A bedsit has 1 bedroom.", "type": "numeric", - "min": 1, - "max": 12, + "min": 0, + "max": 150, "step": 1, "width": 2 } diff --git a/config/forms/2022_2023.json b/config/forms/2022_2023.json index 181d8a19c..9b5aa6bb8 100644 --- a/config/forms/2022_2023.json +++ b/config/forms/2022_2023.json @@ -561,7 +561,7 @@ "hint_text": "This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0.", "type": "numeric", "min": 0, - "max": 20, + "max": 150, "step": 1, "width": 2 } @@ -583,7 +583,7 @@ "hint_text": "If the property is being offered for let for the first time, enter 0.", "type": "numeric", "min": 0, - "max": 20, + "max": 150, "step": 1, "width": 2 } @@ -697,8 +697,8 @@ "header": "How many bedrooms does the property have?", "hint_text": "If shared accommodation, enter the number of bedrooms occupied by this household. A bedsit has 1 bedroom.", "type": "numeric", - "min": 1, - "max": 12, + "min": 0, + "max": 150, "step": 1, "width": 2 } diff --git a/config/locales/en.yml b/config/locales/en.yml index 7b65a8cb2..eef342674 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -144,11 +144,6 @@ en: numeric: within_range: "%{field} must be between %{min} and %{max}" above_min: "%{field} must be at least %{min}" - whole_number: "%{field} must be a whole number" - nearest_ten: "%{field} must be given to the nearest ten" - nearest_hundredth: "%{field} must be given to the nearest hundredth" - normal_format: "Enter a number" - date: invalid_date: "Enter a date in the correct format, for example 31 1 2022" outside_collection_window: Enter a date within the 22/23 collection year, which is between 1st April 2022 and 31st March 2023 @@ -218,6 +213,8 @@ en: ten_years_before_tenancy_start: "Enter a void date no more than 10 years before the tenancy start date" before_tenancy_start: "Enter a void date that is before the tenancy start date" after_mrcdate: "Void date must be before the major repairs date if provided" + offered: + relet_number: "Enter a number between 0 and 20 for the amount of times the property has been re-let" la: la_invalid_for_org: "%{org_name} does not operate in %{la_name}" postcode_invalid_for_org: "Enter a postcode in an area covered by %{org_name}" @@ -233,6 +230,8 @@ en: one_seven_bedroom_shared: "A shared house must have 1 to 7 bedrooms" one_three_bedroom_single_tenant_shared: "A shared house with fewer than two tenants must have 1 to 3 bedrooms" beds: + non_positive: "Number of bedrooms has to be greater than 0" + over_max: "Number of bedrooms cannot be more than 12" bedsits_have_max_one_bedroom: "Number of bedrooms must be 1 if the property is a bedsit" proptype: bedsits_have_max_one_bedroom: "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms" diff --git a/spec/fixtures/forms/2021_2022.json b/spec/fixtures/forms/2021_2022.json index d25b575cc..97999d8a3 100644 --- a/spec/fixtures/forms/2021_2022.json +++ b/spec/fixtures/forms/2021_2022.json @@ -908,7 +908,7 @@ "hint_text": "Eligible for housing benefit or Universal Credit", "type": "numeric", "min": 0, - "step": 0.01, + "step": 1, "width": 4, "fields-to-add": [ "brent", @@ -924,7 +924,7 @@ "hint_text": "Eligible for housing benefit or Universal Credit", "type": "numeric", "min": 0, - "step": 0.01, + "step": 1, "width": 4, "fields-to-add": [ "brent", @@ -940,7 +940,7 @@ "hint_text": "Not eligible for housing benefit or Universal Credit. For example, hot water excluding water rates.", "type": "numeric", "min": 0, - "step": 0.01, + "step": 1, "width": 4, "fields-to-add": [ "brent", @@ -957,7 +957,7 @@ "type": "numeric", "min": 0, "max": 300, - "step": 0.01, + "step": 1, "width": 4, "fields-to-add": [ "brent", @@ -973,7 +973,7 @@ "hint_text": "This is the total of rent and all charges", "type": "numeric_output", "min": 0, - "step": 0.01, + "step": 1, "width": 4, "readonly": true, "requires_js": true @@ -990,7 +990,7 @@ "hint_text": "", "type": "numeric", "min": 0, - "step": 1, + "step": "1", "width": 5, "prefix": "£", "suffix": " every week" @@ -1000,12 +1000,11 @@ "care_home_charge": { "questions": { "offered": { - "check_answer_label": "Times previously offered since becoming available", - "header": "Since becoming available for re-let, how many times has the property been previously offered?", - "hint_text": "This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0.", + "check_answer_label": "Basic Rent", + "header": "What is the basic rent?", + "hint_text": "Eligible for housing benefit or Universal Credit", "type": "numeric", "min": 0, - "max": 20, "step": 1, "width": 4 } @@ -1019,12 +1018,11 @@ "care_home_charge_bi_weekly": { "questions": { "offered": { - "check_answer_label": "Times previously offered since becoming available", - "header": "Since becoming available for re-let, how many times has the property been previously offered?", - "hint_text": "This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0.", + "check_answer_label": "Basic Rent", + "header": "What is the basic rent?", + "hint_text": "Eligible for housing benefit or Universal Credit", "type": "numeric", "min": 0, - "max": 20, "step": 1, "width": 4 } diff --git a/spec/models/form/lettings/questions/offered_social_let_spec.rb b/spec/models/form/lettings/questions/offered_social_let_spec.rb index ac1930495..6516c661b 100644 --- a/spec/models/form/lettings/questions/offered_social_let_spec.rb +++ b/spec/models/form/lettings/questions/offered_social_let_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Form::Lettings::Questions::OfferedSocialLet, type: :model do let(:page) { instance_double(Form::Page) } it "has correct page" do - expect(question.page).to be page + expect(question.page).to eq page end it "has the correct id" do @@ -26,12 +26,12 @@ RSpec.describe Form::Lettings::Questions::OfferedSocialLet, type: :model do end it "has the correct minimum and maximum values" do - expect(question.min).to be 0 - expect(question.max).to be 20 + expect(question.min).to eq 0 + expect(question.max).to eq 150 end it "has the correct step" do - expect(question.step).to be 1 + expect(question.step).to eq 1 end it "is not marked as derived" do diff --git a/spec/models/form/sales/questions/savings_spec.rb b/spec/models/form/sales/questions/savings_spec.rb index 8e9c4daa4..3f721d7ef 100644 --- a/spec/models/form/sales/questions/savings_spec.rb +++ b/spec/models/form/sales/questions/savings_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Form::Sales::Questions::Savings, type: :model do end it "has the correct check_answer_label" do - expect(question.check_answer_label).to eq("Buyer’s total savings before any deposit paid") + expect(question.check_answer_label).to eq("Buyer’s total savings (to nearest £10) before any deposit paid") end it "has the correct type" do @@ -40,7 +40,7 @@ RSpec.describe Form::Sales::Questions::Savings, type: :model do end it "has correct step" do - expect(question.step).to be 10 + expect(question.step).to eq(1) end it "has correct prefix" do diff --git a/spec/models/lettings_log_spec.rb b/spec/models/lettings_log_spec.rb index 481f7fbbe..fb0f01be2 100644 --- a/spec/models/lettings_log_spec.rb +++ b/spec/models/lettings_log_spec.rb @@ -112,6 +112,10 @@ RSpec.describe LettingsLog do expect(validator).to receive(:validate_shared_housing_rooms) end + it "validates number of times the property has been relet" do + expect(validator).to receive(:validate_property_number_of_times_relet) + end + it "validates tenancy type" do expect(validator).to receive(:validate_fixed_term_tenancy) expect(validator).to receive(:validate_other_tenancy_type) @@ -3100,11 +3104,11 @@ RSpec.describe LettingsLog do end context "when a non setup field is invalid" do - subject(:model) { build(:lettings_log, :completed, offered: 234) } + subject(:model) { described_class.new(beds: 404) } it "blanks it" do model.valid? - expect { model.blank_invalid_non_setup_fields! }.to change(model, :offered) + expect { model.blank_invalid_non_setup_fields! }.to change(model, :beds) end end end diff --git a/spec/models/sales_log_spec.rb b/spec/models/sales_log_spec.rb index 1fc2cf73e..c008269ea 100644 --- a/spec/models/sales_log_spec.rb +++ b/spec/models/sales_log_spec.rb @@ -509,9 +509,9 @@ RSpec.describe SalesLog, type: :model do let(:completed_sales_log) { create(:sales_log, :completed) } it "returns small numbers correctly formatted as currency" do - completed_sales_log.update!(savings: 20) + completed_sales_log.update!(savings: 4) - expect(completed_sales_log.field_formatted_as_currency("savings")).to eq("£20.00") + expect(completed_sales_log.field_formatted_as_currency("savings")).to eq("£4.00") end it "returns quite large numbers correctly formatted as currency" do diff --git a/spec/models/validations/property_validations_spec.rb b/spec/models/validations/property_validations_spec.rb index e918e3405..3b3e42e9e 100644 --- a/spec/models/validations/property_validations_spec.rb +++ b/spec/models/validations/property_validations_spec.rb @@ -6,6 +6,39 @@ RSpec.describe Validations::PropertyValidations do let(:property_validator_class) { Class.new { include Validations::PropertyValidations } } let(:record) { FactoryBot.create(:lettings_log) } + describe "#validate_property_number_of_times_relet" do + let(:expected_error) { I18n.t("validations.property.offered.relet_number") } + + it "does not add an error if the record offered is missing" do + record.offered = nil + property_validator.validate_property_number_of_times_relet(record) + expect(record.errors).to be_empty + end + + it "does not add an error if offered is valid (number between 0 and 20)" do + record.offered = 0 + property_validator.validate_property_number_of_times_relet(record) + expect(record.errors).to be_empty + record.offered = 10 + property_validator.validate_property_number_of_times_relet(record) + expect(record.errors).to be_empty + record.offered = 20 + property_validator.validate_property_number_of_times_relet(record) + expect(record.errors).to be_empty + end + + it "does add an error when offered is invalid" do + record.offered = "invalid" + property_validator.validate_property_number_of_times_relet(record) + expect(record.errors).not_to be_empty + expect(record.errors["offered"]).to include(match(expected_error)) + record.offered = 21 + property_validator.validate_property_number_of_times_relet(record) + expect(record.errors).not_to be_empty + expect(record.errors["offered"]).to include(match(expected_error)) + end + end + describe "#validate_shared_housing_rooms" do context "when number of bedrooms has not been answered" do it "does not add an error" do @@ -96,6 +129,22 @@ RSpec.describe Validations::PropertyValidations do expect(record.errors["beds"]).to include(I18n.t("validations.property.unittype_gn.one_three_bedroom_single_tenant_shared")) end end + + context "when a negative number of bedrooms is entered" do + it "adds an error" do + record.beds = -4 + property_validator.validate_shared_housing_rooms(record) + expect(record.errors["beds"]).to include(I18n.t("validations.property.beds.non_positive")) + end + end + + context "when a room number higher than 12 has been entered" do + it "adds an error" do + record.beds = 13 + property_validator.validate_shared_housing_rooms(record) + expect(record.errors["beds"]).to include(I18n.t("validations.property.beds.over_max")) + end + end end describe "#validate_unitletas" do diff --git a/spec/models/validations/shared_validations_spec.rb b/spec/models/validations/shared_validations_spec.rb index fe3612c3c..8f5038c5d 100644 --- a/spec/models/validations/shared_validations_spec.rb +++ b/spec/models/validations/shared_validations_spec.rb @@ -4,8 +4,8 @@ RSpec.describe Validations::SharedValidations do subject(:shared_validator) { validator_class.new } let(:validator_class) { Class.new { include Validations::SharedValidations } } - let(:lettings_log) { FactoryBot.create(:lettings_log) } - let(:sales_log) { FactoryBot.create(:sales_log, :completed) } + let(:record) { FactoryBot.create(:lettings_log) } + let(:sales_record) { FactoryBot.create(:sales_log, :completed) } let(:fake_2021_2022_form) { Form.new("spec/fixtures/forms/2021_2022.json") } describe "numeric min max validations" do @@ -15,164 +15,102 @@ RSpec.describe Validations::SharedValidations do context "when validating age" do it "validates that person 1's age is a number" do - lettings_log.age1 = "random" - shared_validator.validate_numeric_min_max(lettings_log) - expect(lettings_log.errors["age1"]) + record.age1 = "random" + shared_validator.validate_numeric_min_max(record) + expect(record.errors["age1"]) .to include(match I18n.t("validations.numeric.within_range", field: "Lead tenant’s age", min: 16, max: 120)) end it "validates that other household member ages are a number" do - lettings_log.age2 = "random" - shared_validator.validate_numeric_min_max(lettings_log) - expect(lettings_log.errors["age2"]) + record.age2 = "random" + shared_validator.validate_numeric_min_max(record) + expect(record.errors["age2"]) .to include(match I18n.t("validations.numeric.within_range", field: "Person 2’s age", min: 1, max: 120)) end it "validates that person 1's age is greater than 16" do - lettings_log.age1 = 15 - shared_validator.validate_numeric_min_max(lettings_log) - expect(lettings_log.errors["age1"]) + record.age1 = 15 + shared_validator.validate_numeric_min_max(record) + expect(record.errors["age1"]) .to include(match I18n.t("validations.numeric.within_range", field: "Lead tenant’s age", min: 16, max: 120)) end it "validates that other household member ages are greater than 1" do - lettings_log.age2 = 0 - shared_validator.validate_numeric_min_max(lettings_log) - expect(lettings_log.errors["age2"]) + record.age2 = 0 + shared_validator.validate_numeric_min_max(record) + expect(record.errors["age2"]) .to include(match I18n.t("validations.numeric.within_range", field: "Person 2’s age", min: 1, max: 120)) end it "validates that person 1's age is less than 121" do - lettings_log.age1 = 121 - shared_validator.validate_numeric_min_max(lettings_log) - expect(lettings_log.errors["age1"]) + record.age1 = 121 + shared_validator.validate_numeric_min_max(record) + expect(record.errors["age1"]) .to include(match I18n.t("validations.numeric.within_range", field: "Lead tenant’s age", min: 16, max: 120)) end it "validates that other household member ages are greater than 121" do - lettings_log.age2 = 123 - shared_validator.validate_numeric_min_max(lettings_log) - expect(lettings_log.errors["age2"]) + record.age2 = 123 + shared_validator.validate_numeric_min_max(record) + expect(record.errors["age2"]) .to include(match I18n.t("validations.numeric.within_range", field: "Person 2’s age", min: 1, max: 120)) end it "validates that person 1's age is between 16 and 120" do - lettings_log.age1 = 63 - shared_validator.validate_numeric_min_max(lettings_log) - expect(lettings_log.errors["age1"]).to be_empty + record.age1 = 63 + shared_validator.validate_numeric_min_max(record) + expect(record.errors["age1"]).to be_empty end it "validates that other household member ages are between 1 and 120" do - lettings_log.age6 = 45 - shared_validator.validate_numeric_min_max(lettings_log) - expect(lettings_log.errors["age6"]).to be_empty + record.age6 = 45 + shared_validator.validate_numeric_min_max(record) + expect(record.errors["age6"]).to be_empty end context "with sales log" do it "validates that person 2's age is between 0 and 110 for non joint purchase" do - sales_log.jointpur = 2 - sales_log.hholdcount = 1 - sales_log.details_known_2 = 1 - sales_log.age2 = 130 - shared_validator.validate_numeric_min_max(sales_log) - expect(sales_log.errors["age2"].first).to eq("Person 2’s age must be between 0 and 110") + sales_record.jointpur = 2 + sales_record.hholdcount = 1 + sales_record.details_known_2 = 1 + sales_record.age2 = 130 + shared_validator.validate_numeric_min_max(sales_record) + expect(sales_record.errors["age2"].first).to eq("Person 2’s age must be between 0 and 110") end it "validates that buyer 2's age is between 0 and 110 for joint purchase" do - sales_log.jointpur = 1 - sales_log.age2 = 130 - shared_validator.validate_numeric_min_max(sales_log) - expect(sales_log.errors["age2"].first).to eq("Buyer 2’s age must be between 0 and 110") + sales_record.jointpur = 1 + sales_record.age2 = 130 + shared_validator.validate_numeric_min_max(sales_record) + expect(sales_record.errors["age2"].first).to eq("Buyer 2’s age must be between 0 and 110") end end end it "adds the correct validation text when a question has a min but not a max" do - sales_log.savings = -10 - shared_validator.validate_numeric_min_max(sales_log) - expect(sales_log.errors["savings"]).to include(match I18n.t("validations.numeric.above_min", field: "Buyer’s total savings before any deposit paid", min: "£0")) + sales_record.savings = -10 + shared_validator.validate_numeric_min_max(sales_record) + expect(sales_record.errors["savings"]).to include(match I18n.t("validations.numeric.above_min", field: "Buyer’s total savings (to nearest £10) before any deposit paid", min: "£0")) end context "when validating percent" do it "validates that suffixes are added in the error message" do - sales_log.ownershipsch = 1 - sales_log.staircase = 1 - sales_log.stairbought = 150 - shared_validator.validate_numeric_min_max(sales_log) - expect(sales_log.errors["stairbought"]) + sales_record.ownershipsch = 1 + sales_record.staircase = 1 + sales_record.stairbought = 150 + shared_validator.validate_numeric_min_max(sales_record) + expect(sales_record.errors["stairbought"]) .to include(match I18n.t("validations.numeric.within_range", field: "Percentage bought in this staircasing transaction", min: "0%", max: "100%")) end end context "when validating price" do - it "validates that prefix £ and delimeter ',' is added in the error message" do - sales_log.income1 = -5 - shared_validator.validate_numeric_min_max(sales_log) - expect(sales_log.errors["income1"]) + it "validates that £ prefix and , is added in the error message" do + sales_record.income1 = -5 + shared_validator.validate_numeric_min_max(sales_record) + expect(sales_record.errors["income1"]) .to include(match I18n.t("validations.numeric.within_range", field: "Buyer 1’s gross annual income", min: "£0", max: "£999,999")) end end end - - describe "validating level of accuracy or rounding for numeric questions" do - context "when validating a question with a step of 1" do - it "adds an error if input is a decimal" do - sales_log.income1 = 30_000.5 - shared_validator.validate_numeric_step(sales_log) - expect(sales_log.errors[:income1]).to include I18n.t("validations.numeric.whole_number", field: "Buyer 1’s gross annual income") - end - - it "adds an error if the user attempts to input a number in exponent format" do - sales_log.income1 = "3e5" - shared_validator.validate_numeric_step(sales_log) - expect(sales_log.errors[:income1]).to include I18n.t("validations.numeric.whole_number", field: "Buyer 1’s gross annual income") - end - - it "does not add an error if input is an integer" do - sales_log.income1 = 30_000 - shared_validator.validate_numeric_step(sales_log) - expect(sales_log.errors).to be_empty - end - end - - context "when validating a question with a step of 10" do - it "adds an error if input is not a multiple of ten" do - sales_log.savings = 30_005 - shared_validator.validate_numeric_step(sales_log) - expect(sales_log.errors[:savings]).to include I18n.t("validations.numeric.nearest_ten", field: "Buyer’s total savings before any deposit paid") - end - - it "adds an error if the user attempts to input a number in exponent format" do - sales_log.savings = "3e5" - shared_validator.validate_numeric_step(sales_log) - expect(sales_log.errors[:savings]).to include I18n.t("validations.numeric.nearest_ten", field: "Buyer’s total savings before any deposit paid") - end - - it "does not add an error if input is a multiple of ten" do - sales_log.savings = 30_000 - shared_validator.validate_numeric_step(sales_log) - expect(sales_log.errors).to be_empty - end - end - - context "when validating a question with a step of 0.01" do - it "adds an error if input has more than 2 decimal places" do - sales_log.mscharge = 30.7418 - shared_validator.validate_numeric_step(sales_log) - expect(sales_log.errors[:mscharge]).to include I18n.t("validations.numeric.nearest_hundredth", field: "Monthly leasehold charges") - end - - it "does not add an error if the user attempts to input a number in exponent format" do - sales_log.mscharge = "3e1" - shared_validator.validate_numeric_step(sales_log) - expect(sales_log.errors).to be_empty - end - - it "does not add an error if input has 2 or fewer decimal places" do - sales_log.mscharge = 30.74 - shared_validator.validate_numeric_step(sales_log) - expect(sales_log.errors).to be_empty - end - end - end end diff --git a/spec/requests/lettings_logs_controller_spec.rb b/spec/requests/lettings_logs_controller_spec.rb index 6de5c4db2..d95e0a3fd 100644 --- a/spec/requests/lettings_logs_controller_spec.rb +++ b/spec/requests/lettings_logs_controller_spec.rb @@ -82,7 +82,7 @@ RSpec.describe LettingsLogsController, type: :request do it "validates lettings log parameters" do json_response = JSON.parse(response.body) expect(response).to have_http_status(:unprocessable_entity) - expect(json_response["errors"]).to match_array([["offered", [I18n.t("validations.numeric.within_range", field: "Times previously offered since becoming available", min: 0, max: 20)]], ["age1", [I18n.t("validations.numeric.within_range", field: "Lead tenant’s age", min: 16, max: 120)]]]) + expect(json_response["errors"]).to match_array([["offered", [I18n.t("validations.property.offered.relet_number")]], ["age1", [I18n.t("validations.numeric.within_range", field: "Lead tenant’s age", min: 16, max: 120)]]]) end end diff --git a/spec/services/imports/lettings_logs_import_service_spec.rb b/spec/services/imports/lettings_logs_import_service_spec.rb index 11a25ff22..4621afb2f 100644 --- a/spec/services/imports/lettings_logs_import_service_spec.rb +++ b/spec/services/imports/lettings_logs_import_service_spec.rb @@ -434,7 +434,7 @@ RSpec.describe Imports::LettingsLogsImportService do end it "intercepts the relevant validation error" do - expect(logger).to receive(:warn).with(/Removing offered with error: Times previously offered since becoming available must be between 0 and 20/) + expect(logger).to receive(:warn).with(/Removing offered with error: Enter a number between 0 and 20 for the amount of times the property has been re-let/) expect { lettings_log_service.send(:create_log, lettings_log_xml) } .not_to raise_error end @@ -530,7 +530,7 @@ RSpec.describe Imports::LettingsLogsImportService do end it "intercepts the relevant validation error" do - expect(logger).to receive(:warn).with(/Removing beds with error: Number of bedrooms must be between 1 and 12/) + expect(logger).to receive(:warn).with(/Removing beds with error: Number of bedrooms cannot be more than 12/) expect { lettings_log_service.send(:create_log, lettings_log_xml) } .not_to raise_error end From 53f024899bb8c0309a05104304ef359e96b29772 Mon Sep 17 00:00:00 2001 From: James Rose Date: Tue, 18 Apr 2023 09:31:02 +0100 Subject: [PATCH 03/23] Add `reset_password_token` to sensitive parameters list (#1556) --- config/initializers/filter_parameter_logging.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb index 71d184ad6..928c8f671 100644 --- a/config/initializers/filter_parameter_logging.rb +++ b/config/initializers/filter_parameter_logging.rb @@ -2,5 +2,5 @@ # Configure sensitive parameters which will be filtered from the log file. Rails.application.config.filter_parameters += %i[ - passw secret token crypt salt certificate otp ssn + passw secret token crypt salt certificate otp ssn reset_password_token ] From 3029f51427de3fede2214c440bb5fe13f4e79834 Mon Sep 17 00:00:00 2001 From: Phil Lee Date: Tue, 18 Apr 2023 10:41:16 +0100 Subject: [PATCH 04/23] CLDC-2279 Bulk upload split feature toggle by log type (#1552) # Context - https://digital.dclg.gov.uk/jira/browse/CLDC-2279 # Changes - split bulk upload feature toggle by log type ie lettings and sales - this allows us to toggle with finer granularity - moved `FeatureToggle` from initializer to service class. not sure why it was an initializer in the first place? this means its available to zeitwerk and will be reloaded when needed --- {config/initializers => app/services}/feature_toggle.rb | 6 +++++- app/views/logs/index.html.erb | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) rename {config/initializers => app/services}/feature_toggle.rb (91%) diff --git a/config/initializers/feature_toggle.rb b/app/services/feature_toggle.rb similarity index 91% rename from config/initializers/feature_toggle.rb rename to app/services/feature_toggle.rb index 9986af543..eed6244d1 100644 --- a/config/initializers/feature_toggle.rb +++ b/app/services/feature_toggle.rb @@ -36,7 +36,11 @@ class FeatureToggle true end - def self.bulk_upload_logs? + def self.bulk_upload_lettings_logs? + !Rails.env.production? + end + + def self.bulk_upload_sales_logs? !Rails.env.production? end diff --git a/app/views/logs/index.html.erb b/app/views/logs/index.html.erb index 2d88c9bd6..04209be73 100644 --- a/app/views/logs/index.html.erb +++ b/app/views/logs/index.html.erb @@ -55,8 +55,12 @@ <%= govuk_button_to "Create a new sales log", sales_logs_path, class: "govuk-!-margin-right-6" %> <% end %> - <% if FeatureToggle.bulk_upload_logs? %> - <%= govuk_button_link_to "Upload #{log_type_for_controller(controller)} logs in bulk", bulk_upload_path_for_controller(controller, id: "start"), secondary: true %> + <% if FeatureToggle.bulk_upload_lettings_logs? && log_type_for_controller(controller) == "lettings" %> + <%= govuk_button_link_to "Upload lettings logs in bulk", bulk_upload_path_for_controller(controller, id: "start"), secondary: true %> + <% end %> + + <% if FeatureToggle.bulk_upload_sales_logs? && log_type_for_controller(controller) == "sales" %> + <%= govuk_button_link_to "Upload sales logs in bulk", bulk_upload_path_for_controller(controller, id: "start"), secondary: true %> <% end %> <% end %> From 6daa84b42e9f60c83db079f10a7504affe73bd14 Mon Sep 17 00:00:00 2001 From: Arthur Campbell <51094020+arfacamble@users.noreply.github.com> Date: Tue, 18 Apr 2023 13:00:39 +0100 Subject: [PATCH 05/23] CLDC-2072 changing startdate to another collection year clear invalid answers (#1469) * write tests that invalid answers are cleared when the start date of a log is changed * clear invalid answers when the start date of a log is changed * correct error * undo previous work to take new direction * write tests to cover updated functionality of method in form to reset values when they are not routed to or when answers to radio questions are no longer valid * update method in form to improve readability and to ensure that when the answers to radio questions are no longer valid, taht they are cleared * add back an "end" deleted by mistake and fix a linting issue * make some minor copy and variable name corrections * fix a broken test after updates * remove bulk upload tests for adding validations to unpermitted values to radio questions, these are now being cleared before validation * fix some tests broken after rebasing * add step to age questions in sales * remove list of question ids from the form that should not be cleared, this information should be held on the questions themselves * rename variables * rename instance variable to be nice and long * minor changes after rebase --- app/models/form.rb | 97 ++++++++---- .../form/lettings/questions/address_line1.rb | 1 + .../form/lettings/questions/address_line2.rb | 1 + app/models/form/lettings/questions/county.rb | 1 + app/models/form/lettings/questions/la.rb | 1 + .../form/lettings/questions/location_id.rb | 6 +- .../questions/postcode_for_full_address.rb | 1 + .../form/lettings/questions/postcode_full.rb | 1 + .../form/lettings/questions/postcode_known.rb | 1 + .../form/lettings/questions/ppcodenk.rb | 1 + .../form/lettings/questions/ppostcode_full.rb | 1 + .../lettings/questions/previous_la_known.rb | 1 + app/models/form/lettings/questions/prevloc.rb | 1 + .../form/lettings/questions/town_or_city.rb | 1 + app/models/form/question.rb | 3 +- .../pages/extra_borrowing_value_check.rb | 3 +- .../form/sales/questions/address_line1.rb | 1 + .../form/sales/questions/address_line2.rb | 1 + app/models/form/sales/questions/age2.rb | 1 + app/models/form/sales/questions/county.rb | 1 + app/models/form/sales/questions/person_age.rb | 1 + app/models/form/sales/questions/postcode.rb | 1 + .../questions/postcode_for_full_address.rb | 1 + .../form/sales/questions/previous_la_known.rb | 1 + .../form/sales/questions/previous_postcode.rb | 1 + .../questions/previous_postcode_known.rb | 1 + app/models/form/sales/questions/prevloc.rb | 1 + .../questions/property_local_authority.rb | 1 + .../form/sales/questions/town_or_city.rb | 1 + app/models/log.rb | 2 +- app/views/form/_checkbox_question.html.erb | 10 +- config/forms/2021_2022.json | 7 + config/forms/2022_2023.json | 7 + config/locales/en.yml | 2 +- spec/factories/lettings_log.rb | 6 + spec/factories/sales_log.rb | 24 +++ spec/fixtures/forms/2021_2022.json | 6 + spec/models/form/sales/questions/age2_spec.rb | 4 + .../form/sales/questions/person_age_spec.rb | 4 + .../sales/questions/uprn_confirmation_spec.rb | 2 +- spec/models/form_spec.rb | 140 ++++++++++++++++-- spec/models/sales_log_spec.rb | 6 +- .../lettings/year2022/row_parser_spec.rb | 18 --- .../lettings/year2023/row_parser_spec.rb | 16 -- 44 files changed, 292 insertions(+), 96 deletions(-) diff --git a/app/models/form.rb b/app/models/form.rb index 80fed4444..cc73d2ca6 100644 --- a/app/models/form.rb +++ b/app/models/form.rb @@ -178,48 +178,78 @@ class Form pages.reject { |p| p.routed_to?(log, current_user) } end - def invalidated_questions(log) - invalidated_page_questions(log) + invalidated_conditional_questions(log) - end - - def invalidated_page_questions(log, current_user = nil) - # we're already treating these fields as a special case and reset their values upon saving a log - callback_questions = %w[postcode_known la ppcodenk previous_la_known prevloc postcode_full ppostcode_full location_id address_line1 address_line2 town_or_city county] - questions.reject { |q| q.page.routed_to?(log, current_user) || q.derived? || callback_questions.include?(q.id) } || [] - end + def reset_not_routed_questions_and_invalid_answers(log) + reset_checkbox_questions_if_not_routed(log) - def reset_not_routed_questions(log) - enabled_questions = enabled_page_questions(log) - enabled_question_ids = enabled_questions.map(&:id) + reset_radio_questions_if_not_routed_or_invalid_answers(log) - invalidated_page_questions(log).each do |question| - if %w[radio checkbox].include?(question.type) - enabled_answer_options = enabled_question_ids.include?(question.id) ? enabled_questions.find { |q| q.id == question.id }.answer_options : {} - current_answer_option_valid = enabled_answer_options.present? ? enabled_answer_options.key?(log.public_send(question.id).to_s) : false + reset_free_user_input_questions_if_not_routed(log) + end - if !current_answer_option_valid && log.respond_to?(question.id.to_s) - Rails.logger.debug("Cleared #{question.id} value") - log.public_send("#{question.id}=", nil) + def reset_checkbox_questions_if_not_routed(log) + checkbox_questions = routed_and_not_routed_questions_by_type(log, type: "checkbox") + checkbox_questions[:not_routed].each do |not_routed_question| + valid_options = checkbox_questions[:routed] + .select { |q| q.id == not_routed_question.id } + .flat_map { |q| q.answer_options.keys } + not_routed_question.answer_options.each_key do |invalid_option| + if !log.respond_to?(invalid_option) || valid_options.include?(invalid_option) || log.public_send(invalid_option).nil? + next else - - (question.answer_options.keys - enabled_answer_options.keys).map do |invalid_answer_option| - Rails.logger.debug("Cleared #{invalid_answer_option} value") - log.public_send("#{invalid_answer_option}=", nil) if log.respond_to?(invalid_answer_option) - end + clear_attribute(log, invalid_option) end + end + end + end + + def reset_radio_questions_if_not_routed_or_invalid_answers(log) + radio_questions = routed_and_not_routed_questions_by_type(log, type: "radio") + valid_radio_options = radio_questions[:routed] + .group_by(&:id) + .transform_values! { |q_array| q_array.flat_map { |q| q.answer_options.keys } } + radio_questions[:not_routed].each do |not_routed_question| + question_id = not_routed_question.id + if !log.respond_to?(question_id) || log.public_send(question_id).nil? || valid_radio_options.key?(question_id) + next + else + clear_attribute(log, question_id) + end + end + valid_radio_options.each do |question_id, valid_options| + if !log.respond_to?(question_id) || valid_options.include?(log.public_send(question_id).to_s) + next + else + clear_attribute(log, question_id) + end + end + end + + def reset_free_user_input_questions_if_not_routed(log) + non_radio_checkbox_questions = routed_and_not_routed_questions_by_type(log) + enabled_question_ids = non_radio_checkbox_questions[:routed].map(&:id) + non_radio_checkbox_questions[:not_routed].each do |not_routed_question| + question_id = not_routed_question.id + if log.public_send(question_id).nil? || enabled_question_ids.include?(question_id) + next else - Rails.logger.debug("Cleared #{question.id} value") - log.public_send("#{question.id}=", nil) unless enabled_question_ids.include?(question.id) + clear_attribute(log, question_id) end end end - def enabled_page_questions(log) - questions - invalidated_page_questions(log) + def routed_and_not_routed_questions_by_type(log, type: nil, current_user: nil) + questions_by_type = if type + questions.reject { |q| q.type != type || q.disable_clearing_if_not_routed_or_dynamic_answer_options } + else + questions.reject { |q| %w[radio checkbox].include?(q.type) || q.disable_clearing_if_not_routed_or_dynamic_answer_options } + end + routed, not_routed = questions_by_type.partition { |q| q.page.routed_to?(log, current_user) || q.derived? } + { routed:, not_routed: } end - def invalidated_conditional_questions(log) - questions.reject { |q| q.enabled?(log) } || [] + def clear_attribute(log, attribute) + Rails.logger.debug("Cleared #{attribute} value") + log.public_send("#{attribute}=", nil) end def readonly_questions @@ -230,6 +260,13 @@ class Form questions.select { |q| q.type == "numeric" } end + def previous_page(page_ids, page_index, log, current_user) + prev_page = get_page(page_ids[page_index - 1]) + return prev_page.id if prev_page.routed_to?(log, current_user) + + previous_page(page_ids, page_index - 1, log, current_user) + end + def send_chain(arr, log) Array(arr).inject(log) { |o, a| o.public_send(*a) } end diff --git a/app/models/form/lettings/questions/address_line1.rb b/app/models/form/lettings/questions/address_line1.rb index 4c8b4151b..ef197e4fd 100644 --- a/app/models/form/lettings/questions/address_line1.rb +++ b/app/models/form/lettings/questions/address_line1.rb @@ -7,6 +7,7 @@ class Form::Lettings::Questions::AddressLine1 < ::Form::Question @type = "text" @plain_label = true @check_answer_label = "Q12 - Address" + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def answer_label(log, _current_user = nil) diff --git a/app/models/form/lettings/questions/address_line2.rb b/app/models/form/lettings/questions/address_line2.rb index 16f7c8336..3b2c36dbc 100644 --- a/app/models/form/lettings/questions/address_line2.rb +++ b/app/models/form/lettings/questions/address_line2.rb @@ -5,6 +5,7 @@ class Form::Lettings::Questions::AddressLine2 < ::Form::Question @header = "Address line 2 (optional)" @type = "text" @plain_label = true + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def hidden_in_check_answers?(_log = nil, _current_user = nil) diff --git a/app/models/form/lettings/questions/county.rb b/app/models/form/lettings/questions/county.rb index 360c0966c..9f0dc7138 100644 --- a/app/models/form/lettings/questions/county.rb +++ b/app/models/form/lettings/questions/county.rb @@ -5,6 +5,7 @@ class Form::Lettings::Questions::County < ::Form::Question @header = "County (optional)" @type = "text" @plain_label = true + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def hidden_in_check_answers?(_log = nil, _current_user = nil) diff --git a/app/models/form/lettings/questions/la.rb b/app/models/form/lettings/questions/la.rb index 3cafda054..5c483d786 100644 --- a/app/models/form/lettings/questions/la.rb +++ b/app/models/form/lettings/questions/la.rb @@ -8,6 +8,7 @@ class Form::Lettings::Questions::La < ::Form::Question @check_answers_card_number = 0 @hint_text = "" @question_number = 13 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def answer_options diff --git a/app/models/form/lettings/questions/location_id.rb b/app/models/form/lettings/questions/location_id.rb index 94196534f..80c2516e8 100644 --- a/app/models/form/lettings/questions/location_id.rb +++ b/app/models/form/lettings/questions/location_id.rb @@ -1,6 +1,7 @@ class Form::Lettings::Questions::LocationId < ::Form::Question - def initialize(_id, hsh, page) - super("location_id", hsh, page) + def initialize(id, hsh, page) + super + @id = "location_id" @check_answer_label = "Location" @header = header_text @type = "radio" @@ -11,6 +12,7 @@ class Form::Lettings::Questions::LocationId < ::Form::Question }, } @question_number = 10 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def answer_options diff --git a/app/models/form/lettings/questions/postcode_for_full_address.rb b/app/models/form/lettings/questions/postcode_for_full_address.rb index 015abc2e8..4f41867d7 100644 --- a/app/models/form/lettings/questions/postcode_for_full_address.rb +++ b/app/models/form/lettings/questions/postcode_for_full_address.rb @@ -17,6 +17,7 @@ class Form::Lettings::Questions::PostcodeForFullAddress < ::Form::Question }, } @plain_label = true + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def hidden_in_check_answers?(_log = nil, _current_user = nil) diff --git a/app/models/form/lettings/questions/postcode_full.rb b/app/models/form/lettings/questions/postcode_full.rb index 1f0a25c49..fc7d7691b 100644 --- a/app/models/form/lettings/questions/postcode_full.rb +++ b/app/models/form/lettings/questions/postcode_full.rb @@ -10,5 +10,6 @@ class Form::Lettings::Questions::PostcodeFull < ::Form::Question @check_answers_card_number = 0 @hint_text = "" @inferred_answers = { "la" => { "is_la_inferred" => true } } + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end end diff --git a/app/models/form/lettings/questions/postcode_known.rb b/app/models/form/lettings/questions/postcode_known.rb index 6af30bb19..ea9adb06b 100644 --- a/app/models/form/lettings/questions/postcode_known.rb +++ b/app/models/form/lettings/questions/postcode_known.rb @@ -8,6 +8,7 @@ class Form::Lettings::Questions::PostcodeKnown < ::Form::Question @check_answers_card_number = 0 @hint_text = "" @answer_options = ANSWER_OPTIONS + @disable_clearing_if_not_routed_or_dynamic_answer_options = true @conditional_for = { "postcode_full" => [1] } @hidden_in_check_answers = { "depends_on" => [{ "postcode_known" => 0 }, { "postcode_known" => 1 }] } end diff --git a/app/models/form/lettings/questions/ppcodenk.rb b/app/models/form/lettings/questions/ppcodenk.rb index 33b03d959..25557a166 100644 --- a/app/models/form/lettings/questions/ppcodenk.rb +++ b/app/models/form/lettings/questions/ppcodenk.rb @@ -11,6 +11,7 @@ class Form::Lettings::Questions::Ppcodenk < ::Form::Question @conditional_for = { "ppostcode_full" => [1] } @hidden_in_check_answers = { "depends_on" => [{ "ppcodenk" => 0 }, { "ppcodenk" => 1 }] } @question_number = 80 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end ANSWER_OPTIONS = { "1" => { "value" => "Yes" }, "0" => { "value" => "No" } }.freeze diff --git a/app/models/form/lettings/questions/ppostcode_full.rb b/app/models/form/lettings/questions/ppostcode_full.rb index 0432d1b5b..86b4c7b31 100644 --- a/app/models/form/lettings/questions/ppostcode_full.rb +++ b/app/models/form/lettings/questions/ppostcode_full.rb @@ -11,5 +11,6 @@ class Form::Lettings::Questions::PpostcodeFull < ::Form::Question @hint_text = "" @inferred_answers = { "prevloc" => { "is_previous_la_inferred" => true } } @question_number = 80 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end end diff --git a/app/models/form/lettings/questions/previous_la_known.rb b/app/models/form/lettings/questions/previous_la_known.rb index a9ff11e55..eb80eda4a 100644 --- a/app/models/form/lettings/questions/previous_la_known.rb +++ b/app/models/form/lettings/questions/previous_la_known.rb @@ -11,6 +11,7 @@ class Form::Lettings::Questions::PreviousLaKnown < ::Form::Question @conditional_for = { "prevloc" => [1] } @hidden_in_check_answers = { "depends_on" => [{ "previous_la_known" => 0 }, { "previous_la_known" => 1 }] } @question_number = 81 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end ANSWER_OPTIONS = { "1" => { "value" => "Yes" }, "0" => { "value" => "No" } }.freeze diff --git a/app/models/form/lettings/questions/prevloc.rb b/app/models/form/lettings/questions/prevloc.rb index 229af2c18..e0082602e 100644 --- a/app/models/form/lettings/questions/prevloc.rb +++ b/app/models/form/lettings/questions/prevloc.rb @@ -9,6 +9,7 @@ class Form::Lettings::Questions::Prevloc < ::Form::Question @check_answers_card_number = 0 @hint_text = "Select ‘Northern Ireland’, ‘Scotland’, ‘Wales’ or ‘Outside the UK’ if the household’s last settled home was outside England." @question_number = 81 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def answer_options diff --git a/app/models/form/lettings/questions/town_or_city.rb b/app/models/form/lettings/questions/town_or_city.rb index f1eac8dff..501e9bec4 100644 --- a/app/models/form/lettings/questions/town_or_city.rb +++ b/app/models/form/lettings/questions/town_or_city.rb @@ -5,6 +5,7 @@ class Form::Lettings::Questions::TownOrCity < ::Form::Question @header = "Town or city" @type = "text" @plain_label = true + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def hidden_in_check_answers?(_log = nil, _current_user = nil) diff --git a/app/models/form/question.rb b/app/models/form/question.rb index 8e8c26739..d4b7c9d76 100644 --- a/app/models/form/question.rb +++ b/app/models/form/question.rb @@ -1,5 +1,5 @@ class Form::Question - attr_accessor :id, :header, :hint_text, :description, :questions, + attr_accessor :id, :header, :hint_text, :description, :questions, :disable_clearing_if_not_routed_or_dynamic_answer_options, :type, :min, :max, :step, :width, :fields_to_add, :result_field, :conditional_for, :readonly, :answer_options, :page, :check_answer_label, :inferred_answers, :hidden_in_check_answers, :inferred_check_answers_value, @@ -42,6 +42,7 @@ class Form::Question @unresolved_hint_text = hsh["unresolved_hint_text"] @question_number = hsh["question_number"] @plain_label = hsh["plain_label"] + @disable_clearing_if_not_routed_or_dynamic_answer_options = hsh["disable_clearing_if_not_routed_or_dynamic_answer_options"] end end diff --git a/app/models/form/sales/pages/extra_borrowing_value_check.rb b/app/models/form/sales/pages/extra_borrowing_value_check.rb index 18f975b8d..5fff74db2 100644 --- a/app/models/form/sales/pages/extra_borrowing_value_check.rb +++ b/app/models/form/sales/pages/extra_borrowing_value_check.rb @@ -9,8 +9,7 @@ class Form::Sales::Pages::ExtraBorrowingValueCheck < Form::Page @title_text = { "translation" => "soft_validations.extra_borrowing.title", } - @informative_text = { - } + @informative_text = {} end def questions diff --git a/app/models/form/sales/questions/address_line1.rb b/app/models/form/sales/questions/address_line1.rb index a95051116..edee2e7ee 100644 --- a/app/models/form/sales/questions/address_line1.rb +++ b/app/models/form/sales/questions/address_line1.rb @@ -7,6 +7,7 @@ class Form::Sales::Questions::AddressLine1 < ::Form::Question @type = "text" @plain_label = true @check_answer_label = "Q15 - Address" + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def answer_label(log, _current_user = nil) diff --git a/app/models/form/sales/questions/address_line2.rb b/app/models/form/sales/questions/address_line2.rb index 0b4ff661c..94396a2af 100644 --- a/app/models/form/sales/questions/address_line2.rb +++ b/app/models/form/sales/questions/address_line2.rb @@ -5,6 +5,7 @@ class Form::Sales::Questions::AddressLine2 < ::Form::Question @header = "Address line 2 (optional)" @type = "text" @plain_label = true + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def hidden_in_check_answers?(_log = nil, _current_user = nil) diff --git a/app/models/form/sales/questions/age2.rb b/app/models/form/sales/questions/age2.rb index c0dc567a1..c003eb02e 100644 --- a/app/models/form/sales/questions/age2.rb +++ b/app/models/form/sales/questions/age2.rb @@ -13,6 +13,7 @@ class Form::Sales::Questions::Age2 < ::Form::Question @check_answers_card_number = 2 @max = 110 @min = 0 + @step = 1 @question_number = 28 end end diff --git a/app/models/form/sales/questions/county.rb b/app/models/form/sales/questions/county.rb index 080ac809f..6586cb9e6 100644 --- a/app/models/form/sales/questions/county.rb +++ b/app/models/form/sales/questions/county.rb @@ -5,6 +5,7 @@ class Form::Sales::Questions::County < ::Form::Question @header = "County (optional)" @type = "text" @plain_label = true + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def hidden_in_check_answers?(_log = nil, _current_user = nil) diff --git a/app/models/form/sales/questions/person_age.rb b/app/models/form/sales/questions/person_age.rb index 779e09669..cdbc9f80b 100644 --- a/app/models/form/sales/questions/person_age.rb +++ b/app/models/form/sales/questions/person_age.rb @@ -12,6 +12,7 @@ class Form::Sales::Questions::PersonAge < ::Form::Question @check_answers_card_number = person_index @min = 0 @max = 110 + @step = 1 @question_number = 29 + (4 * person_index) end end diff --git a/app/models/form/sales/questions/postcode.rb b/app/models/form/sales/questions/postcode.rb index 0f72a9585..55e33199a 100644 --- a/app/models/form/sales/questions/postcode.rb +++ b/app/models/form/sales/questions/postcode.rb @@ -17,5 +17,6 @@ class Form::Sales::Questions::Postcode < ::Form::Question "is_la_inferred" => true, }, } + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end end diff --git a/app/models/form/sales/questions/postcode_for_full_address.rb b/app/models/form/sales/questions/postcode_for_full_address.rb index a1e6f8633..5d3b9f122 100644 --- a/app/models/form/sales/questions/postcode_for_full_address.rb +++ b/app/models/form/sales/questions/postcode_for_full_address.rb @@ -17,6 +17,7 @@ class Form::Sales::Questions::PostcodeForFullAddress < ::Form::Question }, } @plain_label = true + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def hidden_in_check_answers?(_log = nil, _current_user = nil) diff --git a/app/models/form/sales/questions/previous_la_known.rb b/app/models/form/sales/questions/previous_la_known.rb index 1a9a646f3..1a4a2f438 100644 --- a/app/models/form/sales/questions/previous_la_known.rb +++ b/app/models/form/sales/questions/previous_la_known.rb @@ -21,6 +21,7 @@ class Form::Sales::Questions::PreviousLaKnown < ::Form::Question "prevloc" => [1], } @question_number = 58 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end ANSWER_OPTIONS = { diff --git a/app/models/form/sales/questions/previous_postcode.rb b/app/models/form/sales/questions/previous_postcode.rb index 568ba66ea..5eb8e6d54 100644 --- a/app/models/form/sales/questions/previous_postcode.rb +++ b/app/models/form/sales/questions/previous_postcode.rb @@ -18,5 +18,6 @@ class Form::Sales::Questions::PreviousPostcode < ::Form::Question }, } @question_number = 57 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end end diff --git a/app/models/form/sales/questions/previous_postcode_known.rb b/app/models/form/sales/questions/previous_postcode_known.rb index e83fda8d3..f53f52bf0 100644 --- a/app/models/form/sales/questions/previous_postcode_known.rb +++ b/app/models/form/sales/questions/previous_postcode_known.rb @@ -21,6 +21,7 @@ class Form::Sales::Questions::PreviousPostcodeKnown < ::Form::Question ], } @question_number = 57 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end ANSWER_OPTIONS = { diff --git a/app/models/form/sales/questions/prevloc.rb b/app/models/form/sales/questions/prevloc.rb index 75bb50e81..71606aa34 100644 --- a/app/models/form/sales/questions/prevloc.rb +++ b/app/models/form/sales/questions/prevloc.rb @@ -12,6 +12,7 @@ class Form::Sales::Questions::Prevloc < ::Form::Question "value" => "Not known", }] @question_number = 58 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def answer_options diff --git a/app/models/form/sales/questions/property_local_authority.rb b/app/models/form/sales/questions/property_local_authority.rb index 04dbed347..df3fe5e88 100644 --- a/app/models/form/sales/questions/property_local_authority.rb +++ b/app/models/form/sales/questions/property_local_authority.rb @@ -6,6 +6,7 @@ class Form::Sales::Questions::PropertyLocalAuthority < ::Form::Question @header = "What is the property’s local authority?" @type = "select" @question_number = 16 + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def answer_options diff --git a/app/models/form/sales/questions/town_or_city.rb b/app/models/form/sales/questions/town_or_city.rb index 9dde3aeb8..25acfe036 100644 --- a/app/models/form/sales/questions/town_or_city.rb +++ b/app/models/form/sales/questions/town_or_city.rb @@ -5,6 +5,7 @@ class Form::Sales::Questions::TownOrCity < ::Form::Question @header = "Town or city" @type = "text" @plain_label = true + @disable_clearing_if_not_routed_or_dynamic_answer_options = true end def hidden_in_check_answers?(_log = nil, _current_user = nil) diff --git a/app/models/log.rb b/app/models/log.rb index 33d3c4547..fc125fd46 100644 --- a/app/models/log.rb +++ b/app/models/log.rb @@ -174,7 +174,7 @@ private def reset_invalidated_dependent_fields! return unless form - form.reset_not_routed_questions(self) + form.reset_not_routed_questions_and_invalid_answers(self) reset_created_by! end diff --git a/app/views/form/_checkbox_question.html.erb b/app/views/form/_checkbox_question.html.erb index c855e2d88..2a9c33941 100644 --- a/app/views/form/_checkbox_question.html.erb +++ b/app/views/form/_checkbox_question.html.erb @@ -6,14 +6,14 @@ hint: { text: question.hint_text&.html_safe } do %> <% after_divider = false %> - <% question.displayed_answer_options(@log).map do |key, options| %> + <% question.displayed_answer_options(@log).map do |key, option| %> <% if key.starts_with?("divider") %> - <% after_divider = true %> - <%= f.govuk_check_box_divider %> + <% after_divider = true %> + <%= f.govuk_check_box_divider %> <% else %> <%= f.govuk_check_box question.id, key, - label: { text: options["value"] }, - hint: { text: options["hint"] }, + label: { text: option["value"] }, + hint: { text: option["hint"] }, checked: @log[key] == 1, exclusive: after_divider, **stimulus_html_attributes(question) %> diff --git a/config/forms/2021_2022.json b/config/forms/2021_2022.json index 741195f01..3caedd8cb 100644 --- a/config/forms/2021_2022.json +++ b/config/forms/2021_2022.json @@ -24,6 +24,7 @@ "header": "Do you know the property’s postcode?", "hint_text": "", "type": "radio", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "1": { "value": "Yes" @@ -54,6 +55,7 @@ "hint_text": "", "type": "text", "width": 5, + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "inferred_answers": { "la": { "is_la_inferred": true @@ -82,6 +84,7 @@ "header": "What is the local authority of the property?", "hint_text": "", "type": "select", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "": "Select an option", "E07000223": "Adur", @@ -6482,6 +6485,7 @@ "header": "Do you know the postcode of the household’s last settled accommodation?", "hint_text": "This is also known as the household’s ‘last settled home’.", "type": "radio", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "1": { "value": "Yes" @@ -6512,6 +6516,7 @@ "hint_text": "", "type": "text", "width": 5, + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "inferred_answers": { "prevloc": { "is_previous_la_inferred": true @@ -6535,6 +6540,7 @@ "header": "Do you know the local authority of the household’s last settled accommodation?", "hint_text": "This is also known as the household’s ‘last settled home’.", "type": "radio", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "hidden_in_check_answers": { "depends_on": [ { @@ -6564,6 +6570,7 @@ "header": "Select a local authority", "hint_text": "Select ‘Northern Ireland’, ‘Scotland’, ‘Wales’ or ‘Outside the UK’ if the household’s last settled home was outside England.", "type": "select", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "": "Select an option", "S12000033": "Aberdeen City", diff --git a/config/forms/2022_2023.json b/config/forms/2022_2023.json index 9b5aa6bb8..abb9064fe 100644 --- a/config/forms/2022_2023.json +++ b/config/forms/2022_2023.json @@ -24,6 +24,7 @@ "header": "Do you know the property’s postcode?", "hint_text": "", "type": "radio", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "1": { "value": "Yes" @@ -54,6 +55,7 @@ "hint_text": "", "type": "text", "width": 5, + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "inferred_answers": { "la": { "is_la_inferred": true @@ -82,6 +84,7 @@ "header": "What is the local authority of the property?", "hint_text": "", "type": "select", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "": "Select an option", "E07000223": "Adur", @@ -6435,6 +6438,7 @@ "header": "Do you know the postcode of the household’s last settled accommodation?", "hint_text": "This is also known as the household’s ‘last settled home’.", "type": "radio", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "1": { "value": "Yes" @@ -6465,6 +6469,7 @@ "hint_text": "", "type": "text", "width": 5, + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "inferred_answers": { "prevloc": { "is_previous_la_inferred": true @@ -6488,6 +6493,7 @@ "header": "Do you know the local authority of the household’s last settled accommodation?", "hint_text": "This is also known as the household’s ‘last settled home’.", "type": "radio", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "hidden_in_check_answers": { "depends_on": [ { @@ -6517,6 +6523,7 @@ "header": "Select a local authority", "hint_text": "Select ‘Northern Ireland’, ‘Scotland’, ‘Wales’ or ‘Outside the UK’ if the household’s last settled home was outside England.", "type": "select", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "": "Select an option", "S12000033": "Aberdeen City", diff --git a/config/locales/en.yml b/config/locales/en.yml index eef342674..16583a911 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -365,7 +365,7 @@ en: over_20: "The lead tenant must be under 20 as you told us their housing situation immediately before this letting was a children’s home or foster care" ecstat: retired_over_70: "Person %{person_num} must be retired if over 70" - child_under_16: "Person’s %{person_num} working situation must be ’child under 16‘ as you told us they’re under 16" + child_under_16: "Person %{person_num}’s working situation must be ‘child under 16’ as you told us they’re under 16" child_over_16: "Answer cannot be ‘child under 16’ as you told us the person %{person_num} is older than 16" not_student_16_19: "Person’s %{person_num} working situation must be full-time student or prefers not to say as you told us they’re between 16 and 19." student_16_19: diff --git a/spec/factories/lettings_log.rb b/spec/factories/lettings_log.rb index 311e5fecc..58c81e8f3 100644 --- a/spec/factories/lettings_log.rb +++ b/spec/factories/lettings_log.rb @@ -158,6 +158,12 @@ FactoryBot.define do sheltered { 0 } household_charge { 0 } end + trait :sheltered_housing do + needstype { 2 } + end + trait :startdate_today do + startdate { Time.zone.today } + end created_at { Time.zone.today } updated_at { Time.zone.today } end diff --git a/spec/factories/sales_log.rb b/spec/factories/sales_log.rb index 4bea77a65..c61b0cd93 100644 --- a/spec/factories/sales_log.rb +++ b/spec/factories/sales_log.rb @@ -10,6 +10,30 @@ FactoryBot.define do type { 8 } saledate { Time.utc(2023, 2, 2, 10, 36, 49) } end + trait :shared_ownership do + ownershipsch { 1 } + type { 30 } + end + trait :privacy_notice_seen do + privacynotice { 1 } + end + trait :saledate_today do + saledate { Time.zone.today } + end + trait :shared_ownership_setup_complete do + saledate_today + ownershipsch { 1 } + type { 30 } + jointpur { 2 } + end + trait :outright_sale_setup_complete do + saledate_today + ownershipsch { 3 } + type { 10 } + companybuy { 2 } + buylivein { 1 } + jointpur { 2 } + end trait :completed do ownershipsch { 2 } type { 8 } diff --git a/spec/fixtures/forms/2021_2022.json b/spec/fixtures/forms/2021_2022.json index 97999d8a3..69f23bbd6 100644 --- a/spec/fixtures/forms/2021_2022.json +++ b/spec/fixtures/forms/2021_2022.json @@ -411,6 +411,7 @@ "hint_text": "Type ahead to filter the options", "type": "select", "check_answer_label": "Accessible Select", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "": "Select an option", "E07000223": "Adur", @@ -473,6 +474,7 @@ "hint_text": "Type ahead to filter the options", "type": "select", "check_answer_label": "Accessible Select", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "": "Select an option", "E07000223": "Adur", @@ -500,6 +502,7 @@ "header": "Do you know the property’s postcode?", "hint_text": "", "type": "radio", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "1": { "value": "Yes" @@ -521,6 +524,7 @@ "hint_text": "", "type": "text", "width": 5, + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "inferred_answers": { "la": { "is_la_inferred": true @@ -544,6 +548,7 @@ "header": "Do you know what local authority the property is located in?", "hint_text": "", "type": "radio", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "answer_options": { "0": { "value": "No" @@ -1151,6 +1156,7 @@ "header": "Postcode for the previous accommodation", "hint_text": "If the household has moved from settled accommodation immediately prior to being re-housed", "type": "text", + "disable_clearing_if_not_routed_or_dynamic_answer_options": true, "width": 5 } } diff --git a/spec/models/form/sales/questions/age2_spec.rb b/spec/models/form/sales/questions/age2_spec.rb index e98f41f93..fc5dfa77a 100644 --- a/spec/models/form/sales/questions/age2_spec.rb +++ b/spec/models/form/sales/questions/age2_spec.rb @@ -59,4 +59,8 @@ RSpec.describe Form::Sales::Questions::Age2, type: :model do it "has the correct max" do expect(question.max).to eq(110) end + + it "has the correct step" do + expect(question.step).to be 1 + end end diff --git a/spec/models/form/sales/questions/person_age_spec.rb b/spec/models/form/sales/questions/person_age_spec.rb index 9f475f6b6..1358ec4d2 100644 --- a/spec/models/form/sales/questions/person_age_spec.rb +++ b/spec/models/form/sales/questions/person_age_spec.rb @@ -40,6 +40,10 @@ RSpec.describe Form::Sales::Questions::PersonAge, type: :model do expect(question.width).to eq(3) end + it "has the correct step" do + expect(question.step).to be 1 + end + context "with person 2" do let(:person_index) { 2 } let(:question_id) { "age2" } diff --git a/spec/models/form/sales/questions/uprn_confirmation_spec.rb b/spec/models/form/sales/questions/uprn_confirmation_spec.rb index 27f8ec125..d4917ee67 100644 --- a/spec/models/form/sales/questions/uprn_confirmation_spec.rb +++ b/spec/models/form/sales/questions/uprn_confirmation_spec.rb @@ -50,7 +50,7 @@ RSpec.describe Form::Sales::Questions::UprnConfirmation, type: :model do context "when address is present" do it "returns formatted value" do - log = create(:sales_log, address_line1: "1, Test Street", town_or_city: "Test Town", county: "Test County", postcode_full: "AA1 1AA", uprn: "1234", uprn_known: 1) + log = build(:sales_log, :outright_sale_setup_complete, address_line1: "1, Test Street", town_or_city: "Test Town", county: "Test County", postcode_full: "AA1 1AA", uprn: "1234", uprn_known: 1) expect(question.notification_banner(log)).to eq( { diff --git a/spec/models/form_spec.rb b/spec/models/form_spec.rb index f2a80efe3..5045d0280 100644 --- a/spec/models/form_spec.rb +++ b/spec/models/form_spec.rb @@ -204,32 +204,140 @@ RSpec.describe Form, type: :model do end end - describe "invalidated_page_questions" do - let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, needstype: 1) } - let(:expected_invalid) { %w[scheme_id retirement_value_check condition_effects cbl conditional_question_no_second_question net_income_value_check dependent_question offered layear declaration] } + describe "#reset_not_routed_questions_and_invalid_answers" do + around do |example| + Singleton.__init__(FormHandler) + Timecop.freeze(now) do + FormHandler.instance.use_real_forms! + example.run + end + FormHandler.instance.use_fake_forms! + end + + let(:now) { Time.zone.local(2023, 5, 5) } + + context "when there are multiple radio questions for attribute X" do + context "and attribute Y is changed such that a different question for X is routed to" do + let(:log) { FactoryBot.create(:lettings_log, :setup_completed, :sheltered_housing, startdate: now, renewal: 0, prevten:) } + + context "and the value of X remains valid" do + let(:prevten) { 36 } - context "when dependencies are not met" do - it "returns an array of question keys whose pages conditions are not met" do - expect(form.invalidated_page_questions(lettings_log).map(&:id).uniq).to eq(expected_invalid) + it "the value of this attribute is not cleared" do + log.renewal = 1 + log.form.reset_not_routed_questions_and_invalid_answers(log) + expect(log.prevten).to be 36 + end + end + + context "and the value of X is now invalid" do + let(:prevten) { 30 } + + it "the value of this attribute is cleared" do + log.renewal = 1 + log.form.reset_not_routed_questions_and_invalid_answers(log) + expect(log.prevten).to be nil + end + end end end - context "with two pages having the same question and only one has dependencies met" do - it "returns an array of question keys whose pages conditions are not met" do - lettings_log["preg_occ"] = "No" - expect(form.invalidated_page_questions(lettings_log).map(&:id).uniq).to eq(expected_invalid) + context "when there is one radio question for attribute X" do + context "and the start date or sale date is changed such that the collection year changes and there are different options" do + let(:log) { FactoryBot.create(:lettings_log, :setup_completed, :sheltered_housing, startdate: now, sheltered:) } + + context "and the value of X remains valid" do + let(:sheltered) { 2 } + + it "the value of this attribute is not cleared" do + log.update!(startdate: Time.zone.local(2023, 1, 1)) + expect(log.sheltered).to be 2 + end + end + + context "and the value of X is now invalid" do + let(:sheltered) { 5 } + + it "the value of this attribute is cleared" do + log.update!(startdate: Time.zone.local(2023, 1, 1)) + expect(log.sheltered).to be nil + end + end end end - context "when a question is marked as `derived` and `depends_on: false`" do - let(:lettings_log) { FactoryBot.build(:lettings_log, :in_progress, startdate: Time.utc(2022, 4, 2, 10, 36, 49)) } + context "when there is one free user input question for an attribute X" do + let(:log) { FactoryBot.create(:sales_log, :shared_ownership_setup_complete, staircase: 1, stairbought: 25) } - it "does not count it's questions as invalidated" do - expect(form.enabled_page_questions(lettings_log).map(&:id).uniq).to include("tshortfall_known") + context "and attribute Y is changed such that it is no longer routed to" do + it "the value of this attribute is cleared" do + expect(log.stairbought).to be 25 + log.staircase = 2 + log.form.reset_not_routed_questions_and_invalid_answers(log) + expect(log.stairbought).to be nil + end end + end + + context "when there are multiple free user input questions for attribute X" do + context "and attribute Y is changed such that a different question for X is routed to" do + let(:log) { FactoryBot.create(:sales_log, :saledate_today, :shared_ownership, :privacy_notice_seen, jointpur: 1, jointmore: 2, hholdcount: expected_hholdcount) } + let(:expected_hholdcount) { 2 } + + it "the value of this attribute is not cleared" do + log.jointpur = 2 + log.form.reset_not_routed_questions_and_invalid_answers(log) + expect(log.hholdcount).to eq expected_hholdcount + end + end + + context "and attribute Y is changed such that no questions for X are routed to" do + let(:log) { FactoryBot.create(:sales_log, :shared_ownership_setup_complete, value: initial_value) } + let(:initial_value) { 200_000.to_d } + + it "the value of this attribute is cleared" do + expect(log.value).to eq initial_value + log.ownershipsch = 2 + log.form.reset_not_routed_questions_and_invalid_answers(log) + expect(log.value).to be nil + end + end + end + + context "when a value is changed such that a checkbox question is no longer routed to" do + let(:log) { FactoryBot.create(:lettings_log, :setup_completed, startdate: now, reasonpref: 1, rp_homeless: 1, rp_medwel: 1, rp_hardship: 1) } + + it "all attributes relating to that checkbox question are cleared" do + expect(log.rp_homeless).to be 1 + log.reasonpref = 2 + log.form.reset_not_routed_questions_and_invalid_answers(log) + expect(log.rp_homeless).to be nil + expect(log.rp_medwel).to be nil + expect(log.rp_hardship).to be nil + end + end + + context "when an attribute is derived, but no questions for that attribute are routed to" do + let(:log) { FactoryBot.create(:sales_log, :outright_sale_setup_complete, value: 200_000) } + + it "the value of this attribute is not cleared" do + expect(log.deposit).to be nil + log.update!(mortgageused: 2) + expect(log.form.questions.any? { |q| q.id == "deposit" && q.page.routed_to?(log, nil) }).to be false + expect(log.deposit).not_to be nil + end + end + + context "when an attribute is related to a callback question with no set answer options, and no questions for that attribute are routed to" do + let(:location) { FactoryBot.create(:location) } + let(:log) { FactoryBot.create(:lettings_log, :startdate_today) } - it "does not route to the page" do - expect(form.invalidated_pages(lettings_log).map(&:id)).to include("outstanding_amount_known") + # Pages::PropertyPostcode and questions inside have been removed from form. do we not want to delete and migration delete_column? + it "the value of this attribute is not cleared" do + expect(log.form.questions.find { |q| q.id == "location_id" }.answer_options.keys).to be_empty + log.location_id = location.id + log.form.reset_not_routed_questions_and_invalid_answers(log) + expect(log.location_id).not_to be nil end end end diff --git a/spec/models/sales_log_spec.rb b/spec/models/sales_log_spec.rb index c008269ea..be03fa0fe 100644 --- a/spec/models/sales_log_spec.rb +++ b/spec/models/sales_log_spec.rb @@ -181,7 +181,7 @@ RSpec.describe SalesLog, type: :model do let(:sales_log) { create(:sales_log, :completed) } it "correctly derives and saves exday, exmonth and exyear" do - sales_log.update!(exdate: Time.gm(2022, 5, 4), saledate: Time.gm(2022, 7, 4), ownershipsch: 1, staircase: 2, resale: 2) + sales_log.update!(exdate: Time.gm(2022, 5, 4), saledate: Time.gm(2022, 7, 4), ownershipsch: 1, type: 18, staircase: 2, resale: 2, proplen: 0) record_from_db = ActiveRecord::Base.connection.execute("select exday, exmonth, exyear from sales_logs where id=#{sales_log.id}").to_a[0] expect(record_from_db["exday"]).to eq(4) expect(record_from_db["exmonth"]).to eq(5) @@ -571,8 +571,8 @@ RSpec.describe SalesLog, type: :model do end end - context "when service errors" do - let(:sales_log) { create(:sales_log, uprn_known: 1, uprn: "123456789", uprn_confirmed: 1) } + context "when the API returns an error" do + let(:sales_log) { build(:sales_log, :outright_sale_setup_complete, uprn_known: 1, uprn: "123456789", uprn_confirmed: 1) } let(:error_message) { "error" } it "adds error to sales log" do diff --git a/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb index a39f0969c..fd60c4a7b 100644 --- a/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb @@ -852,16 +852,6 @@ RSpec.describe BulkUpload::Lettings::Year2022::RowParser do end end - describe "#field_134" do - context "when an unpermitted value" do - let(:attributes) { { bulk_upload:, field_134: "3" } } - - it "has errors on the field" do - expect(parser.errors[:field_134]).to be_present - end - end - end - describe "#field_103" do context "when null" do let(:attributes) { setup_section_params.merge({ field_103: nil }) } @@ -874,14 +864,6 @@ RSpec.describe BulkUpload::Lettings::Year2022::RowParser do expect(parser.errors[:field_103]).to eql(["You must answer type of building"]) end end - - context "when unpermitted values" do - let(:attributes) { setup_section_params.merge({ field_103: "4" }) } - - it "returns an error" do - expect(parser.errors[:field_103]).to be_present - end - end end end diff --git a/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb index 37d8e24a5..3c472472e 100644 --- a/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb @@ -793,14 +793,6 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do end describe "#field_6" do # renewal - context "when an unpermitted value" do - let(:attributes) { { bulk_upload:, field_6: "3" } } - - it "has errors on the field" do - expect(parser.errors[:field_6]).to be_present - end - end - context "when blank" do let(:attributes) { { bulk_upload:, field_1: owning_org.old_visible_id, field_6: "" } } @@ -842,14 +834,6 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do expect(parser.errors[:field_30]).to eql(["You must answer type of building"]) end end - - context "when unpermitted values" do - let(:attributes) { setup_section_params.merge({ field_30: "4" }) } - - it "returns an error" do - expect(parser.errors[:field_30]).to be_present - end - end end describe "#field_52" do # age2 From df8111aab2d1a66241b19beb1f5f465a394ac35b Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Tue, 18 Apr 2023 17:13:37 +0100 Subject: [PATCH 06/23] Always route to uprn page on sales for 23/24 (#1559) * Remove routed to on uprn page * update tests --- app/models/form/sales/pages/uprn.rb | 4 ---- spec/models/form/sales/pages/uprn_spec.rb | 19 ------------------- spec/models/form/sales/questions/uprn_spec.rb | 1 + 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/app/models/form/sales/pages/uprn.rb b/app/models/form/sales/pages/uprn.rb index ad247207f..c4ae5b357 100644 --- a/app/models/form/sales/pages/uprn.rb +++ b/app/models/form/sales/pages/uprn.rb @@ -11,10 +11,6 @@ class Form::Sales::Pages::Uprn < ::Form::Page ] end - def routed_to?(log, _current_user = nil) - log.uprn_known == 1 - end - def skip_text "Enter address instead" end diff --git a/spec/models/form/sales/pages/uprn_spec.rb b/spec/models/form/sales/pages/uprn_spec.rb index a0cd5b7bc..b015ce877 100644 --- a/spec/models/form/sales/pages/uprn_spec.rb +++ b/spec/models/form/sales/pages/uprn_spec.rb @@ -35,25 +35,6 @@ RSpec.describe Form::Sales::Pages::Uprn, type: :model do expect(page.skip_text).to eq("Enter address instead") end - describe "has correct routed_to?" do - context "when uprn_known != 1" do - let(:log) { create(:sales_log, uprn_known: 0) } - - it "returns false" do - expect(page.routed_to?(log)).to eq(false) - end - end - - context "when uprn_known == 1" do - let(:log) { create(:sales_log) } - - it "returns true" do - log.uprn_known = 1 - expect(page.routed_to?(log)).to eq(true) - end - end - end - describe "has correct skip_href" do context "when log is nil" do it "is nil" do diff --git a/spec/models/form/sales/questions/uprn_spec.rb b/spec/models/form/sales/questions/uprn_spec.rb index 46e635868..f1e5394d6 100644 --- a/spec/models/form/sales/questions/uprn_spec.rb +++ b/spec/models/form/sales/questions/uprn_spec.rb @@ -56,6 +56,7 @@ RSpec.describe Form::Sales::Questions::Uprn, type: :model do let(:log) do create( :sales_log, + :completed, address_line1: "1, Test Street", town_or_city: "Test Town", county: "Test County", From 6b660535953eba06753badf8e80cc5063068b432 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:22:14 +0100 Subject: [PATCH 07/23] CLDC-2055 Add merging organisations merge request page (#1535) * Create merge_request table and paths * Save merging organisations wip * Add update organisations * Add merging organisations validation * merge fixes * Update schema to have merge_request_organisations * Add relationships between merge request and organisations * Update validations and saving organisations * Add ability to remove merging orgs from the list * Allow support users to create merge request for any organisation * Update wording in organisations view * Allow adding other merging organisations * Add back button, update content * Add validation if the organisation is not selected * Fix path * Use generic update method * Update validations * fix remove organisation * remove reloads * Update routes * Authenticate scope * PR review changes * Add status, run validations unless the status is unsubmitted and save requesting organisation as a merging organisation as well * PR comments * Display continue button when there are at least 2 merging organisations --- app/controllers/merge_requests_controller.rb | 86 ++++++ app/controllers/organisations_controller.rb | 4 + app/models/merge_request.rb | 11 + app/models/merge_request_organisation.rb | 34 +++ .../merge_requests/organisations.html.erb | 49 +++ ...{merge.html.erb => merge_request.html.erb} | 9 +- app/views/organisations/show.html.erb | 2 +- config/locales/en.yml | 3 + config/routes.rb | 11 +- ...20230412111338_add_merge_requests_table.rb | 9 + .../20230413135407_add_merge_organisations.rb | 9 + ...30418095819_add_status_to_merge_request.rb | 5 + db/schema.rb | 31 +- .../merge_requests_controller_spec.rb | 278 ++++++++++++++++++ .../requests/organisations_controller_spec.rb | 10 +- 15 files changed, 532 insertions(+), 19 deletions(-) create mode 100644 app/controllers/merge_requests_controller.rb create mode 100644 app/models/merge_request.rb create mode 100644 app/models/merge_request_organisation.rb create mode 100644 app/views/merge_requests/organisations.html.erb rename app/views/organisations/{merge.html.erb => merge_request.html.erb} (89%) create mode 100644 db/migrate/20230412111338_add_merge_requests_table.rb create mode 100644 db/migrate/20230413135407_add_merge_organisations.rb create mode 100644 db/migrate/20230418095819_add_status_to_merge_request.rb create mode 100644 spec/requests/merge_requests_controller_spec.rb diff --git a/app/controllers/merge_requests_controller.rb b/app/controllers/merge_requests_controller.rb new file mode 100644 index 000000000..3d1aa3f5f --- /dev/null +++ b/app/controllers/merge_requests_controller.rb @@ -0,0 +1,86 @@ +class MergeRequestsController < ApplicationController + before_action :find_resource, only: %i[update organisations update_organisations remove_merging_organisation] + before_action :authenticate_user! + before_action :authenticate_scope!, except: [:create] + + def create + ActiveRecord::Base.transaction do + @merge_request = MergeRequest.create!(merge_request_params.merge(status: :unsubmitted)) + MergeRequestOrganisation.create!({ merge_request: @merge_request, merging_organisation: @merge_request.requesting_organisation }) + end + redirect_to organisations_merge_request_path(@merge_request) + rescue ActiveRecord::RecordInvalid + render_not_found + end + + def organisations + @answer_options = organisations_answer_options + end + + def update + if @merge_request.update(merge_request_params) + redirect_to next_page_path + else + render previous_template, status: :unprocessable_entity + end + end + + def update_organisations + merge_request_organisation = MergeRequestOrganisation.new(merge_request_organisation_params) + @answer_options = organisations_answer_options + if merge_request_organisation.save + render :organisations + else + render :organisations, status: :unprocessable_entity + end + end + + def remove_merging_organisation + MergeRequestOrganisation.find_by(merge_request_organisation_params)&.destroy! + @answer_options = organisations_answer_options + render :organisations + end + +private + + def organisations_answer_options + answer_options = { "" => "Select an option" } + + Organisation.all.pluck(:id, :name).each do |organisation| + answer_options[organisation[0]] = organisation[1] + end + answer_options + end + + def merge_request_params + merge_params = params.fetch(:merge_request, {}).permit(:requesting_organisation_id, :other_merging_organisations, :status) + + if merge_params[:requesting_organisation_id].present? && (current_user.data_coordinator? || current_user.data_provider?) + merge_params[:requesting_organisation_id] = current_user.organisation.id + end + + merge_params + end + + def merge_request_organisation_params + { merge_request: @merge_request, merging_organisation_id: params[:merge_request][:merging_organisation] } + end + + def find_resource + @merge_request = MergeRequest.find(params[:id]) + end + + def next_page_path + absorbing_organisation_merge_request_path(@merge_request) + end + + def previous_template + :organisations + end + + def authenticate_scope! + if current_user.organisation != @merge_request.requesting_organisation && !current_user.support? + render_not_found + end + end +end diff --git a/app/controllers/organisations_controller.rb b/app/controllers/organisations_controller.rb index 1bd4694ec..70943063f 100644 --- a/app/controllers/organisations_controller.rb +++ b/app/controllers/organisations_controller.rb @@ -137,6 +137,10 @@ class OrganisationsController < ApplicationController end end + def merge_request + @merge_request = MergeRequest.new + end + private def org_params diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb new file mode 100644 index 000000000..111d25321 --- /dev/null +++ b/app/models/merge_request.rb @@ -0,0 +1,11 @@ +class MergeRequest < ApplicationRecord + belongs_to :requesting_organisation, class_name: "Organisation" + has_many :merge_request_organisations + has_many :merging_organisations, through: :merge_request_organisations, source: :merging_organisation + + STATUS = { + "unsubmitted" => 0, + "submitted" => 1, + }.freeze + enum status: STATUS +end diff --git a/app/models/merge_request_organisation.rb b/app/models/merge_request_organisation.rb new file mode 100644 index 000000000..08c1d6d45 --- /dev/null +++ b/app/models/merge_request_organisation.rb @@ -0,0 +1,34 @@ +class MergeRequestOrganisation < ApplicationRecord + belongs_to :merge_request, class_name: "MergeRequest" + belongs_to :merging_organisation, class_name: "Organisation" + validates :merge_request, presence: { message: I18n.t("validations.merge_request.merge_request_id.blank") } + validates :merging_organisation, presence: { message: I18n.t("validations.merge_request.merging_organisation_id.blank") } + validate :validate_merging_organisations + + scope :not_unsubmitted, -> { joins(:merge_request).where.not(merge_requests: { status: "unsubmitted" }) } + scope :with_merging_organisation, ->(merging_organisation) { where(merging_organisation:) } + + has_paper_trail + +private + + def validate_merging_organisations + if MergeRequestOrganisation.where(merge_request:, merging_organisation:).count.positive? + errors.add(:merging_organisation, I18n.t("validations.merge_request.organisation_part_of_another_merge")) + end + + if MergeRequestOrganisation.not_unsubmitted.with_merging_organisation(merging_organisation).count.positive? + errors.add(:merging_organisation, I18n.t("validations.merge_request.organisation_part_of_another_merge")) + merge_request.errors.add(:merging_organisation, I18n.t("validations.merge_request.organisation_part_of_another_merge")) + end + + if MergeRequest.not_unsubmitted.where.not(id: merge_request_id).where(requesting_organisation: merging_organisation).count.positive? + errors.add(:merging_organisation, I18n.t("validations.merge_request.organisation_part_of_another_merge")) + merge_request.errors.add(:merging_organisation, I18n.t("validations.merge_request.organisation_part_of_another_merge")) + end + + if merging_organisation_id.blank? || !Organisation.where(id: merging_organisation_id).exists? + merge_request.errors.add(:merging_organisation, I18n.t("validations.merge_request.organisation_not_selected")) + end + end +end diff --git a/app/views/merge_requests/organisations.html.erb b/app/views/merge_requests/organisations.html.erb new file mode 100644 index 000000000..14b70c17c --- /dev/null +++ b/app/views/merge_requests/organisations.html.erb @@ -0,0 +1,49 @@ +<% content_for :before_content do %> + + <% title = "Tell us if your organisation is merging" %> + <% content_for :title, title %> + <%= govuk_back_link href: merge_request_organisation_path(id: @merge_request.requesting_organisation_id) %> +<% end %> + +

Which organisations are merging?

+ +
+
+

Add all organisations to be merged - we have already added your own.

+ +<%= form_with model: @merge_request, url: organisations_merge_request_path, method: :patch do |f| %> + <%= f.govuk_error_summary %> +

Start typing to search

+ <%= render partial: "organisation_relationships/related_organisation_select_question", locals: { + field: :merging_organisation, + question: Form::Question.new("", { "answer_options" => @answer_options }, nil), + f:, + } %> + <%= f.govuk_submit "Add organisation", classes: "govuk-button--secondary" %> + <%= govuk_table do |table| %> + <% @merge_request.merging_organisations.each do |merging_organisation| %> + <%= table.body do |body| %> + <%= body.row do |row| %> + <% row.cell(text: merging_organisation.name) %> + <% row.cell(html_attributes: { + scope: "row", + class: "govuk-!-text-align-right", + }) do %> + <% if @merge_request.requesting_organisation != merging_organisation %> + <%= govuk_link_to("Remove", organisations_remove_merge_request_path(merge_request: { merging_organisation: merging_organisation.id })) %> + <% end %> + <% end %> + <% end %> + <% end %> + <% end %> + <% end %> +<% end %> +<%= form_with model: @merge_request, url: merge_request_path(id: @merge_request.id), method: :patch do |f| %> + <%= govuk_details(summary_text: "I cannot find an organisation on the list") do %> + <%= f.govuk_text_area :other_merging_organisations, label: { text: "Other organisations" }, hint: { text: "List other organisations that are part of the merge but not registered on CORE." }, rows: 9 %> + <% end %> + <% if @merge_request.merging_organisations.count > 1 %> + <%= f.govuk_submit "Continue" %> + <% end %> +<% end %> +
diff --git a/app/views/organisations/merge.html.erb b/app/views/organisations/merge_request.html.erb similarity index 89% rename from app/views/organisations/merge.html.erb rename to app/views/organisations/merge_request.html.erb index 7d72d08b7..d8602b46c 100644 --- a/app/views/organisations/merge.html.erb +++ b/app/views/organisations/merge_request.html.erb @@ -40,9 +40,10 @@ <%= govuk_warning_text text: "You will not be able to submit your request without the above information. Do not start the form until you have obtained all of the information. " %> - <%= govuk_start_button( - text: "Start now", - href: "#", - ) %> + + <%= form_for @merge_request, url: merge_requests_path do |f| %> + <%= f.hidden_field :requesting_organisation_id, value: @organisation.id %> + <%= f.submit "Start now", class: "govuk-button govuk-button--start" %> + <% end %>
diff --git a/app/views/organisations/show.html.erb b/app/views/organisations/show.html.erb index 4561e985b..e2dbb9862 100644 --- a/app/views/organisations/show.html.erb +++ b/app/views/organisations/show.html.erb @@ -36,7 +36,7 @@ <% end %> <% end %> <% if FeatureToggle.merge_organisations_enabled? %> -

Is your organisation merging with another? <%= govuk_link_to "Let us know using this form", merge_organisation_path %>

+

Is your organisation merging with another? <%= govuk_link_to "Let us know using this form", merge_request_organisation_path %>

<% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 16583a911..34dc4651e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -504,6 +504,9 @@ en: discounted_ownership_value: "Mortgage, deposit, and grant total must equal %{value_with_discount}" monthly_rent: higher_than_expected: "Basic monthly rent must be between £0.00 and £9,999.00" + merge_request: + organisation_part_of_another_merge: "This organisation is part of another merge - select a different one" + organisation_not_selected: "Select an organisation from the search list" soft_validations: net_income: diff --git a/config/routes.rb b/config/routes.rb index f6e00083b..95dcb21a3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -119,7 +119,16 @@ Rails.application.routes.draw do get "managing-agents/remove", to: "organisation_relationships#remove_managing_agent" post "managing-agents", to: "organisation_relationships#create_managing_agent" delete "managing-agents", to: "organisation_relationships#delete_managing_agent" - get "merge", to: "organisations#merge" + get "merge-request", to: "organisations#merge_request" + end + end + + resources :merge_requests, path: "/merge-request" do + member do + get "organisations" + patch "organisations", to: "merge_requests#update_organisations" + get "organisations/remove", to: "merge_requests#remove_merging_organisation" + get "absorbing-organisation" end end diff --git a/db/migrate/20230412111338_add_merge_requests_table.rb b/db/migrate/20230412111338_add_merge_requests_table.rb new file mode 100644 index 000000000..2aaeb2989 --- /dev/null +++ b/db/migrate/20230412111338_add_merge_requests_table.rb @@ -0,0 +1,9 @@ +class AddMergeRequestsTable < ActiveRecord::Migration[7.0] + def change + create_table :merge_requests do |t| + t.integer :requesting_organisation_id + t.text :other_merging_organisations + t.timestamps + end + end +end diff --git a/db/migrate/20230413135407_add_merge_organisations.rb b/db/migrate/20230413135407_add_merge_organisations.rb new file mode 100644 index 000000000..97178f973 --- /dev/null +++ b/db/migrate/20230413135407_add_merge_organisations.rb @@ -0,0 +1,9 @@ +class AddMergeOrganisations < ActiveRecord::Migration[7.0] + def change + create_table :merge_request_organisations do |t| + t.integer :merge_request_id, foreign_key: true + t.integer :merging_organisation_id, foreign_key: true + t.timestamps + end + end +end diff --git a/db/migrate/20230418095819_add_status_to_merge_request.rb b/db/migrate/20230418095819_add_status_to_merge_request.rb new file mode 100644 index 000000000..a6a3b01b9 --- /dev/null +++ b/db/migrate/20230418095819_add_status_to_merge_request.rb @@ -0,0 +1,5 @@ +class AddStatusToMergeRequest < ActiveRecord::Migration[7.0] + def change + add_column :merge_requests, :status, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index e121fda09..e87364403 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.0].define(version: 2023_04_12_143245) do +ActiveRecord::Schema[7.0].define(version: 2023_04_18_095819) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -354,6 +354,21 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_12_143245) do t.string "collection" end + create_table "merge_request_organisations", force: :cascade do |t| + t.integer "merge_request_id" + t.integer "merging_organisation_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "merge_requests", force: :cascade do |t| + t.integer "requesting_organisation_id" + t.text "other_merging_organisations" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "status" + end + create_table "organisation_relationships", force: :cascade do |t| t.integer "child_organisation_id" t.integer "parent_organisation_id" @@ -519,6 +534,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_12_143245) do t.integer "prevten" t.integer "mortgageused" t.integer "wchair" + t.integer "income2_value_check" t.integer "armedforcesspouse" t.datetime "hodate", precision: nil t.integer "hoday" @@ -543,14 +559,13 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_12_143245) do t.integer "retirement_value_check" t.integer "hodate_check" t.integer "extrabor_value_check" - t.integer "grant_value_check" - t.integer "staircase_bought_value_check" t.integer "deposit_and_mortgage_value_check" t.integer "shared_ownership_deposit_value_check" + t.integer "grant_value_check" + t.integer "value_value_check" t.integer "old_persons_shared_ownership_value_check" - t.integer "income2_value_check" + t.integer "staircase_bought_value_check" t.integer "monthly_charges_value_check" - t.integer "value_value_check" t.integer "details_known_5" t.integer "details_known_6" t.integer "saledate_check" @@ -560,10 +575,9 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_12_143245) do t.integer "ethnicbuy2" t.integer "proplen_asked" t.string "old_id" - t.integer "pregblank" t.integer "buy2living" t.integer "prevtenbuy2" - t.integer "nationalbuy2" + t.integer "pregblank" t.string "uprn" t.integer "uprn_known" t.integer "uprn_confirmed" @@ -571,10 +585,11 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_12_143245) do t.string "address_line2" t.string "town_or_city" t.string "county" + t.integer "nationalbuy2" t.integer "discounted_sale_value_check" t.integer "student_not_child_value_check" - t.integer "buyer_livein_value_check" t.integer "percentage_discount_value_check" + t.integer "buyer_livein_value_check" t.index ["bulk_upload_id"], name: "index_sales_logs_on_bulk_upload_id" t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id" t.index ["old_id"], name: "index_sales_logs_on_old_id", unique: true diff --git a/spec/requests/merge_requests_controller_spec.rb b/spec/requests/merge_requests_controller_spec.rb new file mode 100644 index 000000000..ac7094df6 --- /dev/null +++ b/spec/requests/merge_requests_controller_spec.rb @@ -0,0 +1,278 @@ +require "rails_helper" + +RSpec.describe MergeRequestsController, type: :request do + let(:organisation) { user.organisation } + let(:other_organisation) { FactoryBot.create(:organisation, name: "Other Test Org") } + let(:headers) { { "Accept" => "text/html" } } + let(:page) { Capybara::Node::Simple.new(response.body) } + let(:user) { FactoryBot.create(:user, :data_coordinator) } + let(:support_user) { FactoryBot.create(:user, :support, organisation:) } + let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation) } + let(:other_merge_request) { MergeRequest.create!(requesting_organisation: other_organisation) } + + context "when user is signed in with a data coordinator user" do + before do + sign_in user + end + + describe "#organisations" do + let(:params) { { merge_request: { requesting_organisation_id: "9", status: "unsubmitted" } } } + + context "when creating a new merge request" do + before do + organisation.update!(name: "Test Org") + post "/merge-request", headers:, params: + end + + it "creates merge request with requesting organisation" do + follow_redirect! + expect(page).to have_content("Which organisations are merging?") + expect(page).to have_content("Test Org") + expect(page).not_to have_link("Remove") + end + + context "when passing a different requesting organisation id" do + let(:params) { { merge_request: { requesting_organisation_id: other_organisation.id, status: "unsubmitted" } } } + + it "creates merge request with current user organisation" do + follow_redirect! + expect(MergeRequest.count).to eq(1) + expect(MergeRequest.first.requesting_organisation_id).to eq(organisation.id) + expect(MergeRequest.first.merging_organisations.count).to eq(1) + expect(MergeRequest.first.merging_organisations.first.id).to eq(organisation.id) + end + end + end + + context "when viewing existing merge request" do + before do + organisation.update!(name: "Test Org") + get "/merge-request/#{merge_request.id}/organisations", headers:, params: + end + + it "shows merge request with requesting organisation" do + expect(page).to have_content("Which organisations are merging?") + expect(page).to have_content("Test Org") + end + end + + context "when viewing existing merge request of a different (unauthorised) organisation" do + before do + get "/merge-request/#{other_merge_request.id}/organisations", headers:, params: + end + + it "shows merge request with requesting organisation" do + expect(response).to have_http_status(:not_found) + end + end + end + + describe "#update_organisations" do + let(:params) { { merge_request: { merging_organisation: other_organisation.id } } } + + context "when updating a merge request with a new organisation" do + before do + patch "/merge-request/#{merge_request.id}/organisations", headers:, params: + end + + it "updates the merge request" do + merge_request.reload + expect(merge_request.merging_organisations.count).to eq(1) + expect(page).to have_content("Test Org") + expect(page).to have_content("Other Test Org") + expect(page).to have_link("Remove") + end + end + + context "when the user selects an organisation that requested another merge" do + let(:params) { { merge_request: { merging_organisation: other_organisation.id } } } + + before do + MergeRequest.create!(requesting_organisation_id: other_organisation.id, status: "submitted") + patch "/merge-request/#{merge_request.id}/organisations", headers:, params: + end + + it "does not update the merge request" do + merge_request.reload + expect(merge_request.merging_organisations.count).to eq(0) + expect(response).to have_http_status(:unprocessable_entity) + expect(page).to have_content(I18n.t("validations.merge_request.organisation_part_of_another_merge")) + end + end + + context "when the user selects an organisation that has another non submitted merge" do + let(:params) { { merge_request: { merging_organisation: other_organisation.id } } } + + before do + MergeRequest.create!(requesting_organisation_id: other_organisation.id, status: "unsubmitted") + patch "/merge-request/#{merge_request.id}/organisations", headers:, params: + end + + it "updates the merge request" do + merge_request.reload + expect(merge_request.merging_organisations.count).to eq(1) + expect(page).not_to have_content(I18n.t("validations.merge_request.organisation_part_of_another_merge")) + end + end + + context "when the user selects an organisation that is a part of another merge" do + let(:another_organisation) { FactoryBot.create(:organisation, name: "Other Test Org") } + let(:params) { { merge_request: { merging_organisation: another_organisation.id } } } + + before do + existing_merge_request = MergeRequest.create!(requesting_organisation_id: other_organisation.id, status: "submitted") + MergeRequestOrganisation.create!(merge_request_id: existing_merge_request.id, merging_organisation_id: another_organisation.id) + patch "/merge-request/#{merge_request.id}/organisations", headers:, params: + end + + it "does not update the merge request" do + merge_request.reload + expect(merge_request.merging_organisations.count).to eq(0) + expect(response).to have_http_status(:unprocessable_entity) + expect(page).to have_content(I18n.t("validations.merge_request.organisation_part_of_another_merge")) + end + end + + context "when the user selects an organisation that is a part of another unsubmitted merge" do + let(:another_organisation) { FactoryBot.create(:organisation, name: "Other Test Org") } + let(:params) { { merge_request: { merging_organisation: another_organisation.id } } } + + before do + existing_merge_request = MergeRequest.create!(requesting_organisation_id: other_organisation.id, status: "unsubmitted") + MergeRequestOrganisation.create!(merge_request_id: existing_merge_request.id, merging_organisation_id: another_organisation.id) + patch "/merge-request/#{merge_request.id}/organisations", headers:, params: + end + + it "does not update the merge request" do + merge_request.reload + expect(merge_request.merging_organisations.count).to eq(1) + expect(page).not_to have_content(I18n.t("validations.merge_request.organisation_part_of_another_merge")) + end + end + + context "when the user selects an organisation that is a part of current merge" do + let(:another_organisation) { FactoryBot.create(:organisation, name: "Other Test Org") } + let(:params) { { merge_request: { merging_organisation: another_organisation.id } } } + + before do + merge_request.merging_organisations << another_organisation + patch "/merge-request/#{merge_request.id}/organisations", headers:, params: + end + + it "does not update the merge request" do + merge_request.reload + expect(merge_request.merging_organisations.count).to eq(1) + end + end + + context "when the user selects an organisation that is requesting this merge" do + let(:params) { { merge_request: { merging_organisation: merge_request.requesting_organisation_id } } } + + before do + patch "/merge-request/#{merge_request.id}/organisations", headers:, params: + end + + it "does not update the merge request" do + merge_request.reload + expect(page).not_to have_content(I18n.t("validations.merge_request.organisation_part_of_another_merge")) + expect(merge_request.merging_organisations.count).to eq(1) + end + end + + context "when the user does not select an organisation" do + let(:params) { { merge_request: { merging_organisation: nil } } } + + before do + patch "/merge-request/#{merge_request.id}/organisations", headers:, params: + end + + it "does not update the merge request" do + merge_request.reload + expect(merge_request.merging_organisations.count).to eq(0) + expect(response).to have_http_status(:unprocessable_entity) + expect(page).to have_content(I18n.t("validations.merge_request.organisation_not_selected")) + end + end + + context "when the user selects non existent id" do + let(:params) { { merge_request: { merging_organisation: "clearly_not_an_id" } } } + + before do + patch "/merge-request/#{merge_request.id}/organisations", headers:, params: + end + + it "does not update the merge request" do + merge_request.reload + expect(merge_request.merging_organisations.count).to eq(0) + expect(response).to have_http_status(:unprocessable_entity) + expect(page).to have_content(I18n.t("validations.merge_request.organisation_not_selected")) + end + end + end + + describe "#remove_organisation" do + let(:params) { { merge_request: { merging_organisation: other_organisation.id } } } + + context "when removing an organisation from merge request" do + before do + MergeRequestOrganisation.create!(merge_request_id: merge_request.id, merging_organisation_id: other_organisation.id) + get "/merge-request/#{merge_request.id}/organisations/remove", headers:, params: + end + + it "updates the merge request" do + expect(merge_request.merging_organisations.count).to eq(0) + expect(page).not_to have_link("Remove") + end + end + + context "when removing an organisation that is not part of a merge from merge request" do + before do + get "/merge-request/#{merge_request.id}/organisations/remove", headers:, params: + end + + it "does not throw an error" do + expect(merge_request.merging_organisations.count).to eq(0) + expect(page).not_to have_link("Remove") + end + end + end + + describe "#other_merging_organisations" do + let(:params) { { merge_request: { other_merging_organisations: "A list of other merging organisations" } } } + + context "when adding other merging organisations" do + before do + MergeRequestOrganisation.create!(merge_request_id: merge_request.id, merging_organisation_id: other_organisation.id) + patch "/merge-request/#{merge_request.id}", headers:, params: + end + + it "updates the merge request" do + merge_request.reload + expect(merge_request.other_merging_organisations).to eq("A list of other merging organisations") + end + end + end + end + + context "when user is signed in as a support user" do + before do + allow(support_user).to receive(:need_two_factor_authentication?).and_return(false) + sign_in support_user + end + + describe "#organisations" do + let(:params) { { merge_request: { requesting_organisation_id: other_organisation.id, status: "unsubmitted" } } } + + before do + organisation.update!(name: "Test Org") + post "/merge-request", headers:, params: + end + + it "creates merge request with requesting organisation" do + follow_redirect! + expect(MergeRequest.count).to eq(1) + expect(MergeRequest.first.requesting_organisation_id).to eq(other_organisation.id) + end + end + end +end diff --git a/spec/requests/organisations_controller_spec.rb b/spec/requests/organisations_controller_spec.rb index 459987fdc..d526f218f 100644 --- a/spec/requests/organisations_controller_spec.rb +++ b/spec/requests/organisations_controller_spec.rb @@ -230,7 +230,7 @@ RSpec.describe OrganisationsController, type: :request do it "displays a link to merge organisations" do expect(page).to have_content("Is your organisation merging with another?") - expect(page).to have_link("Let us know using this form", href: "/organisations/#{organisation.id}/merge") + expect(page).to have_link("Let us know using this form", href: "/organisations/#{organisation.id}/merge-request") end end @@ -444,7 +444,7 @@ RSpec.describe OrganisationsController, type: :request do describe "#merge" do context "with an organisation that the user belongs to" do before do - get "/organisations/#{organisation.id}/merge", headers:, params: {} + get "/organisations/#{organisation.id}/merge-request", headers:, params: {} end it "shows the correct content" do @@ -455,14 +455,14 @@ RSpec.describe OrganisationsController, type: :request do expect(page).to have_link("Back", href: "/organisations/#{organisation.id}") end - it "has a correct start no button" do - expect(page).to have_link("Start now", href: "#") + it "has a correct start now button" do + expect(page).to have_button("Start now") end end context "with organisation that are not in scope for the user, i.e. that they do not belong to" do before do - get "/organisations/#{unauthorised_organisation.id}/merge", headers:, params: {} + get "/organisations/#{unauthorised_organisation.id}/merge-request", headers:, params: {} end it "returns not found 404 from org details route" do From b3cf6724f3f6ca517efee03542b673cc46b1e48f Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:36:23 +0100 Subject: [PATCH 08/23] Add 23/24 fields to import mappings (#1560) * Import location fields for 2023/24 logs * Map 23/24 location fields for sales logs * Add remaining 23/24 import mappings * Remove redundant mappings * Add default mappings * Remove year check --- app/models/log.rb | 2 +- .../imports/lettings_logs_import_service.rb | 17 +- .../imports/sales_logs_import_service.rb | 41 ++++- .../00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml | 5 + .../sales_logs/outright_sale_sales_log.xml | 6 + .../sales_logs/shared_ownership_sales_log.xml | 9 + .../lettings_logs_import_service_spec.rb | 91 +++++++++ .../imports/sales_logs_import_service_spec.rb | 172 ++++++++++++++++++ 8 files changed, 335 insertions(+), 8 deletions(-) diff --git a/app/models/log.rb b/app/models/log.rb index fc125fd46..4ee748eda 100644 --- a/app/models/log.rb +++ b/app/models/log.rb @@ -48,7 +48,7 @@ class Log < ApplicationRecord service = UprnClient.new(uprn) service.call - return errors.add(:uprn, service.error) if service.error.present? + return errors.add(:uprn, :uprn_error, message: service.error) if service.error.present? presenter = UprnDataPresenter.new(service.result) diff --git a/app/services/imports/lettings_logs_import_service.rb b/app/services/imports/lettings_logs_import_service.rb index 01c71faef..b5f2d02b9 100644 --- a/app/services/imports/lettings_logs_import_service.rb +++ b/app/services/imports/lettings_logs_import_service.rb @@ -202,6 +202,14 @@ module Imports attributes["first_time_property_let_as_social_housing"] = first_time_let(attributes["rsnvac"]) attributes["declaration"] = declaration(xml_doc) + attributes["uprn"] = string_or_nil(xml_doc, "UPRN") + attributes["uprn_known"] = attributes["uprn"].present? ? 1 : 0 + attributes["uprn_confirmed"] = attributes["uprn"].present? ? 1 : 0 + attributes["address_line1"] = string_or_nil(xml_doc, "AddressLine1") + attributes["address_line2"] = string_or_nil(xml_doc, "AddressLine2") + attributes["town_or_city"] = string_or_nil(xml_doc, "TownCity") + attributes["county"] = string_or_nil(xml_doc, "County") + set_partial_charges_to_zero(attributes) # Supported Housing fields @@ -333,6 +341,13 @@ module Imports return save_lettings_log(attributes, previous_status) end + if lettings_log.errors.of_kind?(:uprn, :uprn_error) + @logger.warn("Log #{lettings_log.old_id}: Setting uprn_known to no with error: #{lettings_log.errors[:uprn].join(', ')}") + @logs_overridden << lettings_log.old_id + attributes["uprn_known"] = 0 + return save_lettings_log(attributes, previous_status) + end + @logger.error("Log #{lettings_log.old_id}: Failed to import") lettings_log.errors.each do |error| @logger.error("Validation error: Field #{error.attribute}:") @@ -360,7 +375,7 @@ module Imports end def fields_not_present_in_softwire_data - %w[majorrepairs illness_type_0 tshortfall_known pregnancy_value_check retirement_value_check rent_value_check net_income_value_check major_repairs_date_value_check void_date_value_check carehome_charges_value_check housingneeds_type housingneeds_other created_by] + %w[majorrepairs illness_type_0 tshortfall_known pregnancy_value_check retirement_value_check rent_value_check net_income_value_check major_repairs_date_value_check void_date_value_check carehome_charges_value_check housingneeds_type housingneeds_other created_by uprn_known uprn_confirmed] end def check_status_completed(lettings_log, previous_status) diff --git a/app/services/imports/sales_logs_import_service.rb b/app/services/imports/sales_logs_import_service.rb index 2d6551189..0f97df89a 100644 --- a/app/services/imports/sales_logs_import_service.rb +++ b/app/services/imports/sales_logs_import_service.rb @@ -126,10 +126,6 @@ module Imports attributes["postcode_full"] = parse_postcode(string_or_nil(xml_doc, "Q14Postcode")) attributes["pcodenk"] = 0 if attributes["postcode_full"].present? # known if given attributes["soctenant"] = 0 if attributes["ownershipsch"] == 1 - attributes["ethnic_group2"] = nil # 23/24 variable - attributes["ethnicbuy2"] = nil # 23/24 variable - attributes["prevshared"] = nil # 23/24 variable - attributes["staircasesale"] = nil # 23/24 variable attributes["previous_la_known"] = 1 if attributes["prevloc"].present? if attributes["la"].present? @@ -158,6 +154,27 @@ module Imports attributes["buyer_livein_value_check"] = 0 attributes["percentage_discount_value_check"] = 0 + # 2023/34 attributes + attributes["uprn"] = string_or_nil(xml_doc, "UPRN") + attributes["uprn_known"] = attributes["uprn"].present? ? 1 : 0 + attributes["uprn_confirmed"] = attributes["uprn"].present? ? 1 : 0 + attributes["address_line1"] = string_or_nil(xml_doc, "AddressLine1") + attributes["address_line2"] = string_or_nil(xml_doc, "AddressLine2") + attributes["town_or_city"] = string_or_nil(xml_doc, "TownCity") + attributes["county"] = string_or_nil(xml_doc, "County") + + attributes["proplen_asked"] = 0 if attributes["proplen"]&.positive? + attributes["proplen_asked"] = 1 if attributes["proplen"]&.zero? + + attributes["prevshared"] = unsafe_string_as_integer(xml_doc, "PREVSHARED") + attributes["ethnicbuy2"] = unsafe_string_as_integer(xml_doc, "P2Eth") + attributes["ethnic_group2"] = ethnic_group(attributes["ethnicbuy2"]) + attributes["nationalbuy2"] = unsafe_string_as_integer(xml_doc, "P2Nat") + attributes["buy2living"] = unsafe_string_as_integer(xml_doc, "buy2livein") + + attributes["staircasesale"] = unsafe_string_as_integer(xml_doc, "STAIRCASESALE") + attributes["prevtenbuy2"] = unsafe_string_as_integer(xml_doc, "PREVTENBUY2") + # Sets the log creator owner_id = meta_field_value(xml_doc, "owner-user-id").strip if owner_id.present? @@ -233,6 +250,11 @@ module Imports @logs_overridden << sales_log.old_id attributes.delete("mortgage") save_sales_log(attributes, previous_status) + elsif sales_log.errors.of_kind?(:uprn, :uprn_error) + @logger.warn("Log #{sales_log.old_id}: Setting uprn_known to no with error: #{sales_log.errors[:uprn].join(', ')}") + @logs_overridden << sales_log.old_id + attributes["uprn_known"] = 0 + save_sales_log(attributes, previous_status) else @logger.error("Log #{sales_log.old_id}: Failed to import") sales_log.errors.each do |error| @@ -282,7 +304,9 @@ module Imports student_not_child_value_check discounted_sale_value_check buyer_livein_value_check - percentage_discount_value_check] + percentage_discount_value_check + uprn_known + uprn_confirmed] end def check_status_completed(sales_log, previous_status) @@ -461,12 +485,14 @@ module Imports safe_string_as_decimal(xml_doc, "Q29MonthlyCharges") when 2 safe_string_as_decimal(xml_doc, "Q37MonthlyCharges") + when 3 + safe_string_as_decimal(xml_doc, "Q44MonthlyCharges") end end def ownership_from_type(attributes) case attributes["type"] - when 2, 24, 18, 16, 28, 31, 30 + when 2, 24, 18, 16, 28, 31, 30, 32 1 # shared ownership when 8, 14, 27, 9, 29, 21, 22 2 # discounted ownership @@ -562,6 +588,9 @@ module Imports attributes["relat2"] ||= "R" attributes["inc2mort"] ||= 3 attributes["buy2livein"] ||= 1 unless attributes["ownershipsch"] == 3 + attributes["ethnic_group2"] ||= 17 + attributes["ethnicbuy2"] ||= 17 + attributes["nationalbuy2"] ||= 13 end # other household members characteristics diff --git a/spec/fixtures/imports/logs/00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml b/spec/fixtures/imports/logs/00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml index 8bc6a935c..cd0e03d74 100644 --- a/spec/fixtures/imports/logs/00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml +++ b/spec/fixtures/imports/logs/00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml @@ -131,6 +131,11 @@ <_2cYears/> + 12345678 + + + + SR8 3HF Durham E06000047 diff --git a/spec/fixtures/imports/sales_logs/outright_sale_sales_log.xml b/spec/fixtures/imports/sales_logs/outright_sale_sales_log.xml index 4bbdc9981..ce17ff3fc 100644 --- a/spec/fixtures/imports/sales_logs/outright_sale_sales_log.xml +++ b/spec/fixtures/imports/sales_logs/outright_sale_sales_log.xml @@ -32,6 +32,11 @@ 1 1 Flat or maisonette 1 Purpose built + + + + + SW1A 1AA Westminster E09000033 @@ -275,6 +280,7 @@ 300000 + 1 diff --git a/spec/fixtures/imports/sales_logs/shared_ownership_sales_log.xml b/spec/fixtures/imports/sales_logs/shared_ownership_sales_log.xml index 6e0c11174..b69cb22c2 100644 --- a/spec/fixtures/imports/sales_logs/shared_ownership_sales_log.xml +++ b/spec/fixtures/imports/sales_logs/shared_ownership_sales_log.xml @@ -24,6 +24,7 @@ 2 No 1 Yes + 2 No 2 Yes @@ -32,6 +33,12 @@ 2 1 Flat or maisonette 1 Purpose built + + + + + + SW1A 1AA Westminster E09000033 @@ -47,6 +54,8 @@ + + diff --git a/spec/services/imports/lettings_logs_import_service_spec.rb b/spec/services/imports/lettings_logs_import_service_spec.rb index 4621afb2f..4fbd485c0 100644 --- a/spec/services/imports/lettings_logs_import_service_spec.rb +++ b/spec/services/imports/lettings_logs_import_service_spec.rb @@ -1151,5 +1151,96 @@ RSpec.describe Imports::LettingsLogsImportService do expect(lettings_log.hbrentshortfall).to be_nil end end + + context "when setting location fields for 23/24 logs" do + let(:lettings_log_id) { "00d2343e-d5fa-4c89-8400-ec3854b0f2b4" } + let(:lettings_log_file) { open_file(fixture_directory, lettings_log_id) } + let(:lettings_log_xml) { Nokogiri::XML(lettings_log_file) } + + around do |example| + Timecop.freeze(Time.zone.local(2023, 4, 1)) do + Singleton.__init__(FormHandler) + example.run + end + Timecop.return + Singleton.__init__(FormHandler) + end + + before do + lettings_log_xml.at_xpath("//xmlns:DAY").content = "10" + lettings_log_xml.at_xpath("//xmlns:MONTH").content = "4" + lettings_log_xml.at_xpath("//xmlns:YEAR").content = "2023" + lettings_log_xml.at_xpath("//xmlns:UPRN").content = "123456781234" + lettings_log_xml.at_xpath("//xmlns:AddressLine1").content = "address 1" + lettings_log_xml.at_xpath("//xmlns:AddressLine2").content = "address 2" + lettings_log_xml.at_xpath("//xmlns:TownCity").content = "towncity" + lettings_log_xml.at_xpath("//xmlns:County").content = "county" + lettings_log_xml.at_xpath("//xmlns:POSTCODE").content = "A1" + lettings_log_xml.at_xpath("//xmlns:POSTCOD2").content = "1AA" + + body = { + results: [ + { + DPA: { + "POSTCODE": "LS16 6FT", + "POST_TOWN": "Westminster", + "PO_BOX_NUMBER": "321", + "DOUBLE_DEPENDENT_LOCALITY": "Double Dependent Locality", + }, + }, + ], + }.to_json + + stub_request(:get, "https://api.os.uk/search/places/v1/uprn?key=OS_DATA_KEY&uprn=123456781234") + .to_return(status: 200, body:, headers: {}) + stub_request(:get, "https://api.os.uk/search/places/v1/uprn?key=OS_DATA_KEY&uprn=123") + .to_return(status: 500, body: "{}", headers: {}) + + allow(logger).to receive(:warn).and_return(nil) + end + + it "correctly sets address if uprn is not given" do + lettings_log_xml.at_xpath("//xmlns:UPRN").content = "" + lettings_log_service.send(:create_log, lettings_log_xml) + + lettings_log = LettingsLog.find_by(old_id: lettings_log_id) + expect(lettings_log&.uprn_known).to eq(0) # no + expect(lettings_log&.uprn).to be_nil + expect(lettings_log&.address_line1).to eq("address 1") + expect(lettings_log&.address_line2).to eq("address 2") + expect(lettings_log&.town_or_city).to eq("towncity") + expect(lettings_log&.county).to eq("county") + expect(lettings_log&.postcode_full).to eq("A1 1AA") + end + + it "correctly sets address and uprn if uprn is given" do + lettings_log_service.send(:create_log, lettings_log_xml) + + lettings_log = LettingsLog.find_by(old_id: lettings_log_id) + expect(lettings_log&.uprn_known).to eq(1) + expect(lettings_log&.uprn).to eq("123456781234") + expect(lettings_log&.address_line1).to eq("321") + expect(lettings_log&.address_line2).to eq("Double Dependent Locality") + expect(lettings_log&.town_or_city).to eq("Westminster") + expect(lettings_log&.postcode_full).to eq("LS16 6FT") + expect(lettings_log&.la).to eq("E08000035") + end + + it "correctly sets address and uprn if uprn is given but not recognised" do + lettings_log_xml.at_xpath("//xmlns:UPRN").content = "123" + + lettings_log_service.send(:create_log, lettings_log_xml) + + lettings_log = LettingsLog.find_by(old_id: lettings_log_id) + expect(lettings_log&.uprn_known).to eq(0) + expect(lettings_log&.uprn).to be_nil + expect(lettings_log&.address_line1).to eq("address 1") + expect(lettings_log&.address_line2).to eq("address 2") + expect(lettings_log&.town_or_city).to eq("towncity") + expect(lettings_log&.county).to eq("county") + expect(lettings_log&.postcode_full).to eq("A1 1AA") + expect(lettings_log&.la).to eq("E06000047") + end + end end end diff --git a/spec/services/imports/sales_logs_import_service_spec.rb b/spec/services/imports/sales_logs_import_service_spec.rb index 5862efa23..2e7eea168 100644 --- a/spec/services/imports/sales_logs_import_service_spec.rb +++ b/spec/services/imports/sales_logs_import_service_spec.rb @@ -220,6 +220,87 @@ RSpec.describe Imports::SalesLogsImportService do end end + context "with 23/24 logs" do + around do |example| + Timecop.freeze(Time.zone.local(2023, 4, 1)) do + Singleton.__init__(FormHandler) + example.run + end + Timecop.return + Singleton.__init__(FormHandler) + end + + before do + sales_log_xml.at_xpath("//xmlns:DAY").content = "10" + sales_log_xml.at_xpath("//xmlns:MONTH").content = "4" + sales_log_xml.at_xpath("//xmlns:YEAR").content = "2023" + sales_log_xml.at_xpath("//xmlns:UPRN").content = "" + sales_log_xml.at_xpath("//xmlns:AddressLine1").content = "address 1" + sales_log_xml.at_xpath("//xmlns:AddressLine2").content = "address 2" + sales_log_xml.at_xpath("//xmlns:TownCity").content = "towncity" + sales_log_xml.at_xpath("//xmlns:County").content = "county" + sales_log_xml.at_xpath("//xmlns:Q14Postcode").content = "A1 1AA" + end + + context "with outright sale type" do + let(:sales_log_id) { "outright_sale_sales_log" } + + before do + sales_log_xml.at_xpath("//xmlns:Q44MonthlyCharges").content = "40" + end + + it "successfully creates a completed outright sale log" do + expect(logger).not_to receive(:error) + expect(logger).not_to receive(:warn) + expect(logger).not_to receive(:info) + expect { sales_log_service.send(:create_log, sales_log_xml) } + .to change(SalesLog, :count).by(1) + end + end + + context "with shared sale type" do + let(:sales_log_id) { "shared_ownership_sales_log" } + + before do + sales_log_xml.at_xpath("//xmlns:joint").content = "1 Yes" + sales_log_xml.at_xpath("//xmlns:JointMore").content = "2 No" + sales_log_xml.at_xpath("//xmlns:PREVSHARED").content = "1" + sales_log_xml.at_xpath("//xmlns:Q16aProplen2").content = "0" + sales_log_xml.at_xpath("//xmlns:Q20Bedrooms").content = "2" + sales_log_xml.at_xpath("//xmlns:P2Eth").content = "2 White: Irish" + sales_log_xml.at_xpath("//xmlns:P2Nat").content = "18 United Kingdom" + sales_log_xml.at_xpath("//xmlns:buy2livein").content = "1" + end + + it "successfully creates a completed shared sale log" do + expect(logger).not_to receive(:error) + expect(logger).not_to receive(:warn) + expect(logger).not_to receive(:info) + expect { sales_log_service.send(:create_log, sales_log_xml) } + .to change(SalesLog, :count).by(1) + sales_log = SalesLog.find_by(old_id: sales_log_id) + expect(sales_log.proplen_asked).to eq(1) + end + end + + context "with discounted sale type" do + let(:sales_log_id) { "shared_ownership_sales_log" } + + before do + sales_log_xml.at_xpath("//xmlns:PREVSHARED").content = "1" + sales_log_xml.at_xpath("//xmlns:Q20Bedrooms").content = "2" + end + + it "successfully creates a completed shared sale log" do + expect(logger).not_to receive(:error) + expect(logger).not_to receive(:warn) + expect(logger).not_to receive(:info) + expect { sales_log_service.send(:create_log, sales_log_xml) } + .to change(SalesLog, :count).by(1) + end + end + end + context "and the mortgage soft validation is triggered (mortgage_value_check)" do let(:sales_log_id) { "discounted_ownership_sales_log" } @@ -1336,6 +1417,97 @@ RSpec.describe Imports::SalesLogsImportService do end end + context "when setting location fields for 23/24 logs" do + let(:sales_log_id) { "outright_sale_sales_log" } + + around do |example| + Timecop.freeze(Time.zone.local(2023, 4, 1)) do + Singleton.__init__(FormHandler) + example.run + end + Timecop.return + Singleton.__init__(FormHandler) + end + + before do + sales_log_xml.at_xpath("//xmlns:DAY").content = "10" + sales_log_xml.at_xpath("//xmlns:MONTH").content = "4" + sales_log_xml.at_xpath("//xmlns:YEAR").content = "2023" + sales_log_xml.at_xpath("//xmlns:UPRN").content = "123456781234" + sales_log_xml.at_xpath("//xmlns:AddressLine1").content = "address 1" + sales_log_xml.at_xpath("//xmlns:AddressLine2").content = "address 2" + sales_log_xml.at_xpath("//xmlns:TownCity").content = "towncity" + sales_log_xml.at_xpath("//xmlns:County").content = "county" + sales_log_xml.at_xpath("//xmlns:Q14Postcode").content = "A1 1AA" + + body = { + results: [ + { + DPA: { + "POSTCODE": "LS16 6FT", + "POST_TOWN": "Westminster", + "PO_BOX_NUMBER": "321", + "DOUBLE_DEPENDENT_LOCALITY": "Double Dependent Locality", + }, + }, + ], + }.to_json + + stub_request(:get, "https://api.os.uk/search/places/v1/uprn?key=OS_DATA_KEY&uprn=123456781234") + .to_return(status: 200, body:, headers: {}) + stub_request(:get, "https://api.os.uk/search/places/v1/uprn?key=OS_DATA_KEY&uprn=123") + .to_return(status: 500, body: "{}", headers: {}) + + WebMock.stub_request(:get, /api.postcodes.io\/postcodes\/LS166FT/) + .to_return(status: 200, body: '{"status":200,"result":{"admin_district":"Westminster","codes":{"admin_district":"E08000035"}}}', headers: {}) + + allow(logger).to receive(:warn).and_return(nil) + end + + it "correctly sets address if uprn is not given" do + sales_log_xml.at_xpath("//xmlns:UPRN").content = "" + sales_log_service.send(:create_log, sales_log_xml) + + sales_log = SalesLog.find_by(old_id: sales_log_id) + expect(sales_log&.uprn_known).to eq(0) # no + expect(sales_log&.uprn).to be_nil + expect(sales_log&.address_line1).to eq("address 1") + expect(sales_log&.address_line2).to eq("address 2") + expect(sales_log&.town_or_city).to eq("towncity") + expect(sales_log&.county).to eq("county") + expect(sales_log&.postcode_full).to eq("A1 1AA") + end + + it "correctly sets address and uprn if uprn is given" do + sales_log_service.send(:create_log, sales_log_xml) + + sales_log = SalesLog.find_by(old_id: sales_log_id) + expect(sales_log&.uprn_known).to eq(1) + expect(sales_log&.uprn).to eq("123456781234") + expect(sales_log&.address_line1).to eq("321") + expect(sales_log&.address_line2).to eq("Double Dependent Locality") + expect(sales_log&.town_or_city).to eq("Westminster") + expect(sales_log&.postcode_full).to eq("LS16 6FT") + expect(sales_log&.la).to eq("E08000035") + end + + it "correctly sets address and uprn if uprn is given but not recognised" do + sales_log_xml.at_xpath("//xmlns:UPRN").content = "123" + + sales_log_service.send(:create_log, sales_log_xml) + + sales_log = SalesLog.find_by(old_id: sales_log_id) + expect(sales_log&.uprn_known).to eq(0) + expect(sales_log&.uprn).to be_nil + expect(sales_log&.address_line1).to eq("address 1") + expect(sales_log&.address_line2).to eq("address 2") + expect(sales_log&.town_or_city).to eq("towncity") + expect(sales_log&.county).to eq("county") + expect(sales_log&.postcode_full).to eq("A1 1AA") + expect(sales_log&.la).to eq("E09000033") + end + end + context "when setting default buyer 1 previous tenancy" do let(:sales_log_id) { "outright_sale_sales_log" } From c5dc8ebd12ae71a83f6a3b28228e19c92483f542 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:40:16 +0100 Subject: [PATCH 09/23] Import support type as missing (#1561) * Import support type as missing * Set support_type to nil if it's not given * Fix casing --- .../imports/sales_logs_import_service.rb | 2 +- .../imports/scheme_location_import_service.rb | 9 ++++++++- .../sales_logs/shared_ownership_sales_log.xml | 2 +- .../imports/sales_logs_import_service_spec.rb | 2 +- .../scheme_location_import_service_spec.rb | 18 ++++++++++++++++++ 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/app/services/imports/sales_logs_import_service.rb b/app/services/imports/sales_logs_import_service.rb index 0f97df89a..8702a8846 100644 --- a/app/services/imports/sales_logs_import_service.rb +++ b/app/services/imports/sales_logs_import_service.rb @@ -170,7 +170,7 @@ module Imports attributes["ethnicbuy2"] = unsafe_string_as_integer(xml_doc, "P2Eth") attributes["ethnic_group2"] = ethnic_group(attributes["ethnicbuy2"]) attributes["nationalbuy2"] = unsafe_string_as_integer(xml_doc, "P2Nat") - attributes["buy2living"] = unsafe_string_as_integer(xml_doc, "buy2livein") + attributes["buy2living"] = unsafe_string_as_integer(xml_doc, "BUY2LIVEIN") attributes["staircasesale"] = unsafe_string_as_integer(xml_doc, "STAIRCASESALE") attributes["prevtenbuy2"] = unsafe_string_as_integer(xml_doc, "PREVTENBUY2") diff --git a/app/services/imports/scheme_location_import_service.rb b/app/services/imports/scheme_location_import_service.rb index a26139aec..93093e6da 100644 --- a/app/services/imports/scheme_location_import_service.rb +++ b/app/services/imports/scheme_location_import_service.rb @@ -77,7 +77,7 @@ module Imports attributes["scheme_type"] = safe_string_as_integer(xml_doc, "scheme-type") registered_under_care_act = safe_string_as_integer(xml_doc, "reg-home-type") attributes["registered_under_care_act"] = registered_under_care_act&.zero? ? nil : registered_under_care_act - attributes["support_type"] = safe_string_as_integer(xml_doc, "support-type") + attributes["support_type"] = support_type(xml_doc) attributes["intended_stay"] = string_or_nil(xml_doc, "intended-stay") attributes["mobility_type"] = string_or_nil(xml_doc, "mobility-type") attributes["primary_client_group"] = string_or_nil(xml_doc, "client-group-1") @@ -205,5 +205,12 @@ module Imports date = string_or_nil(xml_doc, attribute) Time.zone.parse(date) if date end + + def support_type(xml_doc) + type = safe_string_as_integer(xml_doc, "support-type") + return unless type + + Scheme::SUPPORT_TYPE.value?(type) ? type : 0 + end end end diff --git a/spec/fixtures/imports/sales_logs/shared_ownership_sales_log.xml b/spec/fixtures/imports/sales_logs/shared_ownership_sales_log.xml index b69cb22c2..8df5cc9a1 100644 --- a/spec/fixtures/imports/sales_logs/shared_ownership_sales_log.xml +++ b/spec/fixtures/imports/sales_logs/shared_ownership_sales_log.xml @@ -24,7 +24,7 @@ 2 No 1 Yes - + 2 No 2 Yes diff --git a/spec/services/imports/sales_logs_import_service_spec.rb b/spec/services/imports/sales_logs_import_service_spec.rb index 2e7eea168..c06b7bf42 100644 --- a/spec/services/imports/sales_logs_import_service_spec.rb +++ b/spec/services/imports/sales_logs_import_service_spec.rb @@ -269,7 +269,7 @@ RSpec.describe Imports::SalesLogsImportService do sales_log_xml.at_xpath("//xmlns:Q20Bedrooms").content = "2" sales_log_xml.at_xpath("//xmlns:P2Eth").content = "2 White: Irish" sales_log_xml.at_xpath("//xmlns:P2Nat").content = "18 United Kingdom" - sales_log_xml.at_xpath("//xmlns:buy2livein").content = "1" + sales_log_xml.at_xpath("//xmlns:BUY2LIVEIN").content = "1" end it "successfully creates a completed shared sale log" do diff --git a/spec/services/imports/scheme_location_import_service_spec.rb b/spec/services/imports/scheme_location_import_service_spec.rb index 186fba818..5c45a92aa 100644 --- a/spec/services/imports/scheme_location_import_service_spec.rb +++ b/spec/services/imports/scheme_location_import_service_spec.rb @@ -208,5 +208,23 @@ RSpec.describe Imports::SchemeLocationImportService do expect(location.scheme.confirmed).to be_falsey end end + + context "and support_type is not a valid one" do + before { location_xml.at_xpath("//scheme:support-type").content = "1" } + + it "sets the support type to missing" do + location = location_service.create_scheme_location(location_xml) + expect(location.scheme.support_type).to eq("Missing") + end + end + + context "and support_type is not answered" do + before { location_xml.at_xpath("//scheme:support-type").content = "" } + + it "sets the support type to nil" do + location = location_service.create_scheme_location(location_xml) + expect(location.scheme.support_type).to eq(nil) + end + end end end From 7d6374a3384cce8652c4f6cc641be12ccbdcb8d3 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 19 Apr 2023 14:53:58 +0100 Subject: [PATCH 10/23] Do not raise an error if an incomplete log is missing location and scheme (#1565) --- .../imports/lettings_logs_import_service.rb | 22 +++++++++++-------- .../lettings_logs_import_service_spec.rb | 21 ++++++++++++++++++ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/app/services/imports/lettings_logs_import_service.rb b/app/services/imports/lettings_logs_import_service.rb index b5f2d02b9..a16dc7acd 100644 --- a/app/services/imports/lettings_logs_import_service.rb +++ b/app/services/imports/lettings_logs_import_service.rb @@ -219,15 +219,19 @@ module Imports schemes = Scheme.where(old_visible_id: scheme_old_visible_id, owning_organisation_id: attributes["owning_organisation_id"]) location = Location.find_by(old_visible_id: location_old_visible_id, scheme: schemes) - raise "No matching location for scheme #{scheme_old_visible_id} and location #{location_old_visible_id} (visible IDs)" if location.nil? - - # Set the scheme via location, because the scheme old visible ID can be duplicated at import - attributes["location_id"] = location.id - attributes["scheme_id"] = location.scheme.id - attributes["sheltered"] = unsafe_string_as_integer(xml_doc, "Q1e") - attributes["chcharge"] = safe_string_as_decimal(xml_doc, "Q18b") - attributes["household_charge"] = household_charge(xml_doc) - attributes["is_carehome"] = is_carehome(location.scheme) + if location.nil? && [location_old_visible_id, scheme_old_visible_id].all?(&:present?) && previous_status != "saved" + raise "No matching location for scheme #{scheme_old_visible_id} and location #{location_old_visible_id} (visible IDs)" + end + + if location.present? + # Set the scheme via location, because the scheme old visible ID can be duplicated at import + attributes["location_id"] = location.id + attributes["scheme_id"] = location.scheme.id + attributes["sheltered"] = unsafe_string_as_integer(xml_doc, "Q1e") + attributes["chcharge"] = safe_string_as_decimal(xml_doc, "Q18b") + attributes["household_charge"] = household_charge(xml_doc) + attributes["is_carehome"] = is_carehome(location.scheme) + end end # Handles confidential schemes diff --git a/spec/services/imports/lettings_logs_import_service_spec.rb b/spec/services/imports/lettings_logs_import_service_spec.rb index 4fbd485c0..92a5fc059 100644 --- a/spec/services/imports/lettings_logs_import_service_spec.rb +++ b/spec/services/imports/lettings_logs_import_service_spec.rb @@ -963,6 +963,27 @@ RSpec.describe Imports::LettingsLogsImportService do end end + context "and the scheme and location is not given" do + let(:lettings_log_id) { "0b4a68df-30cc-474a-93c0-a56ce8fdad3b" } + + before do + lettings_log_xml.at_xpath("//xmlns:_1cmangroupcode").content = "" + lettings_log_xml.at_xpath("//xmlns:_1cschemecode").content = "" + lettings_log_xml.at_xpath("//xmlns:Q25").content = "" + lettings_log_xml.at_xpath("//meta:status").content = "saved" + end + + it "saves log without location and scheme" do + expect(logger).not_to receive(:warn) + lettings_log_service.send(:create_log, lettings_log_xml) + lettings_log = LettingsLog.find_by(old_id: lettings_log_id) + + expect(lettings_log.scheme_id).to be_nil + expect(lettings_log.location_id).to be_nil + expect(lettings_log.status).to eq("in_progress") + end + end + context "and this is a supported housing log with a single location under a scheme" do let(:lettings_log_id) { "0b4a68df-30cc-474a-93c0-a56ce8fdad3b" } From 210a25eaf5db6e7fc0ff65432dc4589131174436 Mon Sep 17 00:00:00 2001 From: Phil Lee Date: Wed, 19 Apr 2023 15:03:36 +0100 Subject: [PATCH 11/23] fix bulk upload summary error ordering (#1562) --- .../summary.html.erb | 2 +- .../summary.html.erb_spec.rb | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 spec/views/bulk_upload_lettings_results/summary.html.erb_spec.rb diff --git a/app/views/bulk_upload_lettings_results/summary.html.erb b/app/views/bulk_upload_lettings_results/summary.html.erb index 533e2af05..51aa652f8 100644 --- a/app/views/bulk_upload_lettings_results/summary.html.erb +++ b/app/views/bulk_upload_lettings_results/summary.html.erb @@ -20,7 +20,7 @@ <% end %> <% c.with_tab(label: "Full error report") do %> - <% @bulk_upload.bulk_upload_errors.group_by(&:row).each do |_row, errors_for_row| %> + <% @bulk_upload.bulk_upload_errors.order_by_cell.group_by(&:row).each do |_row, errors_for_row| %> <%= render BulkUploadErrorRowComponent.new(bulk_upload_errors: errors_for_row) %> <% end %> <% end %> diff --git a/spec/views/bulk_upload_lettings_results/summary.html.erb_spec.rb b/spec/views/bulk_upload_lettings_results/summary.html.erb_spec.rb new file mode 100644 index 000000000..25499133d --- /dev/null +++ b/spec/views/bulk_upload_lettings_results/summary.html.erb_spec.rb @@ -0,0 +1,20 @@ +require "rails_helper" + +RSpec.describe "bulk_upload_lettings_results/summary.html.erb" do + let(:bulk_upload) { create(:bulk_upload, :lettings) } + + before do + create(:bulk_upload_error, bulk_upload:, cell: "AA100", row: "100", col: "AA") + create(:bulk_upload_error, bulk_upload:, cell: "Z100", row: "100", col: "Z") + end + + it "renders errors ordered by cell" do + assign(:bulk_upload, bulk_upload) + + render + + fragment = Capybara::Node::Simple.new(rendered) + + expect(fragment.find_css("table tbody th").map(&:inner_text)).to eql(%w[Z100 AA100]) + end +end From 3f27008034d31d1bd0f3859dc0ce58a51e209442 Mon Sep 17 00:00:00 2001 From: Phil Lee Date: Wed, 19 Apr 2023 15:04:54 +0100 Subject: [PATCH 12/23] CLDC-2235 Bulk upload now support invalid options (#1564) # Context - https://digital.dclg.gov.uk/jira/browse/CLDC-2235 - Support invalid options for bulk upload. one use case when user for a new collection year supplies a value only valid in the previous collection year # Changes - this validation works before `log.valid?` clears any fields - as a result there is the potential to get 2 errors on a field for when it becomes blanked and invalid option occur together - bulk upload validations are now split so that they run before or after `log.valid?`. this is due to the fact that `log.valid?` heavily mutates the `log` object. so we want to validate both before and after the data mutates depending on what needs to be checked - errors must be duplicated and merged as calling `valid?` clears any existing errors on the object - all validations are assigned a specific context otherwise they are added to the default context and will also be called when a context is given --- .../lettings/year2022/row_parser.rb | 110 ++++++++--------- .../lettings/year2023/row_parser.rb | 112 +++++++++--------- .../lettings/year2022/row_parser_spec.rb | 8 ++ .../lettings/year2023/row_parser_spec.rb | 2 +- 4 files changed, 124 insertions(+), 108 deletions(-) diff --git a/app/services/bulk_upload/lettings/year2022/row_parser.rb b/app/services/bulk_upload/lettings/year2022/row_parser.rb index bfb7940ed..4033c6167 100644 --- a/app/services/bulk_upload/lettings/year2022/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2022/row_parser.rb @@ -278,58 +278,58 @@ class BulkUpload::Lettings::Year2022::RowParser attribute :field_134, :integer validates :field_1, presence: { message: I18n.t("validations.not_answered", question: "letting type") }, - inclusion: { in: (1..12).to_a, message: I18n.t("validations.invalid_option", question: "letting type") } - validates :field_4, presence: { if: proc { [2, 4, 6, 8, 10, 12].include?(field_1) } } - - validates :field_12, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 1 must be a number or the letter R" } - validates :field_13, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 2 must be a number or the letter R" }, allow_blank: true - validates :field_14, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 3 must be a number or the letter R" }, allow_blank: true - validates :field_15, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 4 must be a number or the letter R" }, allow_blank: true - validates :field_16, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 5 must be a number or the letter R" }, allow_blank: true - validates :field_17, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 6 must be a number or the letter R" }, allow_blank: true - validates :field_18, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 7 must be a number or the letter R" }, allow_blank: true - validates :field_19, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 8 must be a number or the letter R" }, allow_blank: true - - validates :field_96, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (day)") } - validates :field_97, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (month)") } - validates :field_98, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (year)") } - - validates :field_98, format: { with: /\A\d{2}\z/, message: I18n.t("validations.setup.startdate.year_not_two_digits") } - - validate :validate_data_types - validate :validate_nulls - validate :validate_relevant_collection_window - validate :validate_la_with_local_housing_referral - validate :validate_cannot_be_la_referral_if_general_needs_and_la - validate :validate_leaving_reason_for_renewal - validate :validate_lettings_type_matches_bulk_upload - validate :validate_only_one_housing_needs_type - validate :validate_no_disabled_needs_conjunction - validate :validate_dont_know_disabled_needs_conjunction - validate :validate_no_and_dont_know_disabled_needs_conjunction - - validate :validate_owning_org_data_given - validate :validate_owning_org_exists - validate :validate_owning_org_owns_stock - validate :validate_owning_org_permitted - - validate :validate_managing_org_data_given - validate :validate_managing_org_exists - validate :validate_managing_org_related - - validate :validate_scheme_related - validate :validate_scheme_exists - validate :validate_scheme_data_given - - validate :validate_location_related - validate :validate_location_exists - validate :validate_location_data_given - - validate :validate_created_by_exists - validate :validate_created_by_related - validate :validate_rent_type - - validate :validate_valid_radio_option + inclusion: { in: (1..12).to_a, message: I18n.t("validations.invalid_option", question: "letting type") }, on: :after_log + validates :field_4, presence: { if: proc { [2, 4, 6, 8, 10, 12].include?(field_1) } }, on: :after_log + + validates :field_12, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 1 must be a number or the letter R" }, on: :after_log + validates :field_13, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 2 must be a number or the letter R" }, allow_blank: true, on: :after_log + validates :field_14, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 3 must be a number or the letter R" }, allow_blank: true, on: :after_log + validates :field_15, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 4 must be a number or the letter R" }, allow_blank: true, on: :after_log + validates :field_16, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 5 must be a number or the letter R" }, allow_blank: true, on: :after_log + validates :field_17, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 6 must be a number or the letter R" }, allow_blank: true, on: :after_log + validates :field_18, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 7 must be a number or the letter R" }, allow_blank: true, on: :after_log + validates :field_19, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 8 must be a number or the letter R" }, allow_blank: true, on: :after_log + + validates :field_96, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (day)") }, on: :after_log + validates :field_97, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (month)") }, on: :after_log + validates :field_98, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (year)") }, on: :after_log + + validates :field_98, format: { with: /\A\d{2}\z/, message: I18n.t("validations.setup.startdate.year_not_two_digits") }, on: :after_log + + validate :validate_data_types, on: :after_log + validate :validate_nulls, 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_lettings_type_matches_bulk_upload, on: :after_log + validate :validate_only_one_housing_needs_type, on: :after_log + validate :validate_no_disabled_needs_conjunction, on: :after_log + validate :validate_dont_know_disabled_needs_conjunction, on: :after_log + validate :validate_no_and_dont_know_disabled_needs_conjunction, on: :after_log + + validate :validate_owning_org_data_given, on: :after_log + validate :validate_owning_org_exists, on: :after_log + validate :validate_owning_org_owns_stock, on: :after_log + validate :validate_owning_org_permitted, on: :after_log + + validate :validate_managing_org_data_given, on: :after_log + validate :validate_managing_org_exists, on: :after_log + validate :validate_managing_org_related, on: :after_log + + validate :validate_scheme_related, on: :after_log + validate :validate_scheme_exists, on: :after_log + validate :validate_scheme_data_given, on: :after_log + + validate :validate_location_related, on: :after_log + validate :validate_location_exists, on: :after_log + validate :validate_location_data_given, on: :after_log + + validate :validate_created_by_exists, on: :after_log + validate :validate_created_by_related, on: :after_log + validate :validate_rent_type, on: :after_log + + validate :validate_valid_radio_option, on: :before_log def self.question_for_field(field) QUESTIONS[field] @@ -340,9 +340,13 @@ class BulkUpload::Lettings::Year2022::RowParser return true if blank_row? + super(:before_log) + before_errors = errors.dup + log.valid? - super + super(:after_log) + errors.merge!(before_errors) log.errors.each do |error| fields = field_mapping_for_errors[error.attribute] || [] diff --git a/app/services/bulk_upload/lettings/year2023/row_parser.rb b/app/services/bulk_upload/lettings/year2023/row_parser.rb index 43b229aa3..31205992f 100644 --- a/app/services/bulk_upload/lettings/year2023/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2023/row_parser.rb @@ -280,59 +280,59 @@ class BulkUpload::Lettings::Year2023::RowParser attribute :field_134, :decimal validates :field_5, presence: { message: I18n.t("validations.not_answered", question: "letting type") }, - inclusion: { in: (1..12).to_a, message: I18n.t("validations.invalid_option", question: "letting type") } - validates :field_16, presence: { if: proc { [2, 4, 6, 8, 10, 12].include?(field_5) } } - - validates :field_46, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 1 must be a number or the letter R" } - validates :field_52, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 2 must be a number or the letter R" }, allow_blank: true - validates :field_56, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 3 must be a number or the letter R" }, allow_blank: true - validates :field_60, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 4 must be a number or the letter R" }, allow_blank: true - validates :field_64, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 5 must be a number or the letter R" }, allow_blank: true - validates :field_68, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 6 must be a number or the letter R" }, allow_blank: true - validates :field_72, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 7 must be a number or the letter R" }, allow_blank: true - validates :field_76, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 8 must be a number or the letter R" }, allow_blank: true - - validates :field_6, presence: { message: I18n.t("validations.not_answered", question: "property renewal") } - validates :field_7, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (day)") } - validates :field_8, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (month)") } - validates :field_9, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (year)") } - - validates :field_9, format: { with: /\A\d{2}\z/, message: I18n.t("validations.setup.startdate.year_not_two_digits") } - - validate :validate_needs_type_present - validate :validate_data_types - validate :validate_nulls - validate :validate_relevant_collection_window - validate :validate_la_with_local_housing_referral - validate :validate_cannot_be_la_referral_if_general_needs_and_la - validate :validate_leaving_reason_for_renewal - validate :validate_lettings_type_matches_bulk_upload - validate :validate_only_one_housing_needs_type - validate :validate_no_disabled_needs_conjunction - validate :validate_dont_know_disabled_needs_conjunction - validate :validate_no_and_dont_know_disabled_needs_conjunction - - validate :validate_owning_org_data_given - validate :validate_owning_org_exists - validate :validate_owning_org_owns_stock - validate :validate_owning_org_permitted - - validate :validate_managing_org_data_given - validate :validate_managing_org_exists - validate :validate_managing_org_related - - validate :validate_scheme_related - validate :validate_scheme_exists - validate :validate_scheme_data_given - - validate :validate_location_related - validate :validate_location_exists - validate :validate_location_data_given - - validate :validate_created_by_exists - validate :validate_created_by_related - - validate :validate_valid_radio_option + inclusion: { in: (1..12).to_a, message: I18n.t("validations.invalid_option", question: "letting type") }, on: :after_log + validates :field_16, presence: { if: proc { [2, 4, 6, 8, 10, 12].include?(field_5) } }, on: :after_log + + validates :field_46, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 1 must be a number or the letter R" }, on: :after_log + validates :field_52, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 2 must be a number or the letter R" }, allow_blank: true, on: :after_log + validates :field_56, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 3 must be a number or the letter R" }, allow_blank: true, on: :after_log + validates :field_60, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 4 must be a number or the letter R" }, allow_blank: true, on: :after_log + validates :field_64, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 5 must be a number or the letter R" }, allow_blank: true, on: :after_log + validates :field_68, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 6 must be a number or the letter R" }, allow_blank: true, on: :after_log + validates :field_72, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 7 must be a number or the letter R" }, allow_blank: true, on: :after_log + validates :field_76, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 8 must be a number or the letter R" }, allow_blank: true, on: :after_log + + validates :field_6, presence: { message: I18n.t("validations.not_answered", question: "property renewal") }, on: :after_log + validates :field_7, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (day)") }, on: :after_log + validates :field_8, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (month)") }, on: :after_log + validates :field_9, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (year)") }, on: :after_log + + validates :field_9, format: { with: /\A\d{2}\z/, message: I18n.t("validations.setup.startdate.year_not_two_digits") }, on: :after_log + + validate :validate_needs_type_present, on: :after_log + validate :validate_data_types, on: :after_log + validate :validate_nulls, 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_lettings_type_matches_bulk_upload, on: :after_log + validate :validate_only_one_housing_needs_type, on: :after_log + validate :validate_no_disabled_needs_conjunction, on: :after_log + validate :validate_dont_know_disabled_needs_conjunction, on: :after_log + validate :validate_no_and_dont_know_disabled_needs_conjunction, on: :after_log + + validate :validate_owning_org_data_given, on: :after_log + validate :validate_owning_org_exists, on: :after_log + validate :validate_owning_org_owns_stock, on: :after_log + validate :validate_owning_org_permitted, on: :after_log + + validate :validate_managing_org_data_given, on: :after_log + validate :validate_managing_org_exists, on: :after_log + validate :validate_managing_org_related, on: :after_log + + validate :validate_scheme_related, on: :after_log + validate :validate_scheme_exists, on: :after_log + validate :validate_scheme_data_given, on: :after_log + + validate :validate_location_related, on: :after_log + validate :validate_location_exists, on: :after_log + validate :validate_location_data_given, on: :after_log + + validate :validate_created_by_exists, on: :after_log + validate :validate_created_by_related, on: :after_log + + validate :validate_valid_radio_option, on: :before_log def self.question_for_field(field) QUESTIONS[field] @@ -343,9 +343,13 @@ class BulkUpload::Lettings::Year2023::RowParser return true if blank_row? + super(:before_log) + before_errors = errors.dup + log.valid? - super + super(:after_log) + errors.merge!(before_errors) log.errors.each do |error| fields = field_mapping_for_errors[error.attribute] || [] diff --git a/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb index fd60c4a7b..b6769becb 100644 --- a/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb @@ -507,6 +507,14 @@ RSpec.describe BulkUpload::Lettings::Year2022::RowParser do expect(parser.errors[:field_52]).to be_present end end + + context "when not a valid option" do + let(:attributes) { setup_section_params.merge({ bulk_upload:, field_52: "99" }) } + + it "has error for invalid option" do + expect(parser.errors[:field_52]).to include("Enter a valid value for What is the tenant's main reason for the household leaving their last settled home?") + end + end end end diff --git a/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb index 3c472472e..ff1e00c4c 100644 --- a/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb @@ -509,7 +509,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do let(:attributes) { setup_section_params.merge({ field_102: "7" }) } it "returns an error" do - expect(parser.errors[:field_102]).to be_present + expect(parser.errors[:field_102]).to include("Enter a valid value for What is the tenant’s main reason for the household leaving their last settled home?") end end end From 55b6b8d9515765bd3f1baa4e0e83dbe16611bef9 Mon Sep 17 00:00:00 2001 From: Jack <113976590+bibblobcode@users.noreply.github.com> Date: Wed, 19 Apr 2023 16:43:21 +0100 Subject: [PATCH 13/23] CLDC-2229 Do not show hint text when outright sale (#1563) --- .../form/sales/questions/purchase_price.rb | 12 ++++++-- .../sales/questions/purchase_price_spec.rb | 30 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/app/models/form/sales/questions/purchase_price.rb b/app/models/form/sales/questions/purchase_price.rb index c98a39ef3..ce9b58af6 100644 --- a/app/models/form/sales/questions/purchase_price.rb +++ b/app/models/form/sales/questions/purchase_price.rb @@ -8,17 +8,23 @@ class Form::Sales::Questions::PurchasePrice < ::Form::Question @min = 0 @width = 5 @prefix = "£" - @hint_text = "For all schemes, including Right to Acquire (RTA), Right to Buy (RTB), Voluntary Right to Buy (VRTB) or Preserved Right to Buy (PRTB) sales, enter the full price of the property without any discount" + @hint_text = hint_text @ownership_sch = ownershipsch @question_number = question_number end def question_number case @ownership_sch - when 2 + when 2 # discounted ownership scheme 100 - when 3 + when 3 # outright sale 110 end end + + def hint_text + return if @ownership_sch == 3 # outright sale + + "For all schemes, including Right to Acquire (RTA), Right to Buy (RTB), Voluntary Right to Buy (VRTB) or Preserved Right to Buy (PRTB) sales, enter the full price of the property without any discount" + end end diff --git a/spec/models/form/sales/questions/purchase_price_spec.rb b/spec/models/form/sales/questions/purchase_price_spec.rb index e9e7ee3c3..a6a4f6cbe 100644 --- a/spec/models/form/sales/questions/purchase_price_spec.rb +++ b/spec/models/form/sales/questions/purchase_price_spec.rb @@ -37,6 +37,36 @@ RSpec.describe Form::Sales::Questions::PurchasePrice, type: :model do ) end + it "has the correct question_number" do + expect(question.question_number).to be_nil + end + + context "when discounted ownership scheme" do + subject(:question) { described_class.new(question_id, question_definition, page, ownershipsch: 2) } + + it "has the correct hint" do + expect(question.hint_text).to eq( + "For all schemes, including Right to Acquire (RTA), Right to Buy (RTB), Voluntary Right to Buy (VRTB) or Preserved Right to Buy (PRTB) sales, enter the full price of the property without any discount", + ) + end + + it "has the correct question_number" do + expect(question.question_number).to eq(100) + end + end + + context "when outright sale" do + subject(:question) { described_class.new(question_id, question_definition, page, ownershipsch: 3) } + + it "has the correct hint" do + expect(question.hint_text).to be_nil + end + + it "has the correct question_number" do + expect(question.question_number).to eq(110) + end + end + it "has correct width" do expect(question.width).to eq(5) end From 9fb44d7aefb422048245f6b38c5b4df572e67543 Mon Sep 17 00:00:00 2001 From: Jack <113976590+bibblobcode@users.noreply.github.com> Date: Thu, 20 Apr 2023 08:48:09 +0100 Subject: [PATCH 14/23] [CLDC-2240] Show correct copy when log older than previous collection year (#1569) --- app/helpers/tasklist_helper.rb | 5 ++++- spec/helpers/tasklist_helper_spec.rb | 22 ++++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/app/helpers/tasklist_helper.rb b/app/helpers/tasklist_helper.rb index 6a12cff29..678ef044f 100644 --- a/app/helpers/tasklist_helper.rb +++ b/app/helpers/tasklist_helper.rb @@ -1,5 +1,6 @@ module TasklistHelper include GovukLinkHelper + include CollectionTimeHelper def get_next_incomplete_section(log) log.form.subsections.find { |subsection| subsection.is_incomplete?(log) } @@ -34,7 +35,9 @@ module TasklistHelper "You can #{govuk_link_to 'review and make changes to this log', link} until #{log.form.end_date.to_formatted_s(:govuk_date)}.".html_safe else - "This log is from the #{log.form.start_date.year}/#{log.form.start_date.year + 1} collection window, which is now closed." + start_year = log.startdate ? collection_start_year_for_date(log.startdate) : log.form.start_date.year + + "This log is from the #{start_year}/#{start_year + 1} collection window, which is now closed." end end diff --git a/spec/helpers/tasklist_helper_spec.rb b/spec/helpers/tasklist_helper_spec.rb index 3d4874632..ac6075fd2 100644 --- a/spec/helpers/tasklist_helper_spec.rb +++ b/spec/helpers/tasklist_helper_spec.rb @@ -131,10 +131,19 @@ RSpec.describe TasklistHelper do it "returns relevant text" do Timecop.freeze(now + 1.year) do - expect(review_log_text(sales_log)).to eq("This log is from the 2021/2022 collection window, which is now closed.") + expect(review_log_text(sales_log)).to eq("This log is from the 2022/2023 collection window, which is now closed.") end end end + + context "when older_than_previous_collection_year" do + let(:now) { Time.utc(2023, 6, 1) } + let(:sales_log) { build(:sales_log, :completed, saledate: Time.utc(2022, 2, 1)) } + + it "returns relevant text" do + expect(review_log_text(sales_log)).to eq("This log is from the 2021/2022 collection window, which is now closed.") + end + end end context "with lettings log" do @@ -168,10 +177,19 @@ RSpec.describe TasklistHelper do it "returns relevant text" do Timecop.freeze(now + 1.year) do - expect(review_log_text(sales_log)).to eq("This log is from the 2021/2022 collection window, which is now closed.") + expect(review_log_text(sales_log)).to eq("This log is from the 2022/2023 collection window, which is now closed.") end end end + + context "when older_than_previous_collection_year" do + let(:now) { Time.utc(2023, 6, 1) } + let(:lettings_log) { build(:lettings_log, :completed, startdate: Time.utc(2022, 2, 1)) } + + it "returns relevant text" do + expect(review_log_text(lettings_log)).to eq("This log is from the 2021/2022 collection window, which is now closed.") + end + end end end end From fd24a9c96438d0db3a631fc82c2d2c0a28642931 Mon Sep 17 00:00:00 2001 From: Jack <113976590+bibblobcode@users.noreply.github.com> Date: Thu, 20 Apr 2023 09:49:02 +0100 Subject: [PATCH 15/23] CLDC-2227 Reorder UPRN and address question on sales logs (#1571) --- app/models/form/sales/subsections/property_information.rb | 2 +- .../form/sales/subsections/property_information_spec.rb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/models/form/sales/subsections/property_information.rb b/app/models/form/sales/subsections/property_information.rb index 74a064dca..9efac327f 100644 --- a/app/models/form/sales/subsections/property_information.rb +++ b/app/models/form/sales/subsections/property_information.rb @@ -8,13 +8,13 @@ class Form::Sales::Subsections::PropertyInformation < ::Form::Subsection def pages @pages ||= [ - uprn_questions, Form::Sales::Pages::PropertyNumberOfBedrooms.new(nil, nil, self), Form::Sales::Pages::AboutPriceValueCheck.new("about_price_bedrooms_value_check", nil, self), Form::Sales::Pages::PropertyUnitType.new(nil, nil, self), Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_property_type_value_check", nil, self), Form::Sales::Pages::PercentageDiscountValueCheck.new("percentage_discount_proptype_value_check", nil, self), Form::Sales::Pages::PropertyBuildingType.new(nil, nil, self), + uprn_questions, postcode_and_la_questions, Form::Sales::Pages::AboutPriceValueCheck.new("about_price_la_value_check", nil, self), Form::Sales::Pages::PropertyWheelchairAccessible.new(nil, nil, self), diff --git a/spec/models/form/sales/subsections/property_information_spec.rb b/spec/models/form/sales/subsections/property_information_spec.rb index 559db5ea6..2ad141502 100644 --- a/spec/models/form/sales/subsections/property_information_spec.rb +++ b/spec/models/form/sales/subsections/property_information_spec.rb @@ -41,16 +41,16 @@ RSpec.describe Form::Sales::Subsections::PropertyInformation, type: :model do it "has correct pages" do expect(property_information.pages.map(&:id)).to eq( %w[ - uprn - uprn_confirmation - address - property_local_authority property_number_of_bedrooms about_price_bedrooms_value_check property_unit_type monthly_charges_property_type_value_check percentage_discount_proptype_value_check property_building_type + uprn + uprn_confirmation + address + property_local_authority about_price_la_value_check property_wheelchair_accessible ], From 1cd8c01841cd4c84c7acd1b013e8a3f08e96bff4 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Thu, 20 Apr 2023 10:06:37 +0100 Subject: [PATCH 16/23] Enable collection window validation (#1567) --- app/models/validations/setup_validations.rb | 2 +- app/services/feature_toggle.rb | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/models/validations/setup_validations.rb b/app/models/validations/setup_validations.rb index bd12d3249..ff814ea35 100644 --- a/app/models/validations/setup_validations.rb +++ b/app/models/validations/setup_validations.rb @@ -3,7 +3,7 @@ module Validations::SetupValidations include CollectionTimeHelper def validate_startdate_setup(record) - return unless record.startdate && date_valid?("startdate", record) && FeatureToggle.startdate_collection_window_validation_enabled? + return unless record.startdate && date_valid?("startdate", record) unless record.startdate.between?(active_collection_start_date, current_collection_end_date) record.errors.add :startdate, startdate_validation_error_message diff --git a/app/services/feature_toggle.rb b/app/services/feature_toggle.rb index eed6244d1..e7ca0ef51 100644 --- a/app/services/feature_toggle.rb +++ b/app/services/feature_toggle.rb @@ -4,10 +4,6 @@ class FeatureToggle Rails.env.production? || Rails.env.test? || Rails.env.staging? end - def self.startdate_collection_window_validation_enabled? - Rails.env.production? || Rails.env.test? - end - def self.startdate_two_week_validation_enabled? Rails.env.production? || Rails.env.test? || Rails.env.staging? end From fae04812278961de7ea03ccc1388a3074f369442 Mon Sep 17 00:00:00 2001 From: Phil Lee Date: Thu, 20 Apr 2023 10:27:48 +0100 Subject: [PATCH 17/23] CLDC-1887 Update links to bulk upload assets (#1566) # Context - https://digital.dclg.gov.uk/jira/browse/CLDC-1887 # Changes - Update links to bulk upload assets --- app/controllers/start_controller.rb | 40 ++++++++++++++++++ .../layouts/_collection_resources.html.erb | 25 +++++++++++ config/routes.rb | 10 ++++- ...load-lettings-legacy-template-2023-24.xlsx | Bin 0 -> 14783 bytes 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 public/files/bulk-upload-lettings-legacy-template-2023-24.xlsx diff --git a/app/controllers/start_controller.rb b/app/controllers/start_controller.rb index d707d3e91..671b63805 100644 --- a/app/controllers/start_controller.rb +++ b/app/controllers/start_controller.rb @@ -28,4 +28,44 @@ class StartController < ApplicationController type: "application/pdf", ) end + + def download_23_24_lettings_bulk_upload_template + send_file( + Rails.root.join("public/files/bulk-upload-lettings-template-2023-24.xlsx"), + filename: "2023-24-lettings-bulk-upload-template.xlsx", + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ) + end + + def download_23_24_lettings_bulk_upload_legacy_template + send_file( + Rails.root.join("public/files/bulk-upload-lettings-legacy-template-2023-24.xlsx"), + filename: "2023-24-lettings-bulk-upload-legacy-template.xlsx", + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ) + end + + def download_23_24_lettings_bulk_upload_specification + send_file( + Rails.root.join("public/files/bulk-upload-lettings-specification-2023-24.xlsx"), + filename: "2023-24-lettings-bulk-upload-specification.xlsx", + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ) + end + + def download_22_23_lettings_bulk_upload_template + send_file( + Rails.root.join("public/files/bulk-upload-lettings-template-2022-23.xlsx"), + filename: "2022-23-lettings-bulk-upload-template.xlsx", + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ) + end + + def download_22_23_lettings_bulk_upload_specification + send_file( + Rails.root.join("public/files/bulk-upload-lettings-specification-2022-23.xlsx"), + filename: "2022-23-lettings-bulk-upload-specification.xlsx", + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ) + end end diff --git a/app/views/layouts/_collection_resources.html.erb b/app/views/layouts/_collection_resources.html.erb index 37e9c4b94..f583bd813 100644 --- a/app/views/layouts/_collection_resources.html.erb +++ b/app/views/layouts/_collection_resources.html.erb @@ -9,6 +9,21 @@ href: download_23_24_lettings_form_path, metadata: "PDF, 278 KB, 8 pages", }, + { + name: "Lettings bulk upload template (2023/24) *new question ordering*", + href: download_23_24_lettings_bulk_upload_template_path, + metadata: "Microsoft Excel, 15 KB", + }, + { + name: "Lettings bulk upload template (2023/24)", + href: download_23_24_lettings_bulk_upload_legacy_template_path, + metadata: "Microsoft Excel, 15 KB", + }, + { + name: "Lettings bulk upload specification (2023/24)", + href: download_23_24_lettings_bulk_upload_specification_path, + metadata: "Microsoft Excel, 90 KB", + }, ]) %>

Lettings 22/23

@@ -18,6 +33,16 @@ href: "https://core.communities.gov.uk/public/download/guides-and-manuals/2022-23%20Lettings%20paper%20form.pdf?download-format=pdf", metadata: "PDF, 654 KB, 4 pages", }, + { + name: "Lettings bulk upload template (2022/23)", + href: download_22_23_lettings_bulk_upload_template_path, + metadata: "Microsoft Excel, 36 KB", + }, + { + name: "Lettings bulk upload specification (2022/23)", + href: download_22_23_lettings_bulk_upload_specification_path, + metadata: "Microsoft Excel, 53 KB", + }, ]) %>

Sales 23/24

diff --git a/config/routes.rb b/config/routes.rb index 95dcb21a3..e9542ebec 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -35,9 +35,17 @@ Rails.application.routes.draw do get "/accessibility-statement", to: "content#accessibility_statement" get "/privacy-notice", to: "content#privacy_notice" get "/data-sharing-agreement", to: "content#data_sharing_agreement" + + get "/download-23-24-lettings-form", to: "start#download_23_24_lettings_form" + get "/download-23-24-lettings-bulk-upload-template", to: "start#download_23_24_lettings_bulk_upload_template" + get "/download-23-24-lettings-bulk-upload-legacy-template", to: "start#download_23_24_lettings_bulk_upload_legacy_template" + get "/download-23-24-lettings-bulk-upload-specification", to: "start#download_23_24_lettings_bulk_upload_specification" + + get "/download-22-23-lettings-bulk-upload-template", to: "start#download_22_23_lettings_bulk_upload_template" + get "/download-22-23-lettings-bulk-upload-specification", to: "start#download_22_23_lettings_bulk_upload_specification" + get "/download-23-24-sales-form", to: "start#download_23_24_sales_form" get "/download-22-23-sales-form", to: "start#download_22_23_sales_form" - get "/download-23-24-lettings-form", to: "start#download_23_24_lettings_form" resource :account, only: %i[show edit], controller: "users" do get "edit/password", to: "users#edit_password" diff --git a/public/files/bulk-upload-lettings-legacy-template-2023-24.xlsx b/public/files/bulk-upload-lettings-legacy-template-2023-24.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..22b630cd378a4df7bb8f6c09f1961aa94a7e4c04 GIT binary patch literal 14783 zcmaib1AHZ2(s%5MZQHhO+qUhAZF6EzY}>Z&Wa4C!3BJs;?|wV`{Px|qZr{4yb*t;# zu5(Ux-};}DmjVVs27rKo0H702R0a5h&_DOxt?7&%4cshj&7A(Z!$9k9W1T&tW7|g$ z1AdKk0jsiQp^1(pgseLwOAot4Vv7|YI|f^@;X5nIYT*TlYctOGd^Kt7-1Jk}5Xqb9 ztPT;>6Q$ushY0q{u0ENZJz!Zsa9C73ZqW8u0|ragbVmu{rI&6=9fD6(zow)r59bae z%*lMimjnFb1kN(ZDG68Asfsg^TYiWPuYs0a(!H}@;0)mXyLXx8pe7cmCxLA27j}Xm zRKpk0C)1tEGVt#TJo2xALw*5F5kR4=DU>be6szKbZ`_dV@0bspBZ^&Pi+UrQ$isj* za{bh;LY^a`wWPXTi6)|MKfX4b1LJ%hA8BF#xxZOUIm+5dPGw!IyHzz&PuQ#{F9i+( zZfFq*3J3rI4h#Sw|9`pC&Cb!v$=t-m*@^D2kAJvQN6T$Za_JTH4zJ_Y*D#3KmTI)J z3j$_{6;03Mj-B|1vuRY{k>l>14-j!irP2DebK*c*t-0xOYjLw3iv;iE?fCR&XW9Av zmfq{#8QrH88&^8_%1jS8r`w0Kqr?1Tk)7LJV}r-9)hmlfjt}RZ$%o_h>DbTbl_&G# z?be4=2m1h+yW5-dwM+fg90#X!!!MV2v(r26Esgh>M$hc7q+R4a_s&j?j_+u`ovuPw zE>>(&vdHz``RUrb1vign+bn8zxf5=PhqH z2lvNued_A+VCb(^B?d*?-nZHN={IK+c_R9gQ(wHuoF7MiAFnrRya%xx5g**#ywvX+ ztBn{(%O(A}cgLy>LwZ6tx_70dLk;M3-j_UpcQeb!GO`beLA*L@dz8n2r_j^+E2Qv&Wc#CI!>8lgVn zdtai5X8ZE<=jn`SD7f2GWT@VJ@1`Px$NpJ3RW`Nb&bjwv_vzj3Dc$_==^GEhxL0WIJ*(!)KL|`x>dE0 zl(4(|10B-k9By-#DeowJ<#-F$EeZ ztKBH?Iu-nuvlNEhA4=-I` z)p&8=G-I^rFESK14TK5$t1xb03osd9Go!30#a+=IM$D@*PGN~4F|yfm9Veogjh)jK zpfPw%7lei&nJ@_orlU+p*iA%1zH%TThfTD8^Oph(0LsCGf@<+4Cf`}D{pN2)2LkHt zMM1u^K_viqr5h;?c(nxq<^H59-_wrtDZwOS-_bP!1%I_lg6ZuQg+$=CX6yw-%WT^5 zkByEqf{|qvjAL+Lg^(E9F(nelD98<_HoC8eH;wW|or{{C7R+RL(FkJ_<%4z^Ix!=d z26>~tPyd!1%#UIax6K4UHS0h+V5!45jUJMUau_OZ#jKU~Gfc*Jwu)I3KJhBjhv1Y$KMEVjv zvS2aGeSMt8mp5u-O(8z$^rg?1{*%b%w-(NCEwSHPpFg_T<+>h;u_`b(`}2#}iy-Oq z9Wyd#O>jQ0ym`)_tB~2OJ=3x$4PUWt1WOuqVE>T#st~}S zUw(^_ev5=ZMfsV*(wQR&j5dbkLDHG%Pz3X&A%Y_f;d$_Xih}x}(?wCjg3&mmDdA!0 zf-qHT08k{ODR->s3NclBR&~u;%Ke+)){9EftSptkhPe|gl>!mm2v#Y88tw&a7eIlD zoS;mq22-dO>!uW=v#Demx}2$aL_CzKfl{iL>L!<}7kH`^8YZ8qq(``vs|8c4mg^>$ zt5={q!rKM0SQRfsiu`6OR;s1VxN$xE>56nqRT_C)JKvz7 zmn*BpcW$Ih+4|;KlwJncezU#b?CdwYWkGt0c0F4zc)oZ#@~-hzN99{oDIq@zYM){; z^Pz9c*k9&%0(J+fGhC6Rrv<6fiR%j$n&D)>cYwTb4ZwE=1s~tb+2e`;v}RVUAtKn%L?z#q&!hJ(OBWF$0}9swmt{iW{HNRYhw9kP|+ASDixV2Iv$_ScXE z(alBA{CIj)9;*;eNDRgq86P#u%g#t@6=+_yh@G zoYIhXBAzWot4eHte>S9Ry{Jq7Uh3@_C*#~)k|~qmTF{KONi5_k;v7+s;@~4B2gyNH zoRc6}5DX*+1>N3FPgOO|=e(-pST^P8_HyF&%BK z1hFjn%X!MqNf9xMvpN)HJ9)j0_`4Pb?Fz&bsalNwY68<0-%6+-JKpj~$L5U>E$VJf zmWs5*ZN6X2FH{uvD=FyFhBvB(=6g)db01se+%ZpkWE{WpV}zUR%E#AG7kb|rEk;9$ zoQYDLs6g#`d@q5V8~5(Nt=n)}Y<@GJoA}(wH|q#D0|>vsMKXN{h=8%aaP7a_{~QVG zV#ywubv1E{Wsznh7*(AZGBFs3#!Z=t0vITEW2NpkhS&BTAmr?M!R#8<5{qN3mO;j# zh`e8Jt99Z6FwmxJxR@Vrz*aB8S{G@=cXfCQh2^4yz}LwVKpx%~4cq%3RCz-qp_PurPd?UUe$N9l_|Z zAzY0?buv?>BeK?UZvbOEXS{ai33#**Yd6n$H@hYY#bnC8dgk`RnhwLTcf~4Ze^U2- zk77;MoVw(;D}qf)_Rt5Vx;QIiw8H)7z@5}B>nY}L%VCRa%4<2f*wCP?$X_Kn0jN7J zK@i1mG;2d}2JBKgCfKDQ1qh|Xi$_5cuzGVt6>7cBosQ_hQBYJ{l87E#Eg?9R&@#Ae zZ!kxRN*cxPF-y~++`aLb^w-Jko$ZK}#%$5IL~81TjtTS1b;eOarFF&zcZQK)Ue&BW zRoXI>5R1koNEC3TB$Iy7|FSM$AesWuJ&U*HOUCu6{3?m0P0nq1#eMb8WB0*RO`oHN zK1~&M{ML2+rTh4BV7y~^d1|akRh%e2{6xp4urorG3k( zHd8X`42*1828Daq6|{{TQT}YYtN_nZ_PTt><9uHOCnptPTFU=eCi?gGyfq#X083;n zA}|c&s1(?It&#pTY85l}DX3(;npe}vdis>~@`l3N9hCd?fsGU*pRi%`JP5y7@>fH# zr|`n8#Gqs?6lV6PIbVkCnAlsX*}M8)B_~`!j4IZ4_vUgoC`Ac|f^m`-N{PqDAql<& zWg$AMqT|^7Gw>rYk{$%aN!1-kurCK~z&lEf1i>4j*THava0_7w*@SSy;4m$igcVrH zrY)MCBoV~a?80Nk)b8SA#nkQ6W5v|B8ix_turGud*|4vK6xeXArThoh6z1A-2kjcu zbZD;ie>1L_rhW52SWXj;*t!c_o{x!)JW#>(6`EX95RZQO)|#t}x52R=PYiu#+}w0Oa^?r_Zhv_AJavG$ zPeD#|?0P6(uV=ZFK=Vu^U%ks zo|v3o4;(IIkWR|QCt=I9uu3Mldczx@?n1#(k1Iq5^We74u)?#<+J3y2fr#!;mTD#2 zQ7{FxItxmm@zt8o+#G2xPj{Gwp?facSfJdd?E!*=pipk)^ysFV`y#3>8*Stf*F_$G zz1lH!00Y5Hnn*7$l=g|?CIZ)02Cki8ipGI-fFVSDh;f`@oQcXvHB$q}6o;u|ymE{8 zJ7X4F2vUC*UZ^gw^)(2b4G0_VSe{mk$nRf@QU8DLvJEm;e}eqTH%FVJ2&1IS&@Cz z3Axf6eyq5D8}VB<;CkqWye#>-8FHEAkXX|hemN9GCk}>*g*YO(-=~I;6b%PyTmWg3 zSQnpXYiVsvMqCr07jtH9OkT)|g+WPCB+Z9;G>q>U2>O97RykGq z4EhpKAa$dbk4keQrzROPf_TRvJzGXEE>DP=4nTR0pnZSHD07}okOZ&BV)Cm+O26`lt zjT7}?Pzwk8HiDfNzdFqDoL?8=FcjEBWfb<aDF=fvIR}GaX-nE93@Dn@f?((1MTER9gP2Q2lZ^o$ zt@Hv3G8!C)WaK@wv3+T6IipmR62*al)w9>_OAY);vRc9L3})bgx5B(^)1g1(*Y zz@-`g)u1x2(deRp(&DhVbbDorB6q5$HQt>h-;i}oBedv<+DyAJPq&{^Ti$yx#mvZT znuNugLi%*eE3|6%oJF-bC!^UZ%u8ZH!}k-BM$Kb9+B%)M>X1!^(87CbPJu>a&9bC? z*kP-HYWVD!rljtW+KwZYroY34SZl~*ywSRKRExBH@X)UQHD9D7T0(1l+axSyzoUpw ztFAD4baq2#?beamEwiEHzG1wx{&h&0#B7T(iKZ1U12Ok*# zK-u%ZWf_V7k+A(E%c$pQV*O{Xk@oj-^t2RIA3Z$S4w8E)y;tH=a2WHYXpn6YuD*CN zoHZ`-&Fp$+bpB4?;l#9eza9%5Q$1ENI|o(X0VsXl*y$)N;?$-?Con3KW^{c$u|Xb$ zOlP?)v(XrvLzcHOMy*0$TEaFo!lF1Bkz0;Il=q1cGV8)+~l?I!iE<#rG8#pW~Pw{x?b28~7pAw?7=&?Yp%!_#L`M27~! zQ|ufO-=-wY2_=R2-wJ(8hO61_)OknX?pjxb#iiE~lKQ}3)6NIuKZI{y?#5ID?(;e( z;Vs$2+Vk|r^bh10VbET8LVe$=s0AyDeF>Db;g>vuO}Yrm`vT>chReBtZgWV5|(Yvl|a?)Y+O;vOkDG*$MR zp0y^1@t%0s33ReoemUyzO(`A(9Nd0HQB1<4i@ zeNI$X5ThswcN?BiEhvS^A=BTR(j$){w zGz^5?Djy0d>v&#>U2o^%PI-R1K><#!)qcq4nk=ZHVRrWz$9nIH3a#W{Eulx(r(Q|b zhl9Z2rbpJ-G*x8Ck#A_gR|15gq5uxMQ)l&4w+JE(KnXF$rTkTHk;kj3Bw_W-ItHZO zX?E2d4#JS@c?5lvL4eR4TW;&p_@R4QwgSf8pw|NCmn*QbHm2*+sjPFV3;;AM^s;m5 zFZF^qkf=ib7L~YUUn`N`_}HX+gEtJ~mFeHgBKV|8cKwUf*Un8b$gLVzr#4<%_EMIH z$Fa}Q(uENq4}yxa%k5A`PnOvN#wC1}^Wh|ZLi{+1|LCDqc;BE!LN69%U4>Dywf?~x z*?C^h8$m?J{pO!15^x@4sC$H>9gD>wimhqEN6DPh*Y55Y^Zi$y($!p+IsV@M$LF^6)mvNBKn@jq!eYDU**|t?W90MDo;kBRww~WF-+E7D#1%1K z7{de~Jk9bFvYYLEJ(7vbdKTzCI+UT`8PRy?zNnKiqI>@2tm$}|{M`K0=-^_})(#bmK=d;sn;%b-Y5Lq~Lm=$s-FV(?{EtVO*d7m*2o5u&w* zbz=s9n88H34Q_(XrwroA&QZh;=^6P{zXI2=)hE_k_##na>|YN6@!{c-Mab+<3skdBwUfw5AC3s+{l?xCj?Ax zw7gv>1WujEI3u@ODB{)wUHG@OJX9 zyqc5~=KUhv5{E+GY2oq@@4}ee*oD!%cua1*vfaCQP9HEu7EoZ3WLcrh@c4UMh_K*y zQFj7kQsbF}Zd!=+QG14n*Wej~>0D8b{9=Wj$g&Pl4xBCGCF`yal@a%1Z$OCivVlXP zqZ;Z|aG+|iR{yY%2}9|q9hpFWaY0*%35~b}d~*K82}e|1%_B$9d5EV((fTfJpyaPz z3V?yN@jyg5a}xj~Dd0lFH`o3+5G@#G^3g75w|4|>NcENM^9fkkIih0#Ffi#+`{txw zp&5!$7-4Skp|V^-dP<;QgOkjNiNA#|kA@%htZ^_QN+ykc+wB-692<~<5Ofh0a3~bA zPApj?IY_H*?9c-)t8Y_y1o|q;>5Huw%|lK$jv?nl4FowU7l#C*?iz8Z3UC`EaNssq zsjg?L)Q83yPvl9(V~$=aOu^?tpgZDo!wdZl!%ch?$d1y*TR+4crkS#qwsE%WrocO-7ilR!#MzttzaV^tR=7?R$OzH=NqUQi(l8 zQm1Zay88`VL-S`tn%Ny3R|j0|r#YOC4}>R%X&7QSJ$%ngRZB%7Nh)iHLN3dc&nS`6 zNl+pS((kgCv~VDp?X^=zi=1d{)$wXi5ImZ}Ife`%^6hcTc1D{H=(+s#c`Gcup$i#; zljs={jjn^{vkgR{7cTP`8?n9gy55ErWcO*&4(AZo`V3%It0Z9S9EjHx@BBhYMqm{p04qS#j=Fi{h} zO5a?u6QSy#1Xk(8_>7a08p0t^jFi5ZX2p^U-?iW@qYle?U|B%r)as2QnubH!qU7=m z`_A9k!O?(`HLvgO9XLAJk{T>ykY!#K$3HIg+A#t~;!S#8yl>j!J!v&Ie1+lhMVmCG zZ-hI_8feVuskyej+6=V{RhODHu0ojtR`l*m-;+W%xBE&m)qTOXC`HM@9nRRpbxTIw zfevmL5E#(MNjClnNaO)JF8SON12GaZ?CcrfS$PPU^qR>~KgAR<^Sor_Hg@ zqjzBz$RWX+D+Yl~Bh<}6$wY@Gj|d%oo|iMxDF*&AePb9|=})3RjVfi002~Zf z$?df*tQLW$J2ufX?8uRO=Z}H1F5_8qyRc%XpLSaZSfjhL)uw@o*iyPR#mO2a(RA3A0Ne~$jr|bPI%P?QCm5J{p(vVw}5MD0qr6koPq2=5)jZ>la&%o|A6XcdiG%I$~BuiOwXqkJE z{5pn$&dz9Sj56b=4G(f-JYB!M-`c+kK^|6)h>u*>uU9_41eg;dx;qWM$NA)w6_3rV z$&}q%X2gwJ?$=E6Yq{*z)ELNEkkpJ2Dd{3x=xp0!NRcX`6k6X_80})Buu~k(i}bai zQ%bz4zp|<@`a=ty9a*6+=psbbdPsUa-mREQha(NxPP_AlG%y|T+st;wkt_#aAR@|6 z3`ic}HFH{QPHTeH1hz1gXl3?MGfMOziT^yqExBjL1Y|{=t91$iG1mSD!QF#>%Ux?d zrMJ9Y(dWSvJWkT-0fb`>`&G&@8!;zgPHyW2oh!>YcBvGj4p2_2yu$$$IiorrcP4N+ zF^Fe$27NHw8d4xCozIZ7h{1>*Jom2E3Ml@vw*~W-R_N?1^DH2}1n*kSw5!wN+MN^c zKv9N^z*xXf&-?X#7bmM-v_`NAtEdf-Fsc0m!@_aR#*NT&D*Eg(ug$8IR&5_R^(Esf zD{T`E(#R(9AUAWYCrKm}Cqlh>O>KNP5_)>OTU(DKJ>+UwS?IY`h@_2b`*9=aoYojR z#I3Y@zk+>AX=c878E6tqO!j0-qk5xa*D(jziL|ea7iu#sS8kkcekLh~z7hUAX!vGz zyI=;gX%Gybu^vc%k8P3)OWbP;S!5F4+?+;2?IA-B^B^2&GB&_|6g@fV3Vwn}0%fzl z4=!;$2f>evpgp~hSGyBs?B{d}l=KV4W98iEE?)srC2rv*{%a{ki9~|NmGKtJc`iec zoD00Y1#a5r5s6Cm3{%`z00c3Co~Nj|RU)H8Qvv;ie(v!jN(!8!tpiE#q73thoA3di?1b z2<_OSCDHo<(8E)wt44(!B;Ht!-&gLnMZ^cl(T<=%1X>O8lcF+WvoOGUAIQ(g*?&C~ z?_(KnjsWZs-cl;0GUuPe@UZQ-7$eYrD-ZzMCdwtF3Exdv0vxfh*<;Wk0dxd< zSlm_#)gFB{wDBuU(Kx~D@m!p-tQFbN-G*frLIWWP^pq~G6CS^XZl{)6Ml_U#YXkoJ z3Gx@wf{q8)gFO01$#b>umCm$6<~77z>+NkD$EpIAV(GK4Q<{f9B{jjXJ)Gmm%MASF zfj1x%+O|PcF*_{0OIR4~>_B0|0P1HH*vQurx_A*!3cgQpu>SYHW0zMLsB!c;B=<7j z#9uRr@S^HQnU}A*Y}p(HMw$8IZZQ@C6trxe5EXF?Y{;P6nZqpt2|SrawQ<8)UoVJBgE7`#W|c! zo-3(ed^*(37Ms`G>K!BTm!AKGLA1D#FOEi%(_Qlxr!)FyiN%U=2oiC$KzDmKf%C5cnxsRUc5Xk-fl z2zRYUA}miLi|V3dC2)v?>WYonqaEyPSQa zVX+XzAdM-)w$Yp)Auj0SB~&D=^TnV-Gm98r;3ifz5oEU1Q6K?tbq@ZZ_8X&DMu|oZ^;CdpnsCx;>FzrQ+ives0)I&>@`A;ZmUA^F`(7jZ&M`bC< z+Wm$X=_d>ySU#zeXvpF=lk=n_Ns-l{!;v9zm$|!c^0HhObYOaABw zaw=W7iS$wVCfY|q$jQULzAoHYNzHqc3_oaitePHn@W;xOi_4&|j0UJ!-6Ms2^y%Y^ zP3?)&h#~+D0&B}d&aKtLTs2ybboK>Ph^Vu}5@6JWs7jdntEh8NnJ#dN#>mOc+*PCf z%7%L99Y$|CN~@R9q>wpHfK7QoFydCkEUZ5m(BF~YfhvlGLz5+YN$kig&Y6We#}dDc zV8($KCwB3rQEG<3ze-5ck}*DvCwk~x8?WZ3l;_oBg0leIJYG^GyT=PFIEa*9vn{at z;kkoND2}*m>do(~eaphYxv{}{>5j<$EQDo(F-0jS)0h*SEcMJ;{37Gf z4;e$?KbJ-)4{@^#R~p!wAcLbYst-xa8Hp4LrDJAUf0BPuBYXB1eKw@`z1lkMb!vMz zjBY7d2aV^0%2lYwCdNe>t^Qpse7Y8oG2lG|@B%ncm^5wlwH^1oKMINYk){)U78<{9 zAcJ)dA>jCy)g;uJW)B`e4QOyW>f0S6|EAgK%yrhcvLLRxXF3eyF^6s)2mCf!ouHhY z?Ce$&|7T@GGI7;Tl`C?rZnRBaV1gAzV9k7#EE6wxc4t`eO)lJt>tk-5YttBUvg&N^ zjk*x&P{#>J^9az=NBR;{w%)kS^UXIsU2j5#ezA{r;T?1Hf@ z{5PpzFrhb3I5%ZPzRb68MMPVTom}}c*etkZRM?2N&D#i@1mRm0Nwbv#^Zi|SHAMWT zGYDn_%6BirGL<>mEGNiRf@0Wi_`>vIx!zHo1u-YHiN2|-r+7%SA*0B`o__ZHkO z#LxYE~|tGbKOZf z&uimn+H{YP_x+WXd&U-UPO-yyp$l=7Kn2P}Rgc`c-RZ8s!4w6RRb;b+q*4^(WOLpD zj-u`+9?uJU1% zkHqe*a)1b)4s0vXp_oysA5PIu+HgbR(GI>QK-!49^4>EP27cPN`<026U*Bx;1+yGheNop{+c-MgBydCWT1 ze8#)XyG@^unc)>a>zy1o_?QSN>7cyIOt-#=GQ-8K1=bm;I$`C=jQ%?ZuP(K)*{284b&ZImMItNkm*Hv|f9F z0j3vKhjO!6aFnkC=9a$3QR7QG8sv^it&Xir>PLuBc5OnaZTE*gOsPBVah-lu+Cf`- z^adUqhK$wbhE_q07Y9U1u!8oyP4%<&fQ_$UawmYm7Sn9#+hrZ6w2PM`GoOT=lF6#$ktGOZ~Sn~Y4adK#AX zrOd}gfDQ#fw<^wNr~UTUIiEU*A}^c7>aE8zZ|bH7{lz&i(x*$D$d29VW~=AbDwqa2 zHhaUW4L^q+E!w@kn%e8!=Wa!c18>}!x3h&){1!BzjPI2++luCx3T&ap3_niUqT5D# zD#V03%5H6=^s{tFyigl0c2ci&NhMy6`~ZLF1W!*jvgbZ?>-@_9EhqTpe{zD(9@Zv* zB?AvNb?nyYk>5an!NqTD-EAt8lyR;}d#&P*6?SIq_^S-ZV9yKqy!ZiNnilKOp5t(Lx_t)BMmEx9rQ*gdp0$Wmh%zJjbURRgTb4%Ykw zjVpyJ0`C}IN{XOn1<;A201Lv%FGdJi?-qh-5z!ujuuOLlW+=YL9P?a&AUIpq3P4C0 zogRX1+!gNG&R%d28uyN0_k}x%8zL-roAApnJRzS5)OO#&3}n2!rZ5^5t6+jO1c_sE z4bXQSds3tEV`x$w`^N9~mp=zRMU^L&q-2ntZE}@psTnfad+szxbHAIC4;1fbvXM4cExvAWp`49gqT9_=i7|8B4yY;uE}VlzY0`*wEobjn-n)6)=y$M| zV2?r;yZafu&13a(Ov#4FIB&;FlP@NgcO6saNhLP(aaJ@Qr6^3hhUI%kZKfgkRRI>g zjTEqj$w9*4Bdt&tTx>84Iyzb>!HV|6bIXk+u~4w`JS;2}9_sc%r&-8R-|knDb5z*B zHDOd)L|8f)GpWjPn8pfepeYC4G8DyP9=O#&D%pTG!cfm2|5^Y%(=P$m)Kp%z*D;Qq zfu2W07IAhdnL~%J{mP(%%Vw7`+QebxJ#Jh5`U1fpWs%#i7e>b z{4*9UJr#XGScEj#QIMu>l$9!(Z(E|^qe|^>^fsN-c~wpgP1r2MikHe_4;`dknk;Wj zTZCG9RqNhz4;j_PnH9xP2>oi+oaHFe-XE1Iv-nTUtJ@W(Xf7>WHpwxYV-J1$bdLt= z;9mDkA&v7{)){T-mSd?XKJ8_50wqbx21gT#$^iito+w#;6f7o-5lymX+c3N#sZ%69 zi%DOn*iG5^gme2S0`IiUo*CL_b;q_jWjL`GDt875+Sa8@4#?6)xr&PMT&9L8NXT1k zI|7b|*Izrfh(WVAt|(*_<38G@5|J0O_GGgf&(&2)Bm&NVp=&h@>TW`=E|^N*%SeL1 z&afF#)R008k{A?UF1o-4bLg0|@km>_!mYeP|DMHDd=2)IpP7AW>i;&2;r=m;f5V7| zc6L^Om3t&eeU^K`hse!v32y}&2~&lMuP7&fZfm$bD@caBV#b-w8(oQU!ui(u-(R!r zcvnAcw=uJscLE(*$*j{8Kq~DOSCwSDy0=iV5`spm4%ReAMp1xP(d63kj;Fp-PeoUu&ejUN2iIJ>u zzw9KHPE3q!UhPqr#?+Asd8;2fxp5p{y6eEhb&_g!d!+Gt#|tk?s^u$X@$-&+*3_0J zW`bjS8-b5Ej93pX8T@`6YH%@!!H^aWbyoV7I0Z}F*VPQBemxpIe3mqP8d>&L;5($^ zNm#r*CG;)MInWR2?;_hFF*1>V6^zJ?*RHYG!Af8Cp$6BCQp{c*f;L{tL+u(3fqS6eswrzB4mj{?uT^OKYZ#2FVMNBK2 z?%U6_LLl&LGZTEtP%l;$wTWpHX{8(&OFzPp*E5djREc|R0&+kndMD#zmNBzOo3P-Q zQ==rlp?3TxOOp_w=fPSL(9f@HrcN^&XL0fTg)FSTOQl=4Ve2&LF&dH1U6!HQu9uq{ z@(yE;flK%WIK2{2nYVawBoa@}?=SE>1*2E#?9(5uK>uyjLVs%f!(IOaatpu)(8Ghy ze1hDjkBasNQF%2Cq}E?>zcmRF@RwLr^j%&mMnk72KHvEkSEhOx!rdF-Q+*q8)Hg>t z?ZpIKe(c}@fL73?A*&`k0>eoxXyrE%HM zQSl1&PH>CDV#|Ral=0ZJ4RZXuE$)mU-Z+iyqBSP7Zv0SpbW6YCcg5?Zg zGC{ssA?&EYk%{IQge9m{65IYwm2$7yy+@K3EJG%*;TNmS$WY(zqVFIB=FtHYUBrpu zdltj`cJ(^rdvzuwAii;~%2gOH;b3`UVoW(^J4D=WK(s#L0Ib4@4Ie(DA`$&4)OrJ-`5gkOBTx5cg+&*YAS3f2aSgJnrvS{#?KD?^dKf+x@ii$C{0QRfhfD%AYGB zey{cT7uBNt-O7Jl`SEvyed>wW8rT=_+Nzk8NvP&Jp6YH zf8LUQKR*787-|1u;r~BO{{7THBkAvZ*1zZ{!~b{3Kks0FKl#s~@;li6i@cfsI{AMI zy??j!=ZyUwg8oI{ng1;y{r$v02i?E>KAi;s;6F@nc_~n^-`1f%Um2gF5RUEl(f Date: Thu, 20 Apr 2023 10:30:17 +0100 Subject: [PATCH 18/23] Enable bulk upload lettings feature toggle (#1572) --- app/services/feature_toggle.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/feature_toggle.rb b/app/services/feature_toggle.rb index e7ca0ef51..a25d99b84 100644 --- a/app/services/feature_toggle.rb +++ b/app/services/feature_toggle.rb @@ -33,7 +33,7 @@ class FeatureToggle end def self.bulk_upload_lettings_logs? - !Rails.env.production? + true end def self.bulk_upload_sales_logs? From 225a89df37169ef8af11265b48b72902dedd3668 Mon Sep 17 00:00:00 2001 From: Phil Lee Date: Thu, 20 Apr 2023 11:43:53 +0100 Subject: [PATCH 19/23] CLDC-2233 Link to correct bulk upload csv (#1573) # Context - https://digital.dclg.gov.uk/jira/browse/CLDC-2233 # Changes - Rework to link to correct 23/24 legacy bulk upload template --- app/models/forms/bulk_upload_lettings/prepare_your_file.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/models/forms/bulk_upload_lettings/prepare_your_file.rb b/app/models/forms/bulk_upload_lettings/prepare_your_file.rb index c30cf88ad..b5d28a7a2 100644 --- a/app/models/forms/bulk_upload_lettings/prepare_your_file.rb +++ b/app/models/forms/bulk_upload_lettings/prepare_your_file.rb @@ -26,7 +26,12 @@ module Forms end def old_template_path - "/files/bulk-upload-lettings-template-2022-23.xlsx" + case year + when 2022 + "/files/bulk-upload-lettings-template-2022-23.xlsx" + when 2023 + "/files/bulk-upload-lettings-legacy-template-2023-24.xlsx" + end end def template_path From 612abef3182c2db5d407a1d8ce216f0b2c08886c Mon Sep 17 00:00:00 2001 From: Phil Lee Date: Thu, 20 Apr 2023 17:14:53 +0100 Subject: [PATCH 20/23] CLDC-2302 Rescue bulk upload date parsing errors (#1576) --- .../lettings/year2022/row_parser.rb | 4 +++ .../lettings/year2023/row_parser.rb | 4 +++ .../lettings/year2022/row_parser_spec.rb | 32 +++++++++++++++---- .../lettings/year2023/row_parser_spec.rb | 32 +++++++++++++++---- 4 files changed, 60 insertions(+), 12 deletions(-) diff --git a/app/services/bulk_upload/lettings/year2022/row_parser.rb b/app/services/bulk_upload/lettings/year2022/row_parser.rb index 4033c6167..e7f212b55 100644 --- a/app/services/bulk_upload/lettings/year2022/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2022/row_parser.rb @@ -1093,6 +1093,8 @@ private def voiddate Date.new(field_91 + 2000, field_90, field_89) if field_91.present? && field_90.present? && field_89.present? + rescue Date::Error + Date.new end def majorrepairs @@ -1101,6 +1103,8 @@ private def mrcdate Date.new(field_94 + 2000, field_93, field_92) if field_94.present? && field_93.present? && field_92.present? + rescue Date::Error + Date.new end def prevloc diff --git a/app/services/bulk_upload/lettings/year2023/row_parser.rb b/app/services/bulk_upload/lettings/year2023/row_parser.rb index 31205992f..84f30cc7b 100644 --- a/app/services/bulk_upload/lettings/year2023/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2023/row_parser.rb @@ -1317,10 +1317,14 @@ private def mrcdate Date.new(field_38 + 2000, field_37, field_36) if field_38.present? && field_37.present? && field_36.present? + rescue Date::Error + Date.new end def voiddate Date.new(field_35 + 2000, field_34, field_33) if field_35.present? && field_34.present? && field_33.present? + rescue Date::Error + Date.new end def first_time_property_let_as_social_housing diff --git a/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb index b6769becb..7b4f30c85 100644 --- a/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb @@ -1346,10 +1346,20 @@ RSpec.describe BulkUpload::Lettings::Year2022::RowParser do end describe "#mrcdate" do - let(:attributes) { { bulk_upload:, field_92: "13", field_93: "12", field_94: "22" } } + context "when valid" do + let(:attributes) { { bulk_upload:, field_92: "13", field_93: "12", field_94: "22" } } - it "sets value given" do - expect(parser.log.mrcdate).to eq(Date.new(2022, 12, 13)) + it "sets value given" do + expect(parser.log.mrcdate).to eq(Date.new(2022, 12, 13)) + end + end + + context "when invalid" do + let(:attributes) { { bulk_upload:, field_92: "13", field_93: "13", field_94: "22" } } + + it "does not raise an error" do + expect { parser.log.mrcdate }.not_to raise_error + end end end @@ -1372,10 +1382,20 @@ RSpec.describe BulkUpload::Lettings::Year2022::RowParser do end describe "#voiddate" do - let(:attributes) { { bulk_upload:, field_89: "13", field_90: "12", field_91: "22" } } + context "when valid" do + let(:attributes) { { bulk_upload:, field_89: "13", field_90: "12", field_91: "22" } } - it "sets value given" do - expect(parser.log.voiddate).to eq(Date.new(2022, 12, 13)) + it "sets value given" do + expect(parser.log.voiddate).to eq(Date.new(2022, 12, 13)) + end + end + + context "when invalid" do + let(:attributes) { { bulk_upload:, field_89: "13", field_90: "13", field_91: "22" } } + + it "does not raise an error" do + expect { parser.log.voiddate }.not_to raise_error + end end end diff --git a/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb index ff1e00c4c..a94eaac64 100644 --- a/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb @@ -1356,10 +1356,20 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do end describe "#mrcdate" do - let(:attributes) { { bulk_upload:, field_36: "13", field_37: "12", field_38: "22" } } + context "when valid" do + let(:attributes) { { bulk_upload:, field_36: "13", field_37: "12", field_38: "22" } } - it "sets value given" do - expect(parser.log.mrcdate).to eq(Date.new(2022, 12, 13)) + it "sets value given" do + expect(parser.log.mrcdate).to eq(Date.new(2022, 12, 13)) + end + end + + context "when invalid" do + let(:attributes) { { bulk_upload:, field_36: "13", field_37: "13", field_38: "22" } } + + it "does not raise an error" do + expect { parser.log.mrcdate }.not_to raise_error + end end end @@ -1382,10 +1392,20 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do end describe "#voiddate" do - let(:attributes) { { bulk_upload:, field_33: "13", field_34: "12", field_35: "22" } } + context "when valid" do + let(:attributes) { { bulk_upload:, field_33: "13", field_34: "12", field_35: "22" } } - it "sets value given" do - expect(parser.log.voiddate).to eq(Date.new(2022, 12, 13)) + it "sets value given" do + expect(parser.log.voiddate).to eq(Date.new(2022, 12, 13)) + end + end + + context "when invalid" do + let(:attributes) { { bulk_upload:, field_33: "13", field_34: "13", field_35: "22" } } + + it "does not raise an error" do + expect { parser.log.voiddate }.not_to raise_error + end end end From 720f7ca0e2b0a53f70d2b70a2e16fb4e46beba3b Mon Sep 17 00:00:00 2001 From: Arthur Campbell <51094020+arfacamble@users.noreply.github.com> Date: Fri, 21 Apr 2023 12:46:43 +0100 Subject: [PATCH 21/23] CLDC-1945 numeric questions decimal letter entry (#1558) * rename variables in test file * write tests for new validations * validate that answers to numeric questions are given to the correct level of accuracy and in normal format with only digits * add error messages for new validations * add or alter step on numeric questions in sales, amend one test given step changes * copy change * remove validation that is specific to number format and fold that functionality into accuracy validation, rejig tests accordingly * fix various tests * remove reference to currency in validation message that concerns step only and doesn't check whether firled is currency related * alter copy to avoid redundant repetition in validaiton message * save changes made in rebase * changes after PO review two step values had been missed in previous work in various places there were custom validations applied that duplicated the functionality of the generic min max validation that is applied to all numeric questions, in these situations sometimes the min and max on the question class was inconsistent with the limit that triggered validations these have been corrected and made consistent various tests were affected by this and have been amended accordingly * remove tests introduced by mistake in rebase * amend step value on earnings --- app/models/form/lettings/questions/beds.rb | 4 +- .../form/lettings/questions/earnings.rb | 2 +- app/models/form/lettings/questions/offered.rb | 2 +- .../lettings/questions/offered_social_let.rb | 2 +- app/models/form/sales/questions/age1.rb | 1 + .../form/sales/questions/deposit_amount.rb | 3 +- .../form/sales/questions/deposit_discount.rb | 1 + app/models/form/sales/questions/discount.rb | 1 + app/models/form/sales/questions/equity.rb | 1 + app/models/form/sales/questions/grant.rb | 1 + .../form/sales/questions/leasehold_charges.rb | 1 + .../form/sales/questions/monthly_rent.rb | 1 + .../form/sales/questions/mortgage_amount.rb | 1 + .../form/sales/questions/mortgage_length.rb | 1 + .../questions/number_of_others_in_property.rb | 1 + .../form/sales/questions/previous_bedrooms.rb | 1 + .../questions/property_number_of_bedrooms.rb | 1 + .../form/sales/questions/purchase_price.rb | 1 + app/models/form/sales/questions/savings.rb | 4 +- .../form/sales/questions/staircase_bought.rb | 1 + .../form/sales/questions/staircase_owned.rb | 1 + app/models/form/sales/questions/value.rb | 1 + .../validations/property_validations.rb | 26 --- .../sales/sale_information_validations.rb | 2 +- app/models/validations/shared_validations.rb | 21 +++ .../imports/lettings_logs_import_service.rb | 4 +- config/forms/2021_2022.json | 8 +- config/forms/2022_2023.json | 8 +- config/locales/en.yml | 9 +- spec/fixtures/forms/2021_2022.json | 26 +-- .../questions/offered_social_let_spec.rb | 8 +- .../form/sales/questions/savings_spec.rb | 4 +- spec/models/lettings_log_spec.rb | 8 +- spec/models/sales_log_spec.rb | 4 +- .../validations/property_validations_spec.rb | 49 ------ .../validations/shared_validations_spec.rb | 158 ++++++++++++------ .../requests/lettings_logs_controller_spec.rb | 2 +- .../lettings_logs_import_service_spec.rb | 4 +- 38 files changed, 199 insertions(+), 175 deletions(-) diff --git a/app/models/form/lettings/questions/beds.rb b/app/models/form/lettings/questions/beds.rb index 75d2a835d..6fa6c7c2b 100644 --- a/app/models/form/lettings/questions/beds.rb +++ b/app/models/form/lettings/questions/beds.rb @@ -7,8 +7,8 @@ class Form::Lettings::Questions::Beds < ::Form::Question @type = "numeric" @width = 2 @check_answers_card_number = 0 - @max = 150 - @min = 0 + @max = 12 + @min = 1 @hint_text = "If shared accommodation, enter the number of bedrooms occupied by this household. A bedsit has 1 bedroom." @step = 1 @question_number = 22 diff --git a/app/models/form/lettings/questions/earnings.rb b/app/models/form/lettings/questions/earnings.rb index 445710f09..fdf702740 100644 --- a/app/models/form/lettings/questions/earnings.rb +++ b/app/models/form/lettings/questions/earnings.rb @@ -10,7 +10,7 @@ class Form::Lettings::Questions::Earnings < ::Form::Question @min = 0 @guidance_partial = "what_counts_as_income" @hint_text = "" - @step = 1 + @step = 0.01 @prefix = "£" @suffix = [ { "label" => " every week", "depends_on" => { "incfreq" => 1 } }, diff --git a/app/models/form/lettings/questions/offered.rb b/app/models/form/lettings/questions/offered.rb index e41def301..2c9828d31 100644 --- a/app/models/form/lettings/questions/offered.rb +++ b/app/models/form/lettings/questions/offered.rb @@ -7,7 +7,7 @@ class Form::Lettings::Questions::Offered < ::Form::Question @type = "numeric" @width = 2 @check_answers_card_number = 0 - @max = 150 + @max = 20 @min = 0 @hint_text = I18n.t("hints.offered") @step = 1 diff --git a/app/models/form/lettings/questions/offered_social_let.rb b/app/models/form/lettings/questions/offered_social_let.rb index d3ac29516..dd57acbfe 100644 --- a/app/models/form/lettings/questions/offered_social_let.rb +++ b/app/models/form/lettings/questions/offered_social_let.rb @@ -7,7 +7,7 @@ class Form::Lettings::Questions::OfferedSocialLet < ::Form::Question @type = "numeric" @width = 2 @check_answers_card_number = 0 - @max = 150 + @max = 20 @min = 0 @hint_text = I18n.t("hints.offered") @step = 1 diff --git a/app/models/form/sales/questions/age1.rb b/app/models/form/sales/questions/age1.rb index d32083b53..0ae3bf682 100644 --- a/app/models/form/sales/questions/age1.rb +++ b/app/models/form/sales/questions/age1.rb @@ -19,6 +19,7 @@ class Form::Sales::Questions::Age1 < ::Form::Question @check_answers_card_number = 1 @min = 16 @max = 110 + @step = 1 @question_number = 20 end end diff --git a/app/models/form/sales/questions/deposit_amount.rb b/app/models/form/sales/questions/deposit_amount.rb index fb6c0e563..784bb56a1 100644 --- a/app/models/form/sales/questions/deposit_amount.rb +++ b/app/models/form/sales/questions/deposit_amount.rb @@ -6,8 +6,9 @@ class Form::Sales::Questions::DepositAmount < ::Form::Question @header = "How much cash deposit was paid on the property?" @type = "numeric" @min = 0 - @width = 5 @max = 999_999 + @step = 1 + @width = 5 @prefix = "£" @hint_text = "Enter the total cash sum paid by the buyer towards the property that was not funded by the mortgage" @derived = true diff --git a/app/models/form/sales/questions/deposit_discount.rb b/app/models/form/sales/questions/deposit_discount.rb index 577da5a91..ae6521392 100644 --- a/app/models/form/sales/questions/deposit_discount.rb +++ b/app/models/form/sales/questions/deposit_discount.rb @@ -7,6 +7,7 @@ class Form::Sales::Questions::DepositDiscount < ::Form::Question @type = "numeric" @min = 0 @max = 999_999 + @step = 1 @width = 5 @prefix = "£" @hint_text = "Enter the total cash discount given on the property being purchased through the Social HomeBuy scheme" diff --git a/app/models/form/sales/questions/discount.rb b/app/models/form/sales/questions/discount.rb index 8c5cf7132..ee39b8916 100644 --- a/app/models/form/sales/questions/discount.rb +++ b/app/models/form/sales/questions/discount.rb @@ -7,6 +7,7 @@ class Form::Sales::Questions::Discount < ::Form::Question @type = "numeric" @min = 0 @max = 100 + @step = 1 @width = 5 @suffix = "%" @hint_text = "For Right to Buy (RTB), Preserved Right to Buy (PRTB), and Voluntary Right to Buy (VRTB)

diff --git a/app/models/form/sales/questions/equity.rb b/app/models/form/sales/questions/equity.rb index 0119fea69..4db09a31f 100644 --- a/app/models/form/sales/questions/equity.rb +++ b/app/models/form/sales/questions/equity.rb @@ -7,6 +7,7 @@ class Form::Sales::Questions::Equity < ::Form::Question @type = "numeric" @min = 0 @max = 100 + @step = 1 @width = 5 @suffix = "%" @hint_text = "Enter the amount of initial equity held by the purchaser (for example, 25% or 50%)" diff --git a/app/models/form/sales/questions/grant.rb b/app/models/form/sales/questions/grant.rb index 9b0fd3091..e113be536 100644 --- a/app/models/form/sales/questions/grant.rb +++ b/app/models/form/sales/questions/grant.rb @@ -7,6 +7,7 @@ class Form::Sales::Questions::Grant < ::Form::Question @type = "numeric" @min = 0 @max = 999_999 + @step = 1 @width = 5 @prefix = "£" @hint_text = "For all schemes except Right to Buy (RTB), Preserved Right to Buy (PRTB), Voluntary Right to Buy (VRTB) and Rent to Buy" diff --git a/app/models/form/sales/questions/leasehold_charges.rb b/app/models/form/sales/questions/leasehold_charges.rb index 22ed7246e..2b2afb3e4 100644 --- a/app/models/form/sales/questions/leasehold_charges.rb +++ b/app/models/form/sales/questions/leasehold_charges.rb @@ -6,6 +6,7 @@ class Form::Sales::Questions::LeaseholdCharges < ::Form::Question @header = "Enter the total monthly charge" @type = "numeric" @min = 1 + @step = 0.01 @width = 5 @prefix = "£" @ownershipsch = ownershipsch diff --git a/app/models/form/sales/questions/monthly_rent.rb b/app/models/form/sales/questions/monthly_rent.rb index 2a28bc691..75c4a7ce5 100644 --- a/app/models/form/sales/questions/monthly_rent.rb +++ b/app/models/form/sales/questions/monthly_rent.rb @@ -6,6 +6,7 @@ class Form::Sales::Questions::MonthlyRent < ::Form::Question @header = "What is the basic monthly rent?" @type = "numeric" @min = 0 + @step = 0.01 @width = 5 @prefix = "£" @hint_text = "Amount paid before any charges" diff --git a/app/models/form/sales/questions/mortgage_amount.rb b/app/models/form/sales/questions/mortgage_amount.rb index 9b199b2aa..ce4b548e1 100644 --- a/app/models/form/sales/questions/mortgage_amount.rb +++ b/app/models/form/sales/questions/mortgage_amount.rb @@ -6,6 +6,7 @@ class Form::Sales::Questions::MortgageAmount < ::Form::Question @header = "What is the mortgage amount?" @type = "numeric" @min = 1 + @step = 1 @width = 5 @prefix = "£" @hint_text = "Enter the amount of mortgage agreed with the mortgage lender. Exclude any deposits or cash payments. Numeric in pounds. Rounded to the nearest pound." diff --git a/app/models/form/sales/questions/mortgage_length.rb b/app/models/form/sales/questions/mortgage_length.rb index adaa94d01..218ea03a1 100644 --- a/app/models/form/sales/questions/mortgage_length.rb +++ b/app/models/form/sales/questions/mortgage_length.rb @@ -7,6 +7,7 @@ class Form::Sales::Questions::MortgageLength < ::Form::Question @type = "numeric" @min = 0 @max = 60 + @step = 1 @width = 5 @suffix = " years" @hint_text = "You should round up to the nearest year. Value should not exceed 60 years." diff --git a/app/models/form/sales/questions/number_of_others_in_property.rb b/app/models/form/sales/questions/number_of_others_in_property.rb index 556ddf837..450b12eb0 100644 --- a/app/models/form/sales/questions/number_of_others_in_property.rb +++ b/app/models/form/sales/questions/number_of_others_in_property.rb @@ -9,6 +9,7 @@ class Form::Sales::Questions::NumberOfOthersInProperty < ::Form::Question @width = 2 @min = 0 @max = 15 + @step = 1 @question_number = 35 end diff --git a/app/models/form/sales/questions/previous_bedrooms.rb b/app/models/form/sales/questions/previous_bedrooms.rb index d9f9aaddb..8f36a8485 100644 --- a/app/models/form/sales/questions/previous_bedrooms.rb +++ b/app/models/form/sales/questions/previous_bedrooms.rb @@ -8,6 +8,7 @@ class Form::Sales::Questions::PreviousBedrooms < ::Form::Question @width = 5 @min = 1 @max = 6 + @step = 1 @hint_text = "For bedsits enter 1" @question_number = 85 end diff --git a/app/models/form/sales/questions/property_number_of_bedrooms.rb b/app/models/form/sales/questions/property_number_of_bedrooms.rb index 9dbdbf3e8..b37253244 100644 --- a/app/models/form/sales/questions/property_number_of_bedrooms.rb +++ b/app/models/form/sales/questions/property_number_of_bedrooms.rb @@ -9,6 +9,7 @@ class Form::Sales::Questions::PropertyNumberOfBedrooms < ::Form::Question @width = 10 @min = 1 @max = 9 + @step = 1 @question_number = 11 end end diff --git a/app/models/form/sales/questions/purchase_price.rb b/app/models/form/sales/questions/purchase_price.rb index ce9b58af6..62e42f27b 100644 --- a/app/models/form/sales/questions/purchase_price.rb +++ b/app/models/form/sales/questions/purchase_price.rb @@ -6,6 +6,7 @@ class Form::Sales::Questions::PurchasePrice < ::Form::Question @header = "What is the full purchase price?" @type = "numeric" @min = 0 + @step = 0.01 @width = 5 @prefix = "£" @hint_text = hint_text diff --git a/app/models/form/sales/questions/savings.rb b/app/models/form/sales/questions/savings.rb index 086a0ec8d..6c01ed2fd 100644 --- a/app/models/form/sales/questions/savings.rb +++ b/app/models/form/sales/questions/savings.rb @@ -2,12 +2,12 @@ class Form::Sales::Questions::Savings < ::Form::Question def initialize(id, hsh, page) super @id = "savings" - @check_answer_label = "Buyer’s total savings (to nearest £10) before any deposit paid" + @check_answer_label = "Buyer’s total savings before any deposit paid" @header = "Enter their total savings to the nearest £10" @type = "numeric" @width = 5 @prefix = "£" - @step = 1 + @step = 10 @min = 0 @question_number = 72 end diff --git a/app/models/form/sales/questions/staircase_bought.rb b/app/models/form/sales/questions/staircase_bought.rb index 9e54e92d3..dfdb273f5 100644 --- a/app/models/form/sales/questions/staircase_bought.rb +++ b/app/models/form/sales/questions/staircase_bought.rb @@ -8,6 +8,7 @@ class Form::Sales::Questions::StaircaseBought < ::Form::Question @width = 5 @min = 0 @max = 100 + @step = 1 @suffix = "%" @question_number = 77 end diff --git a/app/models/form/sales/questions/staircase_owned.rb b/app/models/form/sales/questions/staircase_owned.rb index b8d5a65ec..2b4d89861 100644 --- a/app/models/form/sales/questions/staircase_owned.rb +++ b/app/models/form/sales/questions/staircase_owned.rb @@ -8,6 +8,7 @@ class Form::Sales::Questions::StaircaseOwned < ::Form::Question @width = 5 @min = 0 @max = 100 + @step = 1 @suffix = "%" @question_number = 78 end diff --git a/app/models/form/sales/questions/value.rb b/app/models/form/sales/questions/value.rb index 4736f37af..d349a99df 100644 --- a/app/models/form/sales/questions/value.rb +++ b/app/models/form/sales/questions/value.rb @@ -6,6 +6,7 @@ class Form::Sales::Questions::Value < ::Form::Question @header = "What was the full purchase price?" @type = "numeric" @min = 0 + @step = 1 @width = 5 @prefix = "£" @hint_text = "Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser)" diff --git a/app/models/validations/property_validations.rb b/app/models/validations/property_validations.rb index d2b6d2a0b..e9680facf 100644 --- a/app/models/validations/property_validations.rb +++ b/app/models/validations/property_validations.rb @@ -2,24 +2,6 @@ module Validations::PropertyValidations # Validations methods need to be called 'validate_' to run on model save # or 'validate_' to run on submit as well - def validate_property_number_of_times_relet(record) - return unless record.offered - - # Since offered is an integer type ActiveRecord will automatically cast that for us - # but it's type casting is a little lax so "random" becomes 0. To make sure that doesn't pass - # validation and then get silently dropped we attempt strict type casting on the original value - # as part of our validation. - begin - Integer(record.offered_before_type_cast) - rescue ArgumentError - record.errors.add :offered, I18n.t("validations.property.offered.relet_number") - end - - if record.offered.negative? || record.offered > 20 - record.errors.add :offered, :over_20, message: I18n.t("validations.property.offered.relet_number") - end - end - REFERRAL_INVALID_TMP = [8, 10, 12, 13, 14, 15].freeze def validate_rsnvac(record) if !record.first_time_property_let_as_social_housing? && record.has_first_let_vacancy_reason? @@ -51,10 +33,6 @@ module Validations::PropertyValidations end def validate_shared_housing_rooms(record) - if record.beds.present? && record.beds <= 0 - record.errors.add :beds, I18n.t("validations.property.beds.non_positive") - end - unless record.unittype_gn.nil? if record.is_bedsit? && record.beds != 1 && record.beds.present? record.errors.add :unittype_gn, I18n.t("validations.property.unittype_gn.one_bedroom_bedsit") @@ -70,10 +48,6 @@ module Validations::PropertyValidations record.errors.add :beds, I18n.t("validations.property.unittype_gn.one_seven_bedroom_shared") end end - - if record.beds.present? && record.beds > 12 - record.errors.add :beds, :over_max, message: I18n.t("validations.property.beds.over_max") - end end def validate_uprn(record) diff --git a/app/models/validations/sales/sale_information_validations.rb b/app/models/validations/sales/sale_information_validations.rb index 42dbde2a5..bf3c2af86 100644 --- a/app/models/validations/sales/sale_information_validations.rb +++ b/app/models/validations/sales/sale_information_validations.rb @@ -12,7 +12,7 @@ module Validations::Sales::SaleInformationValidations end def validate_years_living_in_property_before_purchase(record) - return unless record.proplen && record.proplen.nonzero? + return unless record.proplen&.nonzero? case record.type when 18 diff --git a/app/models/validations/shared_validations.rb b/app/models/validations/shared_validations.rb index f032a089c..6a32563a7 100644 --- a/app/models/validations/shared_validations.rb +++ b/app/models/validations/shared_validations.rb @@ -35,6 +35,27 @@ module Validations::SharedValidations end end + def validate_numeric_step(record) + record.form.numeric_questions.each do |question| + next unless question.step + next unless record[question.id] && question.page.routed_to?(record, nil) + + value = record.public_send("#{question.id}_before_type_cast") + field = question.check_answer_label || question.id + incorrect_accuracy = (value.to_d * 100) % (question.step * 100) != 0 + + if question.step < 1 && incorrect_accuracy + record.errors.add question.id.to_sym, I18n.t("validations.numeric.nearest_hundredth", field:) + elsif incorrect_accuracy || value.to_d != value.to_i # if the user enters a value in exponent notation (eg '4e1') the to_i method does not convert this to the correct value + field = question.check_answer_label || question.id + case question.step + when 1 then record.errors.add question.id.to_sym, I18n.t("validations.numeric.whole_number", field:) + when 10 then record.errors.add question.id.to_sym, I18n.t("validations.numeric.nearest_ten", field:) + end + end + end + end + def validate_property_postcode(record) postcode = record.postcode_full if record.postcode_known? && (postcode.blank? || !postcode.match(POSTCODE_REGEXP)) diff --git a/app/services/imports/lettings_logs_import_service.rb b/app/services/imports/lettings_logs_import_service.rb index a16dc7acd..1bc98925a 100644 --- a/app/services/imports/lettings_logs_import_service.rb +++ b/app/services/imports/lettings_logs_import_service.rb @@ -302,10 +302,10 @@ module Imports %i[prevten over_20_foster_care] => %w[prevten age1], %i[prevten non_temp_accommodation] => %w[prevten rsnvac], %i[joint not_joint_tenancy] => %w[joint], - %i[offered over_20] => %w[offered], + %i[offered outside_the_range] => %w[offered], %i[earnings over_hard_max] => %w[ecstat1], %i[tshortfall no_outstanding_charges] => %w[tshortfall hbrentshortfall], - %i[beds over_max] => %w[beds], + %i[beds outside_the_range] => %w[beds], %i[tcharge complete_1_of_3] => %w[brent scharge pscharge supcharg tcharge], %i[scharge under_min] => %w[brent scharge pscharge supcharg tcharge], %i[tshortfall must_be_positive] => %w[tshortfall tshortfall_known], diff --git a/config/forms/2021_2022.json b/config/forms/2021_2022.json index 3caedd8cb..84065e019 100644 --- a/config/forms/2021_2022.json +++ b/config/forms/2021_2022.json @@ -569,7 +569,7 @@ "hint_text": "This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0.", "type": "numeric", "min": 0, - "max": 150, + "max": 20, "step": 1, "width": 2 } @@ -591,7 +591,7 @@ "hint_text": "If the property is being offered for let for the first time, enter 0.", "type": "numeric", "min": 0, - "max": 150, + "max": 20, "step": 1, "width": 2 } @@ -705,8 +705,8 @@ "header": "How many bedrooms does the property have?", "hint_text": "If shared accommodation, enter the number of bedrooms occupied by this household. A bedsit has 1 bedroom.", "type": "numeric", - "min": 0, - "max": 150, + "min": 1, + "max": 12, "step": 1, "width": 2 } diff --git a/config/forms/2022_2023.json b/config/forms/2022_2023.json index abb9064fe..c3b4e3341 100644 --- a/config/forms/2022_2023.json +++ b/config/forms/2022_2023.json @@ -564,7 +564,7 @@ "hint_text": "This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0.", "type": "numeric", "min": 0, - "max": 150, + "max": 20, "step": 1, "width": 2 } @@ -586,7 +586,7 @@ "hint_text": "If the property is being offered for let for the first time, enter 0.", "type": "numeric", "min": 0, - "max": 150, + "max": 20, "step": 1, "width": 2 } @@ -700,8 +700,8 @@ "header": "How many bedrooms does the property have?", "hint_text": "If shared accommodation, enter the number of bedrooms occupied by this household. A bedsit has 1 bedroom.", "type": "numeric", - "min": 0, - "max": 150, + "min": 1, + "max": 12, "step": 1, "width": 2 } diff --git a/config/locales/en.yml b/config/locales/en.yml index 34dc4651e..a77b286df 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -144,6 +144,11 @@ en: numeric: within_range: "%{field} must be between %{min} and %{max}" above_min: "%{field} must be at least %{min}" + whole_number: "%{field} must be a whole number" + nearest_ten: "%{field} must be given to the nearest ten" + nearest_hundredth: "%{field} must be given to the nearest hundredth" + normal_format: "Enter a number" + date: invalid_date: "Enter a date in the correct format, for example 31 1 2022" outside_collection_window: Enter a date within the 22/23 collection year, which is between 1st April 2022 and 31st March 2023 @@ -213,8 +218,6 @@ en: ten_years_before_tenancy_start: "Enter a void date no more than 10 years before the tenancy start date" before_tenancy_start: "Enter a void date that is before the tenancy start date" after_mrcdate: "Void date must be before the major repairs date if provided" - offered: - relet_number: "Enter a number between 0 and 20 for the amount of times the property has been re-let" la: la_invalid_for_org: "%{org_name} does not operate in %{la_name}" postcode_invalid_for_org: "Enter a postcode in an area covered by %{org_name}" @@ -230,8 +233,6 @@ en: one_seven_bedroom_shared: "A shared house must have 1 to 7 bedrooms" one_three_bedroom_single_tenant_shared: "A shared house with fewer than two tenants must have 1 to 3 bedrooms" beds: - non_positive: "Number of bedrooms has to be greater than 0" - over_max: "Number of bedrooms cannot be more than 12" bedsits_have_max_one_bedroom: "Number of bedrooms must be 1 if the property is a bedsit" proptype: bedsits_have_max_one_bedroom: "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms" diff --git a/spec/fixtures/forms/2021_2022.json b/spec/fixtures/forms/2021_2022.json index 69f23bbd6..139ac8399 100644 --- a/spec/fixtures/forms/2021_2022.json +++ b/spec/fixtures/forms/2021_2022.json @@ -913,7 +913,7 @@ "hint_text": "Eligible for housing benefit or Universal Credit", "type": "numeric", "min": 0, - "step": 1, + "step": 0.01, "width": 4, "fields-to-add": [ "brent", @@ -929,7 +929,7 @@ "hint_text": "Eligible for housing benefit or Universal Credit", "type": "numeric", "min": 0, - "step": 1, + "step": 0.01, "width": 4, "fields-to-add": [ "brent", @@ -945,7 +945,7 @@ "hint_text": "Not eligible for housing benefit or Universal Credit. For example, hot water excluding water rates.", "type": "numeric", "min": 0, - "step": 1, + "step": 0.01, "width": 4, "fields-to-add": [ "brent", @@ -962,7 +962,7 @@ "type": "numeric", "min": 0, "max": 300, - "step": 1, + "step": 0.01, "width": 4, "fields-to-add": [ "brent", @@ -978,7 +978,7 @@ "hint_text": "This is the total of rent and all charges", "type": "numeric_output", "min": 0, - "step": 1, + "step": 0.01, "width": 4, "readonly": true, "requires_js": true @@ -995,7 +995,7 @@ "hint_text": "", "type": "numeric", "min": 0, - "step": "1", + "step": 1, "width": 5, "prefix": "£", "suffix": " every week" @@ -1005,11 +1005,12 @@ "care_home_charge": { "questions": { "offered": { - "check_answer_label": "Basic Rent", - "header": "What is the basic rent?", - "hint_text": "Eligible for housing benefit or Universal Credit", + "check_answer_label": "Times previously offered since becoming available", + "header": "Since becoming available for re-let, how many times has the property been previously offered?", + "hint_text": "This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0.", "type": "numeric", "min": 0, + "max": 20, "step": 1, "width": 4 } @@ -1023,11 +1024,12 @@ "care_home_charge_bi_weekly": { "questions": { "offered": { - "check_answer_label": "Basic Rent", - "header": "What is the basic rent?", - "hint_text": "Eligible for housing benefit or Universal Credit", + "check_answer_label": "Times previously offered since becoming available", + "header": "Since becoming available for re-let, how many times has the property been previously offered?", + "hint_text": "This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0.", "type": "numeric", "min": 0, + "max": 20, "step": 1, "width": 4 } diff --git a/spec/models/form/lettings/questions/offered_social_let_spec.rb b/spec/models/form/lettings/questions/offered_social_let_spec.rb index 6516c661b..ac1930495 100644 --- a/spec/models/form/lettings/questions/offered_social_let_spec.rb +++ b/spec/models/form/lettings/questions/offered_social_let_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Form::Lettings::Questions::OfferedSocialLet, type: :model do let(:page) { instance_double(Form::Page) } it "has correct page" do - expect(question.page).to eq page + expect(question.page).to be page end it "has the correct id" do @@ -26,12 +26,12 @@ RSpec.describe Form::Lettings::Questions::OfferedSocialLet, type: :model do end it "has the correct minimum and maximum values" do - expect(question.min).to eq 0 - expect(question.max).to eq 150 + expect(question.min).to be 0 + expect(question.max).to be 20 end it "has the correct step" do - expect(question.step).to eq 1 + expect(question.step).to be 1 end it "is not marked as derived" do diff --git a/spec/models/form/sales/questions/savings_spec.rb b/spec/models/form/sales/questions/savings_spec.rb index 3f721d7ef..8e9c4daa4 100644 --- a/spec/models/form/sales/questions/savings_spec.rb +++ b/spec/models/form/sales/questions/savings_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Form::Sales::Questions::Savings, type: :model do end it "has the correct check_answer_label" do - expect(question.check_answer_label).to eq("Buyer’s total savings (to nearest £10) before any deposit paid") + expect(question.check_answer_label).to eq("Buyer’s total savings before any deposit paid") end it "has the correct type" do @@ -40,7 +40,7 @@ RSpec.describe Form::Sales::Questions::Savings, type: :model do end it "has correct step" do - expect(question.step).to eq(1) + expect(question.step).to be 10 end it "has correct prefix" do diff --git a/spec/models/lettings_log_spec.rb b/spec/models/lettings_log_spec.rb index fb0f01be2..481f7fbbe 100644 --- a/spec/models/lettings_log_spec.rb +++ b/spec/models/lettings_log_spec.rb @@ -112,10 +112,6 @@ RSpec.describe LettingsLog do expect(validator).to receive(:validate_shared_housing_rooms) end - it "validates number of times the property has been relet" do - expect(validator).to receive(:validate_property_number_of_times_relet) - end - it "validates tenancy type" do expect(validator).to receive(:validate_fixed_term_tenancy) expect(validator).to receive(:validate_other_tenancy_type) @@ -3104,11 +3100,11 @@ RSpec.describe LettingsLog do end context "when a non setup field is invalid" do - subject(:model) { described_class.new(beds: 404) } + subject(:model) { build(:lettings_log, :completed, offered: 234) } it "blanks it" do model.valid? - expect { model.blank_invalid_non_setup_fields! }.to change(model, :beds) + expect { model.blank_invalid_non_setup_fields! }.to change(model, :offered) end end end diff --git a/spec/models/sales_log_spec.rb b/spec/models/sales_log_spec.rb index be03fa0fe..310fb48d1 100644 --- a/spec/models/sales_log_spec.rb +++ b/spec/models/sales_log_spec.rb @@ -509,9 +509,9 @@ RSpec.describe SalesLog, type: :model do let(:completed_sales_log) { create(:sales_log, :completed) } it "returns small numbers correctly formatted as currency" do - completed_sales_log.update!(savings: 4) + completed_sales_log.update!(savings: 20) - expect(completed_sales_log.field_formatted_as_currency("savings")).to eq("£4.00") + expect(completed_sales_log.field_formatted_as_currency("savings")).to eq("£20.00") end it "returns quite large numbers correctly formatted as currency" do diff --git a/spec/models/validations/property_validations_spec.rb b/spec/models/validations/property_validations_spec.rb index 3b3e42e9e..e918e3405 100644 --- a/spec/models/validations/property_validations_spec.rb +++ b/spec/models/validations/property_validations_spec.rb @@ -6,39 +6,6 @@ RSpec.describe Validations::PropertyValidations do let(:property_validator_class) { Class.new { include Validations::PropertyValidations } } let(:record) { FactoryBot.create(:lettings_log) } - describe "#validate_property_number_of_times_relet" do - let(:expected_error) { I18n.t("validations.property.offered.relet_number") } - - it "does not add an error if the record offered is missing" do - record.offered = nil - property_validator.validate_property_number_of_times_relet(record) - expect(record.errors).to be_empty - end - - it "does not add an error if offered is valid (number between 0 and 20)" do - record.offered = 0 - property_validator.validate_property_number_of_times_relet(record) - expect(record.errors).to be_empty - record.offered = 10 - property_validator.validate_property_number_of_times_relet(record) - expect(record.errors).to be_empty - record.offered = 20 - property_validator.validate_property_number_of_times_relet(record) - expect(record.errors).to be_empty - end - - it "does add an error when offered is invalid" do - record.offered = "invalid" - property_validator.validate_property_number_of_times_relet(record) - expect(record.errors).not_to be_empty - expect(record.errors["offered"]).to include(match(expected_error)) - record.offered = 21 - property_validator.validate_property_number_of_times_relet(record) - expect(record.errors).not_to be_empty - expect(record.errors["offered"]).to include(match(expected_error)) - end - end - describe "#validate_shared_housing_rooms" do context "when number of bedrooms has not been answered" do it "does not add an error" do @@ -129,22 +96,6 @@ RSpec.describe Validations::PropertyValidations do expect(record.errors["beds"]).to include(I18n.t("validations.property.unittype_gn.one_three_bedroom_single_tenant_shared")) end end - - context "when a negative number of bedrooms is entered" do - it "adds an error" do - record.beds = -4 - property_validator.validate_shared_housing_rooms(record) - expect(record.errors["beds"]).to include(I18n.t("validations.property.beds.non_positive")) - end - end - - context "when a room number higher than 12 has been entered" do - it "adds an error" do - record.beds = 13 - property_validator.validate_shared_housing_rooms(record) - expect(record.errors["beds"]).to include(I18n.t("validations.property.beds.over_max")) - end - end end describe "#validate_unitletas" do diff --git a/spec/models/validations/shared_validations_spec.rb b/spec/models/validations/shared_validations_spec.rb index 8f5038c5d..fe3612c3c 100644 --- a/spec/models/validations/shared_validations_spec.rb +++ b/spec/models/validations/shared_validations_spec.rb @@ -4,8 +4,8 @@ RSpec.describe Validations::SharedValidations do subject(:shared_validator) { validator_class.new } let(:validator_class) { Class.new { include Validations::SharedValidations } } - let(:record) { FactoryBot.create(:lettings_log) } - let(:sales_record) { FactoryBot.create(:sales_log, :completed) } + let(:lettings_log) { FactoryBot.create(:lettings_log) } + let(:sales_log) { FactoryBot.create(:sales_log, :completed) } let(:fake_2021_2022_form) { Form.new("spec/fixtures/forms/2021_2022.json") } describe "numeric min max validations" do @@ -15,102 +15,164 @@ RSpec.describe Validations::SharedValidations do context "when validating age" do it "validates that person 1's age is a number" do - record.age1 = "random" - shared_validator.validate_numeric_min_max(record) - expect(record.errors["age1"]) + lettings_log.age1 = "random" + shared_validator.validate_numeric_min_max(lettings_log) + expect(lettings_log.errors["age1"]) .to include(match I18n.t("validations.numeric.within_range", field: "Lead tenant’s age", min: 16, max: 120)) end it "validates that other household member ages are a number" do - record.age2 = "random" - shared_validator.validate_numeric_min_max(record) - expect(record.errors["age2"]) + lettings_log.age2 = "random" + shared_validator.validate_numeric_min_max(lettings_log) + expect(lettings_log.errors["age2"]) .to include(match I18n.t("validations.numeric.within_range", field: "Person 2’s age", min: 1, max: 120)) end it "validates that person 1's age is greater than 16" do - record.age1 = 15 - shared_validator.validate_numeric_min_max(record) - expect(record.errors["age1"]) + lettings_log.age1 = 15 + shared_validator.validate_numeric_min_max(lettings_log) + expect(lettings_log.errors["age1"]) .to include(match I18n.t("validations.numeric.within_range", field: "Lead tenant’s age", min: 16, max: 120)) end it "validates that other household member ages are greater than 1" do - record.age2 = 0 - shared_validator.validate_numeric_min_max(record) - expect(record.errors["age2"]) + lettings_log.age2 = 0 + shared_validator.validate_numeric_min_max(lettings_log) + expect(lettings_log.errors["age2"]) .to include(match I18n.t("validations.numeric.within_range", field: "Person 2’s age", min: 1, max: 120)) end it "validates that person 1's age is less than 121" do - record.age1 = 121 - shared_validator.validate_numeric_min_max(record) - expect(record.errors["age1"]) + lettings_log.age1 = 121 + shared_validator.validate_numeric_min_max(lettings_log) + expect(lettings_log.errors["age1"]) .to include(match I18n.t("validations.numeric.within_range", field: "Lead tenant’s age", min: 16, max: 120)) end it "validates that other household member ages are greater than 121" do - record.age2 = 123 - shared_validator.validate_numeric_min_max(record) - expect(record.errors["age2"]) + lettings_log.age2 = 123 + shared_validator.validate_numeric_min_max(lettings_log) + expect(lettings_log.errors["age2"]) .to include(match I18n.t("validations.numeric.within_range", field: "Person 2’s age", min: 1, max: 120)) end it "validates that person 1's age is between 16 and 120" do - record.age1 = 63 - shared_validator.validate_numeric_min_max(record) - expect(record.errors["age1"]).to be_empty + lettings_log.age1 = 63 + shared_validator.validate_numeric_min_max(lettings_log) + expect(lettings_log.errors["age1"]).to be_empty end it "validates that other household member ages are between 1 and 120" do - record.age6 = 45 - shared_validator.validate_numeric_min_max(record) - expect(record.errors["age6"]).to be_empty + lettings_log.age6 = 45 + shared_validator.validate_numeric_min_max(lettings_log) + expect(lettings_log.errors["age6"]).to be_empty end context "with sales log" do it "validates that person 2's age is between 0 and 110 for non joint purchase" do - sales_record.jointpur = 2 - sales_record.hholdcount = 1 - sales_record.details_known_2 = 1 - sales_record.age2 = 130 - shared_validator.validate_numeric_min_max(sales_record) - expect(sales_record.errors["age2"].first).to eq("Person 2’s age must be between 0 and 110") + sales_log.jointpur = 2 + sales_log.hholdcount = 1 + sales_log.details_known_2 = 1 + sales_log.age2 = 130 + shared_validator.validate_numeric_min_max(sales_log) + expect(sales_log.errors["age2"].first).to eq("Person 2’s age must be between 0 and 110") end it "validates that buyer 2's age is between 0 and 110 for joint purchase" do - sales_record.jointpur = 1 - sales_record.age2 = 130 - shared_validator.validate_numeric_min_max(sales_record) - expect(sales_record.errors["age2"].first).to eq("Buyer 2’s age must be between 0 and 110") + sales_log.jointpur = 1 + sales_log.age2 = 130 + shared_validator.validate_numeric_min_max(sales_log) + expect(sales_log.errors["age2"].first).to eq("Buyer 2’s age must be between 0 and 110") end end end it "adds the correct validation text when a question has a min but not a max" do - sales_record.savings = -10 - shared_validator.validate_numeric_min_max(sales_record) - expect(sales_record.errors["savings"]).to include(match I18n.t("validations.numeric.above_min", field: "Buyer’s total savings (to nearest £10) before any deposit paid", min: "£0")) + sales_log.savings = -10 + shared_validator.validate_numeric_min_max(sales_log) + expect(sales_log.errors["savings"]).to include(match I18n.t("validations.numeric.above_min", field: "Buyer’s total savings before any deposit paid", min: "£0")) end context "when validating percent" do it "validates that suffixes are added in the error message" do - sales_record.ownershipsch = 1 - sales_record.staircase = 1 - sales_record.stairbought = 150 - shared_validator.validate_numeric_min_max(sales_record) - expect(sales_record.errors["stairbought"]) + sales_log.ownershipsch = 1 + sales_log.staircase = 1 + sales_log.stairbought = 150 + shared_validator.validate_numeric_min_max(sales_log) + expect(sales_log.errors["stairbought"]) .to include(match I18n.t("validations.numeric.within_range", field: "Percentage bought in this staircasing transaction", min: "0%", max: "100%")) end end context "when validating price" do - it "validates that £ prefix and , is added in the error message" do - sales_record.income1 = -5 - shared_validator.validate_numeric_min_max(sales_record) - expect(sales_record.errors["income1"]) + it "validates that prefix £ and delimeter ',' is added in the error message" do + sales_log.income1 = -5 + shared_validator.validate_numeric_min_max(sales_log) + expect(sales_log.errors["income1"]) .to include(match I18n.t("validations.numeric.within_range", field: "Buyer 1’s gross annual income", min: "£0", max: "£999,999")) end end end + + describe "validating level of accuracy or rounding for numeric questions" do + context "when validating a question with a step of 1" do + it "adds an error if input is a decimal" do + sales_log.income1 = 30_000.5 + shared_validator.validate_numeric_step(sales_log) + expect(sales_log.errors[:income1]).to include I18n.t("validations.numeric.whole_number", field: "Buyer 1’s gross annual income") + end + + it "adds an error if the user attempts to input a number in exponent format" do + sales_log.income1 = "3e5" + shared_validator.validate_numeric_step(sales_log) + expect(sales_log.errors[:income1]).to include I18n.t("validations.numeric.whole_number", field: "Buyer 1’s gross annual income") + end + + it "does not add an error if input is an integer" do + sales_log.income1 = 30_000 + shared_validator.validate_numeric_step(sales_log) + expect(sales_log.errors).to be_empty + end + end + + context "when validating a question with a step of 10" do + it "adds an error if input is not a multiple of ten" do + sales_log.savings = 30_005 + shared_validator.validate_numeric_step(sales_log) + expect(sales_log.errors[:savings]).to include I18n.t("validations.numeric.nearest_ten", field: "Buyer’s total savings before any deposit paid") + end + + it "adds an error if the user attempts to input a number in exponent format" do + sales_log.savings = "3e5" + shared_validator.validate_numeric_step(sales_log) + expect(sales_log.errors[:savings]).to include I18n.t("validations.numeric.nearest_ten", field: "Buyer’s total savings before any deposit paid") + end + + it "does not add an error if input is a multiple of ten" do + sales_log.savings = 30_000 + shared_validator.validate_numeric_step(sales_log) + expect(sales_log.errors).to be_empty + end + end + + context "when validating a question with a step of 0.01" do + it "adds an error if input has more than 2 decimal places" do + sales_log.mscharge = 30.7418 + shared_validator.validate_numeric_step(sales_log) + expect(sales_log.errors[:mscharge]).to include I18n.t("validations.numeric.nearest_hundredth", field: "Monthly leasehold charges") + end + + it "does not add an error if the user attempts to input a number in exponent format" do + sales_log.mscharge = "3e1" + shared_validator.validate_numeric_step(sales_log) + expect(sales_log.errors).to be_empty + end + + it "does not add an error if input has 2 or fewer decimal places" do + sales_log.mscharge = 30.74 + shared_validator.validate_numeric_step(sales_log) + expect(sales_log.errors).to be_empty + end + end + end end diff --git a/spec/requests/lettings_logs_controller_spec.rb b/spec/requests/lettings_logs_controller_spec.rb index d95e0a3fd..6de5c4db2 100644 --- a/spec/requests/lettings_logs_controller_spec.rb +++ b/spec/requests/lettings_logs_controller_spec.rb @@ -82,7 +82,7 @@ RSpec.describe LettingsLogsController, type: :request do it "validates lettings log parameters" do json_response = JSON.parse(response.body) expect(response).to have_http_status(:unprocessable_entity) - expect(json_response["errors"]).to match_array([["offered", [I18n.t("validations.property.offered.relet_number")]], ["age1", [I18n.t("validations.numeric.within_range", field: "Lead tenant’s age", min: 16, max: 120)]]]) + expect(json_response["errors"]).to match_array([["offered", [I18n.t("validations.numeric.within_range", field: "Times previously offered since becoming available", min: 0, max: 20)]], ["age1", [I18n.t("validations.numeric.within_range", field: "Lead tenant’s age", min: 16, max: 120)]]]) end end diff --git a/spec/services/imports/lettings_logs_import_service_spec.rb b/spec/services/imports/lettings_logs_import_service_spec.rb index 92a5fc059..ec41ba366 100644 --- a/spec/services/imports/lettings_logs_import_service_spec.rb +++ b/spec/services/imports/lettings_logs_import_service_spec.rb @@ -434,7 +434,7 @@ RSpec.describe Imports::LettingsLogsImportService do end it "intercepts the relevant validation error" do - expect(logger).to receive(:warn).with(/Removing offered with error: Enter a number between 0 and 20 for the amount of times the property has been re-let/) + expect(logger).to receive(:warn).with(/Removing offered with error: Times previously offered since becoming available must be between 0 and 20/) expect { lettings_log_service.send(:create_log, lettings_log_xml) } .not_to raise_error end @@ -530,7 +530,7 @@ RSpec.describe Imports::LettingsLogsImportService do end it "intercepts the relevant validation error" do - expect(logger).to receive(:warn).with(/Removing beds with error: Number of bedrooms cannot be more than 12/) + expect(logger).to receive(:warn).with(/Removing beds with error: Number of bedrooms must be between 1 and 12/) expect { lettings_log_service.send(:create_log, lettings_log_xml) } .not_to raise_error end From b3620073b98d71e695b2910b6af773247e06057a Mon Sep 17 00:00:00 2001 From: Phil Lee Date: Fri, 21 Apr 2023 12:48:39 +0100 Subject: [PATCH 22/23] Use review env for review apps (#1579) # Context - We are mixing environments for review apps at the moment with staging - As a result of this change bulk upload now works will CSV download # Changes - Created a new environment `dev` to store dev specific variables independent of staging - Review apps now use this particular set of variables rather than the staging set --- .github/workflows/review_pipeline.yml | 8 ++++---- .github/workflows/review_teardown_pipeline.yml | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/review_pipeline.yml b/.github/workflows/review_pipeline.yml index 440dddf5c..d5921e049 100644 --- a/.github/workflows/review_pipeline.yml +++ b/.github/workflows/review_pipeline.yml @@ -18,7 +18,7 @@ jobs: postgres: name: Provision postgres runs-on: ubuntu-latest - environment: staging + environment: review steps: - name: Install Cloud Foundry CLI @@ -44,7 +44,7 @@ jobs: redis: name: Provision redis runs-on: ubuntu-latest - environment: staging + environment: review steps: - name: Install Cloud Foundry CLI @@ -70,7 +70,7 @@ jobs: deploy: name: Deploy review app runs-on: ubuntu-latest - environment: staging + environment: review needs: [postgres, redis] permissions: issues: write @@ -125,7 +125,7 @@ jobs: cf set-env $APP_NAME IMPORT_PAAS_INSTANCE $IMPORT_PAAS_INSTANCE cf set-env $APP_NAME EXPORT_PAAS_INSTANCE "dluhc-core-review-export-bucket" cf set-env $APP_NAME S3_CONFIG $S3_CONFIG - cf set-env $APP_NAME CSV_DOWNLOAD_PAAS_INSTANCE "dluhc-core-staging-csv-bucket" + cf set-env $APP_NAME CSV_DOWNLOAD_PAAS_INSTANCE "dluhc-core-review-csv-bucket" cf set-env $APP_NAME SENTRY_DSN $SENTRY_DSN cf set-env $APP_NAME APP_HOST "https://dluhc-core-review-${{ github.event.pull_request.number }}.london.cloudapps.digital" diff --git a/.github/workflows/review_teardown_pipeline.yml b/.github/workflows/review_teardown_pipeline.yml index 08032596d..3962243c4 100644 --- a/.github/workflows/review_teardown_pipeline.yml +++ b/.github/workflows/review_teardown_pipeline.yml @@ -14,7 +14,7 @@ jobs: app: name: Teardown app runs-on: ubuntu-latest - environment: staging + environment: review steps: - name: Install Cloud Foundry CLI @@ -40,7 +40,7 @@ jobs: postgres: name: Teardown postgres runs-on: ubuntu-latest - environment: staging + environment: review needs: [app] steps: @@ -67,7 +67,7 @@ jobs: redis: name: Teardown redis runs-on: ubuntu-latest - environment: staging + environment: review needs: [app] steps: From 87d4497558f07b4097a858b84232b9e7bd3083c1 Mon Sep 17 00:00:00 2001 From: natdeanlewissoftwire <94526761+natdeanlewissoftwire@users.noreply.github.com> Date: Fri, 21 Apr 2023 14:06:06 +0100 Subject: [PATCH 23/23] CLDC-2199 Fix LA/rent soft validation bugs (#1538) * feat: unrelated currency formatting * Revert "feat: unrelated currency formatting" This reverts commit 75bd3efbf808b245cc3765f8417655a3a3488f69. * feat: add soft val pages to property information section (not to setup) * feat: update tests * feat: update tests * feat: add validation to setpu section * refactor: lint * test: update * feat: set nil as default check answers card no * feat: use : to define more explicitly * feat: make optional as affects setup * feat: update tests * feat: put la in nil card with rest of uprn/address qs * feat: route to uprn confirmation from check answers changes to uprn even though not an interruption screen * feat: update tests * feat: update tests * refactor: lint * feat: update tests * feat: update tests * feat: wip blank fields and dependent fields on upload tos ee if valid and can upload with missing info - this is not the exact ac on the ticket yet * Revert "feat: wip blank fields and dependent fields on upload tos ee if valid and can upload with missing info - this is not the exact ac on the ticket yet" This reverts commit 136e5f6f129905270a97c75dc45fc26b06d0b7e9. * feat: fix uprn hidden in check answers * feat: fix tests * feat: fix tests --- app/controllers/form_controller.rb | 4 +++- .../lettings/pages/max_rent_value_check.rb | 8 ++++---- .../lettings/pages/min_rent_value_check.rb | 20 +++++++++---------- app/models/form/lettings/questions/la.rb | 2 +- .../lettings/questions/rent_value_check.rb | 6 +++--- .../form/lettings/questions/uprn_known.rb | 13 +++++++++++- .../subsections/income_and_benefits.rb | 4 ++-- .../subsections/property_information.rb | 4 ++++ app/models/form/lettings/subsections/setup.rb | 8 ++++++++ app/models/form/sales/questions/uprn_known.rb | 7 ++++++- app/models/lettings_log.rb | 16 ++++++++++++--- app/models/validations/soft_validations.rb | 7 ++++++- .../fixtures/files/lettings_logs_download.csv | 4 ++-- .../lettings_logs_download_codes_only.csv | 4 ++-- .../pages/max_rent_value_check_spec.rb | 2 +- .../pages/min_rent_value_check_spec.rb | 11 ++++++++-- .../lettings/questions/uprn_known_spec.rb | 16 ++++++++++++++- .../subsections/income_and_benefits_spec.rb | 4 ++-- .../subsections/property_information_spec.rb | 8 ++++++++ .../form/lettings/subsections/setup_spec.rb | 16 +++++++++++++++ .../form/sales/questions/uprn_known_spec.rb | 16 ++++++++++++++- spec/models/form_handler_spec.rb | 8 ++++---- spec/models/lettings_log_spec.rb | 2 ++ .../csv/lettings_log_csv_service_spec.rb | 2 +- 24 files changed, 148 insertions(+), 44 deletions(-) diff --git a/app/controllers/form_controller.rb b/app/controllers/form_controller.rb index e7bbf38ec..dbc5ba490 100644 --- a/app/controllers/form_controller.rb +++ b/app/controllers/form_controller.rb @@ -126,7 +126,7 @@ private next_page = form.get_page(next_page_id) previous_page = form.previous_page_id(@page, @log, current_user) - if next_page&.interruption_screen? || next_page_id == previous_page + if next_page&.interruption_screen? || next_page_id == previous_page || CONFIRMATION_PAGE_IDS.include?(next_page_id) return send("#{@log.class.name.underscore}_#{next_page_id}_path", @log, { referrer: "check_answers" }) else return send("#{@log.model_name.param_key}_#{form.subsection_for_page(@page).id}_check_answers_path", @log) @@ -180,4 +180,6 @@ private redirect_to lettings_log_path(@log) unless @log.collection_period_open? end + + CONFIRMATION_PAGE_IDS = %w[uprn_confirmation].freeze end diff --git a/app/models/form/lettings/pages/max_rent_value_check.rb b/app/models/form/lettings/pages/max_rent_value_check.rb index 8ae9b7d9c..956e07f44 100644 --- a/app/models/form/lettings/pages/max_rent_value_check.rb +++ b/app/models/form/lettings/pages/max_rent_value_check.rb @@ -1,7 +1,6 @@ class Form::Lettings::Pages::MaxRentValueCheck < ::Form::Page - def initialize(id, hsh, subsection) - super - @id = "max_rent_value_check" + def initialize(id, hsh, subsection, check_answers_card_number: nil) + super(id, hsh, subsection) @depends_on = [{ "rent_in_soft_max_range?" => true }] @title_text = { "translation" => "soft_validations.rent.outside_range_title", @@ -23,9 +22,10 @@ class Form::Lettings::Pages::MaxRentValueCheck < ::Form::Page }, ], } + @check_answers_card_number = check_answers_card_number end def questions - @questions ||= [Form::Lettings::Questions::RentValueCheck.new(nil, nil, self)] + @questions ||= [Form::Lettings::Questions::RentValueCheck.new(nil, nil, self, check_answers_card_number: @check_answers_card_number)] end end diff --git a/app/models/form/lettings/pages/min_rent_value_check.rb b/app/models/form/lettings/pages/min_rent_value_check.rb index eda8819ee..86b53951c 100644 --- a/app/models/form/lettings/pages/min_rent_value_check.rb +++ b/app/models/form/lettings/pages/min_rent_value_check.rb @@ -1,7 +1,6 @@ class Form::Lettings::Pages::MinRentValueCheck < ::Form::Page - def initialize(id, hsh, subsection) - super - @id = "min_rent_value_check" + def initialize(id, hsh, subsection, check_answers_card_number: nil) + super(id, hsh, subsection) @depends_on = [{ "rent_in_soft_min_range?" => true }] @title_text = { "translation" => "soft_validations.rent.outside_range_title", @@ -13,17 +12,16 @@ class Form::Lettings::Pages::MinRentValueCheck < ::Form::Page } @informative_text = { "translation" => "soft_validations.rent.min_hint_text", - "arguments" => [ - { - "key" => "field_formatted_as_currency", - "arguments_for_key" => "soft_min_for_period", - "i18n_template" => "soft_min_for_period", - }, - ], + "arguments" => [{ + "key" => "field_formatted_as_currency", + "arguments_for_key" => "soft_min_for_period", + "i18n_template" => "soft_min_for_period", + }], } + @check_answers_card_number = check_answers_card_number end def questions - @questions ||= [Form::Lettings::Questions::RentValueCheck.new(nil, nil, self)] + @questions ||= [Form::Lettings::Questions::RentValueCheck.new(nil, nil, self, check_answers_card_number: @check_answers_card_number)] end end diff --git a/app/models/form/lettings/questions/la.rb b/app/models/form/lettings/questions/la.rb index 5c483d786..f9d650918 100644 --- a/app/models/form/lettings/questions/la.rb +++ b/app/models/form/lettings/questions/la.rb @@ -5,7 +5,7 @@ class Form::Lettings::Questions::La < ::Form::Question @check_answer_label = "Local Authority" @header = "What is the property’s local authority?" @type = "select" - @check_answers_card_number = 0 + @check_answers_card_number = nil @hint_text = "" @question_number = 13 @disable_clearing_if_not_routed_or_dynamic_answer_options = true diff --git a/app/models/form/lettings/questions/rent_value_check.rb b/app/models/form/lettings/questions/rent_value_check.rb index eac22908a..6bca43e30 100644 --- a/app/models/form/lettings/questions/rent_value_check.rb +++ b/app/models/form/lettings/questions/rent_value_check.rb @@ -1,11 +1,11 @@ class Form::Lettings::Questions::RentValueCheck < ::Form::Question - def initialize(id, hsh, page) - super + def initialize(id, hsh, page, check_answers_card_number:) + super(id, hsh, page) @id = "rent_value_check" @check_answer_label = "Total rent confirmation" @header = "Are you sure this is correct?" @type = "interruption_screen" - @check_answers_card_number = 0 + @check_answers_card_number = check_answers_card_number @answer_options = ANSWER_OPTIONS @hidden_in_check_answers = { "depends_on" => [{ "rent_value_check" => 0 }, { "rent_value_check" => 1 }] } end diff --git a/app/models/form/lettings/questions/uprn_known.rb b/app/models/form/lettings/questions/uprn_known.rb index 816a31ffb..95c53ec6a 100644 --- a/app/models/form/lettings/questions/uprn_known.rb +++ b/app/models/form/lettings/questions/uprn_known.rb @@ -9,7 +9,18 @@ class Form::Lettings::Questions::UprnKnown < ::Form::Question @hint_text = "The Unique Property Reference Number (UPRN) is a unique number system created by Ordnance Survey and used by housing providers and sectors UK-wide. For example 10010457355.

You can continue without the UPRN, but it means we will need you to enter the address of the property." @conditional_for = { "uprn" => [1] } - @hidden_in_check_answers = true + @inferred_check_answers_value = [ + { + "condition" => { "uprn_known" => 0 }, + "value" => "Not known", + }, + ] + @hidden_in_check_answers = { + "depends_on" => [ + { "uprn_known" => 0 }, + { "uprn_known" => 1 }, + ], + } end ANSWER_OPTIONS = { diff --git a/app/models/form/lettings/subsections/income_and_benefits.rb b/app/models/form/lettings/subsections/income_and_benefits.rb index 30ba3a7e0..25f5d0ce4 100644 --- a/app/models/form/lettings/subsections/income_and_benefits.rb +++ b/app/models/form/lettings/subsections/income_and_benefits.rb @@ -24,8 +24,8 @@ class Form::Lettings::Subsections::IncomeAndBenefits < ::Form::Subsection Form::Lettings::Pages::RentBiWeekly.new(nil, nil, self), Form::Lettings::Pages::Rent4Weekly.new(nil, nil, self), Form::Lettings::Pages::RentMonthly.new(nil, nil, self), - Form::Lettings::Pages::MinRentValueCheck.new(nil, nil, self), - Form::Lettings::Pages::MaxRentValueCheck.new(nil, nil, self), + Form::Lettings::Pages::MinRentValueCheck.new("brent_min_rent_value_check", nil, self, check_answers_card_number: 0), + Form::Lettings::Pages::MaxRentValueCheck.new("brent_max_rent_value_check", nil, self, check_answers_card_number: 0), Form::Lettings::Pages::Outstanding.new(nil, nil, self), Form::Lettings::Pages::OutstandingAmount.new(nil, nil, self), ].compact diff --git a/app/models/form/lettings/subsections/property_information.rb b/app/models/form/lettings/subsections/property_information.rb index 1290f7cf2..0f921d4a5 100644 --- a/app/models/form/lettings/subsections/property_information.rb +++ b/app/models/form/lettings/subsections/property_information.rb @@ -10,6 +10,8 @@ class Form::Lettings::Subsections::PropertyInformation < ::Form::Subsection @pages ||= [ uprn_questions, Form::Lettings::Pages::PropertyLocalAuthority.new(nil, nil, self), + Form::Lettings::Pages::MinRentValueCheck.new("local_authority_min_rent_value_check", nil, self, check_answers_card_number: nil), + Form::Lettings::Pages::MaxRentValueCheck.new("local_authority_max_rent_value_check", nil, self, check_answers_card_number: nil), Form::Lettings::Pages::FirstTimePropertyLetAsSocialHousing.new(nil, nil, self), Form::Lettings::Pages::PropertyLetType.new(nil, nil, self), Form::Lettings::Pages::PropertyVacancyReasonNotFirstLet.new(nil, nil, self), @@ -20,6 +22,8 @@ class Form::Lettings::Subsections::PropertyInformation < ::Form::Subsection Form::Lettings::Pages::PropertyBuildingType.new(nil, nil, self), Form::Lettings::Pages::PropertyWheelchairAccessible.new(nil, nil, self), Form::Lettings::Pages::PropertyNumberOfBedrooms.new(nil, nil, self), + Form::Lettings::Pages::MinRentValueCheck.new("beds_min_rent_value_check", nil, self, check_answers_card_number: 0), + Form::Lettings::Pages::MaxRentValueCheck.new("beds_max_rent_value_check", nil, self, check_answers_card_number: 0), Form::Lettings::Pages::VoidDate.new(nil, nil, self), Form::Lettings::Pages::VoidDateValueCheck.new(nil, nil, self), Form::Lettings::Pages::PropertyMajorRepairs.new(nil, nil, self), diff --git a/app/models/form/lettings/subsections/setup.rb b/app/models/form/lettings/subsections/setup.rb index e2dbb7f8b..312ed5255 100644 --- a/app/models/form/lettings/subsections/setup.rb +++ b/app/models/form/lettings/subsections/setup.rb @@ -10,14 +10,22 @@ class Form::Lettings::Subsections::Setup < ::Form::Subsection @pages ||= [ organisation_page, stock_owner_page, + Form::Lettings::Pages::MinRentValueCheck.new("stock_owner_min_rent_value_check", nil, self), + Form::Lettings::Pages::MaxRentValueCheck.new("stock_owner_max_rent_value_check", nil, self), managing_organisation_page, created_by_page, Form::Lettings::Pages::NeedsType.new(nil, nil, self), Form::Lettings::Pages::Scheme.new(nil, nil, self), Form::Lettings::Pages::Location.new(nil, nil, self), + Form::Lettings::Pages::MinRentValueCheck.new("needs_type_min_rent_value_check", nil, self), + Form::Lettings::Pages::MaxRentValueCheck.new("needs_type_max_rent_value_check", nil, self), Form::Lettings::Pages::Renewal.new(nil, nil, self), Form::Lettings::Pages::TenancyStartDate.new(nil, nil, self), + Form::Lettings::Pages::MinRentValueCheck.new("start_date_min_rent_value_check", nil, self), + Form::Lettings::Pages::MaxRentValueCheck.new("start_date_max_rent_value_check", nil, self), Form::Lettings::Pages::RentType.new(nil, nil, self), + Form::Lettings::Pages::MinRentValueCheck.new("rent_type_min_rent_value_check", nil, self), + Form::Lettings::Pages::MaxRentValueCheck.new("rent_type_max_rent_value_check", nil, self), Form::Lettings::Pages::TenantCode.new(nil, nil, self), Form::Lettings::Pages::PropertyReference.new(nil, nil, self), ].compact diff --git a/app/models/form/sales/questions/uprn_known.rb b/app/models/form/sales/questions/uprn_known.rb index 22438e662..09f3c54c9 100644 --- a/app/models/form/sales/questions/uprn_known.rb +++ b/app/models/form/sales/questions/uprn_known.rb @@ -15,7 +15,12 @@ class Form::Sales::Questions::UprnKnown < ::Form::Question "value" => "Not known", }, ] - @hidden_in_check_answers = true + @hidden_in_check_answers = { + "depends_on" => [ + { "uprn_known" => 0 }, + { "uprn_known" => 1 }, + ], + } end ANSWER_OPTIONS = { diff --git a/app/models/lettings_log.rb b/app/models/lettings_log.rb index cd1fffd22..6ea58e339 100644 --- a/app/models/lettings_log.rb +++ b/app/models/lettings_log.rb @@ -55,7 +55,7 @@ class LettingsLog < Log scope :filter_by_organisation, ->(org, _user = nil) { where(owning_organisation: org).or(where(managing_organisation: org)) } AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at].freeze - OPTIONAL_FIELDS = %w[first_time_property_let_as_social_housing tenancycode propcode chcharge].freeze + OPTIONAL_FIELDS = %w[rent_value_check first_time_property_let_as_social_housing tenancycode propcode chcharge].freeze RENT_TYPE_MAPPING_LABELS = { 1 => "Social Rent", 2 => "Affordable Rent", 3 => "Intermediate Rent" }.freeze HAS_BENEFITS_OPTIONS = [1, 6, 8, 7].freeze NUM_OF_WEEKS_FROM_PERIOD = { 2 => 26, 3 => 13, 4 => 12, 5 => 50, 6 => 49, 7 => 48, 8 => 47, 9 => 46, 1 => 52, 10 => 53 }.freeze @@ -435,12 +435,22 @@ class LettingsLog < Log end def soft_min_for_period - soft_min = LaRentRange.find_by(start_year: collection_start_year, la:, beds: beds_for_la_rent_range, lettype:).soft_min + soft_min = LaRentRange.find_by( + start_year: collection_start_year, + la:, + beds: beds_for_la_rent_range, + lettype:, + ).soft_min "#{soft_value_for_period(soft_min)} #{SUFFIX_FROM_PERIOD[period].presence || 'every week'}" end def soft_max_for_period - soft_max = LaRentRange.find_by(start_year: collection_start_year, la:, beds: beds_for_la_rent_range, lettype:).soft_max + soft_max = LaRentRange.find_by( + start_year: collection_start_year, + la:, + beds: beds_for_la_rent_range, + lettype:, + ).soft_max "#{soft_value_for_period(soft_max)} #{SUFFIX_FROM_PERIOD[period].presence || 'every week'}" end diff --git a/app/models/validations/soft_validations.rb b/app/models/validations/soft_validations.rb index cfcfb643b..5c571af6a 100644 --- a/app/models/validations/soft_validations.rb +++ b/app/models/validations/soft_validations.rb @@ -40,7 +40,12 @@ module Validations::SoftValidations def rent_in_soft_max_range? return unless brent && weekly_value(brent) && startdate - rent_range = LaRentRange.find_by(start_year: collection_start_year, la:, beds: beds_for_la_rent_range, lettype: get_lettype) + rent_range = LaRentRange.find_by( + start_year: collection_start_year, + la:, + beds: beds_for_la_rent_range, + lettype: get_lettype, + ) if beds.present? && rent_range.present? && beds > LaRentRange::MAX_BEDS weekly_value(brent) > rent_range.soft_max elsif rent_range.present? diff --git a/spec/fixtures/files/lettings_logs_download.csv b/spec/fixtures/files/lettings_logs_download.csv index b9aefc3c9..075792363 100644 --- a/spec/fixtures/files/lettings_logs_download.csv +++ b/spec/fixtures/files/lettings_logs_download.csv @@ -1,2 +1,2 @@ -id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,needstype,renewal,startdate,rent_type_detail,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,hhmemb,relat2,age2,sex2,retirement_value_check,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,is_previous_la_inferred,prevloc_label,prevloc,illness_type_1,illness_type_2,is_la_inferred,la_label,la,postcode_known,postcode_full,previous_la_known,wchair,preg_occ,cbl,earnings,incfreq,net_income_value_check,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,lettype,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,tshortfall,chcharge,ppcodenk,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,has_benefits,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,rent_value_check,old_form_id,lar,irproduct,old_id,joint,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,unresolved,updated_by_id,uprn,uprn_known,uprn_confirmed,address_line1,address_line2,town_or_city,county,carehome_charges_value_check,status_cache,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate -{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,No,DLUHC,DLUHC,2021,Supported housing,,2 October 2021,London Affordable Rent,,,,,,,,,,,,,,,,,,,,No,,,,,No,Westminster,E09000033,,SE1 1TE,,No,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,8,0,0,0,,0,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,0,,,,,,,,,,,,,,,,,,,9,1,,,,,,,,,,,,,,,,not_started,6,{scheme_code},{scheme_service_name},{scheme_sensitive},Missing,No,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,Bungalow,Fitted with equipment and adaptations,Westminster,{location_startdate} +id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,rent_value_check,needstype,renewal,startdate,rent_type_detail,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,hhmemb,relat2,age2,sex2,retirement_value_check,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,is_previous_la_inferred,prevloc_label,prevloc,illness_type_1,illness_type_2,is_la_inferred,la_label,la,postcode_known,postcode_full,previous_la_known,wchair,preg_occ,cbl,earnings,incfreq,net_income_value_check,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,lettype,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,tshortfall,chcharge,ppcodenk,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,has_benefits,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,old_form_id,lar,irproduct,old_id,joint,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,unresolved,updated_by_id,uprn,uprn_known,uprn_confirmed,address_line1,address_line2,town_or_city,county,carehome_charges_value_check,status_cache,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate +{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,No,DLUHC,DLUHC,2021,,Supported housing,,2 October 2021,London Affordable Rent,,,,,,,,,,,,,,,,,,,,No,,,,,No,Westminster,E09000033,,SE1 1TE,,No,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,8,0,0,0,,0,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,0,,,,,,,,,,,,,,,,,,9,1,,,,,,,,,,,,,,,,not_started,6,{scheme_code},{scheme_service_name},{scheme_sensitive},Missing,No,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,Bungalow,Fitted with equipment and adaptations,Westminster,{location_startdate} diff --git a/spec/fixtures/files/lettings_logs_download_codes_only.csv b/spec/fixtures/files/lettings_logs_download_codes_only.csv index dec2664b5..76dde6a7b 100644 --- a/spec/fixtures/files/lettings_logs_download_codes_only.csv +++ b/spec/fixtures/files/lettings_logs_download_codes_only.csv @@ -1,2 +1,2 @@ -id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,needstype,renewal,startdate,rent_type_detail,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,hhmemb,relat2,age2,sex2,retirement_value_check,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,is_previous_la_inferred,prevloc_label,prevloc,illness_type_1,illness_type_2,is_la_inferred,la_label,la,postcode_known,postcode_full,previous_la_known,wchair,preg_occ,cbl,earnings,incfreq,net_income_value_check,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,lettype,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,tshortfall,chcharge,ppcodenk,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,has_benefits,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,rent_value_check,old_form_id,lar,irproduct,old_id,joint,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,unresolved,updated_by_id,uprn,uprn_known,uprn_confirmed,address_line1,address_line2,town_or_city,county,carehome_charges_value_check,status_cache,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate -{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,false,DLUHC,DLUHC,2021,2,,2 October 2021,2,,,,,,,,,,,,,,,,,,,,false,,,,,false,Westminster,E09000033,,SE1 1TE,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,8,0,0,0,,0,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,0,,,,,,,,,,,,,,,,,,,9,1,,,,,,,,,,,,,,,,not_started,6,{scheme_code},{scheme_service_name},{scheme_sensitive},0,1,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,6,A,Westminster,{location_startdate} +id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,rent_value_check,needstype,renewal,startdate,rent_type_detail,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,hhmemb,relat2,age2,sex2,retirement_value_check,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,is_previous_la_inferred,prevloc_label,prevloc,illness_type_1,illness_type_2,is_la_inferred,la_label,la,postcode_known,postcode_full,previous_la_known,wchair,preg_occ,cbl,earnings,incfreq,net_income_value_check,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,lettype,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,tshortfall,chcharge,ppcodenk,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,has_benefits,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,old_form_id,lar,irproduct,old_id,joint,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,unresolved,updated_by_id,uprn,uprn_known,uprn_confirmed,address_line1,address_line2,town_or_city,county,carehome_charges_value_check,status_cache,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate +{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,false,DLUHC,DLUHC,2021,,2,,2 October 2021,2,,,,,,,,,,,,,,,,,,,,false,,,,,false,Westminster,E09000033,,SE1 1TE,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,8,0,0,0,,0,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,0,,,,,,,,,,,,,,,,,,9,1,,,,,,,,,,,,,,,,not_started,6,{scheme_code},{scheme_service_name},{scheme_sensitive},0,1,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,6,A,Westminster,{location_startdate} diff --git a/spec/models/form/lettings/pages/max_rent_value_check_spec.rb b/spec/models/form/lettings/pages/max_rent_value_check_spec.rb index 7a3a5544f..1d712d886 100644 --- a/spec/models/form/lettings/pages/max_rent_value_check_spec.rb +++ b/spec/models/form/lettings/pages/max_rent_value_check_spec.rb @@ -3,7 +3,7 @@ require "rails_helper" RSpec.describe Form::Lettings::Pages::MaxRentValueCheck, type: :model do subject(:page) { described_class.new(page_id, page_definition, subsection) } - let(:page_id) { "shared_ownership_deposit_value_check" } + let(:page_id) { "max_rent_value_check" } let(:page_definition) { nil } let(:subsection) { instance_double(Form::Subsection) } diff --git a/spec/models/form/lettings/pages/min_rent_value_check_spec.rb b/spec/models/form/lettings/pages/min_rent_value_check_spec.rb index 4e68a09c7..818599b06 100644 --- a/spec/models/form/lettings/pages/min_rent_value_check_spec.rb +++ b/spec/models/form/lettings/pages/min_rent_value_check_spec.rb @@ -1,8 +1,9 @@ require "rails_helper" RSpec.describe Form::Lettings::Pages::MinRentValueCheck, type: :model do - subject(:page) { described_class.new(nil, page_definition, subsection) } + subject(:page) { described_class.new(page_id, page_definition, subsection) } + let(:page_id) { "min_rent_value_check" } let(:page_definition) { nil } let(:subsection) { instance_double(Form::Subsection) } @@ -41,8 +42,14 @@ RSpec.describe Form::Lettings::Pages::MinRentValueCheck, type: :model do it "has the correct informative_text" do expect(page.informative_text).to eq({ - "arguments" => [{ "arguments_for_key" => "soft_min_for_period", "i18n_template" => "soft_min_for_period", "key" => "field_formatted_as_currency" }], "translation" => "soft_validations.rent.min_hint_text", + "arguments" => [ + { + "key" => "field_formatted_as_currency", + "arguments_for_key" => "soft_min_for_period", + "i18n_template" => "soft_min_for_period", + }, + ], }) end end diff --git a/spec/models/form/lettings/questions/uprn_known_spec.rb b/spec/models/form/lettings/questions/uprn_known_spec.rb index a79185462..b2c7f9d01 100644 --- a/spec/models/form/lettings/questions/uprn_known_spec.rb +++ b/spec/models/form/lettings/questions/uprn_known_spec.rb @@ -54,6 +54,20 @@ RSpec.describe Form::Lettings::Questions::UprnKnown, type: :model do end it "has the correct hidden_in_check_answers" do - expect(question.hidden_in_check_answers).to eq(true) + expect(question.hidden_in_check_answers).to eq({ + "depends_on" => [ + { "uprn_known" => 0 }, + { "uprn_known" => 1 }, + ], + }) + end + + it "has the correct inferred_check_answers_value" do + expect(question.inferred_check_answers_value).to eq([ + { + "condition" => { "uprn_known" => 0 }, + "value" => "Not known", + }, + ]) end end diff --git a/spec/models/form/lettings/subsections/income_and_benefits_spec.rb b/spec/models/form/lettings/subsections/income_and_benefits_spec.rb index 8dd4f88a3..e914ccc6a 100644 --- a/spec/models/form/lettings/subsections/income_and_benefits_spec.rb +++ b/spec/models/form/lettings/subsections/income_and_benefits_spec.rb @@ -30,8 +30,8 @@ RSpec.describe Form::Lettings::Subsections::IncomeAndBenefits, type: :model do rent_bi_weekly rent_4_weekly rent_monthly - min_rent_value_check - max_rent_value_check + brent_min_rent_value_check + brent_max_rent_value_check outstanding outstanding_amount ], diff --git a/spec/models/form/lettings/subsections/property_information_spec.rb b/spec/models/form/lettings/subsections/property_information_spec.rb index 0e0bc7397..0eac3a27a 100644 --- a/spec/models/form/lettings/subsections/property_information_spec.rb +++ b/spec/models/form/lettings/subsections/property_information_spec.rb @@ -20,6 +20,8 @@ RSpec.describe Form::Lettings::Subsections::PropertyInformation, type: :model do %w[ property_postcode property_local_authority + local_authority_min_rent_value_check + local_authority_max_rent_value_check first_time_property_let_as_social_housing property_let_type property_vacancy_reason_not_first_let @@ -30,6 +32,8 @@ RSpec.describe Form::Lettings::Subsections::PropertyInformation, type: :model do property_building_type property_wheelchair_accessible property_number_of_bedrooms + beds_min_rent_value_check + beds_max_rent_value_check void_date void_date_value_check property_major_repairs @@ -49,6 +53,8 @@ RSpec.describe Form::Lettings::Subsections::PropertyInformation, type: :model do uprn_confirmation address property_local_authority + local_authority_min_rent_value_check + local_authority_max_rent_value_check first_time_property_let_as_social_housing property_let_type property_vacancy_reason_not_first_let @@ -59,6 +65,8 @@ RSpec.describe Form::Lettings::Subsections::PropertyInformation, type: :model do property_building_type property_wheelchair_accessible property_number_of_bedrooms + beds_min_rent_value_check + beds_max_rent_value_check void_date void_date_value_check property_major_repairs diff --git a/spec/models/form/lettings/subsections/setup_spec.rb b/spec/models/form/lettings/subsections/setup_spec.rb index d7da750a8..00f9b5904 100644 --- a/spec/models/form/lettings/subsections/setup_spec.rb +++ b/spec/models/form/lettings/subsections/setup_spec.rb @@ -15,14 +15,22 @@ RSpec.describe Form::Lettings::Subsections::Setup, type: :model do expect(setup.pages.map(&:id)).to eq( %w[ stock_owner + stock_owner_min_rent_value_check + stock_owner_max_rent_value_check managing_organisation created_by needs_type scheme location + needs_type_min_rent_value_check + needs_type_max_rent_value_check renewal tenancy_start_date + start_date_min_rent_value_check + start_date_max_rent_value_check rent_type + rent_type_min_rent_value_check + rent_type_max_rent_value_check tenant_code property_reference ], @@ -42,14 +50,22 @@ RSpec.describe Form::Lettings::Subsections::Setup, type: :model do expect(setup.pages.map(&:id)).to eq( %w[ stock_owner + stock_owner_min_rent_value_check + stock_owner_max_rent_value_check managing_organisation created_by needs_type scheme location + needs_type_min_rent_value_check + needs_type_max_rent_value_check renewal tenancy_start_date + start_date_min_rent_value_check + start_date_max_rent_value_check rent_type + rent_type_min_rent_value_check + rent_type_max_rent_value_check tenant_code property_reference ], diff --git a/spec/models/form/sales/questions/uprn_known_spec.rb b/spec/models/form/sales/questions/uprn_known_spec.rb index 4016b6e8d..faac81698 100644 --- a/spec/models/form/sales/questions/uprn_known_spec.rb +++ b/spec/models/form/sales/questions/uprn_known_spec.rb @@ -54,6 +54,20 @@ RSpec.describe Form::Sales::Questions::UprnKnown, type: :model do end it "has the correct hidden_in_check_answers" do - expect(question.hidden_in_check_answers).to eq(true) + expect(question.hidden_in_check_answers).to eq({ + "depends_on" => [ + { "uprn_known" => 0 }, + { "uprn_known" => 1 }, + ], + }) + end + + it "has the correct inferred_check_answers_value" do + expect(question.inferred_check_answers_value).to eq([ + { + "condition" => { "uprn_known" => 0 }, + "value" => "Not known", + }, + ]) end end diff --git a/spec/models/form_handler_spec.rb b/spec/models/form_handler_spec.rb index 2c4c83022..bbeb56e55 100644 --- a/spec/models/form_handler_spec.rb +++ b/spec/models/form_handler_spec.rb @@ -17,13 +17,13 @@ RSpec.describe FormHandler do it "is able to load a current lettings form" do form = form_handler.get_form("current_lettings") expect(form).to be_a(Form) - expect(form.pages.count).to eq(46) + expect(form.pages.count).to be_positive end it "is able to load a next lettings form" do form = form_handler.get_form("next_lettings") expect(form).to be_a(Form) - expect(form.pages.count).to eq(13) + expect(form.pages.count).to be_positive end end @@ -48,14 +48,14 @@ RSpec.describe FormHandler do it "is able to load a current lettings form" do form = form_handler.get_form("current_lettings") expect(form).to be_a(Form) - expect(form.pages.count).to eq(13) + expect(form.pages.count).to be_positive expect(form.name).to eq("2022_2023_lettings") end it "is able to load a previous lettings form" do form = form_handler.get_form("previous_lettings") expect(form).to be_a(Form) - expect(form.pages.count).to eq(46) + expect(form.pages.count).to be_positive expect(form.name).to eq("2021_2022_lettings") end diff --git a/spec/models/lettings_log_spec.rb b/spec/models/lettings_log_spec.rb index 481f7fbbe..05da0a5a8 100644 --- a/spec/models/lettings_log_spec.rb +++ b/spec/models/lettings_log_spec.rb @@ -2310,6 +2310,7 @@ RSpec.describe LettingsLog do it "returns optional fields" do expect(lettings_log.optional_fields).to eq(%w[ + rent_value_check first_time_property_let_as_social_housing tenancycode propcode @@ -2324,6 +2325,7 @@ RSpec.describe LettingsLog do it "returns optional fields" do expect(lettings_log.optional_fields).to eq(%w[ + rent_value_check first_time_property_let_as_social_housing tenancycode propcode diff --git a/spec/services/csv/lettings_log_csv_service_spec.rb b/spec/services/csv/lettings_log_csv_service_spec.rb index 1a752035d..1ee3b9df1 100644 --- a/spec/services/csv/lettings_log_csv_service_spec.rb +++ b/spec/services/csv/lettings_log_csv_service_spec.rb @@ -20,6 +20,7 @@ RSpec.describe Csv::LettingsLogCsvService do owning_organisation_name managing_organisation_name collection_start_year + rent_value_check needstype renewal startdate @@ -161,7 +162,6 @@ RSpec.describe Csv::LettingsLogCsvService do pscharge supcharg tcharge - rent_value_check hbrentshortfall tshortfall_known tshortfall