Browse Source

Merge branch 'main' into CLDC-1790-sales-validation-content-updates

# Conflicts:
#	config/locales/en.yml
#	db/schema.rb
CLDC-1790-sales-validation-content-updates
natdeanlewissoftwire 2 years ago
parent
commit
79c19d9eab
  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. 3
      app/models/form/question.rb
  8. 8
      app/models/form/sales/pages/age1.rb
  9. 13
      app/models/form/sales/pages/age2.rb
  10. 8
      app/models/form/sales/pages/buyer1_ethnic_group.rb
  11. 8
      app/models/form/sales/pages/buyer1_live_in_property.rb
  12. 8
      app/models/form/sales/pages/buyer1_working_situation.rb
  13. 13
      app/models/form/sales/pages/buyer2_live_in_property.rb
  14. 13
      app/models/form/sales/pages/buyer2_relationship_to_buyer1.rb
  15. 13
      app/models/form/sales/pages/buyer2_working_situation.rb
  16. 17
      app/models/form/sales/pages/deposit_and_mortgage_value_check.rb
  17. 8
      app/models/form/sales/pages/gender_identity1.rb
  18. 13
      app/models/form/sales/pages/gender_identity2.rb
  19. 21
      app/models/form/sales/pages/monthly_charges_value_check.rb
  20. 8
      app/models/form/sales/pages/nationality1.rb
  21. 8
      app/models/form/sales/pages/number_of_others_in_property.rb
  22. 27
      app/models/form/sales/pages/staircase_bought_value_check.rb
  23. 23
      app/models/form/sales/questions/deposit_and_mortgage_value_check.rb
  24. 2
      app/models/form/sales/questions/discounted_ownership_type.rb
  25. 23
      app/models/form/sales/questions/monthly_charges_value_check.rb
  26. 1
      app/models/form/sales/questions/privacy_notice.rb
  27. 2
      app/models/form/sales/questions/staircase_bought.rb
  28. 23
      app/models/form/sales/questions/staircase_bought_value_check.rb
  29. 2
      app/models/form/sales/questions/staircase_owned.rb
  30. 4
      app/models/form/sales/subsections/discounted_ownership_scheme.rb
  31. 1
      app/models/form/sales/subsections/outright_sale.rb
  32. 1
      app/models/form/sales/subsections/property_information.rb
  33. 1
      app/models/form/sales/subsections/setup.rb
  34. 2
      app/models/form/sales/subsections/shared_ownership_scheme.rb
  35. 1
      app/models/log.rb
  36. 5
      app/models/sales_log.rb
  37. 17
      app/models/validations/sales/financial_validations.rb
  38. 14
      app/models/validations/sales/sale_information_validations.rb
  39. 9
      app/models/validations/sales/setup_validations.rb
  40. 18
      app/models/validations/sales/soft_validations.rb
  41. 25
      app/services/bulk_upload/lettings/validator.rb
  42. 6
      app/views/bulk_upload_lettings_results/show.html.erb
  43. 22
      app/views/bulk_upload_lettings_results/summary.html.erb
  44. 22
      config/locales/en.yml
  45. 1
      config/routes.rb
  46. 5
      db/migrate/20230120163049_add_deposit_and_mortgage_value_check_to_sales_logs.rb
  47. 5
      db/migrate/20230125152916_add_staircase_bought_value_check_to_sales_log.rb
  48. 7
      db/migrate/20230127102334_add_monthly_charges_value_check.rb
  49. 7
      db/schema.rb
  50. 6
      db/seeds.rb
  51. 81
      spec/components/bulk_upload_error_summary_table_component_spec.rb
  52. 3
      spec/factories/bulk_upload_error.rb
  53. 8
      spec/factories/sales_log.rb
  54. 2
      spec/models/form/sales/pages/age1_spec.rb
  55. 13
      spec/models/form/sales/pages/age2_spec.rb
  56. 2
      spec/models/form/sales/pages/buyer1_ethnic_group_spec.rb
  57. 2
      spec/models/form/sales/pages/buyer1_live_in_property_spec.rb
  58. 11
      spec/models/form/sales/pages/buyer2_live_in_property_spec.rb
  59. 11
      spec/models/form/sales/pages/buyer2_relationship_to_buyer1_spec.rb
  60. 11
      spec/models/form/sales/pages/buyer2_working_situation_spec.rb
  61. 2
      spec/models/form/sales/pages/gender_identity1_spec.rb
  62. 13
      spec/models/form/sales/pages/gender_identity2_spec.rb
  63. 48
      spec/models/form/sales/pages/monthly_charges_value_check_spec.rb
  64. 2
      spec/models/form/sales/pages/nationality1_spec.rb
  65. 2
      spec/models/form/sales/questions/discounted_ownership_type_spec.rb
  66. 57
      spec/models/form/sales/questions/monthly_charges_value_check_spec.rb
  67. 4
      spec/models/form/sales/questions/privacy_notice_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. 1
      spec/models/form/sales/subsections/outright_sale_spec.rb
  72. 1
      spec/models/form/sales/subsections/property_information_spec.rb
  73. 1
      spec/models/form/sales/subsections/setup_spec.rb
  74. 2
      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. 50
      spec/models/sales_log_spec.rb
  78. 59
      spec/models/validations/sales/financial_validations_spec.rb
  79. 8
      spec/models/validations/sales/sale_information_validations_spec.rb
  80. 49
      spec/models/validations/sales/setup_validations_spec.rb
  81. 137
      spec/models/validations/sales/soft_validations_spec.rb
  82. 6
      spec/models/validations/shared_validations_spec.rb
  83. 17
      spec/requests/bulk_upload_lettings_results_controller_spec.rb
  84. 26
      spec/requests/sales_logs_controller_spec.rb
  85. 146
      spec/services/bulk_upload/lettings/validator_spec.rb
  86. 7
      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

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

