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 %> +
+ 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. +
++ 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) %> +
+