Browse Source

Merge branch 'main' into CLDC-1464-completion-date-validations

# Conflicts:
#	config/locales/en.yml
pull/1259/head
natdeanlewissoftwire 4 years ago
parent
commit
e66ade7780
  1. 25
      app/components/bulk_upload_error_summary_table_component.html.erb
  2. 17
      app/components/bulk_upload_error_summary_table_component.rb
  3. 32
      app/controllers/bulk_upload_lettings_results_controller.rb
  4. 14
      app/controllers/lettings_logs_controller.rb
  5. 4
      app/controllers/modules/logs_filter.rb
  6. 5
      app/helpers/logs_helper.rb
  7. 79
      app/mailers/bulk_upload_mailer.rb
  8. 3
      app/models/bulk_upload.rb
  9. 3
      app/models/derived_variables/sales_log_variables.rb
  10. 17
      app/models/form/sales/pages/deposit_and_mortgage_value_check.rb
  11. 18
      app/models/form/sales/pages/grant_value_check.rb
  12. 21
      app/models/form/sales/pages/old_persons_shared_ownership_value_check.rb
  13. 27
      app/models/form/sales/pages/shared_ownership_deposit_value_check.rb
  14. 27
      app/models/form/sales/pages/staircase_bought_value_check.rb
  15. 23
      app/models/form/sales/questions/deposit_and_mortgage_value_check.rb
  16. 1
      app/models/form/sales/questions/grant.rb
  17. 23
      app/models/form/sales/questions/grant_value_check.rb
  18. 1
      app/models/form/sales/questions/mortgage_length.rb
  19. 23
      app/models/form/sales/questions/old_persons_shared_ownership_value_check.rb
  20. 23
      app/models/form/sales/questions/shared_ownership_deposit_value_check.rb
  21. 2
      app/models/form/sales/questions/staircase_bought.rb
  22. 23
      app/models/form/sales/questions/staircase_bought_value_check.rb
  23. 2
      app/models/form/sales/questions/staircase_owned.rb
  24. 4
      app/models/form/sales/subsections/discounted_ownership_scheme.rb
  25. 2
      app/models/form/sales/subsections/household_characteristics.rb
  26. 2
      app/models/form/sales/subsections/household_needs.rb
  27. 1
      app/models/form/sales/subsections/setup.rb
  28. 6
      app/models/form/sales/subsections/shared_ownership_scheme.rb
  29. 5
      app/models/log.rb
  30. 24
      app/models/sales_log.rb
  31. 4
      app/models/user.rb
  32. 27
      app/models/validations/sales/financial_validations.rb
  33. 18
      app/models/validations/sales/household_validations.rb
  34. 25
      app/models/validations/sales/sale_information_validations.rb
  35. 27
      app/models/validations/sales/soft_validations.rb
  36. 1
      app/services/bulk_upload/lettings/validator.rb
  37. 11
      app/views/bulk_upload_lettings_results/resume.html.erb
  38. 6
      app/views/bulk_upload_lettings_results/show.html.erb
  39. 22
      app/views/bulk_upload_lettings_results/summary.html.erb
  40. 54
      app/views/logs/_log_filters.erb
  41. 4
      app/views/logs/_log_list.html.erb
  42. 62
      app/views/logs/index.html.erb
  43. 14
      config/locales/en.yml
  44. 7
      config/routes.rb
  45. 5
      db/migrate/20230120163049_add_deposit_and_mortgage_value_check_to_sales_logs.rb
  46. 7
      db/migrate/20230123101256_add_shared_ownership_deposit_value_check.rb
  47. 5
      db/migrate/20230123160741_add_grant_value_check_to_sales_log.rb
  48. 7
      db/migrate/20230124105247_add_old_persons_shared_ownership_value_check.rb
  49. 5
      db/migrate/20230125152916_add_staircase_bought_value_check_to_sales_log.rb
  50. 5
      db/migrate/20230126145529_add_column_to_bulk_upload_errors.rb
  51. 8
      db/schema.rb
  52. 81
      spec/components/bulk_upload_error_summary_table_component_spec.rb
  53. 68
      spec/controllers/bulk_upload_lettings_results_controller_spec.rb
  54. 3
      spec/factories/bulk_upload_error.rb
  55. 4
      spec/factories/sales_log.rb
  56. 4
      spec/models/form/sales/pages/buyer1_income_value_check_spec.rb
  57. 4
      spec/models/form/sales/pages/deposit_value_check_spec.rb
  58. 4
      spec/models/form/sales/pages/handover_date_check_spec.rb
  59. 4
      spec/models/form/sales/pages/household_wheelchair_check_spec.rb
  60. 4
      spec/models/form/sales/pages/mortgage_value_check_spec.rb
  61. 44
      spec/models/form/sales/pages/old_persons_shared_ownership_value_check_spec.rb
  62. 4
      spec/models/form/sales/pages/retirement_value_check_spec.rb
  63. 4
      spec/models/form/sales/pages/savings_value_check_spec.rb
  64. 50
      spec/models/form/sales/pages/shared_ownership_deposit_value_check_spec.rb
  65. 4
      spec/models/form/sales/questions/mortgage_length_spec.rb
  66. 57
      spec/models/form/sales/questions/old_persons_shared_ownership_value_check_spec.rb
  67. 61
      spec/models/form/sales/questions/shared_ownership_deposit_value_check_spec.rb
  68. 2
      spec/models/form/sales/questions/staircase_bought_spec.rb
  69. 2
      spec/models/form/sales/questions/staircase_owned_spec.rb
  70. 4
      spec/models/form/sales/subsections/discounted_ownership_scheme_spec.rb
  71. 2
      spec/models/form/sales/subsections/household_characteristics_spec.rb
  72. 2
      spec/models/form/sales/subsections/household_needs_spec.rb
  73. 1
      spec/models/form/sales/subsections/setup_spec.rb
  74. 4
      spec/models/form/sales/subsections/shared_ownership_scheme_spec.rb
  75. 4
      spec/models/form_handler_spec.rb
  76. 4
      spec/models/form_spec.rb
  77. 58
      spec/models/sales_log_spec.rb
  78. 6
      spec/models/user_spec.rb
  79. 85
      spec/models/validations/sales/financial_validations_spec.rb
  80. 115
      spec/models/validations/sales/household_validations_spec.rb
  81. 210
      spec/models/validations/sales/sale_information_validations_spec.rb
  82. 199
      spec/models/validations/sales/soft_validations_spec.rb
  83. 6
      spec/models/validations/shared_validations_spec.rb
  84. 74
      spec/models/validations/soft_validations_spec.rb
  85. 17
      spec/requests/bulk_upload_lettings_results_controller_spec.rb
  86. 104
      spec/requests/lettings_logs_controller_spec.rb
  87. 5
      spec/requests/organisations_controller_spec.rb
  88. 9
      spec/services/bulk_upload/lettings/validator_spec.rb
  89. 34
      spec/support/bulk_upload/log_to_csv.rb

