Browse Source

Merge branch 'CLDC-1923-amend-validation-message-for-single-ended-ranges' of github.com:communitiesuk/submit-social-housing-lettings-and-sales-data into CLDC-1923-amend-validation-message-for-single-ended-ranges

pull/1253/head
Arthur Campbell 3 years ago
parent
commit
4c229d3750
  1. 25
      app/components/bulk_upload_error_summary_table_component.html.erb
  2. 17
      app/components/bulk_upload_error_summary_table_component.rb
  3. 4
      app/controllers/bulk_upload_lettings_results_controller.rb
  4. 79
      app/mailers/bulk_upload_mailer.rb
  5. 3
      app/models/bulk_upload.rb
  6. 3
      app/models/derived_variables/sales_log_variables.rb
  7. 17
      app/models/form/sales/pages/deposit_and_mortgage_value_check.rb
  8. 21
      app/models/form/sales/pages/old_persons_shared_ownership_value_check.rb
  9. 27
      app/models/form/sales/pages/shared_ownership_deposit_value_check.rb
  10. 27
      app/models/form/sales/pages/staircase_bought_value_check.rb
  11. 23
      app/models/form/sales/questions/deposit_and_mortgage_value_check.rb
  12. 23
      app/models/form/sales/questions/old_persons_shared_ownership_value_check.rb
  13. 23
      app/models/form/sales/questions/shared_ownership_deposit_value_check.rb
  14. 2
      app/models/form/sales/questions/staircase_bought.rb
  15. 23
      app/models/form/sales/questions/staircase_bought_value_check.rb
  16. 2
      app/models/form/sales/questions/staircase_owned.rb
  17. 3
      app/models/form/sales/subsections/discounted_ownership_scheme.rb
  18. 2
      app/models/form/sales/subsections/household_characteristics.rb
  19. 2
      app/models/form/sales/subsections/household_needs.rb
  20. 1
      app/models/form/sales/subsections/setup.rb
  21. 6
      app/models/form/sales/subsections/shared_ownership_scheme.rb
  22. 1
      app/models/log.rb
  23. 25
      app/models/sales_log.rb
  24. 27
      app/models/validations/sales/financial_validations.rb
  25. 18
      app/models/validations/sales/household_validations.rb
  26. 23
      app/models/validations/sales/sale_information_validations.rb
  27. 9
      app/models/validations/sales/setup_validations.rb
  28. 21
      app/models/validations/sales/soft_validations.rb
  29. 1
      app/services/bulk_upload/lettings/validator.rb
  30. 6
      app/views/bulk_upload_lettings_results/show.html.erb
  31. 22
      app/views/bulk_upload_lettings_results/summary.html.erb
  32. 27
      config/locales/en.yml
  33. 1
      config/routes.rb
  34. 5
      db/migrate/20230120163049_add_deposit_and_mortgage_value_check_to_sales_logs.rb
  35. 7
      db/migrate/20230123101256_add_shared_ownership_deposit_value_check.rb
  36. 7
      db/migrate/20230124105247_add_old_persons_shared_ownership_value_check.rb
  37. 5
      db/migrate/20230125152916_add_staircase_bought_value_check_to_sales_log.rb
  38. 5
      db/migrate/20230126145529_add_column_to_bulk_upload_errors.rb
  39. 12
      db/schema.rb
  40. 6
      db/seeds.rb
  41. 81
      spec/components/bulk_upload_error_summary_table_component_spec.rb
  42. 3
      spec/factories/bulk_upload_error.rb
  43. 8
      spec/factories/sales_log.rb
  44. 44
      spec/models/form/sales/pages/old_persons_shared_ownership_value_check_spec.rb
  45. 50
      spec/models/form/sales/pages/shared_ownership_deposit_value_check_spec.rb
  46. 57
      spec/models/form/sales/questions/old_persons_shared_ownership_value_check_spec.rb
  47. 61
      spec/models/form/sales/questions/shared_ownership_deposit_value_check_spec.rb
  48. 2
      spec/models/form/sales/questions/staircase_bought_spec.rb
  49. 2
      spec/models/form/sales/questions/staircase_owned_spec.rb
  50. 3
      spec/models/form/sales/subsections/discounted_ownership_scheme_spec.rb
  51. 2
      spec/models/form/sales/subsections/household_characteristics_spec.rb
  52. 2
      spec/models/form/sales/subsections/household_needs_spec.rb
  53. 1
      spec/models/form/sales/subsections/setup_spec.rb
  54. 4
      spec/models/form/sales/subsections/shared_ownership_scheme_spec.rb
  55. 4
      spec/models/form_handler_spec.rb
  56. 4
      spec/models/form_spec.rb
  57. 58
      spec/models/sales_log_spec.rb
  58. 85
      spec/models/validations/sales/financial_validations_spec.rb
  59. 115
      spec/models/validations/sales/household_validations_spec.rb
  60. 65
      spec/models/validations/sales/sale_information_validations_spec.rb
  61. 49
      spec/models/validations/sales/setup_validations_spec.rb
  62. 179
      spec/models/validations/sales/soft_validations_spec.rb
  63. 6
      spec/models/validations/shared_validations_spec.rb
  64. 74
      spec/models/validations/soft_validations_spec.rb
  65. 17
      spec/requests/bulk_upload_lettings_results_controller_spec.rb
  66. 26
      spec/requests/sales_logs_controller_spec.rb
  67. 9
      spec/services/bulk_upload/lettings/validator_spec.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

