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 a0a962b3e..6f92c0375 100644 --- a/app/controllers/bulk_upload_lettings_results_controller.rb +++ b/app/controllers/bulk_upload_lettings_results_controller.rb @@ -6,4 +6,36 @@ class BulkUploadLettingsResultsController < ApplicationController def show @bulk_upload = current_user.bulk_uploads.lettings.find(params[:id]) end + + def resume + @bulk_upload = current_user.bulk_uploads.lettings.find(params[:id]) + + if @bulk_upload.lettings_logs.in_progress.count.positive? + set_bulk_upload_logs_filters + + redirect_to(lettings_logs_path(bulk_upload_id: [@bulk_upload.id])) + else + reset_logs_filters + end + end + + def summary + @bulk_upload = current_user.bulk_uploads.lettings.find(params[:id]) + end + +private + + def reset_logs_filters + session["logs_filters"] = {}.to_json + end + + def set_bulk_upload_logs_filters + hash = { + years: [""], + status: ["", "in_progress"], + user: "all", + } + + session["logs_filters"] = hash.to_json + end end diff --git a/app/controllers/lettings_logs_controller.rb b/app/controllers/lettings_logs_controller.rb index e7d233f87..a7bc2dc56 100644 --- a/app/controllers/lettings_logs_controller.rb +++ b/app/controllers/lettings_logs_controller.rb @@ -3,6 +3,9 @@ class LettingsLogsController < LogsController before_action :session_filters, if: :current_user before_action :set_session_filters, if: :current_user + before_action :extract_bulk_upload_from_session_filters, only: [:index] + before_action :redirect_if_bulk_upload_resolved, only: [:index] + def index respond_to do |format| format.html do @@ -109,6 +112,17 @@ class LettingsLogsController < LogsController private + def redirect_if_bulk_upload_resolved + if @bulk_upload && @bulk_upload.lettings_logs.in_progress.count.zero? + redirect_to resume_bulk_upload_lettings_result_path(@bulk_upload) + end + end + + def extract_bulk_upload_from_session_filters + id = ((@session_filters["bulk_upload_id"] || []).reject(&:blank?))[0] + @bulk_upload = current_user.bulk_uploads.find_by(id:) + end + def permitted_log_params params.require(:lettings_log).permit(LettingsLog.editable_fields) end diff --git a/app/controllers/modules/logs_filter.rb b/app/controllers/modules/logs_filter.rb index 7c60bb027..06109cfb0 100644 --- a/app/controllers/modules/logs_filter.rb +++ b/app/controllers/modules/logs_filter.rb @@ -7,7 +7,9 @@ module Modules::LogsFilter def load_session_filters(specific_org: false) current_filters = session[:logs_filters] new_filters = current_filters.present? ? JSON.parse(current_filters) : {} - current_user.logs_filters(specific_org:).each { |filter| new_filters[filter] = params[filter] if params[filter].present? } + current_user.logs_filters(specific_org:).each do |filter| + new_filters[filter] = params[filter] if params[filter].present? + end params["organisation_select"] == "all" ? new_filters.except("organisation") : new_filters end diff --git a/app/helpers/logs_helper.rb b/app/helpers/logs_helper.rb index 6567f0a13..88ab2b314 100644 --- a/app/helpers/logs_helper.rb +++ b/app/helpers/logs_helper.rb @@ -18,4 +18,9 @@ module LogsHelper bulk_upload_sales_log_path(id:) end end + + def bulk_upload_options(bulk_upload) + array = bulk_upload ? [bulk_upload.id] : [] + array.index_with { |_bulk_upload_id| "With logs from bulk upload" } + end end 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/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/grant_value_check.rb b/app/models/form/sales/pages/grant_value_check.rb new file mode 100644 index 000000000..a739a1654 --- /dev/null +++ b/app/models/form/sales/pages/grant_value_check.rb @@ -0,0 +1,18 @@ +class Form::Sales::Pages::GrantValueCheck < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "grant_value_check" + @depends_on = [ + { + "grant_outside_common_range?" => true, + }, + ] + @informative_text = {} + end + + def questions + @questions ||= [ + Form::Sales::Questions::GrantValueCheck.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/old_persons_shared_ownership_value_check.rb b/app/models/form/sales/pages/old_persons_shared_ownership_value_check.rb new file mode 100644 index 000000000..9198af18a --- /dev/null +++ b/app/models/form/sales/pages/old_persons_shared_ownership_value_check.rb @@ -0,0 +1,21 @@ +class Form::Sales::Pages::OldPersonsSharedOwnershipValueCheck < ::Form::Page + def initialize(id, hsh, subsection) + super + @depends_on = [ + { + "buyers_age_for_old_persons_shared_ownership_invalid?" => true, + }, + ] + @title_text = { + "translation" => "soft_validations.old_persons_shared_ownership", + "arguments" => [], + } + @informative_text = {} + end + + def questions + @questions ||= [ + Form::Sales::Questions::OldPersonsSharedOwnershipValueCheck.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/shared_ownership_deposit_value_check.rb b/app/models/form/sales/pages/shared_ownership_deposit_value_check.rb new file mode 100644 index 000000000..907968028 --- /dev/null +++ b/app/models/form/sales/pages/shared_ownership_deposit_value_check.rb @@ -0,0 +1,27 @@ +class Form::Sales::Pages::SharedOwnershipDepositValueCheck < ::Form::Page + def initialize(id, hsh, subsection) + super + @depends_on = [ + { + "shared_ownership_deposit_invalid?" => true, + }, + ] + @informative_text = {} + @title_text = { + "translation" => "soft_validations.shared_owhership_deposit.title_text", + "arguments" => [ + { + "key" => "expected_shared_ownership_deposit_value", + "label" => false, + "i18n_template" => "expected_shared_ownership_deposit_value", + }, + ], + } + end + + def questions + @questions ||= [ + Form::Sales::Questions::SharedOwnershipDepositValueCheck.new(nil, nil, self), + ] + end +end 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/grant.rb b/app/models/form/sales/questions/grant.rb index 181e2281d..beef93c58 100644 --- a/app/models/form/sales/questions/grant.rb +++ b/app/models/form/sales/questions/grant.rb @@ -6,6 +6,7 @@ class Form::Sales::Questions::Grant < ::Form::Question @header = "What was the amount of any loan, grant, discount or subsidy given?" @type = "numeric" @min = 0 + @max = 999_999 @width = 5 @prefix = "£" @hint_text = "For all schemes except Right to Buy (RTB), Preserved Right to Buy (PRTB), Voluntary Right to Buy (VRTB)" diff --git a/app/models/form/sales/questions/grant_value_check.rb b/app/models/form/sales/questions/grant_value_check.rb new file mode 100644 index 000000000..a7efdbaee --- /dev/null +++ b/app/models/form/sales/questions/grant_value_check.rb @@ -0,0 +1,23 @@ +class Form::Sales::Questions::GrantValueCheck < ::Form::Question + def initialize(id, hsh, page) + super + @id = "grant_value_check" + @check_answer_label = "Grant value confirmation" + @header = "Are you sure? Grants are usually £9,000 - £16,000" + @type = "interruption_screen" + @answer_options = { + "0" => { "value" => "Yes" }, + "1" => { "value" => "No" }, + } + @hidden_in_check_answers = { + "depends_on" => [ + { + "grant_value_check" => 0, + }, + { + "grant_value_check" => 1, + }, + ], + } + end +end diff --git a/app/models/form/sales/questions/mortgage_length.rb b/app/models/form/sales/questions/mortgage_length.rb index 56a358f01..3f1603b75 100644 --- a/app/models/form/sales/questions/mortgage_length.rb +++ b/app/models/form/sales/questions/mortgage_length.rb @@ -6,6 +6,7 @@ class Form::Sales::Questions::MortgageLength < ::Form::Question @header = "What is the length of the mortgage?" @type = "numeric" @min = 0 + @max = 60 @width = 5 @suffix = " years" @hint_text = "You should round up to the nearest year. Value should not exceed 60 years." diff --git a/app/models/form/sales/questions/old_persons_shared_ownership_value_check.rb b/app/models/form/sales/questions/old_persons_shared_ownership_value_check.rb new file mode 100644 index 000000000..c869dd3be --- /dev/null +++ b/app/models/form/sales/questions/old_persons_shared_ownership_value_check.rb @@ -0,0 +1,23 @@ +class Form::Sales::Questions::OldPersonsSharedOwnershipValueCheck < ::Form::Question + def initialize(id, hsh, page) + super + @id = "old_persons_shared_ownership_value_check" + @check_answer_label = "Shared ownership 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" => [ + { + "old_persons_shared_ownership_value_check" => 0, + }, + { + "old_persons_shared_ownership_value_check" => 1, + }, + ], + } + end +end diff --git a/app/models/form/sales/questions/shared_ownership_deposit_value_check.rb b/app/models/form/sales/questions/shared_ownership_deposit_value_check.rb new file mode 100644 index 000000000..3f8111881 --- /dev/null +++ b/app/models/form/sales/questions/shared_ownership_deposit_value_check.rb @@ -0,0 +1,23 @@ +class Form::Sales::Questions::SharedOwnershipDepositValueCheck < ::Form::Question + def initialize(id, hsh, page) + super + @id = "shared_ownership_deposit_value_check" + @check_answer_label = "Shared ownership deposit confirmation" + @type = "interruption_screen" + @header = "Are you sure this is correct?" + @answer_options = { + "0" => { "value" => "Yes" }, + "1" => { "value" => "No" }, + } + @hidden_in_check_answers = { + "depends_on" => [ + { + "shared_ownership_deposit_value_check" => 0, + }, + { + "shared_ownership_deposit_value_check" => 1, + }, + ], + } + end +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 3b994d931..925f651e8 100644 --- a/app/models/form/sales/subsections/discounted_ownership_scheme.rb +++ b/app/models/form/sales/subsections/discounted_ownership_scheme.rb @@ -12,10 +12,13 @@ class Form::Sales::Subsections::DiscountedOwnershipScheme < ::Form::Subsection Form::Sales::Pages::AboutPriceRtb.new(nil, nil, self), Form::Sales::Pages::ExtraBorrowingValueCheck.new("extra_borrowing_price_value_check", nil, self), 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), @@ -24,6 +27,7 @@ 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), ] end diff --git a/app/models/form/sales/subsections/household_characteristics.rb b/app/models/form/sales/subsections/household_characteristics.rb index b4c8badcf..bfccf0fa2 100644 --- a/app/models/form/sales/subsections/household_characteristics.rb +++ b/app/models/form/sales/subsections/household_characteristics.rb @@ -13,6 +13,7 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection Form::Sales::Pages::Age1.new(nil, nil, self), Form::Sales::Pages::RetirementValueCheck.new("age_1_retirement_value_check", nil, self, person_index: 1), Form::Sales::Pages::RetirementValueCheck.new("age_1_retirement_value_check_joint_purchase", nil, self, person_index: 1), + Form::Sales::Pages::OldPersonsSharedOwnershipValueCheck.new("age_1_old_persons_shared_ownership_value_check", nil, self), Form::Sales::Pages::GenderIdentity1.new(nil, nil, self), Form::Sales::Pages::RetirementValueCheck.new("gender_1_retirement_value_check", nil, self, person_index: 1), Form::Sales::Pages::RetirementValueCheck.new("gender_1_retirement_value_check_joint_purchase", nil, self, person_index: 1), @@ -31,6 +32,7 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection Form::Sales::Pages::Buyer2RelationshipToBuyer1.new(nil, nil, self), Form::Sales::Pages::Age2.new(nil, nil, self), Form::Sales::Pages::RetirementValueCheck.new("age_2_retirement_value_check_joint_purchase", nil, self, person_index: 2), + Form::Sales::Pages::OldPersonsSharedOwnershipValueCheck.new("age_2_old_persons_shared_ownership_value_check", nil, self), Form::Sales::Pages::GenderIdentity2.new(nil, nil, self), Form::Sales::Pages::RetirementValueCheck.new("gender_2_retirement_value_check_joint_purchase", nil, self, person_index: 2), Form::Sales::Pages::Buyer2WorkingSituation.new(nil, nil, self), diff --git a/app/models/form/sales/subsections/household_needs.rb b/app/models/form/sales/subsections/household_needs.rb index b5d4d528e..a419cff95 100644 --- a/app/models/form/sales/subsections/household_needs.rb +++ b/app/models/form/sales/subsections/household_needs.rb @@ -2,7 +2,7 @@ class Form::Sales::Subsections::HouseholdNeeds < ::Form::Subsection def initialize(id, hsh, section) super @id = "household_needs" - @label = "Household needs" + @label = "Other household information" @depends_on = [{ "setup_completed?" => true }] end diff --git a/app/models/form/sales/subsections/setup.rb b/app/models/form/sales/subsections/setup.rb index d2261a33b..cb147e9c2 100644 --- a/app/models/form/sales/subsections/setup.rb +++ b/app/models/form/sales/subsections/setup.rb @@ -15,6 +15,7 @@ class Form::Sales::Subsections::Setup < ::Form::Subsection Form::Sales::Pages::SharedOwnershipType.new(nil, nil, self), 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::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 8c05cd82d..0e2e3fdda 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), @@ -21,15 +22,18 @@ class Form::Sales::Subsections::SharedOwnershipScheme < ::Form::Subsection Form::Sales::Pages::PreviousPropertyType.new(nil, nil, self), Form::Sales::Pages::PreviousTenure.new(nil, nil, self), Form::Sales::Pages::AboutPriceSharedOwnership.new(nil, nil, self), + Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_equity_value_check", nil, self), Form::Sales::Pages::Mortgageused.new("mortgage_used_shared_ownership", nil, self), Form::Sales::Pages::MortgageAmount.new("mortgage_amount_shared_ownership", nil, self), + Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_mortgage_amount_value_check", nil, self), Form::Sales::Pages::MortgageLender.new("mortgage_lender_shared_ownership", nil, self), Form::Sales::Pages::MortgageLenderOther.new("mortgage_lender_other_shared_ownership", nil, self), Form::Sales::Pages::MortgageLength.new("mortgage_length_shared_ownership", nil, self), Form::Sales::Pages::ExtraBorrowing.new("extra_borrowing_shared_ownership", nil, self), Form::Sales::Pages::AboutDepositWithDiscount.new(nil, nil, self), Form::Sales::Pages::AboutDepositWithoutDiscount.new("about_deposit_shared_ownership", nil, self), - Form::Sales::Pages::DepositValueCheck.new("shared_ownership_deposit_value_check", nil, self), + Form::Sales::Pages::DepositValueCheck.new("deposit_value_check", nil, self), + 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), ] diff --git a/app/models/log.rb b/app/models/log.rb index 3217251b1..45f3e9607 100644 --- a/app/models/log.rb +++ b/app/models/log.rb @@ -24,6 +24,10 @@ class Log < ApplicationRecord where(created_by: user) end } + scope :filter_by_bulk_upload_id, lambda { |bulk_upload_id, user| + joins(:bulk_upload) + .where(bulk_upload: { id: bulk_upload_id, user: }) + } scope :created_by, ->(user) { where(created_by: user) } def collection_start_year @@ -104,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 ddc401e05..7a57be8ee 100644 --- a/app/models/sales_log.rb +++ b/app/models/sales_log.rb @@ -24,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].freeze + OPTIONAL_FIELDS = %w[purchid old_persons_shared_ownership_value_check].freeze RETIREMENT_AGES = { "M" => 65, "F" => 60, "X" => 65 }.freeze def startdate @@ -160,6 +160,12 @@ class SalesLog < Log end end + def expected_shared_ownership_deposit_value + return unless value && equity + + (value * equity / 100).round(2) + end + def process_postcode(postcode, postcode_known_key, la_inferred_key, la_key) return if postcode.blank? @@ -212,4 +218,18 @@ class SalesLog < Log def old_persons_shared_ownership? type == 24 end + + def shared_owhership_scheme? + ownershipsch == 1 + end + + def buyers_age_for_old_persons_shared_ownership_invalid? + return unless old_persons_shared_ownership? + + (joint_purchase? && ages_unknown_or_under_64?([1, 2])) || (not_joint_purchase? && ages_unknown_or_under_64?([1])) + end + + def ages_unknown_or_under_64?(person_indexes) + person_indexes.all? { |person_num| self["age#{person_num}"].present? && self["age#{person_num}"] < 64 || self["age#{person_num}_known"] == 1 } + end end diff --git a/app/models/user.rb b/app/models/user.rb index 04a545584..4d2aeeca1 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -145,9 +145,9 @@ class User < ApplicationRecord def logs_filters(specific_org: false) if (support? && !specific_org) || organisation.has_managing_agents? - %w[status years user organisation] + %w[status years user organisation bulk_upload_id] else - %w[status years user] + %w[status years user bulk_upload_id] end end diff --git a/app/models/validations/sales/financial_validations.rb b/app/models/validations/sales/financial_validations.rb index 116ecf301..df7dc8df5 100644 --- a/app/models/validations/sales/financial_validations.rb +++ b/app/models/validations/sales/financial_validations.rb @@ -3,11 +3,19 @@ module Validations::Sales::FinancialValidations # or 'validate_' to run on submit as well def validate_income1(record) - if record.ecstat1 && record.income1 && record.ownershipsch == 1 + if record.ecstat1 && record.income1 && record.la && record.ownershipsch == 1 if record.london_property? record.errors.add :income1, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 + record.errors.add :ecstat1, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 + record.errors.add :ownershipsch, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 + record.errors.add :la, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 + record.errors.add :postcode_full, I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000) if record.income1 > 90_000 elsif record.income1 > 80_000 record.errors.add :income1, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) + record.errors.add :ecstat1, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) + record.errors.add :ownershipsch, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) + record.errors.add :la, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) if record.income1 > 80_000 + record.errors.add :postcode_full, I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000) if record.income1 > 80_000 end end end @@ -19,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/household_validations.rb b/app/models/validations/sales/household_validations.rb index 8c03d9db9..4f2290268 100644 --- a/app/models/validations/sales/household_validations.rb +++ b/app/models/validations/sales/household_validations.rb @@ -18,20 +18,6 @@ module Validations::Sales::HouseholdValidations shared_validate_partner_count(record, 6) end - def validate_buyers_age_for_old_persons_shared_ownership(record) - if record.old_persons_shared_ownership? - if record.joint_purchase? && ages_unknown_or_under_64?(record, [1, 2]) - record.errors.add :age1, I18n.t("validations.household.old_persons_shared_ownership") - record.errors.add :age2, I18n.t("validations.household.old_persons_shared_ownership") - record.errors.add :type, I18n.t("validations.household.old_persons_shared_ownership") - end - if record.not_joint_purchase? && ages_unknown_or_under_64?(record, [1]) - record.errors.add :age1, I18n.t("validations.household.old_persons_shared_ownership") - record.errors.add :type, I18n.t("validations.household.old_persons_shared_ownership") - end - end - end - def validate_previous_postcode(record) return unless record.postcode_full && record.ppostcode_full && record.discounted_ownership_sale? @@ -116,8 +102,4 @@ private def tenant_is_economic_child?(economic_status) economic_status == 9 end - - def ages_unknown_or_under_64?(record, person_indexes) - person_indexes.all? { |person_num| record["age#{person_num}"].present? && record["age#{person_num}"] < 64 || record["age#{person_num}_known"] == 1 } - end end diff --git a/app/models/validations/sales/sale_information_validations.rb b/app/models/validations/sales/sale_information_validations.rb index a375256d3..24fdff3ea 100644 --- a/app/models/validations/sales/sale_information_validations.rb +++ b/app/models/validations/sales/sale_information_validations.rb @@ -42,4 +42,29 @@ module Validations::Sales::SaleInformationValidations record.errors.add :fromprop, I18n.t("validations.sale_information.previous_property_type.property_type_bedsit") end end + + def validate_discounted_ownership_value(record) + return unless record.value && record.deposit && record.ownershipsch + return unless record.mortgage || record.mortgageused == 2 + return unless record.discount || record.grant || record.type == 29 + + discount_amount = record.discount ? record.value * record.discount / 100 : 0 + grant_amount = record.grant || 0 + mortgage_amount = record.mortgage || 0 + value_with_discount = (record.value - discount_amount) + if mortgage_amount + record.deposit + grant_amount != value_with_discount && record.discounted_ownership_sale? + %i[mortgage deposit grant value discount ownershipsch].each do |field| + record.errors.add field, I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: sprintf("%.2f", value_with_discount)) + end + end + end + + def validate_basic_monthly_rent(record) + return unless record.mrent && record.ownershipsch && record.type + + if record.shared_owhership_scheme? && !record.old_persons_shared_ownership? && record.mrent > 9999 + record.errors.add :mrent, I18n.t("validations.sale_information.monthly_rent.higher_than_expected") + record.errors.add :type, I18n.t("validations.sale_information.monthly_rent.higher_than_expected") + end + end end diff --git a/app/models/validations/sales/soft_validations.rb b/app/models/validations/sales/soft_validations.rb index e39b5e9d9..0bffe1234 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? @@ -43,9 +47,32 @@ module Validations::Sales::SoftValidations extrabor != 1 && mortgage + deposit > value - value * discount / 100 end + def shared_ownership_deposit_invalid? + return unless mortgage || mortgageused == 2 + return unless cashdis || !is_type_discount? + return unless deposit && value && equity + + cash_discount = cashdis || 0 + mortgage_value = mortgage || 0 + 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 ((saledate.to_date - hodate.to_date).to_i / 365) >= 3 end + + def grant_outside_common_range? + return unless grant + + !grant.between?(9_000, 16_000) + end end diff --git a/app/services/bulk_upload/lettings/validator.rb b/app/services/bulk_upload/lettings/validator.rb index 8cfdee684..b96739836 100644 --- a/app/services/bulk_upload/lettings/validator.rb +++ b/app/services/bulk_upload/lettings/validator.rb @@ -164,6 +164,7 @@ class BulkUpload::Lettings::Validator property_ref: row_parser.field_100, row:, cell: "#{cols[field_number_for_attribute(error.attribute) - col_offset + 1]}#{row}", + col: cols[field_number_for_attribute(error.attribute) - col_offset + 1], ) end end diff --git a/app/views/bulk_upload_lettings_results/resume.html.erb b/app/views/bulk_upload_lettings_results/resume.html.erb new file mode 100644 index 000000000..a5af1bd9d --- /dev/null +++ b/app/views/bulk_upload_lettings_results/resume.html.erb @@ -0,0 +1,11 @@ +
+ You’ve completed all the logs that had errors from your bulk upload. +
+ +<%= govuk_button_link_to "Back to all logs", lettings_logs_path, button: true %> 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) %> +
+