3
app/models/form/question.rb

@ -14,11 +14,11 @@ class Form::Question
def initialize(id, hsh, page)
@id = id
@page = page
@guidance_position = GuidancePosition::TOP
if hsh
@check_answer_label = hsh["check_answer_label"]
@header = hsh["header"]
@guidance_partial = hsh["guidance_partial"]
@guidance_position = GuidancePosition::TOP
@hint_text = hsh["hint_text"]
@type = hsh["type"]
@min = hsh["min"]
@ -206,6 +206,7 @@ class Form::Question
def unanswered_error_message
return I18n.t("validations.declaration.missing") if id == "declaration"
return I18n.t("validations.privacynotice.missing") if id == "privacynotice"
I18n.t("validations.not_answered", question: display_label.downcase)
end

8
app/models/form/sales/pages/age1.rb

@ -2,6 +2,14 @@ class Form::Sales::Pages::Age1 < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_1_age"
@depends_on = [
{
"privacynotice" => 1,
},
{
"noint" => 1,
},
]
end
def questions

13
app/models/form/sales/pages/age2.rb

@ -2,9 +2,16 @@ class Form::Sales::Pages::Age2 < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_age"
@depends_on = [{
"jointpur" => 1,
}]
@depends_on = [
{
"jointpur" => 1,
"privacynotice" => 1,
},
{
"jointpur" => 1,
"noint" => 1,
},
]
end
def questions

8
app/models/form/sales/pages/buyer1_ethnic_group.rb

@ -2,6 +2,14 @@ class Form::Sales::Pages::Buyer1EthnicGroup < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_1_ethnic_group"
@depends_on = [
{
"privacynotice" => 1,
},
{
"noint" => 1,
},
]
end
def questions

8
app/models/form/sales/pages/buyer1_live_in_property.rb

@ -2,6 +2,14 @@ class Form::Sales::Pages::Buyer1LiveInProperty < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_1_live_in_property"
@depends_on = [
{
"privacynotice" => 1,
},
{
"noint" => 1,
},
]
end
def questions

8
app/models/form/sales/pages/buyer1_working_situation.rb

@ -2,6 +2,14 @@ class Form::Sales::Pages::Buyer1WorkingSituation < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_1_working_situation"
@depends_on = [
{
"privacynotice" => 1,
},
{
"noint" => 1,
},
]
end
def questions

13
app/models/form/sales/pages/buyer2_live_in_property.rb

@ -2,9 +2,16 @@ class Form::Sales::Pages::Buyer2LiveInProperty < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_live_in_property"
@depends_on = [{
"jointpur" => 1,
}]
@depends_on = [
{
"jointpur" => 1,
"privacynotice" => 1,
},
{
"jointpur" => 1,
"noint" => 1,
},
]
end
def questions

13
app/models/form/sales/pages/buyer2_relationship_to_buyer1.rb

@ -2,9 +2,16 @@ class Form::Sales::Pages::Buyer2RelationshipToBuyer1 < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_relationship_to_buyer_1"
@depends_on = [{
"jointpur" => 1,
}]
@depends_on = [
{
"jointpur" => 1,
"privacynotice" => 1,
},
{
"jointpur" => 1,
"noint" => 1,
},
]
end
def questions

13
app/models/form/sales/pages/buyer2_working_situation.rb