4
app/controllers/bulk_upload_lettings_results_controller.rb

@ -19,6 +19,10 @@ class BulkUploadLettingsResultsController < ApplicationController
end
end
def summary
@bulk_upload = current_user.bulk_uploads.lettings.find(params[:id])
end
private
def reset_logs_filters

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

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

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

3
app/models/form/sales/subsections/discounted_ownership_scheme.rb

@ -14,9 +14,11 @@ class Form::Sales::Subsections::DiscountedOwnershipScheme < ::Form::Subsection
Form::Sales::Pages::AboutPriceNotRtb.new(nil, nil, self),
Form::Sales::Pages::GrantValueCheck.new(nil, nil, self),
Form::Sales::Pages::PurchasePrice.new("purchase_price_discounted_ownership", nil, self),
Form::Sales::Pages::DepositAndMortgageValueCheck.new("discounted_ownership_deposit_and_mortgage_value_check_after_value_and_discount", nil, self),
Form::Sales::Pages::Mortgageused.new("mortgage_used_discounted_ownership", nil, self),
Form::Sales::Pages::MortgageAmount.new("mortgage_amount_discounted_ownership", nil, self),
Form::Sales::Pages::ExtraBorrowingValueCheck.new("extra_borrowing_mortgage_value_check", nil, self),
Form::Sales::Pages::DepositAndMortgageValueCheck.new("discounted_ownership_deposit_and_mortgage_value_check_after_mortgage", nil, self),
Form::Sales::Pages::MortgageLender.new("mortgage_lender_discounted_ownership", nil, self),
Form::Sales::Pages::MortgageLenderOther.new("mortgage_lender_other_discounted_ownership", nil, self),
Form::Sales::Pages::MortgageLength.new("mortgage_length_discounted_ownership", nil, self),
@ -25,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),
]

1
app/models/log.rb

@ -108,7 +108,6 @@ private
return unless form
form.reset_not_routed_questions(self)
reset_created_by!
end

25
app/models/sales_log.rb

@ -1,4 +1,5 @@
class SalesLogValidator < ActiveModel::Validator
include Validations::Sales::SetupValidations
include Validations::Sales::HouseholdValidations
include Validations::Sales::PropertyValidations
include Validations::SharedValidations
@ -23,18 +24,18 @@ class SalesLog < Log
has_paper_trail
validates_with SalesLogValidator
before_validation :set_derived_fields!
before_validation :reset_invalidated_dependent_fields!
before_validation :process_postcode_changes!, if: :postcode_full_changed?
before_validation :process_previous_postcode_changes!, if: :ppostcode_full_changed?
before_validation :reset_location_fields!, unless: :postcode_known?
before_validation :reset_previous_location_fields!, unless: :previous_postcode_known?
before_validation :set_derived_fields!
scope :filter_by_year, ->(year) { where(saledate: Time.zone.local(year.to_i, 4, 1)...Time.zone.local(year.to_i + 1, 4, 1)) }
scope :search_by, ->(param) { filter_by_id(param) }
scope :filter_by_organisation, ->(org, _user = nil) { where(owning_organisation: org) }
OPTIONAL_FIELDS = %w[purchid].freeze
OPTIONAL_FIELDS = %w[purchid old_persons_shared_ownership_value_check].freeze
RETIREMENT_AGES = { "M" => 65, "F" => 60, "X" => 65 }.freeze
def startdate
@ -159,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?
@ -211,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

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

