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 @@ +
+
+

There are no more logs that need updating

+
+
+ +

+ 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 %> +
- 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/app/views/logs/_log_filters.erb b/app/views/logs/_log_filters.erb index d2a327d99..8fb4f2ba4 100644 --- a/app/views/logs/_log_filters.erb +++ b/app/views/logs/_log_filters.erb @@ -3,13 +3,48 @@

Filters

+
<%= form_with html: { method: :get } do |f| %> - <% years = {"2021": "2021/22", "2022": "2022/23"} %> - <% all_or_yours = {"all": { label: "All" }, "yours": { label: "Yours" } } %> - <%= render partial: "filters/checkbox_filter", locals: { f: f, options: years, label: "Collection year", category: "years" } %> - <%= render partial: "filters/checkbox_filter", locals: { f: f, options: status_filters, label: "Status", category: "status" } %> - <%= render partial: "filters/radio_filter", locals: { f: f, options: all_or_yours, label: "Logs", category: "user", } %> + <% years = { "2021": "2021/22", "2022": "2022/23" } %> + <% all_or_yours = { "all": { label: "All" }, "yours": { label: "Yours" } } %> + + <% if bulk_upload_options(@bulk_upload).present? %> + <%= render partial: "filters/checkbox_filter", + locals: { + f: f, + options: bulk_upload_options(@bulk_upload), + label: "Bulk upload", + category: "bulk_upload_id", + } %> + <% end %> + + <% if bulk_upload_options(@bulk_upload).blank? %> + <%= render partial: "filters/checkbox_filter", + locals: { + f: f, + options: years, + label: "Collection year", + category: "years", + } %> + + <%= render partial: "filters/checkbox_filter", + locals: { + f: f, + options: status_filters, + label: "Status", + category: "status", + } %> + <% end %> + + <%= render partial: "filters/radio_filter", + locals: { + f: f, + options: all_or_yours, + label: "Logs", + category: "user", + } %> + <% if (@current_user.support? || @current_user.organisation.has_managing_agents?) && request.path == "/lettings-logs" %> <%= render partial: "filters/radio_filter", locals: { f: f, @@ -21,14 +56,15 @@ type: "select", label: "Organisation", category: "organisation", - options: organisations_filter_options(@current_user) - } - } + options: organisations_filter_options(@current_user), + }, + }, }, label: "Organisation", - category: "organisation_select" + category: "organisation_select", } %> <% end %> + <%= f.govuk_submit "Apply filters", class: "govuk-!-margin-bottom-0" %> <% end %>
diff --git a/app/views/logs/_log_list.html.erb b/app/views/logs/_log_list.html.erb index 702cd7d1f..1aeaa03f2 100644 --- a/app/views/logs/_log_list.html.erb +++ b/app/views/logs/_log_list.html.erb @@ -1,6 +1,8 @@

<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "logs", path: request.path)) %> - <%= govuk_link_to "Download (CSV)", csv_download_url, type: "text/csv" %> + <% if logs&.first&.lettings? %> + <%= govuk_link_to "Download (CSV)", csv_download_url, type: "text/csv" %> + <% end %>

<% logs.map do |log| %> <%= render(LogSummaryComponent.new(current_user:, log:)) %> diff --git a/app/views/logs/index.html.erb b/app/views/logs/index.html.erb index 3f0d2031d..31768c731 100644 --- a/app/views/logs/index.html.erb +++ b/app/views/logs/index.html.erb @@ -11,32 +11,58 @@ title_id: "impacted-logs-banner", ) do |notification_banner| %> <% notification_banner.heading(text: "A scheme has changed and it has affected #{@unresolved_count} #{'log'.pluralize(@unresolved_count)}") %> -
- <%= govuk_link_to "Update logs", update_logs_lettings_logs_path, class: "govuk-notification-banner__link" %> -
- <% end %> +
+ <%= govuk_link_to "Update logs", update_logs_lettings_logs_path, class: "govuk-notification-banner__link" %> +
<% end %> - <%= render partial: "organisations/headings", locals: current_user.support? ? { main: "Lettings logs", sub: nil } : { main: "Lettings logs", sub: current_user.organisation.name } %> -<% elsif current_page?(controller: 'sales_logs', action: 'index') %> - <%= render partial: "organisations/headings", locals: current_user.support? ? { main: "Sales logs", sub: nil } : { main: "Sales logs", sub: current_user.organisation.name } %> + <% end %> <% end %> -
-
- <% if current_page?(controller: 'lettings_logs', action: 'index') %> - <%= govuk_button_to "Create a new lettings log", lettings_logs_path, class: "govuk-!-margin-right-6" %> - <% end %> +<% if @bulk_upload.blank? %> + <%= render partial: "organisations/headings", locals: current_user.support? ? { main: "#{log_type_for_controller(controller).capitalize} logs", sub: nil } : { main: "#{log_type_for_controller(controller).capitalize} logs", sub: current_user.organisation.name } %> +<% else %> + <%= render partial: "organisations/headings", + locals: { + main: "You need to fix #{pluralize(@pagy.count, 'log')} from your bulk upload", + sub: "#{log_type_for_controller(controller).capitalize} logs (#{@bulk_upload.year_combo})", + } %> - <% if FeatureToggle.sales_log_enabled? && current_page?(controller: 'sales_logs', action: 'index') %> - <%= govuk_button_to "Create a new sales log", sales_logs_path, class: "govuk-!-margin-right-6" %> - <% end %> +
+
+
+

+ The following logs are from your recent bulk upload. They have some incorrect or incomplete data. You’ll need to answer a few more questions for each one to mark them as complete. +

- <% if FeatureToggle.bulk_upload_logs? %> - <%= govuk_button_link_to "Upload #{log_type_for_controller(controller)} logs in bulk", bulk_upload_path_for_controller(controller, id: "start"), secondary: true %> - <% end %> +

+ Bulk Upload details:
+ <%= @bulk_upload.filename %>
+ Uploaded on <%= @bulk_upload.created_at.to_fs(:govuk_date_and_time) %>
+