@ -2,9 +2,16 @@ class Form::Sales::Pages::Buyer2WorkingSituation < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_working_situation"
@depends_on = [{
"jointpur" => 1,
}]
@depends_on = [
{
"jointpur" => 1,
"privacynotice" => 1,
},
{
"jointpur" => 1,
"noint" => 1,
},
]
end
def questions

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

8
app/models/form/sales/pages/gender_identity1.rb

@ -2,6 +2,14 @@ class Form::Sales::Pages::GenderIdentity1 < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_1_gender_identity"
@depends_on = [
{
"privacynotice" => 1,
},
{
"noint" => 1,
},
]
end
def questions

13
app/models/form/sales/pages/gender_identity2.rb

@ -2,9 +2,16 @@ class Form::Sales::Pages::GenderIdentity2 < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_gender_identity"
@depends_on = [{
"jointpur" => 1,
}]
@depends_on = [
{
"jointpur" => 1,
"privacynotice" => 1,
},
{
"jointpur" => 1,
"noint" => 1,
},
]
end
def questions

21
app/models/form/sales/pages/monthly_charges_value_check.rb

@ -0,0 +1,21 @@
class Form::Sales::Pages::MonthlyChargesValueCheck < ::Form::Page
def initialize(id, hsh, subsection)
super
@depends_on = [
{
"monthly_charges_over_soft_max?" => true,
},
]
@title_text = {
"translation" => "soft_validations.monthly_charges_over_soft_max.title_text",
"arguments" => [],
}
@informative_text = {}
end
def questions
@questions ||= [
Form::Sales::Questions::MonthlyChargesValueCheck.new(nil, nil, self),
]
end
end

8
app/models/form/sales/pages/nationality1.rb

@ -2,6 +2,14 @@ class Form::Sales::Pages::Nationality1 < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_1_nationality"
@depends_on = [
{
"privacynotice" => 1,
},
{
"noint" => 1,
},
]
end
def questions

8
app/models/form/sales/pages/number_of_others_in_property.rb

@ -2,6 +2,14 @@ class Form::Sales::Pages::NumberOfOthersInProperty < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "number_of_others_in_property"
@depends_on = [
{
"privacynotice" => 1,
},
{
"noint" => 1,
},
]
end
def questions

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

2
app/models/form/sales/questions/discounted_ownership_type.rb

@ -9,7 +9,7 @@ class Form::Sales::Questions::DiscountedOwnershipType < ::Form::Question
end
ANSWER_OPTIONS = {
"8" => { "value" => "Right to Aquire (RTA)" },
"8" => { "value" => "Right to Acquire (RTA)" },
"14" => { "value" => "Preserved Right to Buy (PRTB)" },
"27" => { "value" => "Voluntary Right to Buy (VRTB)" },
"9" => { "value" => "Right to Buy (RTB)" },

23
app/models/form/sales/questions/monthly_charges_value_check.rb

@ -0,0 +1,23 @@
class Form::Sales::Questions::MonthlyChargesValueCheck < ::Form::Question
def initialize(id, hsh, page)
super
@id = "monthly_charges_value_check"
@check_answer_label = "Monthly charges confirmation"
@header = "Are you sure this is correct?"
@type = "interruption_screen"
@answer_options = {
"0" => { "value" => "Yes" },
"1" => { "value" => "No" },
}
@hidden_in_check_answers = {
"depends_on" => [
{
"monthly_charges_value_check" => 0,
},
{
"monthly_charges_value_check" => 1,
},
],
}
end
end

1
app/models/form/sales/questions/privacy_notice.rb

@ -6,7 +6,6 @@ class Form::Sales::Questions::PrivacyNotice < ::Form::Question
@header = "Declaration"
@type = "checkbox"
@answer_options = ANSWER_OPTIONS
@guidance_position = GuidancePosition::TOP
@guidance_partial = "privacy_notice_buyer"
end

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

@ -14,9 +14,11 @@ class Form::Sales::Subsections::DiscountedOwnershipScheme < ::Form::Subsection
Form::Sales::Pages::AboutPriceNotRtb.new(nil, nil, self),
Form::Sales::Pages::GrantValueCheck.new(nil, nil, self),
Form::Sales::Pages::PurchasePrice.new("purchase_price_discounted_ownership", nil, self),
Form::Sales::Pages::DepositAndMortgageValueCheck.new("discounted_ownership_deposit_and_mortgage_value_check_after_value_and_discount", nil, self),
Form::Sales::Pages::Mortgageused.new("mortgage_used_discounted_ownership", nil, self),
Form::Sales::Pages::MortgageAmount.new("mortgage_amount_discounted_ownership", nil, self),
Form::Sales::Pages::ExtraBorrowingValueCheck.new("extra_borrowing_mortgage_value_check", nil, self),
Form::Sales::Pages::DepositAndMortgageValueCheck.new("discounted_ownership_deposit_and_mortgage_value_check_after_mortgage", nil, self),
Form::Sales::Pages::MortgageLender.new("mortgage_lender_discounted_ownership", nil, self),
Form::Sales::Pages::MortgageLenderOther.new("mortgage_lender_other_discounted_ownership", nil, self),
Form::Sales::Pages::MortgageLength.new("mortgage_length_discounted_ownership", nil, self),
@ -25,7 +27,9 @@ class Form::Sales::Subsections::DiscountedOwnershipScheme < ::Form::Subsection
Form::Sales::Pages::AboutDepositWithoutDiscount.new("about_deposit_discounted_ownership", nil, self),
Form::Sales::Pages::ExtraBorrowingValueCheck.new("extra_borrowing_deposit_value_check", nil, self),
Form::Sales::Pages::DepositValueCheck.new("discounted_ownership_deposit_value_check", nil, self),
Form::Sales::Pages::DepositAndMortgageValueCheck.new("discounted_ownership_deposit_and_mortgage_value_check_after_deposit", nil, self),
Form::Sales::Pages::LeaseholdCharges.new("leasehold_charges_discounted_ownership", nil, self),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_discounted_ownership_value_check", nil, self),
]
end