@ -10,20 +10,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?
@ -108,8 +94,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

23
app/models/validations/sales/sale_information_validations.rb

@ -3,7 +3,7 @@ module Validations::Sales::SaleInformationValidations
return if record.saledate.blank? || record.hodate.blank?
unless record.saledate > record.hodate
record.errors.add :hodate, "Practical completion or handover date must be before exchange date"
record.errors.add :hodate, I18n.t("validations.sale_information.hodate.must_be_before_exdate")
end
end
@ -23,11 +23,15 @@ module Validations::Sales::SaleInformationValidations
def validate_exchange_date(record)
return unless record.exdate && record.saledate
record.errors.add(:exdate, I18n.t("validations.sale_information.exdate.must_be_before_saledate")) if record.exdate > record.saledate
return if (record.saledate.to_date - record.exdate.to_date).to_i / 365 < 1
if record.exdate > record.saledate
record.errors.add :exdate, I18n.t("validations.sale_information.exdate.must_be_before_saledate")
record.errors.add :saledate, I18n.t("validations.sale_information.saledate.must_be_after_exdate")
end
record.errors.add(:exdate, I18n.t("validations.sale_information.exdate.must_be_less_than_1_year_from_saledate"))
if record.saledate - record.exdate >= 1.year
record.errors.add :exdate, I18n.t("validations.sale_information.exdate.must_be_less_than_1_year_from_saledate")
record.errors.add :saledate, I18n.t("validations.sale_information.saledate.must_be_less_than_1_year_from_exdate")
end
end
def validate_previous_property_unit_type(record)
@ -54,4 +58,13 @@ module Validations::Sales::SaleInformationValidations
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

9
app/models/validations/sales/setup_validations.rb

@ -0,0 +1,9 @@
module Validations::Sales::SetupValidations
def validate_saledate(record)
return unless record.saledate
unless Time.zone.local(2022, 4, 1) <= record.saledate && record.saledate < Time.zone.local(2023, 4, 1)
record.errors.add :saledate, I18n.t("validations.setup.saledate.financial_year")
end
end
end

21
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,6 +47,23 @@ 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

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

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

27
config/locales/en.yml

@ -148,6 +148,8 @@ en:
setup:
intermediate_rent_product_name:
blank: "Enter name of other intermediate rent product"
saledate:
financial_year: "Date must be from 22/23 financial year"
startdate:
later_than_14_days_after: "The tenancy start date must not be later than 14 days from today’s date"
before_scheme_end_date: "The tenancy start date must be before the end date for this supported housing scheme"
@ -276,6 +278,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"
@ -369,10 +375,9 @@ 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"
tenancy:
length:
fixed_term_not_required: "You must only answer the length of the tenancy if it's fixed-term"
@ -417,16 +422,19 @@ en:
proplen:
social_homebuy: "Social HomeBuy buyers should not have lived here before"
rent_to_buy: "Rent to Buy buyers should not have lived here before"
hodate:
must_be_before_exdate: "Practical completion or handover date must be before exchange date"
exdate:
must_be_before_saledate:
Contract exchange date must be less than 1 year before completion date
must_be_less_than_1_year_from_saledate:
Contract exchange date must be less than 1 year before completion date
must_be_before_saledate: "Contract exchange date must be less than 1 year before completion date"
must_be_less_than_1_year_from_saledate: "Contract exchange date must be less than 1 year before completion date"
saledate:
must_be_after_exdate: "Completion date must be less than 1 year after contract exchange date"
must_be_less_than_1_year_from_exdate: "Completion date must be less than 1 year after contract exchange date"
previous_property_beds:
property_type_bedsit: "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:
@ -460,6 +468,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:

1
config/routes.rb

@ -137,6 +137,7 @@ Rails.application.routes.draw do
resources :bulk_upload_lettings_results, path: "bulk-upload-results", only: [:show] do
member do
get :resume
get :summary
end
end

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

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

12
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_23_160741) 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_23_160741) 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
@ -486,9 +487,9 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_23_160741) do
t.integer "hoyear"
t.integer "fromprop"
t.integer "socprevten"
t.integer "mortlen"
t.integer "mortgagelender"
t.string "mortgagelenderother"
t.integer "mortlen"
t.integer "extrabor"
t.integer "hhmemb"
t.integer "totadult"
@ -501,10 +502,13 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_23_160741) do
t.boolean "is_la_inferred"
t.bigint "bulk_upload_id"
t.integer "retirement_value_check"
t.integer "deposit_and_mortgage_value_check"
t.integer "grant_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"

