diff --git a/app/components/bulk_upload_error_summary_table_component.html.erb b/app/components/bulk_upload_error_summary_table_component.html.erb new file mode 100644 index 000000000..f1749ee92 --- /dev/null +++ b/app/components/bulk_upload_error_summary_table_component.html.erb @@ -0,0 +1,25 @@ +<%= govuk_table do |table| %> + <% table.caption(size: "m", text: bulk_upload.filename) %> + + <% table.head do |head| %> + <% head.row do |row| %> + <% row.cell(text: "Column", header: true) %> + <% row.cell(text: "Number of rows", header: true) %> + <% row.cell(text: "Question", header: true) %> + <% row.cell(text: "Error", header: true) %> + <% row.cell(text: "Specification", header: true) %> + <% end %> + <% end %> + + <% table.body do |body| %> + <% sorted_errors.each do |error| %> + <% body.row do |row| %> + <% row.cell(text: error[0][0]) %> + <% row.cell(text: error[1]) %> + <% row.cell(text: BulkUpload::Lettings::Validator.question_for_field(error[0][1].to_sym)) %> + <% row.cell(text: error[0][2]) %> + <% row.cell(text: error[0][1]) %> + <% end %> + <% end %> + <% end %> +<% end %> diff --git a/app/components/bulk_upload_error_summary_table_component.rb b/app/components/bulk_upload_error_summary_table_component.rb new file mode 100644 index 000000000..ac236af4d --- /dev/null +++ b/app/components/bulk_upload_error_summary_table_component.rb @@ -0,0 +1,17 @@ +class BulkUploadErrorSummaryTableComponent < ViewComponent::Base + attr_reader :bulk_upload + + def initialize(bulk_upload:) + @bulk_upload = bulk_upload + + super + end + + def sorted_errors + @sorted_errors ||= bulk_upload + .bulk_upload_errors + .group(:col, :field, :error) + .count + .sort_by { |el| el[0][0].rjust(3, "0") } + end +end diff --git a/app/controllers/bulk_upload_lettings_results_controller.rb b/app/controllers/bulk_upload_lettings_results_controller.rb index 1c5342cd8..6f92c0375 100644 --- a/app/controllers/bulk_upload_lettings_results_controller.rb +++ b/app/controllers/bulk_upload_lettings_results_controller.rb @@ -19,6 +19,10 @@ class BulkUploadLettingsResultsController < ApplicationController end end + def summary + @bulk_upload = current_user.bulk_uploads.lettings.find(params[:id]) + end + private def reset_logs_filters diff --git a/app/mailers/bulk_upload_mailer.rb b/app/mailers/bulk_upload_mailer.rb new file mode 100644 index 000000000..5ab0d70b0 --- /dev/null +++ b/app/mailers/bulk_upload_mailer.rb @@ -0,0 +1,79 @@ +class BulkUploadMailer < NotifyMailer + BULK_UPLOAD_COMPLETE_TEMPLATE_ID = "83279578-c890-4168-838b-33c9cf0dc9f0".freeze + BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID = "e27abcd4-5295-48c2-b127-e9ee4b781b75".freeze + BULK_UPLOAD_FAILED_FILE_SETUP_ERROR_TEMPLATE_ID = "24c9f4c7-96ad-470a-ba31-eb51b7cbafd9".freeze + BULK_UPLOAD_FAILED_SERVICE_ERROR_TEMPLATE_ID = "c3f6288c-7a74-4e77-99ee-6c4a0f6e125a".freeze + BULK_UPLOAD_WITH_ERRORS_TEMPLATE_ID = "eb539005-6234-404e-812d-167728cf4274".freeze + + def send_bulk_upload_complete_mail(user, bulk_upload) + send_email( + user.email, + BULK_UPLOAD_COMPLETE_TEMPLATE_ID, + { + title: "[#{bulk_upload} title]", + filename: "[#{bulk_upload} filename]", + upload_timestamp: "[#{bulk_upload} upload_timestamp]", + success_description: "[#{bulk_upload} success_description]", + logs_link: "[#{bulk_upload} logs_link]", + }, + ) + end + + def send_bulk_upload_failed_csv_errors_mail(user, bulk_upload) + send_email( + user.email, + BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID, + { + filename: "[#{bulk_upload} filename]", + upload_timestamp: "[#{bulk_upload} upload_timestamp]", + year_combo: "[#{bulk_upload} year_combo]", + lettings_or_sales: "[#{bulk_upload} lettings_or_sales]", + error_description: "[#{bulk_upload} error_description]", + summary_report_link: "[#{bulk_upload} summary_report_link]", + }, + ) + end + + def send_bulk_upload_failed_file_setup_error_mail(user, bulk_upload) + send_email( + user.email, + BULK_UPLOAD_FAILED_FILE_SETUP_ERROR_TEMPLATE_ID, + { + filename: "[#{bulk_upload} filename]", + upload_timestamp: "[#{bulk_upload} upload_timestamp]", + lettings_or_sales: "[#{bulk_upload} lettings_or_sales]", + year_combo: "[#{bulk_upload} year_combo]", + errors_list: "[#{bulk_upload} errors_list]", + bulk_upload_link: "[#{bulk_upload} bulk_upload_link]", + }, + ) + end + + def send_bulk_upload_failed_service_error_mail(user, bulk_upload) + send_email( + user.email, + BULK_UPLOAD_FAILED_SERVICE_ERROR_TEMPLATE_ID, + { + filename: "[#{bulk_upload} filename]", + upload_timestamp: "[#{bulk_upload} upload_timestamp]", + lettings_or_sales: "[#{bulk_upload} lettings_or_sales]", + year_combo: "[#{bulk_upload} year_combo]", + bulk_upload_link: "[#{bulk_upload} bulk_upload_link]", + }, + ) + end + + def send_bulk_upload_with_errors_mail(user, bulk_upload) + send_email( + user.email, + BULK_UPLOAD_WITH_ERRORS_TEMPLATE_ID, + { + title: "[#{bulk_upload} title]", + filename: "[#{bulk_upload} filename]", + upload_timestamp: "[#{bulk_upload} upload_timestamp]", + error_description: "[#{bulk_upload} error_description]", + summary_report_link: "[#{bulk_upload} summary_report_link]", + }, + ) + end +end diff --git a/app/models/bulk_upload.rb b/app/models/bulk_upload.rb index 7933ac204..3d0ef93f0 100644 --- a/app/models/bulk_upload.rb +++ b/app/models/bulk_upload.rb @@ -3,7 +3,8 @@ class BulkUpload < ApplicationRecord belongs_to :user - has_many :bulk_upload_errors + has_many :bulk_upload_errors, dependent: :destroy + has_many :lettings_logs has_many :sales_logs diff --git a/app/models/derived_variables/sales_log_variables.rb b/app/models/derived_variables/sales_log_variables.rb index 245361a80..16fb6ebd7 100644 --- a/app/models/derived_variables/sales_log_variables.rb +++ b/app/models/derived_variables/sales_log_variables.rb @@ -15,6 +15,9 @@ module DerivedVariables::SalesLogVariables if mscharge_known.present? && mscharge_known.zero? self.mscharge = 0 end + if mortgage_not_used? + self.mortgage = 0 + end self.pcode1, self.pcode2 = postcode_full.split(" ") if postcode_full.present? self.totchild = total_child self.totadult = total_adult + total_elder diff --git a/app/models/form/question.rb b/app/models/form/question.rb index cbfc6aae2..02f9f3235 100644 --- a/app/models/form/question.rb +++ b/app/models/form/question.rb @@ -14,11 +14,11 @@ class Form::Question def initialize(id, hsh, page) @id = id @page = page + @guidance_position = GuidancePosition::TOP if hsh @check_answer_label = hsh["check_answer_label"] @header = hsh["header"] @guidance_partial = hsh["guidance_partial"] - @guidance_position = GuidancePosition::TOP @hint_text = hsh["hint_text"] @type = hsh["type"] @min = hsh["min"] @@ -206,6 +206,7 @@ class Form::Question def unanswered_error_message return I18n.t("validations.declaration.missing") if id == "declaration" + return I18n.t("validations.privacynotice.missing") if id == "privacynotice" I18n.t("validations.not_answered", question: display_label.downcase) end diff --git a/app/models/form/sales/pages/age1.rb b/app/models/form/sales/pages/age1.rb index de9b1ef8d..5aed5c4f3 100644 --- a/app/models/form/sales/pages/age1.rb +++ b/app/models/form/sales/pages/age1.rb @@ -2,6 +2,14 @@ class Form::Sales::Pages::Age1 < ::Form::Page def initialize(id, hsh, subsection) super @id = "buyer_1_age" + @depends_on = [ + { + "privacynotice" => 1, + }, + { + "noint" => 1, + }, + ] end def questions diff --git a/app/models/form/sales/pages/age2.rb b/app/models/form/sales/pages/age2.rb index d849c1465..ff0666988 100644 --- a/app/models/form/sales/pages/age2.rb +++ b/app/models/form/sales/pages/age2.rb @@ -2,9 +2,16 @@ class Form::Sales::Pages::Age2 < ::Form::Page def initialize(id, hsh, subsection) super @id = "buyer_2_age" - @depends_on = [{ - "jointpur" => 1, - }] + @depends_on = [ + { + "jointpur" => 1, + "privacynotice" => 1, + }, + { + "jointpur" => 1, + "noint" => 1, + }, + ] end def questions diff --git a/app/models/form/sales/pages/buyer1_ethnic_group.rb b/app/models/form/sales/pages/buyer1_ethnic_group.rb index af0269290..73d66b328 100644 --- a/app/models/form/sales/pages/buyer1_ethnic_group.rb +++ b/app/models/form/sales/pages/buyer1_ethnic_group.rb @@ -2,6 +2,14 @@ class Form::Sales::Pages::Buyer1EthnicGroup < ::Form::Page def initialize(id, hsh, subsection) super @id = "buyer_1_ethnic_group" + @depends_on = [ + { + "privacynotice" => 1, + }, + { + "noint" => 1, + }, + ] end def questions diff --git a/app/models/form/sales/pages/buyer1_live_in_property.rb b/app/models/form/sales/pages/buyer1_live_in_property.rb index 0d780ffed..deb6275af 100644 --- a/app/models/form/sales/pages/buyer1_live_in_property.rb +++ b/app/models/form/sales/pages/buyer1_live_in_property.rb @@ -2,6 +2,14 @@ class Form::Sales::Pages::Buyer1LiveInProperty < ::Form::Page def initialize(id, hsh, subsection) super @id = "buyer_1_live_in_property" + @depends_on = [ + { + "privacynotice" => 1, + }, + { + "noint" => 1, + }, + ] end def questions diff --git a/app/models/form/sales/pages/buyer1_working_situation.rb b/app/models/form/sales/pages/buyer1_working_situation.rb index 8d56caa47..66cf38e69 100644 --- a/app/models/form/sales/pages/buyer1_working_situation.rb +++ b/app/models/form/sales/pages/buyer1_working_situation.rb @@ -2,6 +2,14 @@ class Form::Sales::Pages::Buyer1WorkingSituation < ::Form::Page def initialize(id, hsh, subsection) super @id = "buyer_1_working_situation" + @depends_on = [ + { + "privacynotice" => 1, + }, + { + "noint" => 1, + }, + ] end def questions diff --git a/app/models/form/sales/pages/buyer2_live_in_property.rb b/app/models/form/sales/pages/buyer2_live_in_property.rb index 8904c21f2..9ba67080b 100644 --- a/app/models/form/sales/pages/buyer2_live_in_property.rb +++ b/app/models/form/sales/pages/buyer2_live_in_property.rb @@ -2,9 +2,16 @@ class Form::Sales::Pages::Buyer2LiveInProperty < ::Form::Page def initialize(id, hsh, subsection) super @id = "buyer_2_live_in_property" - @depends_on = [{ - "jointpur" => 1, - }] + @depends_on = [ + { + "jointpur" => 1, + "privacynotice" => 1, + }, + { + "jointpur" => 1, + "noint" => 1, + }, + ] end def questions diff --git a/app/models/form/sales/pages/buyer2_relationship_to_buyer1.rb b/app/models/form/sales/pages/buyer2_relationship_to_buyer1.rb index 026486883..7a1612d1f 100644 --- a/app/models/form/sales/pages/buyer2_relationship_to_buyer1.rb +++ b/app/models/form/sales/pages/buyer2_relationship_to_buyer1.rb @@ -2,9 +2,16 @@ class Form::Sales::Pages::Buyer2RelationshipToBuyer1 < ::Form::Page def initialize(id, hsh, subsection) super @id = "buyer_2_relationship_to_buyer_1" - @depends_on = [{ - "jointpur" => 1, - }] + @depends_on = [ + { + "jointpur" => 1, + "privacynotice" => 1, + }, + { + "jointpur" => 1, + "noint" => 1, + }, + ] end def questions diff --git a/app/models/form/sales/pages/buyer2_working_situation.rb b/app/models/form/sales/pages/buyer2_working_situation.rb index 4523c34cc..704fc0eac 100644 --- a/app/models/form/sales/pages/buyer2_working_situation.rb +++ b/app/models/form/sales/pages/buyer2_working_situation.rb @@ -2,9 +2,16 @@ class Form::Sales::Pages::Buyer2WorkingSituation < ::Form::Page def initialize(id, hsh, subsection) super @id = "buyer_2_working_situation" - @depends_on = [{ - "jointpur" => 1, - }] + @depends_on = [ + { + "jointpur" => 1, + "privacynotice" => 1, + }, + { + "jointpur" => 1, + "noint" => 1, + }, + ] end def questions diff --git a/app/models/form/sales/pages/deposit_and_mortgage_value_check.rb b/app/models/form/sales/pages/deposit_and_mortgage_value_check.rb new file mode 100644 index 000000000..338889030 --- /dev/null +++ b/app/models/form/sales/pages/deposit_and_mortgage_value_check.rb @@ -0,0 +1,17 @@ +class Form::Sales::Pages::DepositAndMortgageValueCheck < ::Form::Page + def initialize(id, hsh, subsection) + super + @depends_on = [ + { + "mortgage_plus_deposit_less_than_discounted_value?" => true, + }, + ] + @informative_text = {} + end + + def questions + @questions ||= [ + Form::Sales::Questions::DepositAndMortgageValueCheck.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/gender_identity1.rb b/app/models/form/sales/pages/gender_identity1.rb index 2dadce8ba..a9d333cf4 100644 --- a/app/models/form/sales/pages/gender_identity1.rb +++ b/app/models/form/sales/pages/gender_identity1.rb @@ -2,6 +2,14 @@ class Form::Sales::Pages::GenderIdentity1 < ::Form::Page def initialize(id, hsh, subsection) super @id = "buyer_1_gender_identity" + @depends_on = [ + { + "privacynotice" => 1, + }, + { + "noint" => 1, + }, + ] end def questions diff --git a/app/models/form/sales/pages/gender_identity2.rb b/app/models/form/sales/pages/gender_identity2.rb index 3d72842c9..f79c3dc4a 100644 --- a/app/models/form/sales/pages/gender_identity2.rb +++ b/app/models/form/sales/pages/gender_identity2.rb @@ -2,9 +2,16 @@ class Form::Sales::Pages::GenderIdentity2 < ::Form::Page def initialize(id, hsh, subsection) super @id = "buyer_2_gender_identity" - @depends_on = [{ - "jointpur" => 1, - }] + @depends_on = [ + { + "jointpur" => 1, + "privacynotice" => 1, + }, + { + "jointpur" => 1, + "noint" => 1, + }, + ] end def questions diff --git a/app/models/form/sales/pages/monthly_charges_value_check.rb b/app/models/form/sales/pages/monthly_charges_value_check.rb new file mode 100644 index 000000000..909212b52 --- /dev/null +++ b/app/models/form/sales/pages/monthly_charges_value_check.rb @@ -0,0 +1,21 @@ +class Form::Sales::Pages::MonthlyChargesValueCheck < ::Form::Page + def initialize(id, hsh, subsection) + super + @depends_on = [ + { + "monthly_charges_over_soft_max?" => true, + }, + ] + @title_text = { + "translation" => "soft_validations.monthly_charges_over_soft_max.title_text", + "arguments" => [], + } + @informative_text = {} + end + + def questions + @questions ||= [ + Form::Sales::Questions::MonthlyChargesValueCheck.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/nationality1.rb b/app/models/form/sales/pages/nationality1.rb index 65bba7c6a..96723e857 100644 --- a/app/models/form/sales/pages/nationality1.rb +++ b/app/models/form/sales/pages/nationality1.rb @@ -2,6 +2,14 @@ class Form::Sales::Pages::Nationality1 < ::Form::Page def initialize(id, hsh, subsection) super @id = "buyer_1_nationality" + @depends_on = [ + { + "privacynotice" => 1, + }, + { + "noint" => 1, + }, + ] end def questions diff --git a/app/models/form/sales/pages/number_of_others_in_property.rb b/app/models/form/sales/pages/number_of_others_in_property.rb index ed82303ec..c090422fd 100644 --- a/app/models/form/sales/pages/number_of_others_in_property.rb +++ b/app/models/form/sales/pages/number_of_others_in_property.rb @@ -2,6 +2,14 @@ class Form::Sales::Pages::NumberOfOthersInProperty < ::Form::Page def initialize(id, hsh, subsection) super @id = "number_of_others_in_property" + @depends_on = [ + { + "privacynotice" => 1, + }, + { + "noint" => 1, + }, + ] end def questions diff --git a/app/models/form/sales/pages/staircase_bought_value_check.rb b/app/models/form/sales/pages/staircase_bought_value_check.rb new file mode 100644 index 000000000..b7b8178ff --- /dev/null +++ b/app/models/form/sales/pages/staircase_bought_value_check.rb @@ -0,0 +1,27 @@ +class Form::Sales::Pages::StaircaseBoughtValueCheck < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "staircase_bought_value_check" + @depends_on = [ + { + "staircase_bought_above_fifty?" => true, + }, + ] + @title_text = { + "translation" => "soft_validations.staircase_bought_seems_high", + "arguments" => [ + { + "key" => "stairbought", + "i18n_template" => "percentage", + }, + ], + } + @informative_text = {} + end + + def questions + @questions ||= [ + Form::Sales::Questions::StaircaseBoughtValueCheck.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/questions/deposit_and_mortgage_value_check.rb b/app/models/form/sales/questions/deposit_and_mortgage_value_check.rb new file mode 100644 index 000000000..263cf4342 --- /dev/null +++ b/app/models/form/sales/questions/deposit_and_mortgage_value_check.rb @@ -0,0 +1,23 @@ +class Form::Sales::Questions::DepositAndMortgageValueCheck < ::Form::Question + def initialize(id, hsh, page) + super + @id = "deposit_and_mortgage_value_check" + @check_answer_label = "Deposit and mortgage against discount confirmation" + @header = "Are you sure? Mortgage and deposit usually equal or are more than (value - discount)" + @type = "interruption_screen" + @answer_options = { + "0" => { "value" => "Yes" }, + "1" => { "value" => "No" }, + } + @hidden_in_check_answers = { + "depends_on" => [ + { + "deposit_and_mortgage_value_check" => 0, + }, + { + "deposit_and_mortgage_value_check" => 1, + }, + ], + } + end +end diff --git a/app/models/form/sales/questions/discounted_ownership_type.rb b/app/models/form/sales/questions/discounted_ownership_type.rb index 2cbef6117..f75d83c01 100644 --- a/app/models/form/sales/questions/discounted_ownership_type.rb +++ b/app/models/form/sales/questions/discounted_ownership_type.rb @@ -9,7 +9,7 @@ class Form::Sales::Questions::DiscountedOwnershipType < ::Form::Question end ANSWER_OPTIONS = { - "8" => { "value" => "Right to Aquire (RTA)" }, + "8" => { "value" => "Right to Acquire (RTA)" }, "14" => { "value" => "Preserved Right to Buy (PRTB)" }, "27" => { "value" => "Voluntary Right to Buy (VRTB)" }, "9" => { "value" => "Right to Buy (RTB)" }, diff --git a/app/models/form/sales/questions/monthly_charges_value_check.rb b/app/models/form/sales/questions/monthly_charges_value_check.rb new file mode 100644 index 000000000..90fcf4545 --- /dev/null +++ b/app/models/form/sales/questions/monthly_charges_value_check.rb @@ -0,0 +1,23 @@ +class Form::Sales::Questions::MonthlyChargesValueCheck < ::Form::Question + def initialize(id, hsh, page) + super + @id = "monthly_charges_value_check" + @check_answer_label = "Monthly charges 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" => [ + { + "monthly_charges_value_check" => 0, + }, + { + "monthly_charges_value_check" => 1, + }, + ], + } + end +end diff --git a/app/models/form/sales/questions/privacy_notice.rb b/app/models/form/sales/questions/privacy_notice.rb index 5bc84bce6..4d7bb61c6 100644 --- a/app/models/form/sales/questions/privacy_notice.rb +++ b/app/models/form/sales/questions/privacy_notice.rb @@ -6,7 +6,6 @@ class Form::Sales::Questions::PrivacyNotice < ::Form::Question @header = "Declaration" @type = "checkbox" @answer_options = ANSWER_OPTIONS - @guidance_position = GuidancePosition::TOP @guidance_partial = "privacy_notice_buyer" end diff --git a/app/models/form/sales/questions/staircase_bought.rb b/app/models/form/sales/questions/staircase_bought.rb index bdb5ac965..385cfb782 100644 --- a/app/models/form/sales/questions/staircase_bought.rb +++ b/app/models/form/sales/questions/staircase_bought.rb @@ -8,6 +8,6 @@ class Form::Sales::Questions::StaircaseBought < ::Form::Question @width = 5 @min = 0 @max = 100 - @suffix = " percent" + @suffix = "%" end end diff --git a/app/models/form/sales/questions/staircase_bought_value_check.rb b/app/models/form/sales/questions/staircase_bought_value_check.rb new file mode 100644 index 000000000..65fe02e66 --- /dev/null +++ b/app/models/form/sales/questions/staircase_bought_value_check.rb @@ -0,0 +1,23 @@ +class Form::Sales::Questions::StaircaseBoughtValueCheck < ::Form::Question + def initialize(id, hsh, page) + super + @id = "staircase_bought_value_check" + @check_answer_label = "Percentage bought 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" => [ + { + "staircase_bought_value_check" => 0, + }, + { + "staircase_bought_value_check" => 1, + }, + ], + } + end +end diff --git a/app/models/form/sales/questions/staircase_owned.rb b/app/models/form/sales/questions/staircase_owned.rb index 6f9e13b14..7e6dab5d6 100644 --- a/app/models/form/sales/questions/staircase_owned.rb +++ b/app/models/form/sales/questions/staircase_owned.rb @@ -8,6 +8,6 @@ class Form::Sales::Questions::StaircaseOwned < ::Form::Question @width = 5 @min = 0 @max = 100 - @suffix = " percent" + @suffix = "%" end end diff --git a/app/models/form/sales/subsections/discounted_ownership_scheme.rb b/app/models/form/sales/subsections/discounted_ownership_scheme.rb index 2ab47e729..4bac90bde 100644 --- a/app/models/form/sales/subsections/discounted_ownership_scheme.rb +++ b/app/models/form/sales/subsections/discounted_ownership_scheme.rb @@ -14,9 +14,11 @@ class Form::Sales::Subsections::DiscountedOwnershipScheme < ::Form::Subsection Form::Sales::Pages::AboutPriceNotRtb.new(nil, nil, self), Form::Sales::Pages::GrantValueCheck.new(nil, nil, self), Form::Sales::Pages::PurchasePrice.new("purchase_price_discounted_ownership", nil, self), + Form::Sales::Pages::DepositAndMortgageValueCheck.new("discounted_ownership_deposit_and_mortgage_value_check_after_value_and_discount", nil, self), Form::Sales::Pages::Mortgageused.new("mortgage_used_discounted_ownership", nil, self), Form::Sales::Pages::MortgageAmount.new("mortgage_amount_discounted_ownership", nil, self), Form::Sales::Pages::ExtraBorrowingValueCheck.new("extra_borrowing_mortgage_value_check", nil, self), + Form::Sales::Pages::DepositAndMortgageValueCheck.new("discounted_ownership_deposit_and_mortgage_value_check_after_mortgage", nil, self), Form::Sales::Pages::MortgageLender.new("mortgage_lender_discounted_ownership", nil, self), Form::Sales::Pages::MortgageLenderOther.new("mortgage_lender_other_discounted_ownership", nil, self), Form::Sales::Pages::MortgageLength.new("mortgage_length_discounted_ownership", nil, self), @@ -25,7 +27,9 @@ class Form::Sales::Subsections::DiscountedOwnershipScheme < ::Form::Subsection Form::Sales::Pages::AboutDepositWithoutDiscount.new("about_deposit_discounted_ownership", nil, self), Form::Sales::Pages::ExtraBorrowingValueCheck.new("extra_borrowing_deposit_value_check", nil, self), Form::Sales::Pages::DepositValueCheck.new("discounted_ownership_deposit_value_check", nil, self), + Form::Sales::Pages::DepositAndMortgageValueCheck.new("discounted_ownership_deposit_and_mortgage_value_check_after_deposit", nil, self), Form::Sales::Pages::LeaseholdCharges.new("leasehold_charges_discounted_ownership", nil, self), + Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_discounted_ownership_value_check", nil, self), ] end diff --git a/app/models/form/sales/subsections/outright_sale.rb b/app/models/form/sales/subsections/outright_sale.rb index 82099dab6..c6586a251 100644 --- a/app/models/form/sales/subsections/outright_sale.rb +++ b/app/models/form/sales/subsections/outright_sale.rb @@ -18,6 +18,7 @@ class Form::Sales::Subsections::OutrightSale < ::Form::Subsection Form::Sales::Pages::AboutDepositWithoutDiscount.new("about_deposit_outright_sale", nil, self), Form::Sales::Pages::DepositValueCheck.new("outright_sale_deposit_value_check", nil, self), Form::Sales::Pages::LeaseholdCharges.new("leasehold_charges_outright_sale", nil, self), + Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_outright_sale_value_check", nil, self), ] end diff --git a/app/models/form/sales/subsections/property_information.rb b/app/models/form/sales/subsections/property_information.rb index c1c150b9f..801ebcf16 100644 --- a/app/models/form/sales/subsections/property_information.rb +++ b/app/models/form/sales/subsections/property_information.rb @@ -10,6 +10,7 @@ class Form::Sales::Subsections::PropertyInformation < ::Form::Subsection @pages ||= [ Form::Sales::Pages::PropertyNumberOfBedrooms.new(nil, 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::PropertyBuildingType.new(nil, nil, self), Form::Sales::Pages::Postcode.new(nil, nil, self), Form::Sales::Pages::PropertyLocalAuthority.new(nil, nil, self), diff --git a/app/models/form/sales/subsections/setup.rb b/app/models/form/sales/subsections/setup.rb index cb147e9c2..22dcda8b9 100644 --- a/app/models/form/sales/subsections/setup.rb +++ b/app/models/form/sales/subsections/setup.rb @@ -16,6 +16,7 @@ class Form::Sales::Subsections::Setup < ::Form::Subsection Form::Sales::Pages::DiscountedOwnershipType.new(nil, nil, self), Form::Sales::Pages::OutrightOwnershipType.new(nil, nil, self), Form::Sales::Pages::OldPersonsSharedOwnershipValueCheck.new("ownership_type_old_persons_shared_ownership_value_check", nil, self), + Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_type_value_check", nil, self), Form::Sales::Pages::BuyerCompany.new(nil, nil, self), Form::Sales::Pages::BuyerLive.new(nil, nil, self), Form::Sales::Pages::JointPurchase.new(nil, nil, self), diff --git a/app/models/form/sales/subsections/shared_ownership_scheme.rb b/app/models/form/sales/subsections/shared_ownership_scheme.rb index e7ff1cf6e..d83e54e52 100644 --- a/app/models/form/sales/subsections/shared_ownership_scheme.rb +++ b/app/models/form/sales/subsections/shared_ownership_scheme.rb @@ -11,6 +11,7 @@ class Form::Sales::Subsections::SharedOwnershipScheme < ::Form::Subsection Form::Sales::Pages::LivingBeforePurchase.new("living_before_purchase_shared_ownership", nil, self), Form::Sales::Pages::Staircase.new(nil, nil, self), Form::Sales::Pages::AboutStaircase.new(nil, nil, self), + Form::Sales::Pages::StaircaseBoughtValueCheck.new(nil, nil, self), Form::Sales::Pages::Resale.new(nil, nil, self), Form::Sales::Pages::ExchangeDate.new(nil, nil, self), Form::Sales::Pages::HandoverDate.new(nil, nil, self), @@ -35,6 +36,7 @@ class Form::Sales::Subsections::SharedOwnershipScheme < ::Form::Subsection Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_deposit_value_check", nil, self), Form::Sales::Pages::MonthlyRent.new(nil, nil, self), Form::Sales::Pages::LeaseholdCharges.new("leasehold_charges_shared_ownership", nil, self), + Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_shared_ownership_value_check", nil, self), ] end diff --git a/app/models/log.rb b/app/models/log.rb index fcde08bdb..45f3e9607 100644 --- a/app/models/log.rb +++ b/app/models/log.rb @@ -108,7 +108,6 @@ private return unless form form.reset_not_routed_questions(self) - reset_created_by! end diff --git a/app/models/sales_log.rb b/app/models/sales_log.rb index 23004b5c2..1a7d7edcd 100644 --- a/app/models/sales_log.rb +++ b/app/models/sales_log.rb @@ -1,4 +1,5 @@ class SalesLogValidator < ActiveModel::Validator + include Validations::Sales::SetupValidations include Validations::Sales::HouseholdValidations include Validations::Sales::PropertyValidations include Validations::SharedValidations @@ -23,18 +24,18 @@ class SalesLog < Log has_paper_trail validates_with SalesLogValidator - before_validation :set_derived_fields! before_validation :reset_invalidated_dependent_fields! before_validation :process_postcode_changes!, if: :postcode_full_changed? before_validation :process_previous_postcode_changes!, if: :ppostcode_full_changed? before_validation :reset_location_fields!, unless: :postcode_known? before_validation :reset_previous_location_fields!, unless: :previous_postcode_known? + before_validation :set_derived_fields! scope :filter_by_year, ->(year) { where(saledate: Time.zone.local(year.to_i, 4, 1)...Time.zone.local(year.to_i + 1, 4, 1)) } scope :search_by, ->(param) { filter_by_id(param) } scope :filter_by_organisation, ->(org, _user = nil) { where(owning_organisation: org) } - OPTIONAL_FIELDS = %w[purchid old_persons_shared_ownership_value_check].freeze + OPTIONAL_FIELDS = %w[purchid monthly_charges_value_check old_persons_shared_ownership_value_check].freeze RETIREMENT_AGES = { "M" => 65, "F" => 60, "X" => 65 }.freeze def startdate diff --git a/app/models/validations/sales/financial_validations.rb b/app/models/validations/sales/financial_validations.rb index e16565d0a..623cd44b9 100644 --- a/app/models/validations/sales/financial_validations.rb +++ b/app/models/validations/sales/financial_validations.rb @@ -27,4 +27,21 @@ module Validations::Sales::FinancialValidations record.errors.add :cashdis, I18n.t("validations.financial.cash_discount_invalid") end end + + def validate_percentage_bought_not_greater_than_percentage_owned(record) + return unless record.stairbought && record.stairowned + + if record.stairbought > record.stairowned + record.errors.add :stairowned, I18n.t("validations.financial.staircasing.percentage_bought_must_be_greater_than_percentage_owned") + end + end + + def validate_percentage_owned_not_too_much_if_older_person(record) + return unless record.old_persons_shared_ownership? && record.stairowned + + if record.stairowned > 75 + record.errors.add :stairowned, I18n.t("validations.financial.staircasing.older_person_percentage_owned_maximum_75") + record.errors.add :type, I18n.t("validations.financial.staircasing.older_person_percentage_owned_maximum_75") + end + end end diff --git a/app/models/validations/sales/sale_information_validations.rb b/app/models/validations/sales/sale_information_validations.rb index 178101d97..f9aae074f 100644 --- a/app/models/validations/sales/sale_information_validations.rb +++ b/app/models/validations/sales/sale_information_validations.rb @@ -3,7 +3,7 @@ module Validations::Sales::SaleInformationValidations return if record.saledate.blank? || record.hodate.blank? unless record.saledate > record.hodate - record.errors.add :hodate, "Practical completion or handover date must be before exchange date" + record.errors.add :hodate, I18n.t("validations.sale_information.hodate.must_be_before_exdate") end end @@ -23,11 +23,15 @@ module Validations::Sales::SaleInformationValidations def validate_exchange_date(record) return unless record.exdate && record.saledate - record.errors.add(:exdate, I18n.t("validations.sale_information.exdate.must_be_before_saledate")) if record.exdate > record.saledate - - return if (record.saledate.to_date - record.exdate.to_date).to_i / 365 < 1 + if record.exdate > record.saledate + record.errors.add :exdate, I18n.t("validations.sale_information.exdate.must_be_before_saledate") + record.errors.add :saledate, I18n.t("validations.sale_information.saledate.must_be_after_exdate") + end - record.errors.add(:exdate, I18n.t("validations.sale_information.exdate.must_be_less_than_1_year_from_saledate")) + if record.saledate - record.exdate >= 1.year + record.errors.add :exdate, I18n.t("validations.sale_information.exdate.must_be_less_than_1_year_from_saledate") + record.errors.add :saledate, I18n.t("validations.sale_information.saledate.must_be_less_than_1_year_from_exdate") + end end def validate_previous_property_unit_type(record) diff --git a/app/models/validations/sales/setup_validations.rb b/app/models/validations/sales/setup_validations.rb new file mode 100644 index 000000000..0e7a759ee --- /dev/null +++ b/app/models/validations/sales/setup_validations.rb @@ -0,0 +1,9 @@ +module Validations::Sales::SetupValidations + def validate_saledate(record) + return unless record.saledate + + unless Time.zone.local(2022, 4, 1) <= record.saledate && record.saledate < Time.zone.local(2023, 4, 1) + record.errors.add :saledate, I18n.t("validations.setup.saledate.financial_year") + end + end +end diff --git a/app/models/validations/sales/soft_validations.rb b/app/models/validations/sales/soft_validations.rb index 857d0fa07..b3ba6770e 100644 --- a/app/models/validations/sales/soft_validations.rb +++ b/app/models/validations/sales/soft_validations.rb @@ -13,6 +13,10 @@ module Validations::Sales::SoftValidations income1 < ALLOWED_INCOME_RANGES[ecstat1] [:soft_min] end + def staircase_bought_above_fifty? + stairbought && stairbought > 50 + end + def mortgage_over_soft_max? return false unless mortgage && inc1mort && inc2mort return false if income1_used_for_mortgage? && income1.blank? || income2_used_for_mortgage? && income2.blank? @@ -53,6 +57,13 @@ module Validations::Sales::SoftValidations mortgage_value + deposit + cash_discount != value * equity / 100 end + def mortgage_plus_deposit_less_than_discounted_value? + return unless mortgage && deposit && value && discount + + discounted_value = value * (100 - discount) / 100 + mortgage + deposit < discounted_value + end + def hodate_3_years_or_more_saledate? return unless hodate && saledate @@ -64,4 +75,11 @@ module Validations::Sales::SoftValidations !grant.between?(9_000, 16_000) end + + def monthly_charges_over_soft_max? + return unless type && mscharge && proptype + + soft_max = old_persons_shared_ownership? ? 550 : 300 + mscharge > soft_max + end end diff --git a/app/services/bulk_upload/lettings/validator.rb b/app/services/bulk_upload/lettings/validator.rb index b96739836..90da6efb3 100644 --- a/app/services/bulk_upload/lettings/validator.rb +++ b/app/services/bulk_upload/lettings/validator.rb @@ -1,6 +1,9 @@ require "csv" class BulkUpload::Lettings::Validator + COLUMN_PERCENTAGE_ERROR_THRESHOLD = 0.6 + COLUMN_ABSOLUTE_ERROR_THRESHOLD = 16 + include ActiveModel::Validations QUESTIONS = { @@ -171,7 +174,10 @@ class BulkUpload::Lettings::Validator end def create_logs? - row_parsers.all?(&:valid?) + return false if any_setup_sections_incomplete? + return false if over_column_error_threshold? + + row_parsers.all? { |row_parser| row_parser.log.valid? } end def self.question_for_field(field) @@ -180,6 +186,23 @@ class BulkUpload::Lettings::Validator private + def any_setup_sections_incomplete? + row_parsers.any? { |row_parser| row_parser.log.form.setup_sections[0].subsections[0].is_incomplete?(row_parser.log) } + end + + def over_column_error_threshold? + fields = ("field_1".."field_134").to_a + percentage_threshold = (row_parsers.size * COLUMN_PERCENTAGE_ERROR_THRESHOLD).ceil + + fields.any? do |field| + count = row_parsers.count { |row_parser| row_parser.errors[field].present? } + + next if count < COLUMN_ABSOLUTE_ERROR_THRESHOLD + + count > percentage_threshold + end + end + def csv_parser @csv_parser ||= BulkUpload::Lettings::CsvParser.new(path:) end diff --git a/app/views/bulk_upload_lettings_results/show.html.erb b/app/views/bulk_upload_lettings_results/show.html.erb index 452926787..84374e26a 100644 --- a/app/views/bulk_upload_lettings_results/show.html.erb +++ b/app/views/bulk_upload_lettings_results/show.html.erb @@ -1,6 +1,10 @@ +<% content_for :before_content do %> + <%= govuk_back_link(text: "Back", href: summary_bulk_upload_lettings_result_path(@bulk_upload)) %> +<% end %> +
- Bulk Upload for lettings (<%= @bulk_upload.year_combo %>) + Bulk upload for lettings (<%= @bulk_upload.year_combo %>)

We found <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> in your file

diff --git a/app/views/bulk_upload_lettings_results/summary.html.erb b/app/views/bulk_upload_lettings_results/summary.html.erb new file mode 100644 index 000000000..04d8bdb92 --- /dev/null +++ b/app/views/bulk_upload_lettings_results/summary.html.erb @@ -0,0 +1,22 @@ +
+
+ Bulk upload for lettings (<%= @bulk_upload.year_combo %>) +

Correct data export and reupload

+ +

+ We noticed that you have a lot of similar errors for some questions. You can download the specification which we reference below to understand how to correct the data. Once you have fixed these errors you can upload again. +

+
+
+ +<%= render BulkUploadErrorSummaryTableComponent.new(bulk_upload: @bulk_upload) %> + +
+
+

+ You also have other errors in your file which you can either fix them in the CSV file or you can reupload and fix on CORE. <%= govuk_link_to "View the full report", bulk_upload_lettings_result_path(@bulk_upload) %> +

+
+
+ +<%= govuk_button_link_to "Upload your file again", start_bulk_upload_lettings_logs_path %> diff --git a/config/locales/en.yml b/config/locales/en.yml index c92c9574c..03fb7ef7f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -147,6 +147,8 @@ en: setup: intermediate_rent_product_name: blank: "Enter name of other intermediate rent product" + saledate: + financial_year: "Date must be from 22/23 financial year" startdate: later_than_14_days_after: "The tenancy start date must not be later than 14 days from today’s date" before_scheme_end_date: "The tenancy start date must be before the end date for this supported housing scheme" @@ -277,6 +279,10 @@ en: out_of_range: "Household rent and other charges must be between %{min_chcharge} and %{max_chcharge} if paying %{period}" not_provided: "Enter how much rent and other charges the household pays %{period}" cash_discount_invalid: "Cash discount must be £0 - £999,999" + staircasing: + percentage_bought_must_be_greater_than_percentage_owned: "Total percentage buyer now owns must be more than percentage bought in this transaction" + older_person_percentage_owned_maximum_75: "Percentage cannot be above 75% under Older Person's Shared Ownership" + household: reasonpref: not_homeless: "Answer cannot be ‘homeless or about to lose their home’ as the tenant was not homeless immediately prior to this letting" @@ -386,6 +392,9 @@ en: declaration: missing: "You must show the DLUHC privacy notice to the tenant before you can submit this log." + privacynotice: + missing: "You must show the DLUHC privacy notice to the buyer before you can submit this log." + scheme: toggle_date: not_selected: "Select one of the options" @@ -417,11 +426,14 @@ en: proplen: social_homebuy: "Social HomeBuy buyers should not have lived here before" rent_to_buy: "Rent to Buy buyers should not have lived here before" + hodate: + must_be_before_exdate: "Practical completion or handover date must be before exchange date" exdate: - must_be_before_saledate: - Contract exchange date must be less than 1 year before completion date - must_be_less_than_1_year_from_saledate: - Contract exchange date must be less than 1 year before completion date + must_be_before_saledate: "Contract exchange date must be less than 1 year before completion date" + must_be_less_than_1_year_from_saledate: "Contract exchange date must be less than 1 year before completion date" + saledate: + must_be_after_exdate: "Completion date must be less than 1 year after contract exchange date" + must_be_less_than_1_year_from_exdate: "Completion date must be less than 1 year after contract exchange date" previous_property_beds: property_type_bedsit: "Number of bedrooms must be 1 if the property is a bedsit" discounted_ownership_value: "Mortgage, deposit, and grant total must equal £%{value_with_discount}" @@ -464,6 +476,8 @@ en: shared_owhership_deposit: title_text: "Mortgage, deposit and cash discount total should equal £%{expected_shared_ownership_deposit_value}" old_persons_shared_ownership: "At least one buyer should be aged over 64 for Older persons’ shared ownership scheme" + monthly_charges_over_soft_max: + title_text: "The amount of monthly charges is high for this type of property and sale type" devise: two_factor_authentication: diff --git a/config/routes.rb b/config/routes.rb index 913212dd2..be765c8c0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -137,6 +137,7 @@ Rails.application.routes.draw do resources :bulk_upload_lettings_results, path: "bulk-upload-results", only: [:show] do member do get :resume + get :summary end end diff --git a/db/migrate/20230120163049_add_deposit_and_mortgage_value_check_to_sales_logs.rb b/db/migrate/20230120163049_add_deposit_and_mortgage_value_check_to_sales_logs.rb new file mode 100644 index 000000000..a9a80a4bb --- /dev/null +++ b/db/migrate/20230120163049_add_deposit_and_mortgage_value_check_to_sales_logs.rb @@ -0,0 +1,5 @@ +class AddDepositAndMortgageValueCheckToSalesLogs < ActiveRecord::Migration[7.0] + def change + add_column :sales_logs, :deposit_and_mortgage_value_check, :integer + end +end diff --git a/db/migrate/20230125152916_add_staircase_bought_value_check_to_sales_log.rb b/db/migrate/20230125152916_add_staircase_bought_value_check_to_sales_log.rb new file mode 100644 index 000000000..d7e227afa --- /dev/null +++ b/db/migrate/20230125152916_add_staircase_bought_value_check_to_sales_log.rb @@ -0,0 +1,5 @@ +class AddStaircaseBoughtValueCheckToSalesLog < ActiveRecord::Migration[7.0] + def change + add_column :sales_logs, :staircase_bought_value_check, :integer + end +end diff --git a/db/migrate/20230127102334_add_monthly_charges_value_check.rb b/db/migrate/20230127102334_add_monthly_charges_value_check.rb new file mode 100644 index 000000000..a8b118022 --- /dev/null +++ b/db/migrate/20230127102334_add_monthly_charges_value_check.rb @@ -0,0 +1,7 @@ +class AddMonthlyChargesValueCheck < ActiveRecord::Migration[7.0] + def change + change_table :sales_logs, bulk: true do |t| + t.column :monthly_charges_value_check, :integer + end + end +end diff --git a/db/schema.rb b/db/schema.rb index d62ca39e9..95e7799a1 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_01_26_145529) do +ActiveRecord::Schema[7.0].define(version: 2023_01_27_102334) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -504,9 +504,12 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_26_145529) do t.integer "retirement_value_check" t.integer "hodate_check" t.integer "extrabor_value_check" - t.integer "shared_ownership_deposit_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 "old_persons_shared_ownership_value_check" + t.integer "monthly_charges_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 ["owning_organisation_id"], name: "index_sales_logs_on_owning_organisation_id" diff --git a/db/seeds.rb b/db/seeds.rb index 6a4098f9b..15d84a38d 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -148,7 +148,7 @@ unless Rails.env.test? if (Rails.env.development? || Rails.env.review?) && SalesLog.count.zero? SalesLog.find_or_create_by!( - saledate: Date.new(1, 1, 1), + saledate: Date.new(2023, 1, 1), purchid: "1", ownershipsch: 1, type: 2, @@ -157,7 +157,7 @@ unless Rails.env.test? ) SalesLog.find_or_create_by!( - saledate: Date.new(1, 1, 1), + saledate: Date.new(2023, 1, 1), purchid: "1", ownershipsch: 2, type: 9, @@ -166,7 +166,7 @@ unless Rails.env.test? ) SalesLog.find_or_create_by!( - saledate: Date.new(1, 1, 1), + saledate: Date.new(2023, 1, 1), purchid: "1", ownershipsch: 3, type: 10, diff --git a/spec/components/bulk_upload_error_summary_table_component_spec.rb b/spec/components/bulk_upload_error_summary_table_component_spec.rb new file mode 100644 index 000000000..cdb0b58bf --- /dev/null +++ b/spec/components/bulk_upload_error_summary_table_component_spec.rb @@ -0,0 +1,81 @@ +require "rails_helper" + +RSpec.describe BulkUploadErrorSummaryTableComponent, type: :component do + subject(:component) { described_class.new(bulk_upload:) } + + let(:bulk_upload) { create(:bulk_upload) } + + context "when no errors" do + it "does not renders any rows" do + result = render_inline(component) + expect(result).not_to have_selector("tbody tr") + end + end + + context "when there are 2 independent errors" do + let!(:error_2) { create(:bulk_upload_error, bulk_upload:, col: "B", row: 2) } + let!(:error_1) { create(:bulk_upload_error, bulk_upload:, col: "A", row: 1) } + + it "renders rows for each error" do + result = render_inline(component) + expect(result).to have_selector("tbody tr", count: 2) + end + + it "renders rows by col order" do + result = render_inline(component) + order = result.css("tbody tr td:nth-of-type(1)").map(&:content) + expect(order).to eql(%w[A B]) + end + + it "render correct data" do + result = render_inline(component) + + row_1 = result.css("tbody tr:nth-of-type(1) td").map(&:content) + + expect(row_1).to eql([ + "A", + "1", + BulkUpload::Lettings::Validator.question_for_field(error_1.field.to_sym), + error_1.error, + error_1.field, + ]) + + row_2 = result.css("tbody tr:nth-of-type(2) td").map(&:content) + + expect(row_2).to eql([ + "B", + "1", + BulkUpload::Lettings::Validator.question_for_field(error_2.field.to_sym), + error_2.error, + error_2.field, + ]) + end + end + + context "when there are 2 grouped errors" do + let!(:error_1) { create(:bulk_upload_error, bulk_upload:, col: "A", row: 1, field: "field_1") } + + before do + create(:bulk_upload_error, bulk_upload:, col: "A", row: 2, field: "field_1") + end + + it "renders 1 row combining the errors" do + result = render_inline(component) + expect(result).to have_selector("tbody tr", count: 1) + end + + it "render correct data" do + result = render_inline(component) + + row_1 = result.css("tbody tr:nth-of-type(1) td").map(&:content) + + expect(row_1).to eql([ + "A", + "2", + BulkUpload::Lettings::Validator.question_for_field(error_1.field.to_sym), + error_1.error, + error_1.field, + ]) + end + end +end diff --git a/spec/factories/bulk_upload_error.rb b/spec/factories/bulk_upload_error.rb index 1f42f763d..885f27d9d 100644 --- a/spec/factories/bulk_upload_error.rb +++ b/spec/factories/bulk_upload_error.rb @@ -4,7 +4,8 @@ FactoryBot.define do factory :bulk_upload_error do bulk_upload row { rand(9_999) } - cell { "#{('A'..'Z').to_a.sample}#{row}" } + col { ("A".."Z").to_a.sample } + cell { "#{col}#{row}" } tenant_code { SecureRandom.hex(4) } property_ref { SecureRandom.hex(4) } purchaser_code { SecureRandom.hex(4) } diff --git a/spec/factories/sales_log.rb b/spec/factories/sales_log.rb index f83d102d3..085d4ca03 100644 --- a/spec/factories/sales_log.rb +++ b/spec/factories/sales_log.rb @@ -2,18 +2,18 @@ FactoryBot.define do factory :sales_log do created_by { FactoryBot.create(:user) } owning_organisation { created_by.organisation } - created_at { Time.utc(2022, 2, 8, 16, 52, 15) } - updated_at { Time.utc(2022, 2, 8, 16, 52, 15) } + created_at { Time.utc(2023, 2, 8, 16, 52, 15) } + updated_at { Time.utc(2023, 2, 8, 16, 52, 15) } trait :in_progress do purchid { "PC123" } ownershipsch { 2 } type { 8 } - saledate { Time.utc(2022, 2, 2, 10, 36, 49) } + saledate { Time.utc(2023, 2, 2, 10, 36, 49) } end trait :completed do ownershipsch { 2 } type { 8 } - saledate { Time.utc(2022, 2, 2, 10, 36, 49) } + saledate { Time.utc(2023, 2, 2, 10, 36, 49) } companybuy { 1 } jointpur { 1 } beds { 2 } diff --git a/spec/models/form/sales/pages/age1_spec.rb b/spec/models/form/sales/pages/age1_spec.rb index 0c8130bad..6205610a0 100644 --- a/spec/models/form/sales/pages/age1_spec.rb +++ b/spec/models/form/sales/pages/age1_spec.rb @@ -28,6 +28,6 @@ RSpec.describe Form::Sales::Pages::Age1, type: :model do end it "has correct depends_on" do - expect(page.depends_on).to be_nil + expect(page.depends_on).to eq([{ "privacynotice" => 1 }, { "noint" => 1 }]) end end diff --git a/spec/models/form/sales/pages/age2_spec.rb b/spec/models/form/sales/pages/age2_spec.rb index eabb6a2e8..2645b3724 100644 --- a/spec/models/form/sales/pages/age2_spec.rb +++ b/spec/models/form/sales/pages/age2_spec.rb @@ -28,8 +28,15 @@ RSpec.describe Form::Sales::Pages::Age2, type: :model do end it "has correct depends_on" do - expect(page.depends_on).to eq([{ - "jointpur" => 1, - }]) + expect(page.depends_on).to eq([ + { + "jointpur" => 1, + "privacynotice" => 1, + }, + { + "jointpur" => 1, + "noint" => 1, + }, + ]) end end diff --git a/spec/models/form/sales/pages/buyer1_ethnic_group_spec.rb b/spec/models/form/sales/pages/buyer1_ethnic_group_spec.rb index e6e6798f5..52bbaff53 100644 --- a/spec/models/form/sales/pages/buyer1_ethnic_group_spec.rb +++ b/spec/models/form/sales/pages/buyer1_ethnic_group_spec.rb @@ -28,6 +28,6 @@ RSpec.describe Form::Sales::Pages::Buyer1EthnicGroup, type: :model do end it "has correct depends_on" do - expect(page.depends_on).to be_nil + expect(page.depends_on).to eq([{ "privacynotice" => 1 }, { "noint" => 1 }]) end end diff --git a/spec/models/form/sales/pages/buyer1_live_in_property_spec.rb b/spec/models/form/sales/pages/buyer1_live_in_property_spec.rb index 444f14909..6bdd4f5e2 100644 --- a/spec/models/form/sales/pages/buyer1_live_in_property_spec.rb +++ b/spec/models/form/sales/pages/buyer1_live_in_property_spec.rb @@ -28,6 +28,6 @@ RSpec.describe Form::Sales::Pages::Buyer1LiveInProperty, type: :model do end it "has correct depends_on" do - expect(page.depends_on).to be_nil + expect(page.depends_on).to eq([{ "privacynotice" => 1 }, { "noint" => 1 }]) end end diff --git a/spec/models/form/sales/pages/buyer2_live_in_property_spec.rb b/spec/models/form/sales/pages/buyer2_live_in_property_spec.rb index 34644241f..258e37462 100644 --- a/spec/models/form/sales/pages/buyer2_live_in_property_spec.rb +++ b/spec/models/form/sales/pages/buyer2_live_in_property_spec.rb @@ -28,6 +28,15 @@ RSpec.describe Form::Sales::Pages::Buyer2LiveInProperty, type: :model do end it "has correct depends_on" do - expect(page.depends_on).to eq([{ "jointpur" => 1 }]) + expect(page.depends_on).to eq([ + { + "jointpur" => 1, + "privacynotice" => 1, + }, + { + "jointpur" => 1, + "noint" => 1, + }, + ]) end end diff --git a/spec/models/form/sales/pages/buyer2_relationship_to_buyer1_spec.rb b/spec/models/form/sales/pages/buyer2_relationship_to_buyer1_spec.rb index eef011089..a518a1dc4 100644 --- a/spec/models/form/sales/pages/buyer2_relationship_to_buyer1_spec.rb +++ b/spec/models/form/sales/pages/buyer2_relationship_to_buyer1_spec.rb @@ -28,6 +28,15 @@ RSpec.describe Form::Sales::Pages::Buyer2RelationshipToBuyer1, type: :model do end it "has correct depends_on" do - expect(page.depends_on).to eq([{ "jointpur" => 1 }]) + expect(page.depends_on).to eq([ + { + "jointpur" => 1, + "privacynotice" => 1, + }, + { + "jointpur" => 1, + "noint" => 1, + }, + ]) end end diff --git a/spec/models/form/sales/pages/buyer2_working_situation_spec.rb b/spec/models/form/sales/pages/buyer2_working_situation_spec.rb index fe339091b..59c16c583 100644 --- a/spec/models/form/sales/pages/buyer2_working_situation_spec.rb +++ b/spec/models/form/sales/pages/buyer2_working_situation_spec.rb @@ -28,6 +28,15 @@ RSpec.describe Form::Sales::Pages::Buyer2WorkingSituation, type: :model do end it "has correct depends_on" do - expect(page.depends_on).to eq([{ "jointpur" => 1 }]) + expect(page.depends_on).to eq([ + { + "jointpur" => 1, + "privacynotice" => 1, + }, + { + "jointpur" => 1, + "noint" => 1, + }, + ]) end end diff --git a/spec/models/form/sales/pages/gender_identity1_spec.rb b/spec/models/form/sales/pages/gender_identity1_spec.rb index 2b06f2349..5188bb4ae 100644 --- a/spec/models/form/sales/pages/gender_identity1_spec.rb +++ b/spec/models/form/sales/pages/gender_identity1_spec.rb @@ -28,6 +28,6 @@ RSpec.describe Form::Sales::Pages::GenderIdentity1, type: :model do end it "has correct depends_on" do - expect(page.depends_on).to be_nil + expect(page.depends_on).to eq([{ "privacynotice" => 1 }, { "noint" => 1 }]) end end diff --git a/spec/models/form/sales/pages/gender_identity2_spec.rb b/spec/models/form/sales/pages/gender_identity2_spec.rb index 7698f151c..54e7ab565 100644 --- a/spec/models/form/sales/pages/gender_identity2_spec.rb +++ b/spec/models/form/sales/pages/gender_identity2_spec.rb @@ -28,8 +28,15 @@ RSpec.describe Form::Sales::Pages::GenderIdentity2, type: :model do end it "has correct depends_on" do - expect(page.depends_on).to eq([{ - "jointpur" => 1, - }]) + expect(page.depends_on).to eq([ + { + "jointpur" => 1, + "privacynotice" => 1, + }, + { + "jointpur" => 1, + "noint" => 1, + }, + ]) end end diff --git a/spec/models/form/sales/pages/monthly_charges_value_check_spec.rb b/spec/models/form/sales/pages/monthly_charges_value_check_spec.rb new file mode 100644 index 000000000..ac378dc82 --- /dev/null +++ b/spec/models/form/sales/pages/monthly_charges_value_check_spec.rb @@ -0,0 +1,48 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::MonthlyChargesValueCheck, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { "monthly_charges_value_check" } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[monthly_charges_value_check]) + end + + it "has the correct id" do + expect(page.id).to eq("monthly_charges_value_check") + end + + it "has the correct header" do + expect(page.header).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([ + { + "monthly_charges_over_soft_max?" => true, + }, + ]) + end + + it "is interruption screen page" do + expect(page.interruption_screen?).to eq(true) + end + + it "has correct title_text" do + expect(page.title_text).to eq({ + "translation" => "soft_validations.monthly_charges_over_soft_max.title_text", + "arguments" => [], + }) + end + + it "has correct informative_text" do + expect(page.informative_text).to eq({}) + end +end diff --git a/spec/models/form/sales/pages/nationality1_spec.rb b/spec/models/form/sales/pages/nationality1_spec.rb index c39fecdd2..da5a5f802 100644 --- a/spec/models/form/sales/pages/nationality1_spec.rb +++ b/spec/models/form/sales/pages/nationality1_spec.rb @@ -28,6 +28,6 @@ RSpec.describe Form::Sales::Pages::Nationality1, type: :model do end it "has correct depends_on" do - expect(page.depends_on).to be_nil + expect(page.depends_on).to eq([{ "privacynotice" => 1 }, { "noint" => 1 }]) end end diff --git a/spec/models/form/sales/questions/discounted_ownership_type_spec.rb b/spec/models/form/sales/questions/discounted_ownership_type_spec.rb index 51acd2fb5..d1c074023 100644 --- a/spec/models/form/sales/questions/discounted_ownership_type_spec.rb +++ b/spec/models/form/sales/questions/discounted_ownership_type_spec.rb @@ -33,7 +33,7 @@ RSpec.describe Form::Sales::Questions::DiscountedOwnershipType, type: :model do it "has the correct answer_options" do expect(question.answer_options).to eq({ - "8" => { "value" => "Right to Aquire (RTA)" }, + "8" => { "value" => "Right to Acquire (RTA)" }, "14" => { "value" => "Preserved Right to Buy (PRTB)" }, "27" => { "value" => "Voluntary Right to Buy (VRTB)" }, "9" => { "value" => "Right to Buy (RTB)" }, diff --git a/spec/models/form/sales/questions/monthly_charges_value_check_spec.rb b/spec/models/form/sales/questions/monthly_charges_value_check_spec.rb new file mode 100644 index 000000000..a62bac569 --- /dev/null +++ b/spec/models/form/sales/questions/monthly_charges_value_check_spec.rb @@ -0,0 +1,57 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::MonthlyChargesValueCheck, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { "monthly_charges_value_check" } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page) } + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("monthly_charges_value_check") + end + + it "has the correct header" do + expect(question.header).to eq("Are you sure this is correct?") + end + + it "has the correct check_answer_label" do + expect(question.check_answer_label).to eq("Monthly charges confirmation") + end + + it "has the correct type" do + expect(question.type).to eq("interruption_screen") + end + + it "is not marked as derived" do + expect(question.derived?).to be false + end + + it "has the correct hint" do + expect(question.hint_text).to be_nil + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq({ + "0" => { "value" => "Yes" }, + "1" => { "value" => "No" }, + }) + end + + it "has the correct hidden_in_check_answers" do + expect(question.hidden_in_check_answers).to eq({ + "depends_on" => [ + { + "monthly_charges_value_check" => 0, + }, + { + "monthly_charges_value_check" => 1, + }, + ], + }) + end +end diff --git a/spec/models/form/sales/questions/privacy_notice_spec.rb b/spec/models/form/sales/questions/privacy_notice_spec.rb index fc1df796c..1fec22b6e 100644 --- a/spec/models/form/sales/questions/privacy_notice_spec.rb +++ b/spec/models/form/sales/questions/privacy_notice_spec.rb @@ -40,4 +40,8 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do "privacynotice" => { "value" => "The buyer has seen the DLUHC privacy notice" }, }) end + + it "returns correct unanswered_error_message" do + expect(question.unanswered_error_message).to eq("You must show the DLUHC privacy notice to the buyer before you can submit this log.") + end end diff --git a/spec/models/form/sales/questions/staircase_bought_spec.rb b/spec/models/form/sales/questions/staircase_bought_spec.rb index 96716dbd3..7b877cc26 100644 --- a/spec/models/form/sales/questions/staircase_bought_spec.rb +++ b/spec/models/form/sales/questions/staircase_bought_spec.rb @@ -44,7 +44,7 @@ RSpec.describe Form::Sales::Questions::StaircaseBought, type: :model do end it "has correct suffix" do - expect(question.suffix).to eq(" percent") + expect(question.suffix).to eq("%") end it "has correct min" do diff --git a/spec/models/form/sales/questions/staircase_owned_spec.rb b/spec/models/form/sales/questions/staircase_owned_spec.rb index 56b563990..cbd577784 100644 --- a/spec/models/form/sales/questions/staircase_owned_spec.rb +++ b/spec/models/form/sales/questions/staircase_owned_spec.rb @@ -44,7 +44,7 @@ RSpec.describe Form::Sales::Questions::StaircaseOwned, type: :model do end it "has correct suffix" do - expect(question.suffix).to eq(" percent") + expect(question.suffix).to eq("%") end it "has correct min" do diff --git a/spec/models/form/sales/subsections/discounted_ownership_scheme_spec.rb b/spec/models/form/sales/subsections/discounted_ownership_scheme_spec.rb index 7df741091..f2891e801 100644 --- a/spec/models/form/sales/subsections/discounted_ownership_scheme_spec.rb +++ b/spec/models/form/sales/subsections/discounted_ownership_scheme_spec.rb @@ -20,9 +20,11 @@ RSpec.describe Form::Sales::Subsections::DiscountedOwnershipScheme, type: :model about_price_not_rtb grant_value_check purchase_price_discounted_ownership + discounted_ownership_deposit_and_mortgage_value_check_after_value_and_discount mortgage_used_discounted_ownership mortgage_amount_discounted_ownership extra_borrowing_mortgage_value_check + discounted_ownership_deposit_and_mortgage_value_check_after_mortgage mortgage_lender_discounted_ownership mortgage_lender_other_discounted_ownership mortgage_length_discounted_ownership @@ -31,7 +33,9 @@ RSpec.describe Form::Sales::Subsections::DiscountedOwnershipScheme, type: :model about_deposit_discounted_ownership extra_borrowing_deposit_value_check discounted_ownership_deposit_value_check + discounted_ownership_deposit_and_mortgage_value_check_after_deposit leasehold_charges_discounted_ownership + monthly_charges_discounted_ownership_value_check ], ) end diff --git a/spec/models/form/sales/subsections/outright_sale_spec.rb b/spec/models/form/sales/subsections/outright_sale_spec.rb index 45a9f4ca0..239fbe38e 100644 --- a/spec/models/form/sales/subsections/outright_sale_spec.rb +++ b/spec/models/form/sales/subsections/outright_sale_spec.rb @@ -24,6 +24,7 @@ RSpec.describe Form::Sales::Subsections::OutrightSale, type: :model do about_deposit_outright_sale outright_sale_deposit_value_check leasehold_charges_outright_sale + monthly_charges_outright_sale_value_check ], ) end diff --git a/spec/models/form/sales/subsections/property_information_spec.rb b/spec/models/form/sales/subsections/property_information_spec.rb index 00b901142..a17eaafe9 100644 --- a/spec/models/form/sales/subsections/property_information_spec.rb +++ b/spec/models/form/sales/subsections/property_information_spec.rb @@ -16,6 +16,7 @@ RSpec.describe Form::Sales::Subsections::PropertyInformation, type: :model do %w[ property_number_of_bedrooms property_unit_type + monthly_charges_property_type_value_check property_building_type property_postcode property_local_authority diff --git a/spec/models/form/sales/subsections/setup_spec.rb b/spec/models/form/sales/subsections/setup_spec.rb index 4bb95e391..ac4f0246c 100644 --- a/spec/models/form/sales/subsections/setup_spec.rb +++ b/spec/models/form/sales/subsections/setup_spec.rb @@ -23,6 +23,7 @@ RSpec.describe Form::Sales::Subsections::Setup, type: :model do discounted_ownership_type outright_ownership_type ownership_type_old_persons_shared_ownership_value_check + monthly_charges_type_value_check buyer_company buyer_live joint_purchase diff --git a/spec/models/form/sales/subsections/shared_ownership_scheme_spec.rb b/spec/models/form/sales/subsections/shared_ownership_scheme_spec.rb index d131bc605..6da1dfbcd 100644 --- a/spec/models/form/sales/subsections/shared_ownership_scheme_spec.rb +++ b/spec/models/form/sales/subsections/shared_ownership_scheme_spec.rb @@ -17,6 +17,7 @@ RSpec.describe Form::Sales::Subsections::SharedOwnershipScheme, type: :model do living_before_purchase_shared_ownership staircasing about_staircasing + staircase_bought_value_check resale exchange_contracts handover_date @@ -41,6 +42,7 @@ RSpec.describe Form::Sales::Subsections::SharedOwnershipScheme, type: :model do shared_ownership_deposit_value_check monthly_rent leasehold_charges_shared_ownership + monthly_charges_shared_ownership_value_check ], ) end diff --git a/spec/models/form_handler_spec.rb b/spec/models/form_handler_spec.rb index f08e79ca4..ae005a209 100644 --- a/spec/models/form_handler_spec.rb +++ b/spec/models/form_handler_spec.rb @@ -52,14 +52,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(194) + expect(form.pages.count).to eq(203) 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(194) + expect(form.pages.count).to eq(203) expect(form.name).to eq("2021_2022_sales") end end diff --git a/spec/models/form_spec.rb b/spec/models/form_spec.rb index fee42f1bc..71dfd4b60 100644 --- a/spec/models/form_spec.rb +++ b/spec/models/form_spec.rb @@ -218,9 +218,9 @@ RSpec.describe Form, type: :model do expect(form.sections[0].class).to eq(Form::Sales::Sections::Setup) expect(form.subsections.count).to eq(1) expect(form.subsections.first.id).to eq("setup") - expect(form.pages.count).to eq(13) + expect(form.pages.count).to eq(14) expect(form.pages.first.id).to eq("organisation") - expect(form.questions.count).to eq(14) + expect(form.questions.count).to eq(15) expect(form.questions.first.id).to eq("owning_organisation_id") expect(form.start_date).to eq(Time.zone.parse("2022-04-01")) expect(form.end_date).to eq(Time.zone.parse("2023-07-01")) diff --git a/spec/models/sales_log_spec.rb b/spec/models/sales_log_spec.rb index 3924feb65..0b4fa4459 100644 --- a/spec/models/sales_log_spec.rb +++ b/spec/models/sales_log_spec.rb @@ -47,7 +47,7 @@ RSpec.describe SalesLog, type: :model do let(:sales_log) { build(:sales_log) } it "returns optional fields" do - expect(sales_log.optional_fields).to eq(%w[purchid old_persons_shared_ownership_value_check]) + expect(sales_log.optional_fields).to eq(%w[purchid monthly_charges_value_check old_persons_shared_ownership_value_check]) end end @@ -109,7 +109,7 @@ RSpec.describe SalesLog, type: :model do let(:sales_log) { FactoryBot.create(:sales_log, :completed) } it "correctly derives and saves exday, exmonth and exyear" do - sales_log.update!(exdate: Time.gm(2022, 5, 4)) + sales_log.update!(exdate: Time.gm(2022, 5, 4), saledate: Time.gm(2022, 7, 4), ownershipsch: 1, staircase: 2, resale: 2) 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) @@ -140,6 +140,14 @@ RSpec.describe SalesLog, type: :model do expect(record_from_db["pcode1"]).to eq("W6") expect(record_from_db["pcode2"]).to eq("0SP") end + + it "derives a mortgage value of 0 when mortgage is not used" do + # to avoid log failing validations when mortgage value is removed: + new_grant_value = sales_log.grant + sales_log.mortgage + sales_log.update!(mortgageused: 2, grant: new_grant_value) + record_from_db = ActiveRecord::Base.connection.execute("select mortgage from sales_logs where id=#{sales_log.id}").to_a[0] + expect(record_from_db["mortgage"]).to eq(0.0) + end end context "when saving addresses" do @@ -238,39 +246,49 @@ RSpec.describe SalesLog, type: :model do end context "when deriving household variables" do - let!(:household_lettings_log) do - described_class.create!({ + let!(:sales_log) do + FactoryBot.create( + :sales_log, + :completed, jointpur: 1, - hholdcount: 3, + hholdcount: 4, + details_known_1: 1, + details_known_2: 1, + details_known_3: 1, + details_known_4: 1, relat2: "C", relat3: "C", relat4: "X", relat5: "X", - age1: 22, - age2: 40, - age3: 19, + relat6: "P", + ecstat2: 9, + ecstat3: 7, + age1: 47, + age2: 14, + age3: 17, age4: 88, - age5: 14, - }) + age5: 19, + age6: 46, + ) end it "correctly derives and saves hhmemb" do - record_from_db = ActiveRecord::Base.connection.execute("select hhmemb from sales_logs where id=#{household_lettings_log.id}").to_a[0] - expect(record_from_db["hhmemb"]).to eq(5) + record_from_db = ActiveRecord::Base.connection.execute("select hhmemb from sales_logs where id=#{sales_log.id}").to_a[0] + expect(record_from_db["hhmemb"]).to eq(6) end it "correctly derives and saves totchild" do - record_from_db = ActiveRecord::Base.connection.execute("select totchild from sales_logs where id=#{household_lettings_log.id}").to_a[0] + record_from_db = ActiveRecord::Base.connection.execute("select totchild from sales_logs where id=#{sales_log.id}").to_a[0] expect(record_from_db["totchild"]).to eq(2) end it "correctly derives and saves totadult" do - record_from_db = ActiveRecord::Base.connection.execute("select totadult from sales_logs where id=#{household_lettings_log.id}").to_a[0] - expect(record_from_db["totadult"]).to eq(3) + record_from_db = ActiveRecord::Base.connection.execute("select totadult from sales_logs where id=#{sales_log.id}").to_a[0] + expect(record_from_db["totadult"]).to eq(4) end it "correctly derives and saves hhtype" do - record_from_db = ActiveRecord::Base.connection.execute("select hhtype from sales_logs where id=#{household_lettings_log.id}").to_a[0] + record_from_db = ActiveRecord::Base.connection.execute("select hhtype from sales_logs where id=#{sales_log.id}").to_a[0] expect(record_from_db["hhtype"]).to eq(9) end end diff --git a/spec/models/validations/sales/financial_validations_spec.rb b/spec/models/validations/sales/financial_validations_spec.rb index a8f0f118a..10a48c67b 100644 --- a/spec/models/validations/sales/financial_validations_spec.rb +++ b/spec/models/validations/sales/financial_validations_spec.rb @@ -99,4 +99,63 @@ RSpec.describe Validations::Sales::FinancialValidations do expect(record.errors["cashdis"]).to be_empty end end + + describe "#validate_percentage_bought_not_greater_than_percentage_owned" do + let(:record) { FactoryBot.create(:sales_log) } + + it "does not add an error if the percentage bought is less than the percentage owned" 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 + 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 + end + + it "adds an error to stairowned and not stairbought if the percentage bought is more than the percentage owned" do + record.stairbought = 50 + record.stairowned = 40 + financial_validator.validate_percentage_bought_not_greater_than_percentage_owned(record) + expect(record.errors["stairowned"]).to include(match I18n.t("validations.financial.staircasing.percentage_bought_must_be_greater_than_percentage_owned")) + end + end + + describe "#validate_percentage_owned_not_too_much_if_older_person" do + let(:record) { FactoryBot.create(:sales_log) } + + context "when log type is not older persons shared ownership" do + it "does not add an error when percentage owned after staircasing transaction exceeds 75%" 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 + end + end + + context "when log type is older persons shared ownership" do + it "does not add an error when percentage owned after staircasing transaction is less than 75%" 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 + end + + it "adds an error when percentage owned after staircasing transaction exceeds 75%" do + record.type = 24 + record.stairowned = 90 + financial_validator.validate_percentage_owned_not_too_much_if_older_person(record) + expect(record.errors["stairowned"]).to include(match I18n.t("validations.financial.staircasing.older_person_percentage_owned_maximum_75")) + expect(record.errors["type"]).to include(match I18n.t("validations.financial.staircasing.older_person_percentage_owned_maximum_75")) + end + end + end end diff --git a/spec/models/validations/sales/sale_information_validations_spec.rb b/spec/models/validations/sales/sale_information_validations_spec.rb index 82afd6837..37f4b22e1 100644 --- a/spec/models/validations/sales/sale_information_validations_spec.rb +++ b/spec/models/validations/sales/sale_information_validations_spec.rb @@ -111,12 +111,15 @@ RSpec.describe Validations::Sales::SaleInformationValidations do context "when exdate more than 1 year before saledate" do let(:record) { build(:sales_log, exdate: 2.years.ago, saledate: 1.month.ago) } - it "does not add the error" do + it "adds error" do sale_information_validator.validate_exchange_date(record) expect(record.errors[:exdate]).to eq( ["Contract exchange date must be less than 1 year before completion date"], ) + expect(record.errors[:saledate]).to eq( + ["Completion date must be less than 1 year after contract exchange date"], + ) end end @@ -129,6 +132,9 @@ RSpec.describe Validations::Sales::SaleInformationValidations do expect(record.errors[:exdate]).to eq( ["Contract exchange date must be less than 1 year before completion date"], ) + expect(record.errors[:saledate]).to eq( + ["Completion date must be less than 1 year after contract exchange date"], + ) end end diff --git a/spec/models/validations/sales/setup_validations_spec.rb b/spec/models/validations/sales/setup_validations_spec.rb new file mode 100644 index 000000000..74b7834ab --- /dev/null +++ b/spec/models/validations/sales/setup_validations_spec.rb @@ -0,0 +1,49 @@ +require "rails_helper" + +RSpec.describe Validations::Sales::SetupValidations do + subject(:setup_validator) { validator_class.new } + + let(:validator_class) { Class.new { include Validations::Sales::SetupValidations } } + + describe "#validate_saledate" do + context "when saledate is blank" do + let(:record) { FactoryBot.build(:sales_log, saledate: nil) } + + it "does not add an error" do + setup_validator.validate_saledate(record) + + expect(record.errors).to be_empty + end + end + + context "when saledate is in the 22/23 financial year" do + let(:record) { FactoryBot.build(:sales_log, saledate: Time.zone.local(2023, 1, 1)) } + + it "does not add an error" do + setup_validator.validate_saledate(record) + + expect(record.errors).to be_empty + end + end + + context "when saledate is before the 22/23 financial year" do + let(:record) { FactoryBot.build(:sales_log, saledate: Time.zone.local(2022, 1, 1)) } + + it "adds error" do + setup_validator.validate_saledate(record) + + expect(record.errors[:saledate]).to include(I18n.t("validations.setup.saledate.financial_year")) + end + end + + context "when saledate is after the 22/23 financial year" do + let(:record) { FactoryBot.build(:sales_log, saledate: Time.zone.local(2023, 4, 1)) } + + it "adds error" do + setup_validator.validate_saledate(record) + + expect(record.errors[:saledate]).to include(I18n.t("validations.setup.saledate.financial_year")) + end + end + end +end diff --git a/spec/models/validations/sales/soft_validations_spec.rb b/spec/models/validations/sales/soft_validations_spec.rb index b0b253c15..40e658c73 100644 --- a/spec/models/validations/sales/soft_validations_spec.rb +++ b/spec/models/validations/sales/soft_validations_spec.rb @@ -201,6 +201,59 @@ RSpec.describe Validations::Sales::SoftValidations do end end + context "when validating mortgage and deposit against discounted value" do + [ + { + nil_field: "mortgage", + value: 500_000, + deposit: 10_000, + discount: 10, + }, + { + nil_field: "value", + mortgage: 100_000, + deposit: 10_000, + discount: 10, + }, + { + nil_field: "deposit", + value: 500_000, + mortgage: 100_000, + discount: 10, + }, + { + nil_field: "discount", + value: 500_000, + mortgage: 100_000, + deposit: 10_000, + }, + ].each do |test_case| + it "returns false if #{test_case[:nil_field]} is not present" do + record.value = test_case[:value] + record.mortgage = test_case[:mortgage] + record.deposit = test_case[:deposit] + record.discount = test_case[:discount] + expect(record).not_to be_mortgage_plus_deposit_less_than_discounted_value + end + end + + it "returns false if the deposit and mortgage add up to the discounted value or more" do + record.value = 500_000 + record.discount = 20 + record.mortgage = 200_000 + record.deposit = 200_000 + expect(record).not_to be_mortgage_plus_deposit_less_than_discounted_value + end + + it "returns true if the deposit and mortgage add up to less than the discounted value" do + record.value = 500_000 + record.discount = 10 + record.mortgage = 200_000 + record.deposit = 200_000 + expect(record).to be_mortgage_plus_deposit_less_than_discounted_value + end + end + context "when validating extra borrowing" do it "returns false if extrabor not present" do record.mortgage = 50_000 @@ -314,7 +367,7 @@ RSpec.describe Validations::Sales::SoftValidations do .to be_deposit_over_soft_max end - it "returns fals if deposit is less than 4/3 of savings" do + it "returns false if deposit is less than 4/3 of savings" do record.deposit = 7_999 record.savings = 6_000 expect(record) @@ -520,4 +573,86 @@ RSpec.describe Validations::Sales::SoftValidations do expect(record).not_to be_grant_outside_common_range end end + + describe "#staircase_bought_above_fifty" do + it "returns false when stairbought is not set" do + record.stairbought = nil + + expect(record).not_to be_staircase_bought_above_fifty + end + + it "returns false when stairbought is below fifty" do + record.stairbought = 40 + + expect(record).not_to be_staircase_bought_above_fifty + end + + it "returns true when stairbought is above fifty" do + record.stairbought = 70 + + expect(record).to be_staircase_bought_above_fifty + end + end + + describe "#monthly_charges_over_soft_max?" do + it "returns false if mscharge is not given" do + record.mscharge = nil + record.proptype = 4 + record.type = 2 + + expect(record).not_to be_monthly_charges_over_soft_max + end + + it "returns false if proptype is not given" do + record.mscharge = 999 + record.proptype = nil + record.type = 2 + + expect(record).not_to be_monthly_charges_over_soft_max + end + + it "returns false if type is not given" do + record.mscharge = 999 + record.proptype = 4 + record.type = nil + + expect(record).not_to be_monthly_charges_over_soft_max + end + + context "with old persons shared ownership" do + it "returns false if the monthly charge is under 550" do + record.mscharge = 540 + record.proptype = 4 + record.type = 24 + + expect(record).not_to be_monthly_charges_over_soft_max + end + + it "returns true if the monthly charge is over 550" do + record.mscharge = 999 + record.proptype = 4 + record.type = 24 + + expect(record).to be_monthly_charges_over_soft_max + end + end + + context "with non old persons type of ownership" do + it "returns false if the monthly charge is under 300" do + record.mscharge = 280 + record.proptype = 4 + record.type = 18 + + expect(record).not_to be_monthly_charges_over_soft_max + end + + it "returns true if the monthly charge is over 300" do + record.mscharge = 400 + record.proptype = 4 + record.type = 18 + + expect(record).to be_monthly_charges_over_soft_max + end + end + end end diff --git a/spec/models/validations/shared_validations_spec.rb b/spec/models/validations/shared_validations_spec.rb index abdde5f45..c1d8cb3af 100644 --- a/spec/models/validations/shared_validations_spec.rb +++ b/spec/models/validations/shared_validations_spec.rb @@ -70,11 +70,11 @@ RSpec.describe Validations::SharedValidations do end context "when validating percent" do - it "validates that % suffix is added in the error message" do - sales_record.stairbought = "random" + it "validates that suffixes are added in the error message" do + sales_record.stairbought = 150 shared_validator.validate_numeric_min_max(sales_record) expect(sales_record.errors["stairbought"]) - .to include(match I18n.t("validations.numeric.valid", field: "Percentage bought in this staircasing transaction", min: "0 percent", max: "100 percent")) + .to include(match I18n.t("validations.numeric.valid", field: "Percentage bought in this staircasing transaction", min: "0%", max: "100%")) end end diff --git a/spec/requests/bulk_upload_lettings_results_controller_spec.rb b/spec/requests/bulk_upload_lettings_results_controller_spec.rb index 15ba0b7bb..c15fef5b9 100644 --- a/spec/requests/bulk_upload_lettings_results_controller_spec.rb +++ b/spec/requests/bulk_upload_lettings_results_controller_spec.rb @@ -9,12 +9,27 @@ RSpec.describe BulkUploadLettingsResultsController, type: :request do sign_in user end + describe "GET /lettings-logs/bulk-upload-results/:ID/summary" do + it "renders year combo" do + get "/lettings-logs/bulk-upload-results/#{bulk_upload.id}/summary" + + expect(response).to be_successful + expect(response.body).to include("Bulk upload for lettings (2022/23)") + end + + it "renders the bulk upload filename" do + get "/lettings-logs/bulk-upload-results/#{bulk_upload.id}/summary" + + expect(response.body).to include(bulk_upload.filename) + end + end + describe "GET /lettings-logs/bulk-upload-results/:ID" do it "renders correct year" do get "/lettings-logs/bulk-upload-results/#{bulk_upload.id}" expect(response).to be_successful - expect(response.body).to include("Bulk Upload for lettings (2022/23)") + expect(response.body).to include("Bulk upload for lettings (2022/23)") end it "renders correct number of errors" do diff --git a/spec/requests/sales_logs_controller_spec.rb b/spec/requests/sales_logs_controller_spec.rb index f8690a71b..925e3f1f3 100644 --- a/spec/requests/sales_logs_controller_spec.rb +++ b/spec/requests/sales_logs_controller_spec.rb @@ -179,29 +179,29 @@ RSpec.describe SalesLogsController, type: :request do end context "with year filter" do - let!(:sales_log_2021) do + let!(:sales_log_2022) do FactoryBot.create(:sales_log, :in_progress, owning_organisation: organisation, - saledate: Time.zone.local(2022, 3, 1)) + saledate: Time.zone.local(2022, 4, 1)) end - let!(:sales_log_2022) do + let!(:sales_log_2023) do sales_log = FactoryBot.build(:sales_log, :completed, owning_organisation: organisation, - saledate: Time.zone.local(2022, 12, 1)) + saledate: Time.zone.local(2023, 1, 1)) sales_log.save!(validate: false) sales_log end it "shows sales logs for multiple selected years" do get "/sales-logs?years[]=2021&years[]=2022", headers: headers, params: {} - expect(page).to have_link(sales_log_2021.id.to_s) expect(page).to have_link(sales_log_2022.id.to_s) + expect(page).to have_link(sales_log_2023.id.to_s) end it "shows sales logs for one selected year" do - get "/sales-logs?years[]=2021", headers: headers, params: {} - expect(page).to have_link(sales_log_2021.id.to_s) - expect(page).not_to have_link(sales_log_2022.id.to_s) + get "/sales-logs?years[]=2022", headers: headers, params: {} + expect(page).to have_link(sales_log_2022.id.to_s) + expect(page).to have_link(sales_log_2023.id.to_s) end end @@ -214,23 +214,23 @@ RSpec.describe SalesLogsController, type: :request do Timecop.unfreeze end - let!(:sales_log_2021) do + let!(:sales_log_2022) do FactoryBot.create(:sales_log, :completed, owning_organisation: organisation, - saledate: Time.zone.local(2022, 3, 1), + saledate: Time.zone.local(2022, 4, 1), created_by: user) end - let!(:sales_log_2022) do + let!(:sales_log_2023) do FactoryBot.create(:sales_log, owning_organisation: organisation, - saledate: Time.zone.local(2022, 12, 1), + saledate: Time.zone.local(2023, 1, 1), created_by: user) end it "shows sales logs for multiple selected statuses and years" do get "/sales-logs?years[]=2021&years[]=2022&status[]=in_progress&status[]=completed", headers: headers, params: {} - expect(page).to have_link(sales_log_2021.id.to_s) expect(page).to have_link(sales_log_2022.id.to_s) + expect(page).to have_link(sales_log_2023.id.to_s) end end end diff --git a/spec/services/bulk_upload/lettings/validator_spec.rb b/spec/services/bulk_upload/lettings/validator_spec.rb index 661119448..ec9b3b7f7 100644 --- a/spec/services/bulk_upload/lettings/validator_spec.rb +++ b/spec/services/bulk_upload/lettings/validator_spec.rb @@ -78,7 +78,7 @@ RSpec.describe BulkUpload::Lettings::Validator do before do file.write(BulkUpload::LogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_csv_row) - file.rewind + file.close end it "creates validation errors" do @@ -112,4 +112,148 @@ RSpec.describe BulkUpload::Lettings::Validator do end end end + + describe "#create_logs?" do + context "when a log is not valid?" do + let(:log_1) { build(:lettings_log, :completed, created_by: user) } + let(:log_2) { build(:lettings_log, :completed, created_by: user) } + + before do + file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0, overrides: { illness: 100 }).to_csv_row) + file.close + end + + it "returns false" do + validator.call + expect(validator).not_to be_create_logs + end + end + + context "when all logs valid?" do + let(:log_1) { build(:lettings_log, :completed, renttype: 1, created_by: user) } + let(:log_2) { build(:lettings_log, :completed, renttype: 1, created_by: user) } + + before do + file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.close + end + + it "returns true" do + validator.call + expect(validator).to be_create_logs + end + end + + context "when a log has incomplete setup secion" do + let(:log) { build(:lettings_log, :in_progress, created_by: user, startdate: Time.zone.local(2022, 5, 1)) } + + before do + file.write(BulkUpload::LogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.close + end + + it "returns false" do + validator.call + expect(validator).not_to be_create_logs + end + end + + context "when a column has error rate below absolute threshold" do + context "when a column is over 60% error threshold" do + let(:log_1) { build(:lettings_log, :completed, renttype: 1, created_by: user) } + let(:log_2) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) } + let(:log_3) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) } + let(:log_4) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) } + let(:log_5) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) } + + before do + file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.close + end + + it "returns true" do + validator.call + expect(validator).to be_create_logs + end + end + + context "when a column is under 60% error threshold" do + let(:log_1) { build(:lettings_log, :completed, renttype: 1, created_by: user) } + let(:log_2) { build(:lettings_log, :completed, renttype: 1, created_by: user) } + let(:log_3) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) } + let(:log_4) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) } + let(:log_5) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) } + + before do + file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.close + end + + it "returns true" do + validator.call + expect(validator).to be_create_logs + end + end + end + + context "when a column has error rate above absolute threshold" do + before do + stub_const("BulkUpload::Lettings::Validator::COLUMN_ABSOLUTE_ERROR_THRESHOLD", 1) + end + + context "when a column is over 60% error threshold" do + let(:log_1) { build(:lettings_log, :completed, renttype: 1, created_by: user) } + let(:log_2) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) } + let(:log_3) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) } + let(:log_4) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) } + let(:log_5) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) } + + before do + file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.close + end + + it "returns false" do + validator.call + expect(validator).not_to be_create_logs + end + end + + context "when a column is under 60% error threshold" do + let(:log_1) { build(:lettings_log, :completed, renttype: 1, created_by: user) } + let(:log_2) { build(:lettings_log, :completed, renttype: 1, created_by: user) } + let(:log_3) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) } + let(:log_4) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) } + let(:log_5) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) } + + before do + file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0).to_csv_row) + file.close + end + + it "returns true" do + validator.call + expect(validator).to be_create_logs + end + end + end + end end diff --git a/spec/support/bulk_upload/log_to_csv.rb b/spec/support/bulk_upload/log_to_csv.rb index 7734d9fe2..628ee14b8 100644 --- a/spec/support/bulk_upload/log_to_csv.rb +++ b/spec/support/bulk_upload/log_to_csv.rb @@ -1,10 +1,11 @@ class BulkUpload::LogToCsv - attr_reader :log, :line_ending, :col_offset + attr_reader :log, :line_ending, :col_offset, :overrides - def initialize(log:, line_ending: "\n", col_offset: 1) + def initialize(log:, line_ending: "\n", col_offset: 1, overrides: {}) @log = log @line_ending = line_ending @col_offset = col_offset + @overrides = overrides end def to_csv_row @@ -135,7 +136,7 @@ class BulkUpload::LogToCsv nil, log.incfreq, log.sheltered, - log.illness, + overrides[:illness] || log.illness, log.illness_type_1, log.illness_type_2, # 120