1
app/models/form/sales/subsections/outright_sale.rb

@ -18,6 +18,7 @@ class Form::Sales::Subsections::OutrightSale < ::Form::Subsection
Form::Sales::Pages::AboutDepositWithoutDiscount.new("about_deposit_outright_sale", nil, self),
Form::Sales::Pages::DepositValueCheck.new("outright_sale_deposit_value_check", nil, self),
Form::Sales::Pages::LeaseholdCharges.new("leasehold_charges_outright_sale", nil, self),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_outright_sale_value_check", nil, self),
]
end

1
app/models/form/sales/subsections/property_information.rb

@ -10,6 +10,7 @@ class Form::Sales::Subsections::PropertyInformation < ::Form::Subsection
@pages ||= [
Form::Sales::Pages::PropertyNumberOfBedrooms.new(nil, nil, self),
Form::Sales::Pages::PropertyUnitType.new(nil, nil, self),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_property_type_value_check", nil, self),
Form::Sales::Pages::PropertyBuildingType.new(nil, nil, self),
Form::Sales::Pages::Postcode.new(nil, nil, self),
Form::Sales::Pages::PropertyLocalAuthority.new(nil, nil, self),

1
app/models/form/sales/subsections/setup.rb

@ -16,6 +16,7 @@ class Form::Sales::Subsections::Setup < ::Form::Subsection
Form::Sales::Pages::DiscountedOwnershipType.new(nil, nil, self),
Form::Sales::Pages::OutrightOwnershipType.new(nil, nil, self),
Form::Sales::Pages::OldPersonsSharedOwnershipValueCheck.new("ownership_type_old_persons_shared_ownership_value_check", nil, self),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_type_value_check", nil, self),
Form::Sales::Pages::BuyerCompany.new(nil, nil, self),
Form::Sales::Pages::BuyerLive.new(nil, nil, self),
Form::Sales::Pages::JointPurchase.new(nil, nil, self),

2
app/models/form/sales/subsections/shared_ownership_scheme.rb

@ -11,6 +11,7 @@ class Form::Sales::Subsections::SharedOwnershipScheme < ::Form::Subsection
Form::Sales::Pages::LivingBeforePurchase.new("living_before_purchase_shared_ownership", nil, self),
Form::Sales::Pages::Staircase.new(nil, nil, self),
Form::Sales::Pages::AboutStaircase.new(nil, nil, self),
Form::Sales::Pages::StaircaseBoughtValueCheck.new(nil, nil, self),
Form::Sales::Pages::Resale.new(nil, nil, self),
Form::Sales::Pages::ExchangeDate.new(nil, nil, self),
Form::Sales::Pages::HandoverDate.new(nil, nil, self),
@ -35,6 +36,7 @@ class Form::Sales::Subsections::SharedOwnershipScheme < ::Form::Subsection
Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_deposit_value_check", nil, self),
Form::Sales::Pages::MonthlyRent.new(nil, nil, self),
Form::Sales::Pages::LeaseholdCharges.new("leasehold_charges_shared_ownership", nil, self),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_shared_ownership_value_check", nil, self),
]
end