6
db/seeds.rb

@ -148,7 +148,7 @@ unless Rails.env.test?
if (Rails.env.development? || Rails.env.review?) && SalesLog.count.zero?
SalesLog.find_or_create_by!(
saledate: Date.new(1, 1, 1),
saledate: Date.new(2023, 1, 1),
purchid: "1",
ownershipsch: 1,
type: 2,
@ -157,7 +157,7 @@ unless Rails.env.test?
)
SalesLog.find_or_create_by!(
saledate: Date.new(1, 1, 1),
saledate: Date.new(2023, 1, 1),
purchid: "1",
ownershipsch: 2,
type: 9,
@ -166,7 +166,7 @@ unless Rails.env.test?
)
SalesLog.find_or_create_by!(
saledate: Date.new(1, 1, 1),
saledate: Date.new(2023, 1, 1),
purchid: "1",
ownershipsch: 3,
type: 10,

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

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

8
spec/factories/sales_log.rb

@ -2,18 +2,18 @@ FactoryBot.define do
factory :sales_log do
created_by { FactoryBot.create(:user) }
owning_organisation { created_by.organisation }
created_at { Time.utc(2022, 2, 8, 16, 52, 15) }
updated_at { Time.utc(2022, 2, 8, 16, 52, 15) }
created_at { Time.utc(2023, 2, 8, 16, 52, 15) }
updated_at { Time.utc(2023, 2, 8, 16, 52, 15) }
trait :in_progress do
purchid { "PC123" }
ownershipsch { 2 }
type { 8 }
saledate { Time.utc(2022, 2, 2, 10, 36, 49) }
saledate { Time.utc(2023, 2, 2, 10, 36, 49) }
end
trait :completed do
ownershipsch { 2 }
type { 8 }
saledate { Time.utc(2022, 2, 2, 10, 36, 49) }
saledate { Time.utc(2023, 2, 2, 10, 36, 49) }
companybuy { 1 }
jointpur { 1 }
beds { 2 }

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

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

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

3
spec/models/form/sales/subsections/discounted_ownership_scheme_spec.rb

@ -20,9 +20,11 @@ RSpec.describe Form::Sales::Subsections::DiscountedOwnershipScheme, type: :model
about_price_not_rtb
grant_value_check
purchase_price_discounted_ownership
discounted_ownership_deposit_and_mortgage_value_check_after_value_and_discount
mortgage_used_discounted_ownership
mortgage_amount_discounted_ownership
extra_borrowing_mortgage_value_check
discounted_ownership_deposit_and_mortgage_value_check_after_mortgage
mortgage_lender_discounted_ownership
mortgage_lender_other_discounted_ownership
mortgage_length_discounted_ownership
@ -31,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(188)
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(188)
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

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

@ -122,121 +122,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

65
spec/models/validations/sales/sale_information_validations_spec.rb

@ -111,12 +111,15 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
context "when exdate more than 1 year before saledate" do
let(:record) { build(:sales_log, exdate: 2.years.ago, saledate: 1.month.ago) }
it "does not add the error" do
it "adds error" do
sale_information_validator.validate_exchange_date(record)
expect(record.errors[:exdate]).to eq(
["Contract exchange date must be less than 1 year before completion date"],
)
expect(record.errors[:saledate]).to eq(
["Completion date must be less than 1 year after contract exchange date"],
)
end
end
@ -129,6 +132,9 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
expect(record.errors[:exdate]).to eq(
["Contract exchange date must be less than 1 year before completion date"],
)
expect(record.errors[:saledate]).to eq(
["Completion date must be less than 1 year after contract exchange date"],
)
end
end
@ -371,4 +377,61 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
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

49
spec/models/validations/sales/setup_validations_spec.rb

@ -0,0 +1,49 @@
require "rails_helper"
RSpec.describe Validations::Sales::SetupValidations do
subject(:setup_validator) { validator_class.new }
let(:validator_class) { Class.new { include Validations::Sales::SetupValidations } }
describe "#validate_saledate" do
context "when saledate is blank" do
let(:record) { FactoryBot.build(:sales_log, saledate: nil) }
it "does not add an error" do
setup_validator.validate_saledate(record)
expect(record.errors).to be_empty
end
end
context "when saledate is in the 22/23 financial year" do
let(:record) { FactoryBot.build(:sales_log, saledate: Time.zone.local(2023, 1, 1)) }
it "does not add an error" do
setup_validator.validate_saledate(record)
expect(record.errors).to be_empty
end
end
context "when saledate is before the 22/23 financial year" do
let(:record) { FactoryBot.build(:sales_log, saledate: Time.zone.local(2022, 1, 1)) }
it "adds error" do
setup_validator.validate_saledate(record)
expect(record.errors[:saledate]).to include(I18n.t("validations.setup.saledate.financial_year"))
end
end
context "when saledate is after the 22/23 financial year" do
let(:record) { FactoryBot.build(:sales_log, saledate: Time.zone.local(2023, 4, 1)) }
it "adds error" do
setup_validator.validate_saledate(record)
expect(record.errors[:saledate]).to include(I18n.t("validations.setup.saledate.financial_year"))
end
end
end
end

179
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
@ -416,4 +573,24 @@ RSpec.describe Validations::Sales::SoftValidations do
expect(record).not_to be_grant_outside_common_range
end
end
describe "#staircase_bought_above_fifty" do
it "returns false when stairbought is not set" do
record.stairbought = nil
expect(record).not_to be_staircase_bought_above_fifty
end
it "returns false when stairbought is below fifty" do
record.stairbought = 40
expect(record).not_to be_staircase_bought_above_fifty
end
it "returns true when stairbought is above fifty" do
record.stairbought = 70
expect(record).to be_staircase_bought_above_fifty
end
end
end

6
spec/models/validations/shared_validations_spec.rb

@ -76,11 +76,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.within_range", field: "Percentage bought in this staircasing transaction", min: "0 percent", max: "100 percent"))
.to include(match I18n.t("validations.numeric.within_range", 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

26
spec/requests/sales_logs_controller_spec.rb

@ -179,29 +179,29 @@ RSpec.describe SalesLogsController, type: :request do
end
context "with year filter" do
let!(:sales_log_2021) do
let!(:sales_log_2022) do
FactoryBot.create(:sales_log, :in_progress,
owning_organisation: organisation,
saledate: Time.zone.local(2022, 3, 1))
saledate: Time.zone.local(2022, 4, 1))
end
let!(:sales_log_2022) do
let!(:sales_log_2023) do
sales_log = FactoryBot.build(:sales_log, :completed,
owning_organisation: organisation,
saledate: Time.zone.local(2022, 12, 1))
saledate: Time.zone.local(2023, 1, 1))
sales_log.save!(validate: false)
sales_log
end
it "shows sales logs for multiple selected years" do
get "/sales-logs?years[]=2021&years[]=2022", headers: headers, params: {}
expect(page).to have_link(sales_log_2021.id.to_s)
expect(page).to have_link(sales_log_2022.id.to_s)
expect(page).to have_link(sales_log_2023.id.to_s)
end
it "shows sales logs for one selected year" do
get "/sales-logs?years[]=2021", headers: headers, params: {}
expect(page).to have_link(sales_log_2021.id.to_s)
expect(page).not_to have_link(sales_log_2022.id.to_s)
get "/sales-logs?years[]=2022", headers: headers, params: {}
expect(page).to have_link(sales_log_2022.id.to_s)
expect(page).to have_link(sales_log_2023.id.to_s)
end
end
@ -214,23 +214,23 @@ RSpec.describe SalesLogsController, type: :request do
Timecop.unfreeze
end
let!(:sales_log_2021) do
let!(:sales_log_2022) do
FactoryBot.create(:sales_log, :completed,
owning_organisation: organisation,
saledate: Time.zone.local(2022, 3, 1),
saledate: Time.zone.local(2022, 4, 1),
created_by: user)
end
let!(:sales_log_2022) do
let!(:sales_log_2023) do
FactoryBot.create(:sales_log,
owning_organisation: organisation,
saledate: Time.zone.local(2022, 12, 1),
saledate: Time.zone.local(2023, 1, 1),
created_by: user)
end
it "shows sales logs for multiple selected statuses and years" do
get "/sales-logs?years[]=2021&years[]=2022&status[]=in_progress&status[]=completed", headers: headers, params: {}
expect(page).to have_link(sales_log_2021.id.to_s)
expect(page).to have_link(sales_log_2022.id.to_s)
expect(page).to have_link(sales_log_2023.id.to_s)
end
end
end

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

Loading…
Cancel
Save