+
+
+<% end %> + +
+ <% unless @bulk_upload %> +
+ <% if current_page?(controller: 'lettings_logs', action: 'index') %> + <%= govuk_button_to "Create a new lettings log", lettings_logs_path, class: "govuk-!-margin-right-6" %> + <% end %> + + <% if FeatureToggle.sales_log_enabled? && current_page?(controller: 'sales_logs', action: 'index') %> + <%= govuk_button_to "Create a new sales log", sales_logs_path, class: "govuk-!-margin-right-6" %> + <% end %> + + <% if FeatureToggle.bulk_upload_logs? %> + <%= govuk_button_link_to "Upload #{log_type_for_controller(controller)} logs in bulk", bulk_upload_path_for_controller(controller, id: "start"), secondary: true %> + <% end %> +
+ <% end %> <%= render partial: "log_filters" %> +
<%= render SearchComponent.new(current_user:, search_label: "Search by log ID, tenant code, property reference or postcode", value: @searched) %> <%= govuk_section_break(visible: true, size: "m") %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 5aecc018d..c084071ce 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -277,6 +277,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" @@ -370,7 +374,6 @@ en: not_internal_transfer: "Answer cannot be ‘permanently decanted from another property owned by this landlord’ as you told us the source of referral for this tenancy was not an internal transfer" condition_effects: no_choices: "You cannot answer this question as you told us nobody in the household has a physical or mental health condition (or other illness) expected to last 12 months or more" - old_persons_shared_ownership: "Are you sure? At least one buyer should be aged over 64 for Older persons‘ shared ownership scheme" postcode: discounted_ownership: "Last settled accommodation and discounted ownership property postcodes must match" @@ -428,8 +431,10 @@ en: 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: "Bedsit bedroom maximum 1" - previous_property_type: - property_type_bedsit: "A bedsit can not have more than 1 bedroom" + discounted_ownership_value: "Mortgage, deposit, and grant total must equal £%{value_with_discount}" + monthly_rent: + higher_than_expected: "Basic monthly rent must be between £0 and £9,999" + soft_validations: net_income: title_text: "Net income is outside the expected range based on the lead tenant’s working situation" @@ -462,6 +467,9 @@ en: title_text: "You told us the time between the start of the tenancy and the major repairs completion date is more than 2 years" void_date: title_text: "You told us the time between the start of the tenancy and the void date is more than 2 years" + 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" devise: two_factor_authentication: diff --git a/config/routes.rb b/config/routes.rb index 4e5b224c1..be765c8c0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -134,7 +134,12 @@ Rails.application.routes.draw do end end - resources :bulk_upload_lettings_results, path: "bulk-upload-results", only: [:show] + resources :bulk_upload_lettings_results, path: "bulk-upload-results", only: [:show] do + member do + get :resume + get :summary + end + end get "update-logs", to: "lettings_logs#update_logs" 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/20230123101256_add_shared_ownership_deposit_value_check.rb b/db/migrate/20230123101256_add_shared_ownership_deposit_value_check.rb new file mode 100644 index 000000000..07efc5e4a --- /dev/null +++ b/db/migrate/20230123101256_add_shared_ownership_deposit_value_check.rb @@ -0,0 +1,7 @@ +class AddSharedOwnershipDepositValueCheck < ActiveRecord::Migration[7.0] + def change + change_table :sales_logs, bulk: true do |t| + t.column :shared_ownership_deposit_value_check, :integer + end + end +end diff --git a/db/migrate/20230123160741_add_grant_value_check_to_sales_log.rb b/db/migrate/20230123160741_add_grant_value_check_to_sales_log.rb new file mode 100644 index 000000000..9a9cfb8ea --- /dev/null +++ b/db/migrate/20230123160741_add_grant_value_check_to_sales_log.rb @@ -0,0 +1,5 @@ +class AddGrantValueCheckToSalesLog < ActiveRecord::Migration[7.0] + def change + add_column :sales_logs, :grant_value_check, :integer + end +end diff --git a/db/migrate/20230124105247_add_old_persons_shared_ownership_value_check.rb b/db/migrate/20230124105247_add_old_persons_shared_ownership_value_check.rb new file mode 100644 index 000000000..ad5a0c70f --- /dev/null +++ b/db/migrate/20230124105247_add_old_persons_shared_ownership_value_check.rb @@ -0,0 +1,7 @@ +class AddOldPersonsSharedOwnershipValueCheck < ActiveRecord::Migration[7.0] + def change + change_table :sales_logs, bulk: true do |t| + t.column :old_persons_shared_ownership_value_check, :integer + end + 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/20230126145529_add_column_to_bulk_upload_errors.rb b/db/migrate/20230126145529_add_column_to_bulk_upload_errors.rb new file mode 100644 index 000000000..437b5f2cd --- /dev/null +++ b/db/migrate/20230126145529_add_column_to_bulk_upload_errors.rb @@ -0,0 +1,5 @@ +class AddColumnToBulkUploadErrors < ActiveRecord::Migration[7.0] + def change + add_column :bulk_upload_errors, :col, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index 55cb24599..dc431d425 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_18_170602) do +ActiveRecord::Schema[7.0].define(version: 2023_01_26_145529) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -25,6 +25,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_18_170602) do t.text "error" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.text "col" t.index ["bulk_upload_id"], name: "index_bulk_upload_errors_on_bulk_upload_id" end @@ -503,6 +504,11 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_18_170602) do t.integer "retirement_value_check" t.integer "hodate_check" t.integer "extrabor_value_check" + t.integer "grant_value_check" + t.integer "staircase_bought_value_check" + t.integer "deposit_and_mortgage_value_check" + t.integer "shared_ownership_deposit_value_check" + t.integer "old_persons_shared_ownership_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/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/controllers/bulk_upload_lettings_results_controller_spec.rb b/spec/controllers/bulk_upload_lettings_results_controller_spec.rb new file mode 100644 index 000000000..7fb50a76a --- /dev/null +++ b/spec/controllers/bulk_upload_lettings_results_controller_spec.rb @@ -0,0 +1,68 @@ +require "rails_helper" + +RSpec.describe BulkUploadLettingsResultsController do + before do + sign_in user + end + + describe "GET #resume /lettings-logs/bulk-upload-results/:ID/resume" do + let(:user) { create(:user) } + let(:bulk_upload) { create(:bulk_upload, :lettings, user:) } + + context "when there are no logs left to resolve" do + render_views + + it "displays copy to user" do + get :resume, params: { id: bulk_upload.id } + + expect(response.body).to include("There are no more logs that need updating") + end + + it "resets logs filters" do + get :resume, params: { id: bulk_upload.id } + + expect(JSON.parse(session["logs_filters"])).to eql({}) + end + end + + context "when there are logs left to resolve" do + before do + create(:lettings_log, :in_progress, bulk_upload:) + end + + it "clears the year filter" do + hash = { + years: ["", "2022"], + } + + session["logs_filters"] = hash.to_json + + get :resume, params: { id: bulk_upload.id } + + expect(JSON.parse(session["logs_filters"])["years"]).to eql([""]) + end + + it "sets the status filter to in progress" do + session["logs_filters"] ||= {}.to_json + + get :resume, params: { id: bulk_upload.id } + + expect(JSON.parse(session["logs_filters"])["status"]).to eql(["", "in_progress"]) + end + + it "sets the user filter to all" do + session["logs_filters"] ||= {}.to_json + + get :resume, params: { id: bulk_upload.id } + + expect(JSON.parse(session["logs_filters"])["user"]).to eql("all") + end + + it "redirects to logs with bulk upload filter applied" do + get :resume, params: { id: bulk_upload.id } + + expect(response).to redirect_to("/lettings-logs?bulk_upload_id%5B%5D=#{bulk_upload.id}") + end + 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 8ea35619d..085d4ca03 100644 --- a/spec/factories/sales_log.rb +++ b/spec/factories/sales_log.rb @@ -70,10 +70,10 @@ FactoryBot.define do ecstat5 { 2 } ecstat6 { 1 } disabled { 1 } - deposit { 10_000 } + deposit { 80_000 } cashdis { 1_000 } value { 110_000 } - grant { 1_000 } + grant { 10_000 } proplen { 10 } pregyrha { 1 } pregla { 1 } diff --git a/spec/models/form/sales/pages/buyer1_income_value_check_spec.rb b/spec/models/form/sales/pages/buyer1_income_value_check_spec.rb index 313708aaf..f770bd651 100644 --- a/spec/models/form/sales/pages/buyer1_income_value_check_spec.rb +++ b/spec/models/form/sales/pages/buyer1_income_value_check_spec.rb @@ -30,4 +30,8 @@ RSpec.describe Form::Sales::Pages::Buyer1IncomeValueCheck, type: :model do }, ]) end + + it "is interruption screen page" do + expect(page.interruption_screen?).to eq(true) + end end diff --git a/spec/models/form/sales/pages/deposit_value_check_spec.rb b/spec/models/form/sales/pages/deposit_value_check_spec.rb index 961a3b6ec..91e1cccc5 100644 --- a/spec/models/form/sales/pages/deposit_value_check_spec.rb +++ b/spec/models/form/sales/pages/deposit_value_check_spec.rb @@ -30,4 +30,8 @@ RSpec.describe Form::Sales::Pages::DepositValueCheck, type: :model do }, ]) end + + it "is interruption screen page" do + expect(page.interruption_screen?).to eq(true) + end end diff --git a/spec/models/form/sales/pages/handover_date_check_spec.rb b/spec/models/form/sales/pages/handover_date_check_spec.rb index e7ad99c83..86c74fda6 100644 --- a/spec/models/form/sales/pages/handover_date_check_spec.rb +++ b/spec/models/form/sales/pages/handover_date_check_spec.rb @@ -30,4 +30,8 @@ RSpec.describe Form::Sales::Pages::HandoverDateCheck, type: :model do }, ]) end + + it "is interruption screen page" do + expect(page.interruption_screen?).to eq(true) + end end diff --git a/spec/models/form/sales/pages/household_wheelchair_check_spec.rb b/spec/models/form/sales/pages/household_wheelchair_check_spec.rb index 5a6cb34f9..4eb6182d9 100644 --- a/spec/models/form/sales/pages/household_wheelchair_check_spec.rb +++ b/spec/models/form/sales/pages/household_wheelchair_check_spec.rb @@ -30,4 +30,8 @@ RSpec.describe Form::Sales::Pages::HouseholdWheelchairCheck, type: :model do }, ]) end + + it "is interruption screen page" do + expect(page.interruption_screen?).to eq(true) + end end diff --git a/spec/models/form/sales/pages/mortgage_value_check_spec.rb b/spec/models/form/sales/pages/mortgage_value_check_spec.rb index bd6c0bdf5..d54e992f1 100644 --- a/spec/models/form/sales/pages/mortgage_value_check_spec.rb +++ b/spec/models/form/sales/pages/mortgage_value_check_spec.rb @@ -23,6 +23,10 @@ RSpec.describe Form::Sales::Pages::MortgageValueCheck, type: :model do expect(page.header).to be_nil end + it "is interruption screen page" do + expect(page.interruption_screen?).to eq(true) + end + it "has correct depends_on" do expect(page.depends_on).to eq([ { diff --git a/spec/models/form/sales/pages/old_persons_shared_ownership_value_check_spec.rb b/spec/models/form/sales/pages/old_persons_shared_ownership_value_check_spec.rb new file mode 100644 index 000000000..05b6f7734 --- /dev/null +++ b/spec/models/form/sales/pages/old_persons_shared_ownership_value_check_spec.rb @@ -0,0 +1,44 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::OldPersonsSharedOwnershipValueCheck, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { "old_persons_shared_ownership_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[old_persons_shared_ownership_value_check]) + end + + it "has the correct id" do + expect(page.id).to eq("old_persons_shared_ownership_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([ + { + "buyers_age_for_old_persons_shared_ownership_invalid?" => true, + }, + ]) + end + + it "has the correct title_text" do + expect(page.title_text).to eq({ + "translation" => "soft_validations.old_persons_shared_ownership", + "arguments" => [], + }) + end + + it "has the correct informative_text" do + expect(page.informative_text).to eq({}) + end +end diff --git a/spec/models/form/sales/pages/retirement_value_check_spec.rb b/spec/models/form/sales/pages/retirement_value_check_spec.rb index 9c04ef25c..f150e6663 100644 --- a/spec/models/form/sales/pages/retirement_value_check_spec.rb +++ b/spec/models/form/sales/pages/retirement_value_check_spec.rb @@ -600,4 +600,8 @@ RSpec.describe Form::Sales::Pages::RetirementValueCheck, type: :model do end end end + + it "is interruption screen page" do + expect(page.interruption_screen?).to eq(true) + end end diff --git a/spec/models/form/sales/pages/savings_value_check_spec.rb b/spec/models/form/sales/pages/savings_value_check_spec.rb index d4334118d..80606afb5 100644 --- a/spec/models/form/sales/pages/savings_value_check_spec.rb +++ b/spec/models/form/sales/pages/savings_value_check_spec.rb @@ -30,4 +30,8 @@ RSpec.describe Form::Sales::Pages::SavingsValueCheck, type: :model do }, ]) end + + it "is interruption screen page" do + expect(page.interruption_screen?).to eq(true) + end end diff --git a/spec/models/form/sales/pages/shared_ownership_deposit_value_check_spec.rb b/spec/models/form/sales/pages/shared_ownership_deposit_value_check_spec.rb new file mode 100644 index 000000000..afe25f4d3 --- /dev/null +++ b/spec/models/form/sales/pages/shared_ownership_deposit_value_check_spec.rb @@ -0,0 +1,50 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::SharedOwnershipDepositValueCheck, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { "shared_ownership_deposit_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[shared_ownership_deposit_value_check]) + end + + it "has the correct id" do + expect(page.id).to eq("shared_ownership_deposit_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([ + { + "shared_ownership_deposit_invalid?" => true, + }, + ]) + end + + it "has the correct title_text" do + expect(page.title_text).to eq({ + "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 + + it "has the correct informative_text" do + expect(page.informative_text).to eq({}) + end +end diff --git a/spec/models/form/sales/questions/mortgage_length_spec.rb b/spec/models/form/sales/questions/mortgage_length_spec.rb index e96a05174..0f3193244 100644 --- a/spec/models/form/sales/questions/mortgage_length_spec.rb +++ b/spec/models/form/sales/questions/mortgage_length_spec.rb @@ -48,4 +48,8 @@ RSpec.describe Form::Sales::Questions::MortgageLength, type: :model do it "has correct min" do expect(question.min).to eq(0) end + + it "has correct max" do + expect(question.max).to eq(60) + end end diff --git a/spec/models/form/sales/questions/old_persons_shared_ownership_value_check_spec.rb b/spec/models/form/sales/questions/old_persons_shared_ownership_value_check_spec.rb new file mode 100644 index 000000000..5ac066e31 --- /dev/null +++ b/spec/models/form/sales/questions/old_persons_shared_ownership_value_check_spec.rb @@ -0,0 +1,57 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::OldPersonsSharedOwnershipValueCheck, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + 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("old_persons_shared_ownership_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("Shared ownership 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" => [ + { + "old_persons_shared_ownership_value_check" => 0, + }, + { + "old_persons_shared_ownership_value_check" => 1, + }, + ], + }) + end +end diff --git a/spec/models/form/sales/questions/shared_ownership_deposit_value_check_spec.rb b/spec/models/form/sales/questions/shared_ownership_deposit_value_check_spec.rb new file mode 100644 index 000000000..c33892f28 --- /dev/null +++ b/spec/models/form/sales/questions/shared_ownership_deposit_value_check_spec.rb @@ -0,0 +1,61 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::SharedOwnershipDepositValueCheck, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + 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("shared_ownership_deposit_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("Shared ownership deposit 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 a correct check_answers_card_number" do + # expect(question.check_answers_card_number).to eq(0) + # 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" => [ + { + "shared_ownership_deposit_value_check" => 0, + }, + { + "shared_ownership_deposit_value_check" => 1, + }, + ], + }) + 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 44896a0f9..503712bc7 100644 --- a/spec/models/form/sales/subsections/discounted_ownership_scheme_spec.rb +++ b/spec/models/form/sales/subsections/discounted_ownership_scheme_spec.rb @@ -18,10 +18,13 @@ RSpec.describe Form::Sales::Subsections::DiscountedOwnershipScheme, type: :model about_price_rtb extra_borrowing_price_value_check 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 @@ -30,6 +33,7 @@ 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 ], ) diff --git a/spec/models/form/sales/subsections/household_characteristics_spec.rb b/spec/models/form/sales/subsections/household_characteristics_spec.rb index 34b1a7536..f99ebe0aa 100644 --- a/spec/models/form/sales/subsections/household_characteristics_spec.rb +++ b/spec/models/form/sales/subsections/household_characteristics_spec.rb @@ -19,6 +19,7 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model buyer_1_age age_1_retirement_value_check age_1_retirement_value_check_joint_purchase + age_1_old_persons_shared_ownership_value_check buyer_1_gender_identity gender_1_retirement_value_check gender_1_retirement_value_check_joint_purchase @@ -37,6 +38,7 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model buyer_2_relationship_to_buyer_1 buyer_2_age age_2_retirement_value_check_joint_purchase + age_2_old_persons_shared_ownership_value_check buyer_2_gender_identity gender_2_retirement_value_check_joint_purchase buyer_2_working_situation diff --git a/spec/models/form/sales/subsections/household_needs_spec.rb b/spec/models/form/sales/subsections/household_needs_spec.rb index 5395da095..d65f5513e 100644 --- a/spec/models/form/sales/subsections/household_needs_spec.rb +++ b/spec/models/form/sales/subsections/household_needs_spec.rb @@ -30,7 +30,7 @@ RSpec.describe Form::Sales::Subsections::HouseholdNeeds, type: :model do end it "has the correct label" do - expect(household_characteristics.label).to eq("Household needs") + expect(household_characteristics.label).to eq("Other household information") end it "has correct depends on" do diff --git a/spec/models/form/sales/subsections/setup_spec.rb b/spec/models/form/sales/subsections/setup_spec.rb index 36fbf247a..4bb95e391 100644 --- a/spec/models/form/sales/subsections/setup_spec.rb +++ b/spec/models/form/sales/subsections/setup_spec.rb @@ -22,6 +22,7 @@ RSpec.describe Form::Sales::Subsections::Setup, type: :model do shared_ownership_type discounted_ownership_type outright_ownership_type + ownership_type_old_persons_shared_ownership_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 1cafa8072..58a762ccb 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 @@ -27,14 +28,17 @@ RSpec.describe Form::Sales::Subsections::SharedOwnershipScheme, type: :model do previous_property_type shared_ownership_previous_tenure about_price_shared_ownership + shared_ownership_equity_value_check mortgage_used_shared_ownership mortgage_amount_shared_ownership + shared_ownership_mortgage_amount_value_check mortgage_lender_shared_ownership mortgage_lender_other_shared_ownership mortgage_length_shared_ownership extra_borrowing_shared_ownership about_deposit_with_discount about_deposit_shared_ownership + deposit_value_check shared_ownership_deposit_value_check monthly_rent leasehold_charges_shared_ownership diff --git a/spec/models/form_handler_spec.rb b/spec/models/form_handler_spec.rb index 8f9a88f7f..be88b2b94 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(187) + expect(form.pages.count).to eq(198) 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(187) + expect(form.pages.count).to eq(198) 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 110a521e2..fee42f1bc 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(12) + expect(form.pages.count).to eq(13) expect(form.pages.first.id).to eq("organisation") - expect(form.questions.count).to eq(13) + expect(form.questions.count).to eq(14) 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 ccfff433f..3a5823a4c 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]) + expect(sales_log.optional_fields).to eq(%w[purchid 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 @@ -339,4 +357,12 @@ RSpec.describe SalesLog, type: :model do expect(record_from_db["prevloc"]).to eq(nil) end end + + describe "expected_shared_ownership_deposit_value" do + let!(:completed_sales_log) { create(:sales_log, :completed, ownershipsch: 1, type: 2, value: 1000, equity: 50) } + + it "is set to completed for a completed sales log" do + expect(completed_sales_log.expected_shared_ownership_deposit_value).to eq(500) + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 9244ad7e5..b285a55e7 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -123,7 +123,7 @@ RSpec.describe User, type: :model do end it "can filter lettings logs by user, year and status" do - expect(user.logs_filters).to eq(%w[status years user]) + expect(user.logs_filters).to eq(%w[status years user bulk_upload_id]) end end @@ -133,7 +133,7 @@ RSpec.describe User, type: :model do end it "can filter lettings logs by user, year, status and organisation" do - expect(user.logs_filters).to eq(%w[status years user organisation]) + expect(user.logs_filters).to eq(%w[status years user organisation bulk_upload_id]) end end end @@ -159,7 +159,7 @@ RSpec.describe User, type: :model do end it "can filter lettings logs by user, year, status and organisation" do - expect(user.logs_filters).to eq(%w[status years user organisation]) + expect(user.logs_filters).to eq(%w[status years user organisation bulk_upload_id]) end end diff --git a/spec/models/validations/sales/financial_validations_spec.rb b/spec/models/validations/sales/financial_validations_spec.rb index 7b5b8868b..88f36943f 100644 --- a/spec/models/validations/sales/financial_validations_spec.rb +++ b/spec/models/validations/sales/financial_validations_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Validations::Sales::FinancialValidations do let(:validator_class) { Class.new { include Validations::Sales::FinancialValidations } } describe "income validations" do - let(:record) { FactoryBot.create(:sales_log, ownershipsch: 1) } + let(:record) { FactoryBot.create(:sales_log, ownershipsch: 1, la: "E08000035") } context "with shared ownership" do context "and non london borough" do @@ -17,6 +17,14 @@ RSpec.describe Validations::Sales::FinancialValidations do financial_validator.validate_income1(record) expect(record.errors["income1"]) .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000)) + expect(record.errors["ecstat1"]) + .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000)) + expect(record.errors["ownershipsch"]) + .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000)) + expect(record.errors["la"]) + .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000)) + expect(record.errors["postcode_full"]) + .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 80_000)) end end @@ -25,6 +33,10 @@ RSpec.describe Validations::Sales::FinancialValidations do record.ecstat1 = 1 financial_validator.validate_income1(record) expect(record.errors["income1"]).to be_empty + expect(record.errors["ecstat1"]).to be_empty + expect(record.errors["ownershipsch"]).to be_empty + expect(record.errors["la"]).to be_empty + expect(record.errors["postcode_full"]).to be_empty end end @@ -41,6 +53,14 @@ RSpec.describe Validations::Sales::FinancialValidations do financial_validator.validate_income1(record) expect(record.errors["income1"]) .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000)) + expect(record.errors["ecstat1"]) + .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000)) + expect(record.errors["ownershipsch"]) + .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000)) + expect(record.errors["la"]) + .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000)) + expect(record.errors["postcode_full"]) + .to include(match I18n.t("validations.financial.income1.over_hard_max", hard_max: 90_000)) end end @@ -49,6 +69,10 @@ RSpec.describe Validations::Sales::FinancialValidations do record.ecstat1 = 1 financial_validator.validate_income1(record) expect(record.errors["income1"]).to be_empty + expect(record.errors["ecstat1"]).to be_empty + expect(record.errors["ownershipsch"]).to be_empty + expect(record.errors["la"]).to be_empty + expect(record.errors["postcode_full"]).to be_empty end end end @@ -75,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/household_validations_spec.rb b/spec/models/validations/sales/household_validations_spec.rb index 5b227c36d..26d369a35 100644 --- a/spec/models/validations/sales/household_validations_spec.rb +++ b/spec/models/validations/sales/household_validations_spec.rb @@ -164,121 +164,6 @@ RSpec.describe Validations::Sales::HouseholdValidations do expect(record.errors["ecstat2"]) .to include(match I18n.t("validations.household.ecstat.student_16_19.cannot_be_student.child_not_16_19")) end - - context "when it is a joint purchase and both buyers are over 64" do - let(:record) { FactoryBot.build(:sales_log, jointpur: 1, age1: 65, age2: 66, type: 24) } - - it "does not add an error" do - household_validator.validate_buyers_age_for_old_persons_shared_ownership(record) - - expect(record.errors).not_to be_present - end - end - - context "when it is a joint purchase and first buyer is over 64" do - let(:record) { FactoryBot.build(:sales_log, jointpur: 1, age1: 65, age2: 40, type: 24) } - - it "does not add an error" do - household_validator.validate_buyers_age_for_old_persons_shared_ownership(record) - - expect(record.errors).not_to be_present - end - end - - context "when it is a joint purchase and second buyer is over 64" do - let(:record) { FactoryBot.build(:sales_log, jointpur: 1, age1: 43, age2: 64, type: 24) } - - it "does not add an error" do - household_validator.validate_buyers_age_for_old_persons_shared_ownership(record) - - expect(record.errors).not_to be_present - end - end - - context "when it is a joint purchase and neither of the buyers are over 64" do - let(:record) { FactoryBot.build(:sales_log, jointpur: 1, age1: 43, age2: 33, type: 24) } - - it "adds an error" do - household_validator.validate_buyers_age_for_old_persons_shared_ownership(record) - - expect(record.errors["age1"]) - .to include(match I18n.t("validations.household.old_persons_shared_ownership")) - expect(record.errors["age2"]) - .to include(match I18n.t("validations.household.old_persons_shared_ownership")) - expect(record.errors["type"]) - .to include(match I18n.t("validations.household.old_persons_shared_ownership")) - end - end - - context "when it is a joint purchase and first buyer is under 64 and the second buyers' age is unknown" do - let(:record) { FactoryBot.build(:sales_log, jointpur: 1, age1: 43, age2_known: 1, type: 24) } - - it "adds an error" do - household_validator.validate_buyers_age_for_old_persons_shared_ownership(record) - - expect(record.errors["age1"]) - .to include(match I18n.t("validations.household.old_persons_shared_ownership")) - expect(record.errors["age2"]) - .to include(match I18n.t("validations.household.old_persons_shared_ownership")) - expect(record.errors["type"]) - .to include(match I18n.t("validations.household.old_persons_shared_ownership")) - end - end - - context "when it is a joint purchase and neither of the buyers ages are known" do - let(:record) { FactoryBot.build(:sales_log, jointpur: 1, age1_known: 1, age2_known: 1, type: 24) } - - it "adds an error" do - household_validator.validate_buyers_age_for_old_persons_shared_ownership(record) - - expect(record.errors["age1"]) - .to include(match I18n.t("validations.household.old_persons_shared_ownership")) - expect(record.errors["age2"]) - .to include(match I18n.t("validations.household.old_persons_shared_ownership")) - expect(record.errors["type"]) - .to include(match I18n.t("validations.household.old_persons_shared_ownership")) - end - end - - context "when it is not a joint purchase and the buyer is over 64" do - let(:record) { FactoryBot.build(:sales_log, jointpur: 2, age1: 70, type: 24) } - - it "does not add an error" do - household_validator.validate_buyers_age_for_old_persons_shared_ownership(record) - - expect(record.errors).not_to be_present - end - end - - context "when it is not a joint purchase and the buyer is under 64" do - let(:record) { FactoryBot.build(:sales_log, jointpur: 2, age1: 20, type: 24) } - - it "adds an error" do - household_validator.validate_buyers_age_for_old_persons_shared_ownership(record) - - expect(record.errors["age1"]) - .to include(match I18n.t("validations.household.old_persons_shared_ownership")) - expect(record.errors["age2"]) - .to be_empty - expect(record.errors["type"]) - .to include(match I18n.t("validations.household.old_persons_shared_ownership")) - end - end - - context "when it is not a joint purchase and the buyers age is not known" do - let(:record) { FactoryBot.build(:sales_log, jointpur: 2, age1_known: 1, type: 24) } - - it "adds an error" do - household_validator.validate_buyers_age_for_old_persons_shared_ownership(record) - - expect(record.errors["age1"]) - .to include(match I18n.t("validations.household.old_persons_shared_ownership")) - expect(record.errors["age2"]) - .to be_empty - expect(record.errors["type"]) - .to include(match I18n.t("validations.household.old_persons_shared_ownership")) - end - end end describe "previous postcode validations" do diff --git a/spec/models/validations/sales/sale_information_validations_spec.rb b/spec/models/validations/sales/sale_information_validations_spec.rb index 7a12f37a5..7ca507c1d 100644 --- a/spec/models/validations/sales/sale_information_validations_spec.rb +++ b/spec/models/validations/sales/sale_information_validations_spec.rb @@ -224,4 +224,214 @@ RSpec.describe Validations::Sales::SaleInformationValidations do end end end + + describe "#validate_discounted_ownership_value" do + context "when grant is routed to" do + let(:record) { FactoryBot.build(:sales_log, mortgage: 10_000, deposit: 5_000, value: 30_000, ownershipsch: 2, type: 8) } + + context "and not provided" do + before do + record.grant = nil + end + + it "does not add an error" do + sale_information_validator.validate_discounted_ownership_value(record) + + expect(record.errors).to be_empty + end + end + + context "and is provided" do + it "adds an error if mortgage, deposit and grant total does not equal market value" do + record.grant = 3_000 + sale_information_validator.validate_discounted_ownership_value(record) + expect(record.errors[:mortgage]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) + expect(record.errors[:deposit]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) + expect(record.errors[:grant]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) + expect(record.errors[:value]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) + expect(record.errors[:discount]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) + end + + it "does not add an error if mortgage, deposit and grant total equals market value" do + record.grant = 15_000 + sale_information_validator.validate_discounted_ownership_value(record) + expect(record.errors).to be_empty + end + end + end + + context "when discount is routed to" do + let(:record) { FactoryBot.build(:sales_log, mortgage: 10_000, deposit: 5_000, value: 30_000, ownershipsch: 2, type: 9) } + + context "and not provided" do + before do + record.discount = nil + end + + it "does not add an error" do + sale_information_validator.validate_discounted_ownership_value(record) + + expect(record.errors).to be_empty + end + end + + context "and is provided" do + it "adds an error if mortgage and deposit total does not equal market value - discount" do + record.discount = 10 + sale_information_validator.validate_discounted_ownership_value(record) + expect(record.errors[:mortgage]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "27000.00")) + expect(record.errors[:deposit]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "27000.00")) + expect(record.errors[:grant]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "27000.00")) + expect(record.errors[:value]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "27000.00")) + expect(record.errors[:discount]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "27000.00")) + end + + it "does not add an error if mortgage and deposit total equals market value - discount" do + record.discount = 50 + sale_information_validator.validate_discounted_ownership_value(record) + expect(record.errors).to be_empty + end + end + end + + context "when neither discount nor grant is routed to" do + let(:record) { FactoryBot.build(:sales_log, mortgage: 10_000, value: 30_000, ownershipsch: 2, type: 29) } + + it "adds an error if mortgage and deposit total does not equal market value" do + record.deposit = 2_000 + sale_information_validator.validate_discounted_ownership_value(record) + expect(record.errors[:mortgage]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) + expect(record.errors[:deposit]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) + expect(record.errors[:grant]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) + expect(record.errors[:value]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) + expect(record.errors[:discount]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) + end + + it "does not add an error if mortgage and deposit total equals market value" do + record.deposit = 20_000 + sale_information_validator.validate_discounted_ownership_value(record) + expect(record.errors).to be_empty + end + end + + context "when mortgage is routed to" do + let(:record) { FactoryBot.build(:sales_log, mortgageused: 1, deposit: 5_000, grant: 3_000, value: 20_000, discount: 10, ownershipsch: 2) } + + context "and not provided" do + before do + record.mortgage = nil + end + + it "does not add an error" do + sale_information_validator.validate_discounted_ownership_value(record) + + expect(record.errors).to be_empty + end + end + + context "and is provided" do + it "adds an error if mortgage, grant and deposit total does not equal market value - discount" do + record.mortgage = 10 + sale_information_validator.validate_discounted_ownership_value(record) + expect(record.errors[:mortgage]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) + expect(record.errors[:deposit]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) + expect(record.errors[:grant]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) + expect(record.errors[:value]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) + expect(record.errors[:discount]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) + end + + it "does not add an error if mortgage, grant and deposit total equals market value - discount" do + record.mortgage = 10_000 + sale_information_validator.validate_discounted_ownership_value(record) + expect(record.errors).to be_empty + end + end + end + + context "when mortgage is not routed to" do + let(:record) { FactoryBot.build(:sales_log, mortgageused: 2, deposit: 5_000, grant: 3_000, value: 20_000, discount: 10, ownershipsch: 2) } + + it "adds an error if grant and deposit total does not equal market value - discount" do + sale_information_validator.validate_discounted_ownership_value(record) + expect(record.errors[:mortgage]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) + expect(record.errors[:deposit]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) + expect(record.errors[:grant]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) + expect(record.errors[:value]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) + expect(record.errors[:discount]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) + end + + it "does not add an error if mortgage, grant and deposit total equals market value - discount" do + record.grant = 13_000 + sale_information_validator.validate_discounted_ownership_value(record) + expect(record.errors).to be_empty + end + end + + context "when owhership is not discounted" do + let(:record) { FactoryBot.build(:sales_log, mortgage: 10_000, deposit: 5_000, grant: 3_000, value: 20_000, discount: 10, ownershipsch: 1) } + + it "does not add an error" do + sale_information_validator.validate_discounted_ownership_value(record) + + expect(record.errors).to be_empty + end + end + end + + describe "#validate_basic_monthly_rent" do + context "when within permitted bounds" do + let(:record) { build(:sales_log, mrent: 9998, ownershipsch: 1, type: 2) } + + it "does not add an error" do + sale_information_validator.validate_basic_monthly_rent(record) + + expect(record.errors[:mrent]).not_to be_present + expect(record.errors[:type]).not_to be_present + end + end + + context "when the rent is blank" do + let(:record) { build(:sales_log, mrent: nil, ownershipsch: 1, type: 2) } + + it "does not add an error" do + sale_information_validator.validate_basic_monthly_rent(record) + + expect(record.errors[:mrent]).not_to be_present + expect(record.errors[:type]).not_to be_present + end + end + + context "when the type is old persons shared ownership" do + let(:record) { build(:sales_log, mrent: 100_000, ownershipsch: 1, type: 24) } + + it "does not add an error" do + sale_information_validator.validate_basic_monthly_rent(record) + + expect(record.errors[:mrent]).not_to be_present + expect(record.errors[:type]).not_to be_present + end + end + + context "when the type is blank" do + let(:record) { build(:sales_log, mrent: 100_000, ownershipsch: 1, type: nil) } + + it "does not add an error" do + sale_information_validator.validate_basic_monthly_rent(record) + + expect(record.errors[:mrent]).not_to be_present + expect(record.errors[:type]).not_to be_present + end + end + + context "when higher than upper bound" do + let(:record) { build(:sales_log, mrent: 100_000, ownershipsch: 1, type: 2) } + + it "adds an error" do + sale_information_validator.validate_basic_monthly_rent(record) + + expect(record.errors[:mrent]).to include(I18n.t("validations.sale_information.monthly_rent.higher_than_expected")) + expect(record.errors[:type]).to include(I18n.t("validations.sale_information.monthly_rent.higher_than_expected")) + 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 17bd87d96..dc873bc97 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,13 +367,117 @@ 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) .not_to be_deposit_over_soft_max end end + + context "when validating shared ownership deposit" do + it "returns false if MORTGAGE + DEPOSIT + CASHDIS are equal VALUE * EQUITY/100" do + record.mortgage = 1000 + record.deposit = 1000 + record.cashdis = 1000 + record.value = 3000 + record.equity = 100 + + expect(record) + .not_to be_shared_ownership_deposit_invalid + end + + it "returns false if mortgage is used and no mortgage is given" do + record.mortgage = nil + record.deposit = 1000 + record.cashdis = 1000 + record.value = 3000 + record.equity = 100 + + expect(record) + .not_to be_shared_ownership_deposit_invalid + end + + it "returns true if mortgage is not used and no mortgage is given" do + record.mortgage = nil + record.mortgageused = 2 + record.deposit = 1000 + record.cashdis = 1000 + record.value = 3000 + record.equity = 100 + + expect(record) + .to be_shared_ownership_deposit_invalid + end + + it "returns false if no deposit is given" do + record.mortgage = 1000 + record.deposit = nil + record.cashdis = 1000 + record.value = 3000 + record.equity = 100 + + expect(record) + .not_to be_shared_ownership_deposit_invalid + end + + it "returns false if no cashdis is given and cashdis is routed to" do + record.mortgage = 1000 + record.deposit = 1000 + record.type = 18 + record.cashdis = nil + record.value = 3000 + record.equity = 100 + + expect(record) + .not_to be_shared_ownership_deposit_invalid + end + + it "returns true if no cashdis is given and cashdis is not routed to" do + record.mortgage = 1000 + record.deposit = 1000 + record.type = 2 + record.cashdis = nil + record.value = 3000 + record.equity = 100 + + expect(record) + .to be_shared_ownership_deposit_invalid + end + + it "returns false if no value is given" do + record.mortgage = 1000 + record.deposit = 1000 + record.cashdis = 1000 + record.value = nil + record.equity = 100 + + expect(record) + .not_to be_shared_ownership_deposit_invalid + end + + it "returns false if no equity is given" do + record.mortgage = 1000 + record.deposit = 1000 + record.cashdis = 1000 + record.value = 3000 + record.equity = nil + + expect(record) + .not_to be_shared_ownership_deposit_invalid + end + + it "returns true if MORTGAGE + DEPOSIT + CASHDIS are not equal VALUE * EQUITY/100" do + record.mortgage = 1000 + record.deposit = 1000 + record.cashdis = 1000 + record.value = 4323 + record.equity = 100 + + expect(record) + .to be_shared_ownership_deposit_invalid + end + end end describe "hodate_more_than_3_years_before_saledate" do @@ -396,4 +553,44 @@ RSpec.describe Validations::Sales::SoftValidations do expect(record).not_to be_wheelchair_when_not_disabled end end + + describe "#grant_outside_common_range?" do + it "returns true if grant is below 9000" do + record.grant = 1_000 + + expect(record).to be_grant_outside_common_range + end + + it "returns true if grant is above 16000" do + record.grant = 100_000 + + expect(record).to be_grant_outside_common_range + end + + it "returns false if grant is within expected range" do + record.grant = 10_000 + + 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 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/models/validations/soft_validations_spec.rb b/spec/models/validations/soft_validations_spec.rb index 93ed01a20..9ca9436fb 100644 --- a/spec/models/validations/soft_validations_spec.rb +++ b/spec/models/validations/soft_validations_spec.rb @@ -237,4 +237,78 @@ RSpec.describe Validations::SoftValidations do end end end + + describe "old persons shared ownership soft validations" do + context "when it is a joint purchase and both buyers are over 64" do + let(:record) { FactoryBot.build(:sales_log, jointpur: 1, age1: 65, age2: 66, type: 24) } + + it "returns false" do + expect(record.buyers_age_for_old_persons_shared_ownership_invalid?).to be false + end + end + + context "when it is a joint purchase and first buyer is over 64" do + let(:record) { FactoryBot.build(:sales_log, jointpur: 1, age1: 65, age2: 40, type: 24) } + + it "returns false" do + expect(record.buyers_age_for_old_persons_shared_ownership_invalid?).to be false + end + end + + context "when it is a joint purchase and second buyer is over 64" do + let(:record) { FactoryBot.build(:sales_log, jointpur: 1, age1: 43, age2: 64, type: 24) } + + it "returns false" do + expect(record.buyers_age_for_old_persons_shared_ownership_invalid?).to be false + end + end + + context "when it is a joint purchase and neither of the buyers are over 64" do + let(:record) { FactoryBot.build(:sales_log, jointpur: 1, age1: 43, age2: 33, type: 24) } + + it "returns true" do + expect(record.buyers_age_for_old_persons_shared_ownership_invalid?).to be true + end + end + + context "when it is a joint purchase and first buyer is under 64 and the second buyers' age is unknown" do + let(:record) { FactoryBot.build(:sales_log, jointpur: 1, age1: 43, age2_known: 1, type: 24) } + + it "returns true" do + expect(record.buyers_age_for_old_persons_shared_ownership_invalid?).to be true + end + end + + context "when it is a joint purchase and neither of the buyers ages are known" do + let(:record) { FactoryBot.build(:sales_log, jointpur: 1, age1_known: 1, age2_known: 1, type: 24) } + + it "returns true" do + expect(record.buyers_age_for_old_persons_shared_ownership_invalid?).to be true + end + end + + context "when it is not a joint purchase and the buyer is over 64" do + let(:record) { FactoryBot.build(:sales_log, jointpur: 2, age1: 70, type: 24) } + + it "returns false" do + expect(record.buyers_age_for_old_persons_shared_ownership_invalid?).to be false + end + end + + context "when it is not a joint purchase and the buyer is under 64" do + let(:record) { FactoryBot.build(:sales_log, jointpur: 2, age1: 20, type: 24) } + + it "returns true" do + expect(record.buyers_age_for_old_persons_shared_ownership_invalid?).to be true + end + end + + context "when it is not a joint purchase and the buyers age is not known" do + let(:record) { FactoryBot.build(:sales_log, jointpur: 2, age1_known: 1, type: 24) } + + it "returns true" do + expect(record.buyers_age_for_old_persons_shared_ownership_invalid?).to be true + end + end + 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/lettings_logs_controller_spec.rb b/spec/requests/lettings_logs_controller_spec.rb index dc009a16d..1bb0afbf9 100644 --- a/spec/requests/lettings_logs_controller_spec.rb +++ b/spec/requests/lettings_logs_controller_spec.rb @@ -400,6 +400,109 @@ RSpec.describe LettingsLogsController, type: :request do expect(page).not_to have_link(lettings_log_2022.id.to_s) end end + + context "with bulk_upload_id filter" do + context "with bulk upload that belongs to current user" do + let(:organisation) { create(:organisation) } + + let(:user) { create(:user, organisation:) } + let(:bulk_upload) { create(:bulk_upload, user:) } + + let!(:included_log) { create(:lettings_log, :in_progress, bulk_upload:, owning_organisation: organisation) } + let!(:excluded_log) { create(:lettings_log, :in_progress, owning_organisation: organisation) } + + it "returns logs only associated with the bulk upload" do + get "/lettings-logs?bulk_upload_id[]=#{bulk_upload.id}" + + expect(page).to have_content(included_log.id) + expect(page).not_to have_content(excluded_log.id) + end + + it "dislays how many logs remaining to fix" do + get "/lettings-logs?bulk_upload_id[]=#{bulk_upload.id}" + expect(page).to have_content("You need to fix 1 log") + end + + it "displays filter" do + get "/lettings-logs?bulk_upload_id[]=#{bulk_upload.id}" + expect(page).to have_content("With logs from bulk upload") + end + + it "hides collection year filter" do + get "/lettings-logs?bulk_upload_id[]=#{bulk_upload.id}" + expect(page).not_to have_content("Collection year") + end + + it "hides status filter" do + get "/lettings-logs?bulk_upload_id[]=#{bulk_upload.id}" + expect(page).not_to have_content("Status") + end + + it "hides button to create a new log" do + get "/lettings-logs?bulk_upload_id[]=#{bulk_upload.id}" + expect(page).not_to have_content("Create a new lettings log") + end + + it "displays card with help info" do + get "/lettings-logs?bulk_upload_id[]=#{bulk_upload.id}" + expect(page).to have_content("The following logs are from your recent bulk upload") + end + + it "displays meta info about the bulk upload" do + get "/lettings-logs?bulk_upload_id[]=#{bulk_upload.id}" + expect(page).to have_content(bulk_upload.filename) + expect(page).to have_content(bulk_upload.created_at.to_fs(:govuk_date_and_time)) + end + end + + context "with bulk upload that belongs to another user" do + let(:organisation) { create(:organisation) } + + let(:user) { create(:user, organisation:) } + let(:other_user) { create(:user, organisation:) } + let(:bulk_upload) { create(:bulk_upload, user: other_user) } + + let!(:excluded_log) { create(:lettings_log, bulk_upload:, owning_organisation: organisation) } + let!(:also_excluded_log) { create(:lettings_log, owning_organisation: organisation) } + + it "does not return any logs" do + get "/lettings-logs?bulk_upload_id[]=#{bulk_upload.id}" + + expect(page).not_to have_content(excluded_log.id) + expect(page).not_to have_content(also_excluded_log.id) + end + end + + context "when bulk upload has been resolved" do + let(:organisation) { create(:organisation) } + + let(:user) { create(:user, organisation:) } + let(:bulk_upload) { create(:bulk_upload, user:) } + + it "redirects to resume the bulk upload" do + get "/lettings-logs?bulk_upload_id[]=#{bulk_upload.id}" + + expect(response).to redirect_to(resume_bulk_upload_lettings_result_path(bulk_upload)) + end + end + end + + context "without bulk_upload_id" do + it "does not display filter" do + get "/lettings-logs" + expect(page).not_to have_content("With logs from bulk upload") + end + + it "displays button to create a new log" do + get "/lettings-logs" + expect(page).to have_content("Create a new lettings log") + end + + it "does not display card with help info" do + get "/lettings-logs" + expect(page).not_to have_content("The following logs are from your recent bulk upload") + end + end end end @@ -458,6 +561,7 @@ RSpec.describe LettingsLogsController, type: :request do it "includes the search on the CSV link" do search_term = "foo" + FactoryBot.create(:lettings_log, created_by: user, owning_organisation: user.organisation, tenancycode: "foo") get "/lettings-logs?search=#{search_term}", headers: headers, params: {} expect(page).to have_link("Download (CSV)", href: "/lettings-logs/csv-download?search=#{search_term}") end diff --git a/spec/requests/organisations_controller_spec.rb b/spec/requests/organisations_controller_spec.rb index f63dcf54e..004ea538a 100644 --- a/spec/requests/organisations_controller_spec.rb +++ b/spec/requests/organisations_controller_spec.rb @@ -1141,10 +1141,11 @@ RSpec.describe OrganisationsController, type: :request do context "when they view the logs tab" do before do + FactoryBot.create(:lettings_log, owning_organisation: organisation) get "/organisations/#{organisation.id}/lettings-logs" end - it "has a CSV download button with the correct path" do + it "has a CSV download button with the correct path if at least 1 log exists" do expect(page).to have_link("Download (CSV)", href: "/organisations/#{organisation.id}/logs/csv-download") end @@ -1152,7 +1153,7 @@ RSpec.describe OrganisationsController, type: :request do let(:other_organisation) { FactoryBot.create(:organisation) } before do - FactoryBot.create_list(:lettings_log, 3, owning_organisation: organisation) + FactoryBot.create_list(:lettings_log, 2, owning_organisation: organisation) FactoryBot.create_list(:lettings_log, 2, owning_organisation: other_organisation) end diff --git a/spec/services/bulk_upload/lettings/validator_spec.rb b/spec/services/bulk_upload/lettings/validator_spec.rb index e242858bf..661119448 100644 --- a/spec/services/bulk_upload/lettings/validator_spec.rb +++ b/spec/services/bulk_upload/lettings/validator_spec.rb @@ -43,7 +43,14 @@ RSpec.describe BulkUpload::Lettings::Validator do validator.call error = BulkUploadError.first - expect(error.row).to eq("7") + + expect(error.field).to eql("field_96") + expect(error.error).to eql("blank") + expect(error.tenant_code).to eql("123") + expect(error.property_ref).to be_nil + expect(error.row).to eql("7") + expect(error.cell).to eql("CS7") + expect(error.col).to eql("CS") end end diff --git a/spec/support/bulk_upload/log_to_csv.rb b/spec/support/bulk_upload/log_to_csv.rb index 3b49f0b86..7734d9fe2 100644 --- a/spec/support/bulk_upload/log_to_csv.rb +++ b/spec/support/bulk_upload/log_to_csv.rb @@ -159,12 +159,7 @@ class BulkUpload::LogToCsv end def renewal - case log.renewal - when 1 - 1 - when 0 - 2 - end + checkbox_value(log.renewal) end def london_affordable_rent @@ -210,12 +205,7 @@ class BulkUpload::LogToCsv end def previous_postcode_known - case log.ppcodenk - when 1 - 1 - when 0 - 2 - end + checkbox_value(log.ppcodenk) end def homeless @@ -228,25 +218,19 @@ class BulkUpload::LogToCsv end def cbl - case log.cbl - when 0 - 2 - when 1 - 1 - end + checkbox_value(log.cbl) end def chr - case log.chr - when 0 - 2 - when 1 - 1 - end + checkbox_value(log.chr) end def cap - case log.cap + checkbox_value(log.cap) + end + + def checkbox_value(field) + case field when 0 2 when 1