1
app/models/log.rb

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

5
app/models/sales_log.rb

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

17
app/models/validations/sales/financial_validations.rb

@ -27,4 +27,21 @@ module Validations::Sales::FinancialValidations
record.errors.add :cashdis, I18n.t("validations.financial.cash_discount_invalid")
end
end
def validate_percentage_bought_not_greater_than_percentage_owned(record)
return unless record.stairbought && record.stairowned
if record.stairbought > record.stairowned
record.errors.add :stairowned, I18n.t("validations.financial.staircasing.percentage_bought_must_be_greater_than_percentage_owned")
end
end
def validate_percentage_owned_not_too_much_if_older_person(record)
return unless record.old_persons_shared_ownership? && record.stairowned
if record.stairowned > 75
record.errors.add :stairowned, I18n.t("validations.financial.staircasing.older_person_percentage_owned_maximum_75")
record.errors.add :type, I18n.t("validations.financial.staircasing.older_person_percentage_owned_maximum_75")
end
end
end

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

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

18
app/models/validations/sales/soft_validations.rb

@ -13,6 +13,10 @@ module Validations::Sales::SoftValidations
income1 < ALLOWED_INCOME_RANGES[ecstat1] [:soft_min]
end
def staircase_bought_above_fifty?
stairbought && stairbought > 50
end
def mortgage_over_soft_max?
return false unless mortgage && inc1mort && inc2mort
return false if income1_used_for_mortgage? && income1.blank? || income2_used_for_mortgage? && income2.blank?
@ -53,6 +57,13 @@ module Validations::Sales::SoftValidations
mortgage_value + deposit + cash_discount != value * equity / 100
end
def mortgage_plus_deposit_less_than_discounted_value?
return unless mortgage && deposit && value && discount
discounted_value = value * (100 - discount) / 100
mortgage + deposit < discounted_value
end
def hodate_3_years_or_more_saledate?
return unless hodate && saledate
@ -64,4 +75,11 @@ module Validations::Sales::SoftValidations
!grant.between?(9_000, 16_000)
end
def monthly_charges_over_soft_max?
return unless type && mscharge && proptype
soft_max = old_persons_shared_ownership? ? 550 : 300
mscharge > soft_max
end
end

25
app/services/bulk_upload/lettings/validator.rb

@ -1,6 +1,9 @@
require "csv"
class BulkUpload::Lettings::Validator
COLUMN_PERCENTAGE_ERROR_THRESHOLD = 0.6
COLUMN_ABSOLUTE_ERROR_THRESHOLD = 16
include ActiveModel::Validations
QUESTIONS = {
@ -171,7 +174,10 @@ class BulkUpload::Lettings::Validator
end
def create_logs?
row_parsers.all?(&:valid?)
return false if any_setup_sections_incomplete?
return false if over_column_error_threshold?
row_parsers.all? { |row_parser| row_parser.log.valid? }
end
def self.question_for_field(field)
@ -180,6 +186,23 @@ class BulkUpload::Lettings::Validator
private
def any_setup_sections_incomplete?
row_parsers.any? { |row_parser| row_parser.log.form.setup_sections[0].subsections[0].is_incomplete?(row_parser.log) }
end
def over_column_error_threshold?
fields = ("field_1".."field_134").to_a
percentage_threshold = (row_parsers.size * COLUMN_PERCENTAGE_ERROR_THRESHOLD).ceil
fields.any? do |field|
count = row_parsers.count { |row_parser| row_parser.errors[field].present? }
next if count < COLUMN_ABSOLUTE_ERROR_THRESHOLD
count > percentage_threshold
end
end
def csv_parser
@csv_parser ||= BulkUpload::Lettings::CsvParser.new(path:)
end

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

22
config/locales/en.yml

@ -147,6 +147,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"
@ -277,6 +279,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"
@ -386,6 +392,9 @@ en:
declaration:
missing: "You must show the DLUHC privacy notice to the tenant before you can submit this log."
privacynotice:
missing: "You must show the DLUHC privacy notice to the buyer before you can submit this log."
scheme:
toggle_date:
not_selected: "Select one of the options"
@ -417,11 +426,14 @@ 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: "Number of bedrooms must be 1 if the property is a bedsit"
discounted_ownership_value: "Mortgage, deposit, and grant total must equal £%{value_with_discount}"
@ -464,6 +476,8 @@ en:
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"
monthly_charges_over_soft_max:
title_text: "The amount of monthly charges is high for this type of property and sale type"
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

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

7
db/migrate/20230127102334_add_monthly_charges_value_check.rb

@ -0,0 +1,7 @@
class AddMonthlyChargesValueCheck < ActiveRecord::Migration[7.0]
def change
change_table :sales_logs, bulk: true do |t|
t.column :monthly_charges_value_check, :integer
end
end
end

7
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_26_145529) do
ActiveRecord::Schema[7.0].define(version: 2023_01_27_102334) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -504,9 +504,12 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_26_145529) do
t.integer "retirement_value_check"
t.integer "hodate_check"
t.integer "extrabor_value_check"
t.integer "shared_ownership_deposit_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.integer "monthly_charges_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 }