25
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 %>

17
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

32
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

14
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

4
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

5
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

79
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

3
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

3
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

17
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

18
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

21
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

27
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

27
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

23
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

1
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)"

23
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

1
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."

23
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

23
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

2
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

23
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

2
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

4
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

2
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),

2
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

1
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),

6
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),
]

5
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

24
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

4
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

27
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

18
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

25
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

27
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

1
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

11
app/views/bulk_upload_lettings_results/resume.html.erb

@ -0,0 +1,11 @@
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-xl">There are no more logs that need updating</h1>
</div>
</div>
<p class="govuk-body-l">
You’ve completed all the logs that had errors from your bulk upload.
</p>
<%= govuk_button_link_to "Back to all logs", lettings_logs_path, button: true %>

6
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 %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<span class="govuk-caption-l">Bulk Upload for lettings (<%= @bulk_upload.year_combo %>)</span>
<span class="govuk-caption-l">Bulk upload for lettings (<%= @bulk_upload.year_combo %>)</span>
<h1 class="govuk-heading-l">We found <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> in your file</h1>
<div class="govuk-body">

22
app/views/bulk_upload_lettings_results/summary.html.erb

@ -0,0 +1,22 @@
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<span class="govuk-caption-l">Bulk upload for lettings (<%= @bulk_upload.year_combo %>)</span>
<h1 class="govuk-heading-l">Correct data export and reupload</h1>
<p class="govuk-body-l">
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.
</p>
</div>
</div>
<%= render BulkUploadErrorSummaryTableComponent.new(bulk_upload: @bulk_upload) %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<p class="govuk-body">
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) %>
</p>
</div>
</div>
<%= govuk_button_link_to "Upload your file again", start_bulk_upload_lettings_logs_path %>

54
app/views/logs/_log_filters.erb

@ -3,13 +3,48 @@
<div class="app-filter__header">
<h2 class="govuk-heading-m">Filters</h2>
</div>
<div class="app-filter__content">
<%= 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 %>
</div>

4
app/views/logs/_log_list.html.erb

@ -1,6 +1,8 @@
<h2 class="govuk-body">
<%= 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 %>
</h2>
<% logs.map do |log| %>
<%= render(LogSummaryComponent.new(current_user:, log:)) %>

62
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)}") %>
<div class="govuk-notification-banner__heading">
<%= govuk_link_to "Update logs", update_logs_lettings_logs_path, class: "govuk-notification-banner__link" %>
</div>
<% end %>
<div class="govuk-notification-banner__heading">
<%= govuk_link_to "Update logs", update_logs_lettings_logs_path, class: "govuk-notification-banner__link" %>
</div>
<% 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 %>
<div class="app-filter-layout" data-controller="filter-layout">
<div class="govuk-button-group app-filter-toggle govuk-!-margin-bottom-6">
<% 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 %>
<div class="app-card govuk-!-margin-bottom-9">
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<p class="govuk-body-l">
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.
</p>
<% 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 %>
<p class="govuk-body">
<strong>Bulk Upload details:</strong><br>
<%= @bulk_upload.filename %><br>
Uploaded on <%= @bulk_upload.created_at.to_fs(:govuk_date_and_time) %><br>
</p>
</div>
</div>
</div>
<% end %>
<div class="app-filter-layout" data-controller="filter-layout">
<% unless @bulk_upload %>
<div class="govuk-button-group app-filter-toggle govuk-!-margin-bottom-6">
<% 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 %>
</div>
<% end %>
<%= render partial: "log_filters" %>
<div class="app-filter-layout__content">
<%= 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") %>

14
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:

7
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

5
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

7
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

5
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

7
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

5
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

5
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

8
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"

81
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

68
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

3
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) }

4
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 }

4
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

4
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

4
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

4
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

4
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([
{

44
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

4
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

4
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

50
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

4
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

57
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

61
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

2
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

2
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

4
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
],
)

2
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

2
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

1
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

4
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

4
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

4
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"))

58
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

6
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

85
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

115
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

210
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

199
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

6
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

74
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

17
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

104
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

5
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

9
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

34
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

Loading…
Cancel
Save