From 4a31906d45dac7875b8113a924dd4897d37f004b Mon Sep 17 00:00:00 2001 From: David May-Miller Date: Tue, 21 Feb 2023 16:53:30 +0000 Subject: [PATCH] CLDC-853 Added validations for sales income2 (#1101) * CLDC-853 Added hard validations for sales income2 * CLDC-853 Added soft validation for sales income2 * CLDC-853 Fix tests broken by new code * CLDC-853 Add new tests for new page and refactor slightly * CLDC-853 Fix linting errors * CLDC-853 Rename migration and update schema version * CLDC-853 Fix broken sales income2 test * CLDC-853 Rename migration * CLDC-853 Move income 2 to cya card 2 and commonise combined income validation * CLDC-853 Actually use the validate_combined_income method * combine duplicate methods after rebase, ensure hard validations are triggered on all relevant fields * move validation on child income to financial validations to stop it being triggered on lettings logs, minor amendments to tests broken by changes * revamp financial validations tests against income to reflect updates * amend child income validation to reflect specifications and write tests to cover this validation * correct linting errors and play a little code golf * change copy for some validations, add sales log method and amend interruption screen helper to support this * extract duplicate code to private method * update buyer 1 and 2 income value check to be consistent * remove ecstat from income checks, the only ecstat we care about is child which is dealt with elsewhere * rename constant struct with same anme as existing variable * amend tests to reflect the chagnes in validations and copy * enable currency formatting of numbers for inserting into informative_text or title_text * update evil test in form handler spec * rebase and fix conflicts and tests * change a variable name and correct minor rebase errors * update interruption screen helper tests * correct linting errors, minor test failure and typo * add tests for new sales log method for formatting currency * fix merge conflicts --------- Co-authored-by: Arthur Campbell --- app/helpers/interruption_screen_helper.rb | 33 ++- ...bout_price_shared_ownership_value_check.rb | 6 +- .../sales/pages/buyer1_income_value_check.rb | 15 ++ .../sales/pages/buyer2_income_value_check.rb | 35 +++ .../questions/buyer1_income_value_check.rb | 2 +- .../form/sales/questions/buyer2_income.rb | 1 + .../questions/buyer2_income_value_check.rb | 25 ++ .../subsections/household_characteristics.rb | 3 +- .../income_benefits_and_savings.rb | 1 + app/models/log.rb | 4 + app/models/sales_log.rb | 18 ++ .../sales/financial_validations.rb | 63 +++-- .../validations/sales/soft_validations.rb | 12 +- config/locales/en.yml | 12 +- .../20230105103134_add_income2_value_check.rb | 5 + db/schema.rb | 1 + .../interruption_screen_helper_spec.rb | 53 ++++- .../buyer1_income_value_check_spec.rb | 2 +- .../household_characteristics_spec.rb | 6 +- .../income_benefits_and_savings_spec.rb | 2 + spec/models/form_handler_spec.rb | 4 +- spec/models/sales_log_spec.rb | 23 ++ .../sales/financial_validations_spec.rb | 222 ++++++++++++------ 23 files changed, 420 insertions(+), 128 deletions(-) create mode 100644 app/models/form/sales/pages/buyer2_income_value_check.rb create mode 100644 app/models/form/sales/questions/buyer2_income_value_check.rb create mode 100644 db/migrate/20230105103134_add_income2_value_check.rb diff --git a/app/helpers/interruption_screen_helper.rb b/app/helpers/interruption_screen_helper.rb index c30ba7bfa..939f70bb2 100644 --- a/app/helpers/interruption_screen_helper.rb +++ b/app/helpers/interruption_screen_helper.rb @@ -1,17 +1,10 @@ module InterruptionScreenHelper - def display_informative_text(informative_text, lettings_log) + def display_informative_text(informative_text, log) return "" unless informative_text["arguments"] translation_params = {} informative_text["arguments"].each do |argument| - value = if argument["label"] - pre_casing_value = lettings_log.form.get_question(argument["key"], lettings_log).answer_label(lettings_log) - pre_casing_value.downcase - elsif argument["currency"] - number_to_currency(lettings_log.public_send(argument["key"]), delimiter: ",", format: "%n", unit: "£") - else - lettings_log.public_send(argument["key"]) - end + value = get_value_from_argument(log, argument) translation_params[argument["i18n_template"].to_sym] = value end @@ -24,21 +17,27 @@ module InterruptionScreenHelper end end - def display_title_text(title_text, lettings_log) + def display_title_text(title_text, log) return "" if title_text.nil? translation_params = {} arguments = title_text["arguments"] || {} arguments.each do |argument| - value = if argument["label"] - lettings_log.form.get_question(argument["key"], lettings_log).answer_label(lettings_log).downcase - elsif argument["currency"] - number_to_currency(lettings_log.public_send(argument["key"]), delimiter: ",", format: "%n", unit: "£") - else - lettings_log.public_send(argument["key"]) - end + value = get_value_from_argument(log, argument) translation_params[argument["i18n_template"].to_sym] = value end I18n.t(title_text["translation"], **translation_params).to_s end + +private + + def get_value_from_argument(log, argument) + if argument["label"] + log.form.get_question(argument["key"], log).answer_label(log).downcase + elsif argument["arguments_for_key"] + log.public_send(argument["key"], argument["arguments_for_key"]) + else + log.public_send(argument["key"]) + end + end end diff --git a/app/models/form/sales/pages/about_price_shared_ownership_value_check.rb b/app/models/form/sales/pages/about_price_shared_ownership_value_check.rb index f4f0955cd..5b668006a 100644 --- a/app/models/form/sales/pages/about_price_shared_ownership_value_check.rb +++ b/app/models/form/sales/pages/about_price_shared_ownership_value_check.rb @@ -20,14 +20,12 @@ class Form::Sales::Pages::AboutPriceSharedOwnershipValueCheck < ::Form::Page "translation" => "soft_validations.purchase_price.hint_text", "arguments" => [ { - "key" => "purchase_price_soft_min_or_soft_max", - "label" => false, + "key" => "field_formatted_as_currency", + "arguments_for_key" => "purchase_price_soft_min_or_soft_max", "i18n_template" => "soft_min_or_soft_max", - "currency" => true, }, { "key" => "purchase_price_min_or_max_text", - "label" => false, "i18n_template" => "min_or_max", }, ], diff --git a/app/models/form/sales/pages/buyer1_income_value_check.rb b/app/models/form/sales/pages/buyer1_income_value_check.rb index 04540c47f..48d8f5fff 100644 --- a/app/models/form/sales/pages/buyer1_income_value_check.rb +++ b/app/models/form/sales/pages/buyer1_income_value_check.rb @@ -6,6 +6,21 @@ class Form::Sales::Pages::Buyer1IncomeValueCheck < ::Form::Page "income1_under_soft_min?" => true, }, ] + @title_text = { + "translation" => "soft_validations.income.under_soft_min_for_economic_status", + "arguments" => [ + { + "key" => "field_formatted_as_currency", + "arguments_for_key" => "income1", + "i18n_template" => "income", + }, + { + "key" => "income_soft_min_for_ecstat", + "arguments_for_key" => "ecstat1", + "i18n_template" => "minimum", + }, + ], + } @informative_text = {} end diff --git a/app/models/form/sales/pages/buyer2_income_value_check.rb b/app/models/form/sales/pages/buyer2_income_value_check.rb new file mode 100644 index 000000000..598c4c7a6 --- /dev/null +++ b/app/models/form/sales/pages/buyer2_income_value_check.rb @@ -0,0 +1,35 @@ +class Form::Sales::Pages::Buyer2IncomeValueCheck < ::Form::Page + def initialize(id, hsh, subsection) + super + @header = "" + @description = "" + @subsection = subsection + @depends_on = [ + { + "income2_under_soft_min?" => true, + }, + ] + @title_text = { + "translation" => "soft_validations.income.under_soft_min_for_economic_status", + "arguments" => [ + { + "key" => "field_formatted_as_currency", + "arguments_for_key" => "income2", + "i18n_template" => "income", + }, + { + "key" => "income_soft_min_for_ecstat", + "arguments_for_key" => "ecstat2", + "i18n_template" => "minimum", + }, + ], + } + @informative_text = {} + end + + def questions + @questions ||= [ + Form::Sales::Questions::Buyer2IncomeValueCheck.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/questions/buyer1_income_value_check.rb b/app/models/form/sales/questions/buyer1_income_value_check.rb index 0913dd788..8843d6736 100644 --- a/app/models/form/sales/questions/buyer1_income_value_check.rb +++ b/app/models/form/sales/questions/buyer1_income_value_check.rb @@ -3,7 +3,7 @@ class Form::Sales::Questions::Buyer1IncomeValueCheck < ::Form::Question super @id = "income1_value_check" @check_answer_label = "Income confirmation" - @header = "Are you sure this income is correct?" + @header = "Are you sure this is correct?" @type = "interruption_screen" @answer_options = { "0" => { "value" => "Yes" }, diff --git a/app/models/form/sales/questions/buyer2_income.rb b/app/models/form/sales/questions/buyer2_income.rb index 680bf8ae7..f7d96720b 100644 --- a/app/models/form/sales/questions/buyer2_income.rb +++ b/app/models/form/sales/questions/buyer2_income.rb @@ -7,6 +7,7 @@ class Form::Sales::Questions::Buyer2Income < ::Form::Question @type = "numeric" @hint_text = "Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments." @min = 0 + @max = 999_999 @step = 1 @width = 5 @prefix = "£" diff --git a/app/models/form/sales/questions/buyer2_income_value_check.rb b/app/models/form/sales/questions/buyer2_income_value_check.rb new file mode 100644 index 000000000..9508cc59a --- /dev/null +++ b/app/models/form/sales/questions/buyer2_income_value_check.rb @@ -0,0 +1,25 @@ +class Form::Sales::Questions::Buyer2IncomeValueCheck < ::Form::Question + def initialize(id, hsh, page) + super + @id = "income2_value_check" + @check_answer_label = "Income confirmation" + @header = "Are you sure this is correct?" + @type = "interruption_screen" + @answer_options = { + "0" => { "value" => "Yes" }, + "1" => { "value" => "No" }, + } + @hidden_in_check_answers = { + "depends_on" => [ + { + "income2_value_check" => 0, + }, + { + "income2_value_check" => 1, + }, + ], + } + @check_answers_card_number = 2 + @page = page + end +end diff --git a/app/models/form/sales/subsections/household_characteristics.rb b/app/models/form/sales/subsections/household_characteristics.rb index 55ec0b137..181460ae1 100644 --- a/app/models/form/sales/subsections/household_characteristics.rb +++ b/app/models/form/sales/subsections/household_characteristics.rb @@ -34,7 +34,8 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection Form::Sales::Pages::RetirementValueCheck.new("gender_2_buyer_retirement_value_check", nil, self, person_index: 2), ethnic_pages_for_buyer_2, Form::Sales::Pages::Buyer2WorkingSituation.new(nil, nil, self), - Form::Sales::Pages::RetirementValueCheck.new("working_situation_2_buyer_retirement_value_check", nil, self, person_index: 2), + Form::Sales::Pages::RetirementValueCheck.new("working_situation_2_retirement_value_check_joint_purchase", nil, self, person_index: 2), + Form::Sales::Pages::Buyer2IncomeValueCheck.new("working_situation_buyer_2_income_value_check", nil, self), Form::Sales::Pages::Buyer2LiveInProperty.new(nil, nil, self), Form::Sales::Pages::NumberOfOthersInProperty.new(nil, nil, self), Form::Sales::Pages::PersonKnown.new("person_2_known", nil, self, person_index: 2), diff --git a/app/models/form/sales/subsections/income_benefits_and_savings.rb b/app/models/form/sales/subsections/income_benefits_and_savings.rb index fe95bfd8c..7642e84bd 100644 --- a/app/models/form/sales/subsections/income_benefits_and_savings.rb +++ b/app/models/form/sales/subsections/income_benefits_and_savings.rb @@ -15,6 +15,7 @@ class Form::Sales::Subsections::IncomeBenefitsAndSavings < ::Form::Subsection Form::Sales::Pages::MortgageValueCheck.new("buyer_1_mortgage_value_check", nil, self, 1), Form::Sales::Pages::Buyer2Income.new(nil, nil, self), Form::Sales::Pages::MortgageValueCheck.new("buyer_2_income_mortgage_value_check", nil, self, 2), + Form::Sales::Pages::Buyer2IncomeValueCheck.new("buyer_2_income_value_check", nil, self), Form::Sales::Pages::Buyer2Mortgage.new(nil, nil, self), Form::Sales::Pages::MortgageValueCheck.new("buyer_2_mortgage_value_check", nil, self, 2), Form::Sales::Pages::HousingBenefits.new(nil, nil, self), diff --git a/app/models/log.rb b/app/models/log.rb index 0cd3add92..d8aa9e236 100644 --- a/app/models/log.rb +++ b/app/models/log.rb @@ -147,4 +147,8 @@ private self[is_inferred_key] = false self[postcode_key] = nil end + + def format_as_currency(num_string) + ActionController::Base.helpers.number_to_currency(num_string, unit: "£") + end end diff --git a/app/models/sales_log.rb b/app/models/sales_log.rb index 6ed653955..447f37597 100644 --- a/app/models/sales_log.rb +++ b/app/models/sales_log.rb @@ -124,6 +124,10 @@ class SalesLog < Log la && LONDON_BOROUGHS.include?(la) end + def property_not_in_london? + !london_property? + end + def income1_used_for_mortgage? inc1mort == 1 end @@ -256,4 +260,18 @@ class SalesLog < Log def purchase_price_soft_max LaSaleRange.find_by(start_year: collection_start_year, la:, bedrooms: beds).soft_max end + + def income_soft_min_for_ecstat(ecstat_field) + economic_status_code = public_send(ecstat_field) + + return unless ALLOWED_INCOME_RANGES_SALES + + soft_min = ALLOWED_INCOME_RANGES_SALES[economic_status_code]&.soft_min + format_as_currency(soft_min) + end + + def field_formatted_as_currency(field_name) + field_value = public_send(field_name) + format_as_currency(field_value) + end end diff --git a/app/models/validations/sales/financial_validations.rb b/app/models/validations/sales/financial_validations.rb index df7dc8df5..21f3743ca 100644 --- a/app/models/validations/sales/financial_validations.rb +++ b/app/models/validations/sales/financial_validations.rb @@ -3,20 +3,36 @@ module Validations::Sales::FinancialValidations # or 'validate_' to run on submit as well def validate_income1(record) - if record.ecstat1 && record.income1 && record.la && record.ownershipsch == 1 - if record.london_property? - record.errors.add :income1, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 - record.errors.add :ecstat1, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 - record.errors.add :ownershipsch, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 - record.errors.add :la, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 - record.errors.add :postcode_full, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 - elsif record.income1 > 80_000 - record.errors.add :income1, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) - record.errors.add :ecstat1, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) - record.errors.add :ownershipsch, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) - record.errors.add :la, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) if record.income1 > 80_000 - record.errors.add :postcode_full, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) if record.income1 > 80_000 - end + return unless record.income1 && record.la && record.shared_ownership_scheme? + + relevant_fields = %i[income1 ownershipsch la postcode_full] + if record.london_property? && record.income1 > 90_000 + relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.over_hard_max_for_london") } + elsif record.property_not_in_london? && record.income1 > 80_000 + relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.over_hard_max_for_outside_london") } + end + end + + def validate_income2(record) + return unless record.income2 && record.la && record.shared_ownership_scheme? + + relevant_fields = %i[income2 ownershipsch la postcode_full] + if record.london_property? && record.income2 > 90_000 + relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.over_hard_max_for_london") } + elsif record.property_not_in_london? && record.income2 > 80_000 + relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.over_hard_max_for_outside_london") } + end + end + + def validate_combined_income(record) + return unless record.income1 && record.income2 && record.la && record.shared_ownership_scheme? + + combined_income = record.income1 + record.income2 + relevant_fields = %i[income1 income2 ownershipsch la postcode_full] + if record.london_property? && combined_income > 90_000 + relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.combined_over_hard_max_for_london") } + elsif record.property_not_in_london? && combined_income > 80_000 + relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.combined_over_hard_max_for_outside_london") } end end @@ -36,6 +52,15 @@ module Validations::Sales::FinancialValidations end end + def validate_child_income(record) + return unless record.income2 && record.ecstat2 + + if record.income2.positive? && is_economic_status_child?(record.ecstat2) + record.errors.add :ecstat2, I18n.t("validations.financial.income.child_has_income") + record.errors.add :income2, I18n.t("validations.financial.income.child_has_income") + end + end + def validate_percentage_owned_not_too_much_if_older_person(record) return unless record.old_persons_shared_ownership? && record.stairowned @@ -44,4 +69,14 @@ module Validations::Sales::FinancialValidations record.errors.add :type, I18n.t("validations.financial.staircasing.older_person_percentage_owned_maximum_75") end end + +private + + def is_relationship_child?(relationship) + relationship == "C" + end + + def is_economic_status_child?(economic_status) + economic_status == 9 + end end diff --git a/app/models/validations/sales/soft_validations.rb b/app/models/validations/sales/soft_validations.rb index a7b9fd4c0..c1704d948 100644 --- a/app/models/validations/sales/soft_validations.rb +++ b/app/models/validations/sales/soft_validations.rb @@ -1,5 +1,5 @@ module Validations::Sales::SoftValidations - ALLOWED_INCOME_RANGES = { + ALLOWED_INCOME_RANGES_SALES = { 1 => OpenStruct.new(soft_min: 5000), 2 => OpenStruct.new(soft_min: 1500), 3 => OpenStruct.new(soft_min: 1000), @@ -8,9 +8,15 @@ module Validations::Sales::SoftValidations }.freeze def income1_under_soft_min? - return false unless ecstat1 && income1 && ALLOWED_INCOME_RANGES[ecstat1] + return false unless ecstat1 && income1 && ALLOWED_INCOME_RANGES_SALES[ecstat1] - income1 < ALLOWED_INCOME_RANGES[ecstat1][:soft_min] + income1 < ALLOWED_INCOME_RANGES_SALES[ecstat1][:soft_min] + end + + def income2_under_soft_min? + return false unless ecstat2 && income2 && ALLOWED_INCOME_RANGES_SALES[ecstat2] + + income2 < ALLOWED_INCOME_RANGES_SALES[ecstat2][:soft_min] end def staircase_bought_above_fifty? diff --git a/config/locales/en.yml b/config/locales/en.yml index 747ed4414..bc54b74ce 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -223,8 +223,12 @@ en: under_hard_min: "Net income cannot be less than £%{hard_min} per week given the tenant’s working situation" freq_missing: "Select how often the household receives income" earnings_missing: "Enter how much income the household has in total" - income1: - over_hard_max: "Income must be lower than £%{hard_max}" + income: + over_hard_max_for_london: "Income must not exceed £90,000 for properties within London local authorities" + over_hard_max_for_outside_london: "Income must not exceed £80,000 for properties outside London local authorities" + combined_over_hard_max_for_london: "Combined income must not exceed £90,000 for properties within London local authorities" + combined_over_hard_max_for_outside_london: "Combined income must not exceed £80,000 for properties outside London local authorities" + child_has_income: "Child's income must be £0" negative_currency: "Enter an amount above 0" rent: less_than_shortfall: "Enter an amount that is more than the shortfall in basic rent" @@ -467,6 +471,8 @@ en: message: "Net income is lower than expected based on the lead tenant’s working situation. Are you sure this is correct?" in_soft_max_range: message: "Net income is higher than expected based on the lead tenant’s working situation. Are you sure this is correct?" + income: + under_soft_min_for_economic_status: "You said income was %{income}, which is below this working situation's minimum (%{minimum})" rent: outside_range_title: "You told us the rent is %{brent}" min_hint_text: "The minimum rent expected for this type of property in this local authority is £%{soft_min_for_period}." @@ -559,4 +565,4 @@ en: one_argument: "This is based on the tenant’s work situation: %{ecstat1}" title_text: no_argument: "Some test text" - one_argument: "You said this: %{ecstat1}" + one_argument: "You said this: %{argument}" diff --git a/db/migrate/20230105103134_add_income2_value_check.rb b/db/migrate/20230105103134_add_income2_value_check.rb new file mode 100644 index 000000000..38a3ebc65 --- /dev/null +++ b/db/migrate/20230105103134_add_income2_value_check.rb @@ -0,0 +1,5 @@ +class AddIncome2ValueCheck < ActiveRecord::Migration[7.0] + def change + add_column :sales_logs, :income2_value_check, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 2c1ff7692..6cb5420d5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -520,6 +520,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_10_143120) do t.integer "value_value_check" t.integer "old_persons_shared_ownership_value_check" t.integer "staircase_bought_value_check" + t.integer "income2_value_check" t.integer "monthly_charges_value_check" t.integer "details_known_5" t.integer "details_known_6" diff --git a/spec/helpers/interruption_screen_helper_spec.rb b/spec/helpers/interruption_screen_helper_spec.rb index 8153b9d7b..f46aca9ae 100644 --- a/spec/helpers/interruption_screen_helper_spec.rb +++ b/spec/helpers/interruption_screen_helper_spec.rb @@ -13,6 +13,7 @@ RSpec.describe InterruptionScreenHelper do earnings: 750, incfreq: 1, created_by: user, + sex1: "F", ) end @@ -94,6 +95,54 @@ RSpec.describe InterruptionScreenHelper do .to eq("") end end + + context "when an argument is given not for a label" do + translation = "test.title_text.one_argument" + it "returns the correct text" do + informative_text_hash = { + "translation" => translation, + "arguments" => [ + { + "key" => "earnings", + "i18n_template" => "argument", + }, + ], + } + expect(display_informative_text(informative_text_hash, lettings_log)).to eq(I18n.t(translation, argument: lettings_log.earnings)) + end + end + + context "when and argument is given with a key and arguments for the key" do + it "makes the correct method call" do + informative_text_hash = { + "arguments" => [ + { + "key" => "retirement_age_for_person", + "arguments_for_key" => 1, + "i18n_template" => "argument", + }, + ], + } + allow(lettings_log).to receive(:retirement_age_for_person) + display_informative_text(informative_text_hash, lettings_log) + expect(lettings_log).to have_received(:retirement_age_for_person).with(1) + end + + it "returns the correct text" do + translation = "test.title_text.one_argument" + informative_text_hash = { + "translation" => translation, + "arguments" => [ + { + "key" => "retirement_age_for_person", + "arguments_for_key" => 1, + "i18n_template" => "argument", + }, + ], + } + expect(display_informative_text(informative_text_hash, lettings_log)).to eq(I18n.t(translation, argument: lettings_log.retirement_age_for_person(1))) + end + end end describe "display_title_text" do @@ -113,12 +162,12 @@ RSpec.describe InterruptionScreenHelper do { "key" => "ecstat1", "label" => true, - "i18n_template" => "ecstat1", + "i18n_template" => "argument", }, ], } expect(display_title_text(title_text, lettings_log)) - .to eq(I18n.t("test.title_text.one_argument", ecstat1: lettings_log.form.get_question("ecstat1", lettings_log).answer_label(lettings_log).downcase)) + .to eq(I18n.t("test.title_text.one_argument", argument: lettings_log.form.get_question("ecstat1", lettings_log).answer_label(lettings_log).downcase)) end end diff --git a/spec/models/form/sales/questions/buyer1_income_value_check_spec.rb b/spec/models/form/sales/questions/buyer1_income_value_check_spec.rb index 552f4f56f..89801a398 100644 --- a/spec/models/form/sales/questions/buyer1_income_value_check_spec.rb +++ b/spec/models/form/sales/questions/buyer1_income_value_check_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Form::Sales::Questions::Buyer1IncomeValueCheck, type: :model do end it "has the correct header" do - expect(question.header).to eq("Are you sure this income is correct?") + expect(question.header).to eq("Are you sure this is correct?") end it "has the correct check_answer_label" do diff --git a/spec/models/form/sales/subsections/household_characteristics_spec.rb b/spec/models/form/sales/subsections/household_characteristics_spec.rb index 76109b7f4..33e9a1409 100644 --- a/spec/models/form/sales/subsections/household_characteristics_spec.rb +++ b/spec/models/form/sales/subsections/household_characteristics_spec.rb @@ -46,7 +46,8 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model buyer_2_gender_identity gender_2_buyer_retirement_value_check buyer_2_working_situation - working_situation_2_buyer_retirement_value_check + working_situation_2_retirement_value_check_joint_purchase + working_situation_buyer_2_income_value_check buyer_2_live_in_property number_of_others_in_property person_2_known @@ -126,7 +127,8 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model buyer_2_ethnic_background_mixed buyer_2_ethnic_background_white buyer_2_working_situation - working_situation_2_buyer_retirement_value_check + working_situation_2_retirement_value_check_joint_purchase + working_situation_buyer_2_income_value_check buyer_2_live_in_property number_of_others_in_property person_2_known diff --git a/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb b/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb index 74002d5e0..cfc677233 100644 --- a/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb +++ b/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb @@ -27,6 +27,7 @@ RSpec.describe Form::Sales::Subsections::IncomeBenefitsAndSavings, type: :model buyer_1_mortgage_value_check buyer_2_income buyer_2_income_mortgage_value_check + buyer_2_income_value_check buyer_2_mortgage buyer_2_mortgage_value_check housing_benefits @@ -52,6 +53,7 @@ RSpec.describe Form::Sales::Subsections::IncomeBenefitsAndSavings, type: :model buyer_1_mortgage_value_check buyer_2_income buyer_2_income_mortgage_value_check + buyer_2_income_value_check buyer_2_mortgage buyer_2_mortgage_value_check housing_benefits diff --git a/spec/models/form_handler_spec.rb b/spec/models/form_handler_spec.rb index 9b4920198..b04980ce4 100644 --- a/spec/models/form_handler_spec.rb +++ b/spec/models/form_handler_spec.rb @@ -54,14 +54,14 @@ RSpec.describe FormHandler do it "is able to load a current sales form" do form = form_handler.get_form("current_sales") expect(form).to be_a(Form) - expect(form.pages.count).to eq(179) + expect(form.pages.count).to eq(181) expect(form.name).to eq("2022_2023_sales") end it "is able to load a previous sales form" do form = form_handler.get_form("previous_sales") expect(form).to be_a(Form) - expect(form.pages.count).to eq(179) + expect(form.pages.count).to eq(181) expect(form.name).to eq("2021_2022_sales") end end diff --git a/spec/models/sales_log_spec.rb b/spec/models/sales_log_spec.rb index 523c2924b..7b9f7c7ef 100644 --- a/spec/models/sales_log_spec.rb +++ b/spec/models/sales_log_spec.rb @@ -266,6 +266,7 @@ RSpec.describe SalesLog, type: :model do relat4: "X", relat5: "X", relat6: "P", + income2: 0, ecstat2: 9, ecstat3: 7, age1: 47, @@ -370,4 +371,26 @@ RSpec.describe SalesLog, type: :model do expect(completed_sales_log.expected_shared_ownership_deposit_value).to eq(500) end end + + describe "#field_formatted_as_currency" do + let(:completed_sales_log) { FactoryBot.create(:sales_log, :completed) } + + it "returns small numbers correctly formatted as currency" do + completed_sales_log.update!(savings: 4) + + expect(completed_sales_log.field_formatted_as_currency("savings")).to eq("£4.00") + end + + it "returns quite large numbers correctly formatted as currency" do + completed_sales_log.update!(savings: 40_000) + + expect(completed_sales_log.field_formatted_as_currency("savings")).to eq("£40,000.00") + end + + it "returns very large numbers correctly formatted as currency" do + completed_sales_log.update!(savings: 400_000_000) + + expect(completed_sales_log.field_formatted_as_currency("savings")).to eq("£400,000,000.00") + end + end end diff --git a/spec/models/validations/sales/financial_validations_spec.rb b/spec/models/validations/sales/financial_validations_spec.rb index 88f36943f..fae0e4536 100644 --- a/spec/models/validations/sales/financial_validations_spec.rb +++ b/spec/models/validations/sales/financial_validations_spec.rb @@ -5,75 +5,110 @@ RSpec.describe Validations::Sales::FinancialValidations do let(:validator_class) { Class.new { include Validations::Sales::FinancialValidations } } - describe "income validations" do - let(:record) { FactoryBot.create(:sales_log, ownershipsch: 1, la: "E08000035") } - - context "with shared ownership" do - context "and non london borough" do - (0..8).each do |ecstat| - it "adds an error when buyer 1 income is over hard max for ecstat #{ecstat}" do - record.income1 = 85_000 - record.ecstat1 = ecstat - financial_validator.validate_income1(record) - expect(record.errors["income1"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000)) - expect(record.errors["ecstat1"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000)) - expect(record.errors["ownershipsch"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000)) - expect(record.errors["la"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000)) - expect(record.errors["postcode_full"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000)) - end - end - - it "validates that the income is within the expected range for the tenant’s employment status" do - record.income1 = 75_000 - record.ecstat1 = 1 - financial_validator.validate_income1(record) - expect(record.errors["income1"]).to be_empty - expect(record.errors["ecstat1"]).to be_empty - expect(record.errors["ownershipsch"]).to be_empty - expect(record.errors["la"]).to be_empty - expect(record.errors["postcode_full"]).to be_empty - end - end - - context "and a london borough" do - before do - record.update!(la: "E09000030") - record.reload - end - - (0..8).each do |ecstat| - it "adds an error when buyer 1 income is over hard max for ecstat #{ecstat}" do - record.income1 = 95_000 - record.ecstat1 = ecstat - financial_validator.validate_income1(record) - expect(record.errors["income1"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000)) - expect(record.errors["ecstat1"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000)) - expect(record.errors["ownershipsch"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000)) - expect(record.errors["la"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000)) - expect(record.errors["postcode_full"]) - .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000)) - end - end - - it "validates that the income is within the expected range for the tenant’s employment status" do - record.income1 = 85_000 - record.ecstat1 = 1 - financial_validator.validate_income1(record) - expect(record.errors["income1"]).to be_empty - expect(record.errors["ecstat1"]).to be_empty - expect(record.errors["ownershipsch"]).to be_empty - expect(record.errors["la"]).to be_empty - expect(record.errors["postcode_full"]).to be_empty - end + describe "income validations for shared ownership" do + let(:record) { FactoryBot.create(:sales_log, ownershipsch: 1) } + + context "when buying in a non london borough" do + before do + record.update!(la: "E08000035") + record.reload + end + + it "adds errors if buyer 1 has income over 80,000" do + record.income1 = 85_000 + financial_validator.validate_income1(record) + expect(record.errors["income1"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_outside_london")) + expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_outside_london")) + expect(record.errors["la"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_outside_london")) + expect(record.errors["postcode_full"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_outside_london")) + end + + it "adds errors if buyer 2 has income over 80,000" do + record.income2 = 85_000 + financial_validator.validate_income2(record) + expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_outside_london")) + expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_outside_london")) + expect(record.errors["la"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_outside_london")) + expect(record.errors["postcode_full"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_outside_london")) + end + + it "does not add errors if buyer 1 has income below 80_000" do + record.income1 = 75_000 + financial_validator.validate_income1(record) + expect(record.errors).to be_empty + end + + it "does not add errors if buyer 2 has income below 80_000" do + record.income2 = 75_000 + financial_validator.validate_income2(record) + expect(record.errors).to be_empty + end + + it "adds errors when combined income is over 80_000" do + record.income1 = 45_000 + record.income2 = 40_000 + financial_validator.validate_combined_income(record) + expect(record.errors["income1"]).to include(match I18n.t("validations.financial.income.combined_over_hard_max_for_outside_london")) + expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.combined_over_hard_max_for_outside_london")) + end + + it "does not add errors when combined income is under 80_000" do + record.income1 = 35_000 + record.income2 = 40_000 + financial_validator.validate_combined_income(record) + expect(record.errors).to be_empty + end + end + + context "when buying in a london borough" do + before do + record.update!(la: "E09000030") + record.reload + end + + it "adds errors if buyer 1 has income over 90,000" do + record.income1 = 95_000 + financial_validator.validate_income1(record) + expect(record.errors["income1"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_london")) + expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_london")) + expect(record.errors["la"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_london")) + expect(record.errors["postcode_full"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_london")) + end + + it "adds errors if buyer 2 has income over 90,000" do + record.income2 = 95_000 + financial_validator.validate_income2(record) + expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_london")) + expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_london")) + expect(record.errors["la"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_london")) + expect(record.errors["postcode_full"]).to include(match I18n.t("validations.financial.income.over_hard_max_for_london")) + end + + it "does not add errors if buyer 1 has income below 90_000" do + record.income1 = 75_000 + financial_validator.validate_income1(record) + expect(record.errors).to be_empty + end + + it "does not add errors if buyer 2 has income below 90_000" do + record.income2 = 75_000 + financial_validator.validate_income2(record) + expect(record.errors).to be_empty + end + + it "adds errors when combined income is over 90_000" do + record.income1 = 55_000 + record.income2 = 40_000 + financial_validator.validate_combined_income(record) + expect(record.errors["income1"]).to include(match I18n.t("validations.financial.income.combined_over_hard_max_for_london")) + expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.combined_over_hard_max_for_london")) + end + + it "does not add errors when combined income is under 90_000" do + record.income1 = 35_000 + record.income2 = 40_000 + financial_validator.validate_combined_income(record) + expect(record.errors).to be_empty end end end @@ -96,7 +131,7 @@ RSpec.describe Validations::Sales::FinancialValidations do it "does not add an error if the cash discount is in the expected range" do record.cashdis = 10_000 financial_validator.validate_cash_discount(record) - expect(record.errors["cashdis"]).to be_empty + expect(record.errors).to be_empty end end @@ -107,16 +142,14 @@ RSpec.describe Validations::Sales::FinancialValidations do record.stairbought = 20 record.stairowned = 40 financial_validator.validate_percentage_bought_not_greater_than_percentage_owned(record) - expect(record.errors["stairbought"]).to be_empty - expect(record.errors["stairowned"]).to be_empty + expect(record.errors).to be_empty end it "does not add an error if the percentage bought is equal to the percentage owned" do record.stairbought = 30 record.stairowned = 30 financial_validator.validate_percentage_bought_not_greater_than_percentage_owned(record) - expect(record.errors["stairbought"]).to be_empty - expect(record.errors["stairowned"]).to be_empty + expect(record.errors).to be_empty end it "adds an error to stairowned and not stairbought if the percentage bought is more than the percentage owned" do @@ -135,8 +168,7 @@ RSpec.describe Validations::Sales::FinancialValidations do record.type = 2 record.stairowned = 80 financial_validator.validate_percentage_owned_not_too_much_if_older_person(record) - expect(record.errors["stairowned"]).to be_empty - expect(record.errors["type"]).to be_empty + expect(record.errors).to be_empty end end @@ -145,8 +177,7 @@ RSpec.describe Validations::Sales::FinancialValidations do record.type = 24 record.stairowned = 50 financial_validator.validate_percentage_owned_not_too_much_if_older_person(record) - expect(record.errors["stairowned"]).to be_empty - expect(record.errors["type"]).to be_empty + expect(record.errors).to be_empty end it "adds an error when percentage owned after staircasing transaction exceeds 75%" do @@ -158,4 +189,39 @@ RSpec.describe Validations::Sales::FinancialValidations do end end end + + describe "#validate_child_income" do + let(:record) { FactoryBot.create(:sales_log) } + + context "when buyer 2 is not a child" do + before do + record.update!(ecstat2: rand(0..8)) + record.reload + end + + it "does not add an error if buyer 2 has an income" do + record.ecstat2 = rand(0..8) + record.income2 = 40_000 + financial_validator.validate_child_income(record) + expect(record.errors).to be_empty + end + end + + context "when buyer 2 is a child" do + it "does not add an error if buyer 2 has no income" do + record.ecstat2 = 9 + record.income2 = 0 + financial_validator.validate_child_income(record) + expect(record.errors).to be_empty + end + + it "adds errors if buyer 2 has an income" do + record.ecstat2 = 9 + record.income2 = 40_000 + financial_validator.validate_child_income(record) + expect(record.errors["ecstat2"]).to include(match I18n.t("validations.financial.income.child_has_income")) + expect(record.errors["income2"]).to include(match I18n.t("validations.financial.income.child_has_income")) + end + end + end end