2
spec/models/form/sales/pages/age1_spec.rb

@ -28,6 +28,6 @@ RSpec.describe Form::Sales::Pages::Age1, type: :model do
end
it "has correct depends_on" do
expect(page.depends_on).to be_nil
expect(page.depends_on).to eq([{ "privacynotice" => 1 }, { "noint" => 1 }])
end
end

13
spec/models/form/sales/pages/age2_spec.rb

@ -28,8 +28,15 @@ RSpec.describe Form::Sales::Pages::Age2, type: :model do
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{
"jointpur" => 1,
}])
expect(page.depends_on).to eq([
{
"jointpur" => 1,
"privacynotice" => 1,
},
{
"jointpur" => 1,
"noint" => 1,
},
])
end
end

2
spec/models/form/sales/pages/buyer1_ethnic_group_spec.rb

@ -28,6 +28,6 @@ RSpec.describe Form::Sales::Pages::Buyer1EthnicGroup, type: :model do
end
it "has correct depends_on" do
expect(page.depends_on).to be_nil
expect(page.depends_on).to eq([{ "privacynotice" => 1 }, { "noint" => 1 }])
end
end

2
spec/models/form/sales/pages/buyer1_live_in_property_spec.rb

@ -28,6 +28,6 @@ RSpec.describe Form::Sales::Pages::Buyer1LiveInProperty, type: :model do
end
it "has correct depends_on" do
expect(page.depends_on).to be_nil
expect(page.depends_on).to eq([{ "privacynotice" => 1 }, { "noint" => 1 }])
end
end

11
spec/models/form/sales/pages/buyer2_live_in_property_spec.rb

@ -28,6 +28,15 @@ RSpec.describe Form::Sales::Pages::Buyer2LiveInProperty, type: :model do
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "jointpur" => 1 }])
expect(page.depends_on).to eq([
{
"jointpur" => 1,
"privacynotice" => 1,
},
{
"jointpur" => 1,
"noint" => 1,
},
])
end
end

11
spec/models/form/sales/pages/buyer2_relationship_to_buyer1_spec.rb

@ -28,6 +28,15 @@ RSpec.describe Form::Sales::Pages::Buyer2RelationshipToBuyer1, type: :model do
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "jointpur" => 1 }])
expect(page.depends_on).to eq([
{
"jointpur" => 1,
"privacynotice" => 1,
},
{
"jointpur" => 1,
"noint" => 1,
},
])
end
end

11
spec/models/form/sales/pages/buyer2_working_situation_spec.rb

@ -28,6 +28,15 @@ RSpec.describe Form::Sales::Pages::Buyer2WorkingSituation, type: :model do
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "jointpur" => 1 }])
expect(page.depends_on).to eq([
{
"jointpur" => 1,
"privacynotice" => 1,
},
{
"jointpur" => 1,
"noint" => 1,
},
])
end
end

2
spec/models/form/sales/pages/gender_identity1_spec.rb

@ -28,6 +28,6 @@ RSpec.describe Form::Sales::Pages::GenderIdentity1, type: :model do
end
it "has correct depends_on" do
expect(page.depends_on).to be_nil
expect(page.depends_on).to eq([{ "privacynotice" => 1 }, { "noint" => 1 }])
end
end

13
spec/models/form/sales/pages/gender_identity2_spec.rb

@ -28,8 +28,15 @@ RSpec.describe Form::Sales::Pages::GenderIdentity2, type: :model do
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{
"jointpur" => 1,
}])
expect(page.depends_on).to eq([
{
"jointpur" => 1,
"privacynotice" => 1,
},
{
"jointpur" => 1,
"noint" => 1,
},
])
end
end

48
spec/models/form/sales/pages/monthly_charges_value_check_spec.rb

@ -0,0 +1,48 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::MonthlyChargesValueCheck, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { "monthly_charges_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[monthly_charges_value_check])
end
it "has the correct id" do
expect(page.id).to eq("monthly_charges_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([
{
"monthly_charges_over_soft_max?" => true,
},
])
end
it "is interruption screen page" do
expect(page.interruption_screen?).to eq(true)
end
it "has correct title_text" do
expect(page.title_text).to eq({
"translation" => "soft_validations.monthly_charges_over_soft_max.title_text",
"arguments" => [],
})
end
it "has correct informative_text" do
expect(page.informative_text).to eq({})
end
end

2
spec/models/form/sales/pages/nationality1_spec.rb

@ -28,6 +28,6 @@ RSpec.describe Form::Sales::Pages::Nationality1, type: :model do
end
it "has correct depends_on" do
expect(page.depends_on).to be_nil
expect(page.depends_on).to eq([{ "privacynotice" => 1 }, { "noint" => 1 }])
end
end

2
spec/models/form/sales/questions/discounted_ownership_type_spec.rb

@ -33,7 +33,7 @@ RSpec.describe Form::Sales::Questions::DiscountedOwnershipType, type: :model do
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"8" => { "value" => "Right to Aquire (RTA)" },
"8" => { "value" => "Right to Acquire (RTA)" },
"14" => { "value" => "Preserved Right to Buy (PRTB)" },
"27" => { "value" => "Voluntary Right to Buy (VRTB)" },
"9" => { "value" => "Right to Buy (RTB)" },

57
spec/models/form/sales/questions/monthly_charges_value_check_spec.rb

@ -0,0 +1,57 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::MonthlyChargesValueCheck, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { "monthly_charges_value_check" }
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("monthly_charges_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("Monthly charges 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" => [
{
"monthly_charges_value_check" => 0,
},
{
"monthly_charges_value_check" => 1,
},
],
})
end
end

4
spec/models/form/sales/questions/privacy_notice_spec.rb

@ -40,4 +40,8 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do
"privacynotice" => { "value" => "The buyer has seen the DLUHC privacy notice" },
})
end
it "returns correct unanswered_error_message" do
expect(question.unanswered_error_message).to eq("You must show the DLUHC privacy notice to the buyer before you can submit this log.")
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

@ -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,7 +33,9 @@ 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
monthly_charges_discounted_ownership_value_check
],
)
end

1
spec/models/form/sales/subsections/outright_sale_spec.rb

@ -24,6 +24,7 @@ RSpec.describe Form::Sales::Subsections::OutrightSale, type: :model do
about_deposit_outright_sale
outright_sale_deposit_value_check
leasehold_charges_outright_sale
monthly_charges_outright_sale_value_check
],
)
end

1
spec/models/form/sales/subsections/property_information_spec.rb

@ -16,6 +16,7 @@ RSpec.describe Form::Sales::Subsections::PropertyInformation, type: :model do
%w[
property_number_of_bedrooms
property_unit_type
monthly_charges_property_type_value_check
property_building_type
property_postcode
property_local_authority

1
spec/models/form/sales/subsections/setup_spec.rb

@ -23,6 +23,7 @@ RSpec.describe Form::Sales::Subsections::Setup, type: :model do
discounted_ownership_type
outright_ownership_type
ownership_type_old_persons_shared_ownership_value_check
monthly_charges_type_value_check
buyer_company
buyer_live
joint_purchase

2
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
@ -41,6 +42,7 @@ RSpec.describe Form::Sales::Subsections::SharedOwnershipScheme, type: :model do
shared_ownership_deposit_value_check
monthly_rent
leasehold_charges_shared_ownership
monthly_charges_shared_ownership_value_check
],
)
end

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(194)
expect(form.pages.count).to eq(203)
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(194)
expect(form.pages.count).to eq(203)
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(13)
expect(form.pages.count).to eq(14)
expect(form.pages.first.id).to eq("organisation")
expect(form.questions.count).to eq(14)
expect(form.questions.count).to eq(15)
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"))

50
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 old_persons_shared_ownership_value_check])
expect(sales_log.optional_fields).to eq(%w[purchid monthly_charges_value_check 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

59
spec/models/validations/sales/financial_validations_spec.rb

@ -99,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

8
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

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

137
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,7 +367,7 @@ 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)
@ -520,4 +573,86 @@ 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
describe "#monthly_charges_over_soft_max?" do
it "returns false if mscharge is not given" do
record.mscharge = nil
record.proptype = 4
record.type = 2
expect(record).not_to be_monthly_charges_over_soft_max
end
it "returns false if proptype is not given" do
record.mscharge = 999
record.proptype = nil
record.type = 2
expect(record).not_to be_monthly_charges_over_soft_max
end
it "returns false if type is not given" do
record.mscharge = 999
record.proptype = 4
record.type = nil
expect(record).not_to be_monthly_charges_over_soft_max
end
context "with old persons shared ownership" do
it "returns false if the monthly charge is under 550" do
record.mscharge = 540
record.proptype = 4
record.type = 24
expect(record).not_to be_monthly_charges_over_soft_max
end
it "returns true if the monthly charge is over 550" do
record.mscharge = 999
record.proptype = 4
record.type = 24
expect(record).to be_monthly_charges_over_soft_max
end
end
context "with non old persons type of ownership" do
it "returns false if the monthly charge is under 300" do
record.mscharge = 280
record.proptype = 4
record.type = 18
expect(record).not_to be_monthly_charges_over_soft_max
end
it "returns true if the monthly charge is over 300" do
record.mscharge = 400
record.proptype = 4
record.type = 18
expect(record).to be_monthly_charges_over_soft_max
end
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

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

146
spec/services/bulk_upload/lettings/validator_spec.rb

@ -78,7 +78,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
before do
file.write(BulkUpload::LogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.rewind
file.close
end
it "creates validation errors" do
@ -112,4 +112,148 @@ RSpec.describe BulkUpload::Lettings::Validator do
end
end
end
describe "#create_logs?" do
context "when a log is not valid?" do
let(:log_1) { build(:lettings_log, :completed, created_by: user) }
let(:log_2) { build(:lettings_log, :completed, created_by: user) }
before do
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0, overrides: { illness: 100 }).to_csv_row)
file.close
end
it "returns false" do
validator.call
expect(validator).not_to be_create_logs
end
end
context "when all logs valid?" do
let(:log_1) { build(:lettings_log, :completed, renttype: 1, created_by: user) }
let(:log_2) { build(:lettings_log, :completed, renttype: 1, created_by: user) }
before do
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.close
end
it "returns true" do
validator.call
expect(validator).to be_create_logs
end
end
context "when a log has incomplete setup secion" do
let(:log) { build(:lettings_log, :in_progress, created_by: user, startdate: Time.zone.local(2022, 5, 1)) }
before do
file.write(BulkUpload::LogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.close
end
it "returns false" do
validator.call
expect(validator).not_to be_create_logs
end
end
context "when a column has error rate below absolute threshold" do
context "when a column is over 60% error threshold" do
let(:log_1) { build(:lettings_log, :completed, renttype: 1, created_by: user) }
let(:log_2) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
let(:log_3) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
let(:log_4) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
let(:log_5) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
before do
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.close
end
it "returns true" do
validator.call
expect(validator).to be_create_logs
end
end
context "when a column is under 60% error threshold" do
let(:log_1) { build(:lettings_log, :completed, renttype: 1, created_by: user) }
let(:log_2) { build(:lettings_log, :completed, renttype: 1, created_by: user) }
let(:log_3) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
let(:log_4) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
let(:log_5) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
before do
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.close
end
it "returns true" do
validator.call
expect(validator).to be_create_logs
end
end
end
context "when a column has error rate above absolute threshold" do
before do
stub_const("BulkUpload::Lettings::Validator::COLUMN_ABSOLUTE_ERROR_THRESHOLD", 1)
end
context "when a column is over 60% error threshold" do
let(:log_1) { build(:lettings_log, :completed, renttype: 1, created_by: user) }
let(:log_2) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
let(:log_3) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
let(:log_4) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
let(:log_5) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
before do
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.close
end
it "returns false" do
validator.call
expect(validator).not_to be_create_logs
end
end
context "when a column is under 60% error threshold" do
let(:log_1) { build(:lettings_log, :completed, renttype: 1, created_by: user) }
let(:log_2) { build(:lettings_log, :completed, renttype: 1, created_by: user) }
let(:log_3) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
let(:log_4) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
let(:log_5) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
before do
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.close
end
it "returns true" do
validator.call
expect(validator).to be_create_logs
end
end
end
end
end

7
spec/support/bulk_upload/log_to_csv.rb

@ -1,10 +1,11 @@
class BulkUpload::LogToCsv
attr_reader :log, :line_ending, :col_offset
attr_reader :log, :line_ending, :col_offset, :overrides
def initialize(log:, line_ending: "\n", col_offset: 1)
def initialize(log:, line_ending: "\n", col_offset: 1, overrides: {})
@log = log
@line_ending = line_ending
@col_offset = col_offset
@overrides = overrides
end
def to_csv_row
@ -135,7 +136,7 @@ class BulkUpload::LogToCsv
nil,
log.incfreq,
log.sheltered,
log.illness,
overrides[:illness] || log.illness,
log.illness_type_1,
log.illness_type_2, # 120

Loading…
Cancel
Save