Browse Source

Merge branch 'main' into CLDC-2139-show-inferred-locations

pull/1460/head
natdeanlewissoftwire 3 years ago
parent
commit
e88608b516
  1. 4
      app/components/bulk_upload_error_row_component.html.erb
  2. 24
      app/components/bulk_upload_error_row_component.rb
  3. 4
      app/components/bulk_upload_error_summary_table_component.html.erb
  4. 11
      app/components/bulk_upload_error_summary_table_component.rb
  5. 29
      app/controllers/form_controller.rb
  6. 2
      app/controllers/locations_controller.rb
  7. 2
      app/controllers/schemes_controller.rb
  8. 23
      app/helpers/money_formatting_helper.rb
  9. 43
      app/mailers/bulk_upload_mailer.rb
  10. 4
      app/models/form_handler.rb
  11. 5
      app/models/lettings_log.rb
  12. 12
      app/models/location_deactivation_period.rb
  13. 4
      app/models/log.rb
  14. 3
      app/models/sales_log.rb
  15. 10
      app/models/scheme_deactivation_period.rb
  16. 29
      app/models/validations/financial_validations.rb
  17. 8
      app/models/validations/household_validations.rb
  18. 9
      app/models/validations/sales/sale_information_validations.rb
  19. 6
      app/services/bulk_upload/processor.rb
  20. 4
      app/views/bulk_upload_lettings_results/summary.html.erb
  21. 3
      app/views/form/_numeric_output_question.html.erb
  22. 11
      app/views/form/_numeric_question.html.erb
  23. 2
      app/views/form/page.html.erb
  24. 6
      app/views/locations/toggle_active.html.erb
  25. 6
      app/views/schemes/toggle_active.html.erb
  26. 24
      config/locales/en.yml
  27. 2
      config/routes.rb
  28. 29
      spec/components/bulk_upload_error_row_component_spec.rb
  29. 27
      spec/components/bulk_upload_error_summary_table_component_spec.rb
  30. 4
      spec/features/form/page_routing_spec.rb
  31. 4
      spec/features/schemes_spec.rb
  32. 41
      spec/helpers/money_formatting_helper_spec.rb
  33. 38
      spec/mailers/bulk_upload_mailer_spec.rb
  34. 8
      spec/models/lettings_log_spec.rb
  35. 83
      spec/models/location_deactivation_period_spec.rb
  36. 2
      spec/models/sales_log_spec.rb
  37. 68
      spec/models/scheme_deactivation_period_spec.rb
  38. 40
      spec/models/validations/financial_validations_spec.rb
  39. 16
      spec/models/validations/household_validations_spec.rb
  40. 63
      spec/models/validations/sales/sale_information_validations_spec.rb
  41. 51
      spec/requests/form_controller_spec.rb
  42. 8
      spec/services/imports/lettings_logs_import_service_spec.rb

4
app/components/bulk_upload_error_row_component.html.erb

@ -1,9 +1,9 @@
<div class="x-govuk-summary-card govuk-!-margin-bottom-6"> <div class="x-govuk-summary-card govuk-!-margin-bottom-6">
<div class="x-govuk-summary-card__header"> <div class="x-govuk-summary-card__header">
<% if lettings? %> <% if lettings? %>
<h3 class="x-govuk-summary-card__title"><strong>Row <%= row %></strong> <span class="govuk-!-margin-left-3">Tenant code: <%= tenant_code %></span> <span class="govuk-!-margin-left-3">Property reference: <%= property_ref %></span></h3> <h3 class="x-govuk-summary-card__title"><strong>Row <%= row %></strong> <%= tenant_code_html %> <%= property_ref_html %></h3>
<% else %> <% else %>
<h3 class="x-govuk-summary-card__title"><strong>Row <%= row %></strong> <span class="govuk-!-margin-left-3">Purchaser code: <%= purchaser_code %></span></h3> <h3 class="x-govuk-summary-card__title"><strong>Row <%= row %></strong> <%= purchaser_code_html %></h3>
<% end %> <% end %>
</div> </div>

24
app/components/bulk_upload_error_row_component.rb

@ -15,14 +15,38 @@ class BulkUploadErrorRowComponent < ViewComponent::Base
bulk_upload_errors.first.tenant_code bulk_upload_errors.first.tenant_code
end end
def tenant_code_html
return if tenant_code.blank?
content_tag :span, class: "govuk-!-margin-left-3" do
"Tenant code: #{tenant_code}"
end
end
def purchaser_code def purchaser_code
bulk_upload_errors.first.purchaser_code bulk_upload_errors.first.purchaser_code
end end
def purchaser_code_html
return if purchaser_code.blank?
content_tag :span, class: "govuk-!-margin-left-3" do
"Purchaser code: #{purchaser_code}"
end
end
def property_ref def property_ref
bulk_upload_errors.first.property_ref bulk_upload_errors.first.property_ref
end end
def property_ref_html
return if property_ref.blank?
content_tag :span, class: "govuk-!-margin-left-3" do
"Property reference: #{property_ref}"
end
end
def question_for_field(field) def question_for_field(field)
bulk_upload.prefix_namespace::RowParser.question_for_field(field.to_sym) bulk_upload.prefix_namespace::RowParser.question_for_field(field.to_sym)
end end

4
app/components/bulk_upload_error_summary_table_component.html.erb

@ -1,3 +1,7 @@
<p class="govuk-body">
<%= intro %>
</p>
<% sorted_errors.each do |error| %> <% sorted_errors.each do |error| %>
<%= govuk_table do |table| %> <%= govuk_table do |table| %>
<% table.head do |head| %> <% table.head do |head| %>

11
app/components/bulk_upload_error_summary_table_component.rb

@ -15,7 +15,7 @@ class BulkUploadErrorSummaryTableComponent < ViewComponent::Base
@sorted_errors ||= setup_errors.presence || bulk_upload @sorted_errors ||= setup_errors.presence || bulk_upload
.bulk_upload_errors .bulk_upload_errors
.group(:col, :field, :error) .group(:col, :field, :error)
.having("count(*) > ?", display_threshold) .having("count(*) >= ?", display_threshold)
.count .count
.sort_by { |el| el[0][0].rjust(3, "0") } .sort_by { |el| el[0][0].rjust(3, "0") }
end end
@ -24,6 +24,14 @@ class BulkUploadErrorSummaryTableComponent < ViewComponent::Base
sorted_errors.present? sorted_errors.present?
end end
def intro
if setup_errors.present?
"This summary shows important questions that have errors. See full error report for more details."
else
"This summary shows questions that have more than #{BulkUploadErrorSummaryTableComponent::DISPLAY_THRESHOLD - 1} errors. See full error report for more details."
end
end
private private
def setup_errors def setup_errors
@ -31,7 +39,6 @@ private
.bulk_upload_errors .bulk_upload_errors
.where(category: "setup") .where(category: "setup")
.group(:col, :field, :error) .group(:col, :field, :error)
.having("count(*) > ?", display_threshold)
.count .count
.sort_by { |el| el[0][0].rjust(3, "0") } .sort_by { |el| el[0][0].rjust(3, "0") }
end end

29
app/controllers/form_controller.rb

@ -1,7 +1,7 @@
class FormController < ApplicationController class FormController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
before_action :find_resource, only: %i[submit_form review] before_action :find_resource, only: %i[review]
before_action :find_resource_by_named_id, except: %i[submit_form review] before_action :find_resource_by_named_id, except: %i[review]
before_action :check_collection_period, only: %i[submit_form show_page] before_action :check_collection_period, only: %i[submit_form show_page]
def submit_form def submit_form
@ -11,16 +11,15 @@ class FormController < ApplicationController
mandatory_questions_with_no_response = mandatory_questions_with_no_response(responses_for_page) mandatory_questions_with_no_response = mandatory_questions_with_no_response(responses_for_page)
if mandatory_questions_with_no_response.empty? && @log.update(responses_for_page.merge(updated_by: current_user)) if mandatory_questions_with_no_response.empty? && @log.update(responses_for_page.merge(updated_by: current_user))
session[:errors] = session[:fields] = nil
redirect_to(successful_redirect_path) redirect_to(successful_redirect_path)
else else
redirect_path = "#{@log.model_name.param_key}_#{@page.id}_path"
mandatory_questions_with_no_response.map do |question| mandatory_questions_with_no_response.map do |question|
@log.errors.add question.id.to_sym, question.unanswered_error_message @log.errors.add question.id.to_sym, question.unanswered_error_message
end end
session[:errors] = @log.errors.to_json
Rails.logger.info "User triggered validation(s) on: #{@log.errors.map(&:attribute).join(', ')}" Rails.logger.info "User triggered validation(s) on: #{@log.errors.map(&:attribute).join(', ')}"
redirect_to(send(redirect_path, @log)) @subsection = form.subsection_for_page(@page)
restore_error_field_values(@page&.questions)
render "form/page"
end end
else else
render_not_found render_not_found
@ -47,7 +46,6 @@ class FormController < ApplicationController
def show_page def show_page
if @log if @log
restore_error_field_values
page_id = request.path.split("/")[-1].underscore page_id = request.path.split("/")[-1].underscore
@page = form.get_page(page_id) @page = form.get_page(page_id)
@subsection = form.subsection_for_page(@page) @subsection = form.subsection_for_page(@page)
@ -63,17 +61,12 @@ class FormController < ApplicationController
private private
def restore_error_field_values def restore_error_field_values(questions)
if session["errors"] return unless questions
JSON(session["errors"]).each do |field, messages|
messages.each { |message| @log.errors.add field.to_sym, message } questions.each do |question|
end if question&.type == "date" && @log.attributes.key?(question.id)
end @log[question.id] = @log.send("#{question.id}_was")
if session["fields"]
session["fields"].each do |field, value|
if form.get_question(field, @log)&.type != "date" && @log.attributes.key?(field)
@log[field] = value
end
end end
end end
end end

2
app/controllers/locations_controller.rb

@ -269,7 +269,7 @@ private
if params[:location_deactivation_period].blank? if params[:location_deactivation_period].blank?
return return
elsif params[:location_deactivation_period]["#{key}_type".to_sym] == "default" elsif params[:location_deactivation_period]["#{key}_type".to_sym] == "default"
return FormHandler.instance.current_collection_start_date return FormHandler.instance.start_date_of_earliest_open_collection_period
elsif params[:location_deactivation_period][key.to_sym].present? elsif params[:location_deactivation_period][key.to_sym].present?
return params[:location_deactivation_period][key.to_sym] return params[:location_deactivation_period][key.to_sym]
end end

2
app/controllers/schemes_controller.rb

@ -296,7 +296,7 @@ private
if params[:scheme_deactivation_period].blank? if params[:scheme_deactivation_period].blank?
return return
elsif params[:scheme_deactivation_period]["#{key}_type".to_sym] == "default" elsif params[:scheme_deactivation_period]["#{key}_type".to_sym] == "default"
return FormHandler.instance.current_collection_start_date return FormHandler.instance.start_date_of_earliest_open_collection_period
elsif params[:scheme_deactivation_period][key.to_sym].present? elsif params[:scheme_deactivation_period][key.to_sym].present?
return params[:scheme_deactivation_period][key.to_sym] return params[:scheme_deactivation_period][key.to_sym]
end end

23
app/helpers/money_formatting_helper.rb

@ -0,0 +1,23 @@
module MoneyFormattingHelper
include ActionView::Helpers::NumberHelper
def format_money_input(log:, question:)
value = log[question.id]
return unless value
return value unless question.prefix == "£"
number_with_precision(
value,
precision: 2,
)
end
def format_as_currency(num_string)
number_to_currency(
num_string,
unit: "£",
precision: 2,
)
end
end

43
app/mailers/bulk_upload_mailer.rb

@ -5,7 +5,6 @@ class BulkUploadMailer < NotifyMailer
FAILED_CSV_ERRORS_TEMPLATE_ID = "e27abcd4-5295-48c2-b127-e9ee4b781b75".freeze FAILED_CSV_ERRORS_TEMPLATE_ID = "e27abcd4-5295-48c2-b127-e9ee4b781b75".freeze
FAILED_FILE_SETUP_ERROR_TEMPLATE_ID = "24c9f4c7-96ad-470a-ba31-eb51b7cbafd9".freeze FAILED_FILE_SETUP_ERROR_TEMPLATE_ID = "24c9f4c7-96ad-470a-ba31-eb51b7cbafd9".freeze
FAILED_SERVICE_ERROR_TEMPLATE_ID = "c3f6288c-7a74-4e77-99ee-6c4a0f6e125a".freeze FAILED_SERVICE_ERROR_TEMPLATE_ID = "c3f6288c-7a74-4e77-99ee-6c4a0f6e125a".freeze
WITH_ERRORS_TEMPLATE_ID = "eb539005-6234-404e-812d-167728cf4274".freeze
HOW_FIX_UPLOAD_TEMPLATE_ID = "21a07b26-f625-4846-9f4d-39e30937aa24".freeze HOW_FIX_UPLOAD_TEMPLATE_ID = "21a07b26-f625-4846-9f4d-39e30937aa24".freeze
def send_how_fix_upload_mail(bulk_upload:) def send_how_fix_upload_mail(bulk_upload:)
@ -73,25 +72,12 @@ class BulkUploadMailer < NotifyMailer
end end
def send_bulk_upload_failed_file_setup_error_mail(bulk_upload:) def send_bulk_upload_failed_file_setup_error_mail(bulk_upload:)
bulk_upload_link = if bulk_upload.lettings? bulk_upload_link = if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors?
start_bulk_upload_lettings_logs_url summary_bulk_upload_lettings_result_url(bulk_upload)
else else
start_bulk_upload_sales_logs_url bulk_upload_lettings_result_url(bulk_upload)
end end
row_parser_class = bulk_upload.prefix_namespace::RowParser
errors = bulk_upload
.bulk_upload_errors
.where(category: "setup")
.group(:col, :field)
.count
.keys
.sort_by { |_col, field| field }
.map do |col, field|
"- #{row_parser_class.question_for_field(field.to_sym)} (Column #{col})"
end
send_email( send_email(
bulk_upload.user.email, bulk_upload.user.email,
FAILED_FILE_SETUP_ERROR_TEMPLATE_ID, FAILED_FILE_SETUP_ERROR_TEMPLATE_ID,
@ -100,7 +86,6 @@ class BulkUploadMailer < NotifyMailer
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
lettings_or_sales: bulk_upload.log_type, lettings_or_sales: bulk_upload.log_type,
year_combo: bulk_upload.year_combo, year_combo: bulk_upload.year_combo,
errors_list: errors.join("\n"),
bulk_upload_link:, bulk_upload_link:,
}, },
) )
@ -126,26 +111,4 @@ class BulkUploadMailer < NotifyMailer
}, },
) )
end end
def send_bulk_upload_with_errors_mail(bulk_upload:)
count = bulk_upload.logs.where.not(status: %w[completed]).count
n_logs = pluralize(count, "log")
title = "We found #{n_logs} with errors"
error_description = "We created logs from your #{bulk_upload.year_combo} #{bulk_upload.log_type} data. There was a problem with #{count} of the logs. Click the below link to fix these logs."
send_email(
bulk_upload.user.email,
WITH_ERRORS_TEMPLATE_ID,
{
title:,
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
error_description:,
summary_report_link: resume_bulk_upload_lettings_result_url(bulk_upload),
},
)
end
end end

4
app/models/form_handler.rb

@ -77,6 +77,10 @@ class FormHandler
form_mappings[current_collection_start_year - year] form_mappings[current_collection_start_year - year]
end end
def start_date_of_earliest_open_collection_period
in_crossover_period? ? previous_collection_start_date : current_collection_start_date
end
def in_crossover_period?(now: Time.zone.now) def in_crossover_period?(now: Time.zone.now)
lettings_in_crossover_period?(now:) || sales_in_crossover_period?(now:) lettings_in_crossover_period?(now:) || sales_in_crossover_period?(now:)
end end

5
app/models/lettings_log.rb

@ -20,6 +20,7 @@ class LettingsLog < Log
include DerivedVariables::LettingsLogVariables include DerivedVariables::LettingsLogVariables
include Validations::DateValidations include Validations::DateValidations
include Validations::FinancialValidations include Validations::FinancialValidations
include MoneyFormattingHelper
has_paper_trail has_paper_trail
@ -137,7 +138,7 @@ class LettingsLog < Log
def weekly_to_value_per_period(field_value) def weekly_to_value_per_period(field_value)
num_of_weeks = NUM_OF_WEEKS_FROM_PERIOD[period] num_of_weeks = NUM_OF_WEEKS_FROM_PERIOD[period]
((field_value * 52) / num_of_weeks).round(2) format_as_currency((field_value * 52) / num_of_weeks)
end end
def applicable_income_range def applicable_income_range
@ -638,7 +639,7 @@ private
num_of_weeks = NUM_OF_WEEKS_FROM_PERIOD[period] num_of_weeks = NUM_OF_WEEKS_FROM_PERIOD[period]
return "" unless value && num_of_weeks return "" unless value && num_of_weeks
(value * 52 / num_of_weeks).round(2) format_as_currency((value * 52 / num_of_weeks))
end end
def fully_wheelchair_accessible? def fully_wheelchair_accessible?

12
app/models/location_deactivation_period.rb

@ -1,4 +1,6 @@
class LocationDeactivationPeriodValidator < ActiveModel::Validator class LocationDeactivationPeriodValidator < ActiveModel::Validator
include CollectionTimeHelper
def validate(record) def validate(record)
location = record.location location = record.location
recent_deactivation = location.location_deactivation_periods.deactivations_without_reactivation.first recent_deactivation = location.location_deactivation_periods.deactivations_without_reactivation.first
@ -16,7 +18,7 @@ class LocationDeactivationPeriodValidator < ActiveModel::Validator
elsif record.reactivation_date_type == "other" elsif record.reactivation_date_type == "other"
record.errors.add(:reactivation_date, message: I18n.t("validations.location.toggle_date.invalid")) record.errors.add(:reactivation_date, message: I18n.t("validations.location.toggle_date.invalid"))
end end
elsif !record.reactivation_date.between?(location.available_from, Time.zone.local(2200, 1, 1)) elsif record.reactivation_date.before? location.available_from
record.errors.add(:reactivation_date, message: I18n.t("validations.location.toggle_date.out_of_range", date: location.available_from.to_formatted_s(:govuk_date))) record.errors.add(:reactivation_date, message: I18n.t("validations.location.toggle_date.out_of_range", date: location.available_from.to_formatted_s(:govuk_date)))
elsif record.reactivation_date < recent_deactivation.deactivation_date elsif record.reactivation_date < recent_deactivation.deactivation_date
record.errors.add(:reactivation_date, message: I18n.t("validations.location.reactivation.before_deactivation", date: recent_deactivation.deactivation_date.to_formatted_s(:govuk_date))) record.errors.add(:reactivation_date, message: I18n.t("validations.location.reactivation.before_deactivation", date: recent_deactivation.deactivation_date.to_formatted_s(:govuk_date)))
@ -32,10 +34,10 @@ class LocationDeactivationPeriodValidator < ActiveModel::Validator
end end
elsif location.location_deactivation_periods.any? { |period| period.reactivation_date.present? && record.deactivation_date.between?(period.deactivation_date, period.reactivation_date - 1.day) } elsif location.location_deactivation_periods.any? { |period| period.reactivation_date.present? && record.deactivation_date.between?(period.deactivation_date, period.reactivation_date - 1.day) }
record.errors.add(:deactivation_date, message: I18n.t("validations.location.deactivation.during_deactivated_period")) record.errors.add(:deactivation_date, message: I18n.t("validations.location.deactivation.during_deactivated_period"))
else elsif record.deactivation_date.before? FormHandler.instance.start_date_of_earliest_open_collection_period
unless record.deactivation_date.between?(location.available_from, Time.zone.local(2200, 1, 1)) record.errors.add(:deactivation_date, message: I18n.t("validations.location.toggle_date.out_of_range", date: FormHandler.instance.start_date_of_earliest_open_collection_period.to_formatted_s(:govuk_date)))
record.errors.add(:deactivation_date, message: I18n.t("validations.location.toggle_date.out_of_range", date: location.available_from.to_formatted_s(:govuk_date))) elsif record.deactivation_date.before? location.available_from
end record.errors.add(:deactivation_date, message: I18n.t("validations.location.toggle_date.before_creation", date: location.available_from.to_formatted_s(:govuk_date)))
end end
end end
end end

4
app/models/log.rb

@ -217,8 +217,4 @@ private
self[is_inferred_key] = false self[is_inferred_key] = false
self[postcode_key] = nil self[postcode_key] = nil
end end
def format_as_currency(num_string)
ActionController::Base.helpers.number_to_currency(num_string, unit: "£")
end
end end

3
app/models/sales_log.rb

@ -18,6 +18,7 @@ class SalesLog < Log
include DerivedVariables::SalesLogVariables include DerivedVariables::SalesLogVariables
include Validations::Sales::SoftValidations include Validations::Sales::SoftValidations
include Validations::SoftValidations include Validations::SoftValidations
include MoneyFormattingHelper
self.inheritance_column = :_type_disabled self.inheritance_column = :_type_disabled
@ -215,7 +216,7 @@ class SalesLog < Log
def expected_shared_ownership_deposit_value def expected_shared_ownership_deposit_value
return unless value && equity return unless value && equity
(value * equity / 100).round(2) format_as_currency(value * equity / 100)
end end
def process_postcode(postcode, postcode_known_key, la_inferred_key, la_key) def process_postcode(postcode, postcode_known_key, la_inferred_key, la_key)

10
app/models/scheme_deactivation_period.rb

@ -1,4 +1,6 @@
class SchemeDeactivationPeriodValidator < ActiveModel::Validator class SchemeDeactivationPeriodValidator < ActiveModel::Validator
include CollectionTimeHelper
def validate(record) def validate(record)
scheme = record.scheme scheme = record.scheme
recent_deactivation = scheme.scheme_deactivation_periods.deactivations_without_reactivation.first recent_deactivation = scheme.scheme_deactivation_periods.deactivations_without_reactivation.first
@ -32,10 +34,10 @@ class SchemeDeactivationPeriodValidator < ActiveModel::Validator
end end
elsif scheme.scheme_deactivation_periods.any? { |period| period.reactivation_date.present? && record.deactivation_date.between?(period.deactivation_date, period.reactivation_date - 1.day) } elsif scheme.scheme_deactivation_periods.any? { |period| period.reactivation_date.present? && record.deactivation_date.between?(period.deactivation_date, period.reactivation_date - 1.day) }
record.errors.add(:deactivation_date, message: I18n.t("validations.scheme.deactivation.during_deactivated_period")) record.errors.add(:deactivation_date, message: I18n.t("validations.scheme.deactivation.during_deactivated_period"))
else elsif record.deactivation_date.before? FormHandler.instance.start_date_of_earliest_open_collection_period
unless record.deactivation_date.between?(scheme.available_from, Time.zone.local(2200, 1, 1)) record.errors.add(:deactivation_date, message: I18n.t("validations.scheme.toggle_date.out_of_range", date: FormHandler.instance.start_date_of_earliest_open_collection_period.to_formatted_s(:govuk_date)))
record.errors.add(:deactivation_date, message: I18n.t("validations.scheme.toggle_date.out_of_range", date: scheme.available_from.to_formatted_s(:govuk_date))) elsif record.deactivation_date.before? scheme.available_from
end record.errors.add(:deactivation_date, message: I18n.t("validations.scheme.toggle_date.before_creation", date: scheme.available_from.to_formatted_s(:govuk_date)))
end end
end end
end end

29
app/models/validations/financial_validations.rb

@ -1,5 +1,6 @@
module Validations::FinancialValidations module Validations::FinancialValidations
include Validations::SharedValidations include Validations::SharedValidations
include MoneyFormattingHelper
# Validations methods need to be called 'validate_<page_name>' to run on model save # Validations methods need to be called 'validate_<page_name>' to run on model save
# or 'validate_' to run on submit as well # or 'validate_' to run on submit as well
def validate_outstanding_rent_amount(record) def validate_outstanding_rent_amount(record)
@ -24,12 +25,26 @@ module Validations::FinancialValidations
def validate_net_income(record) def validate_net_income(record)
if record.ecstat1 && record.weekly_net_income if record.ecstat1 && record.weekly_net_income
if record.weekly_net_income > record.applicable_income_range.hard_max if record.weekly_net_income > record.applicable_income_range.hard_max
record.errors.add :earnings, :over_hard_max, message: I18n.t("validations.financial.earnings.over_hard_max", hard_max: record.applicable_income_range.hard_max) hard_max = format_as_currency(record.applicable_income_range.hard_max)
record.errors.add :ecstat1, :over_hard_max, message: I18n.t("validations.financial.ecstat.over_hard_max", hard_max: record.applicable_income_range.hard_max) record.errors.add(
:earnings,
:over_hard_max,
message: I18n.t("validations.financial.earnings.over_hard_max", hard_max:),
)
record.errors.add(
:ecstat1,
:over_hard_max,
message: I18n.t("validations.financial.ecstat.over_hard_max", hard_max:),
)
end end
if record.weekly_net_income < record.applicable_income_range.hard_min if record.weekly_net_income < record.applicable_income_range.hard_min
record.errors.add :earnings, :under_hard_min, message: I18n.t("validations.financial.earnings.under_hard_min", hard_min: record.applicable_income_range.hard_min) hard_min = format_as_currency(record.applicable_income_range.hard_min)
record.errors.add(
:earnings,
:under_hard_min,
message: I18n.t("validations.financial.earnings.under_hard_min", hard_min:),
)
end end
end end
@ -120,10 +135,10 @@ module Validations::FinancialValidations
elsif !weekly_value_in_range(record, "chcharge", 10, 1000) elsif !weekly_value_in_range(record, "chcharge", 10, 1000)
max_chcharge = record.weekly_to_value_per_period(1000) max_chcharge = record.weekly_to_value_per_period(1000)
min_chcharge = record.weekly_to_value_per_period(10) min_chcharge = record.weekly_to_value_per_period(10)
max_chcharge = [record.form.get_question("chcharge", record).prefix, max_chcharge].join("") if record.form.get_question("chcharge", record).present? message = I18n.t("validations.financial.carehome.out_of_range", period:, min_chcharge:, max_chcharge:)
min_chcharge = [record.form.get_question("chcharge", record).prefix, min_chcharge].join("") if record.form.get_question("chcharge", record).present?
record.errors.add :period, I18n.t("validations.financial.carehome.out_of_range", period:, min_chcharge:, max_chcharge:) record.errors.add :period, message
record.errors.add :chcharge, :out_of_range, message: I18n.t("validations.financial.carehome.out_of_range", period:, min_chcharge:, max_chcharge:) record.errors.add :chcharge, :out_of_range, message: message
end end
end end
end end

8
app/models/validations/household_validations.rb

@ -121,6 +121,14 @@ module Validations::HouseholdValidations
end end
end end
def validate_combination_of_housing_needs_responses(record)
if record.housingneeds == 1 && record.housingneeds_type == 3 && record.housingneeds_other&.zero?
record.errors.add :housingneeds, I18n.t("validations.household.housingneeds.invalid")
record.errors.add :housingneeds_type, I18n.t("validations.household.housingneeds.invalid")
record.errors.add :housingneeds_other, I18n.t("validations.household.housingneeds.invalid")
end
end
private private
def household_no_illness?(record) def household_no_illness?(record)

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

@ -1,5 +1,6 @@
module Validations::Sales::SaleInformationValidations module Validations::Sales::SaleInformationValidations
include CollectionTimeHelper include CollectionTimeHelper
include MoneyFormattingHelper
def validate_practical_completion_date_before_saledate(record) def validate_practical_completion_date_before_saledate(record)
return if record.saledate.blank? || record.hodate.blank? return if record.saledate.blank? || record.hodate.blank?
@ -54,7 +55,13 @@ module Validations::Sales::SaleInformationValidations
if record.mortgage_deposit_and_grant_total != record.value_with_discount && record.discounted_ownership_sale? if record.mortgage_deposit_and_grant_total != record.value_with_discount && record.discounted_ownership_sale?
%i[mortgage deposit grant value discount ownershipsch].each do |field| %i[mortgage deposit grant value discount ownershipsch].each do |field|
record.errors.add field, I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: sprintf("%.2f", record.value_with_discount)) record.errors.add(
field,
I18n.t(
"validations.sale_information.discounted_ownership_value",
value_with_discount: format_as_currency(record.value_with_discount),
),
)
end end
end end
end end

6
app/services/bulk_upload/processor.rb

@ -59,12 +59,6 @@ private
.deliver_later .deliver_later
end end
def send_fix_errors_mail
BulkUploadMailer
.send_bulk_upload_with_errors_mail(bulk_upload:)
.deliver_later
end
def send_success_mail def send_success_mail
BulkUploadMailer BulkUploadMailer
.send_bulk_upload_complete_mail(user:, bulk_upload:) .send_bulk_upload_complete_mail(user:, bulk_upload:)

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

@ -16,10 +16,6 @@
<div class="govuk-grid-row"> <div class="govuk-grid-row">
<%= govuk_tabs(title: "Error reports") do |c| %> <%= govuk_tabs(title: "Error reports") do |c| %>
<% c.with_tab(label: "Summary") do %> <% c.with_tab(label: "Summary") do %>
<p class="govuk-body">
This summary shows questions that have at least <%= BulkUploadErrorSummaryTableComponent::DISPLAY_THRESHOLD %> errors or more. See full error report for more details.
</p>
<%= render BulkUploadErrorSummaryTableComponent.new(bulk_upload: @bulk_upload) %> <%= render BulkUploadErrorSummaryTableComponent.new(bulk_upload: @bulk_upload) %>
<% end %> <% end %>

3
app/views/form/_numeric_output_question.html.erb

@ -16,7 +16,8 @@
type="number" type="number"
name="lettings_log[tcharge]" name="lettings_log[tcharge]"
for="<%= question.fields_added.present? ? question.fields_added.map { |x| "lettings-log-#{x}-field" }.join(" ") : "" %>"> for="<%= question.fields_added.present? ? question.fields_added.map { |x| "lettings-log-#{x}-field" }.join(" ") : "" %>">
<%= lettings_log[question.id] %></output> <%= format_money_input(log: lettings_log, question:) %>
</output>
<span class="govuk-input__suffix"><%= question.suffix_label(lettings_log) %></span> <span class="govuk-input__suffix"><%= question.suffix_label(lettings_log) %></span>
</div> </div>
</div> </div>

11
app/views/form/_numeric_question.html.erb

@ -1,14 +1,19 @@
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.top_guidance? %> <%= render partial: "form/guidance/#{question.guidance_partial}" if question.top_guidance? %>
<%= f.govuk_number_field question.id.to_sym, <%= f.govuk_number_field(
question.id.to_sym,
caption: caption(caption_text, page_header, conditional), caption: caption(caption_text, page_header, conditional),
label: legend(question, page_header, conditional), label: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe }, hint: { text: question.hint_text&.html_safe },
min: question.min, max: question.max, step: question.step, min: question.min,
max: question.max,
step: question.step,
width: question.width, width: question.width,
readonly: question.read_only?, readonly: question.read_only?,
prefix_text: question.prefix.to_s, prefix_text: question.prefix.to_s,
suffix_text: question.suffix_label(@log), suffix_text: question.suffix_label(@log),
**stimulus_html_attributes(question) %> value: format_money_input(log: @log, question:),
**stimulus_html_attributes(question),
) %>
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.bottom_guidance? %> <%= render partial: "form/guidance/#{question.guidance_partial}" if question.bottom_guidance? %>

2
app/views/form/page.html.erb

@ -5,7 +5,7 @@
<% end %> <% end %>
<div data-controller="govukfrontend"></div> <div data-controller="govukfrontend"></div>
<%= form_with model: @log, url: form_lettings_log_path(@log), method: "post", local: true do |f| %> <%= form_with model: @log, url: request.original_url, method: "post", local: true do |f| %>
<div class="govuk-grid-row"> <div class="govuk-grid-row">
<div class="govuk-grid-column-<%= @page.questions[0].type == "interruption_screen" ? "full-from-desktop" : "two-thirds-from-desktop" %>"> <div class="govuk-grid-column-<%= @page.questions[0].type == "interruption_screen" ? "full-from-desktop" : "two-thirds-from-desktop" %>">
<% remove_other_page_errors(@log, @page) %> <% remove_other_page_errors(@log, @page) %>

6
app/views/locations/toggle_active.html.erb

@ -11,16 +11,16 @@
<%= form_with model: @location_deactivation_period, url: toggle_location_form_path(action, @location), method: "patch", local: true do |f| %> <%= form_with model: @location_deactivation_period, url: toggle_location_form_path(action, @location), method: "patch", local: true do |f| %>
<div class="govuk-grid-row"> <div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds"> <div class="govuk-grid-column-two-thirds">
<% collection_start_date = FormHandler.instance.earliest_open_collection_start_date(now: @location.available_from) %> <% start_date = FormHandler.instance.earliest_open_collection_start_date(now: @location.available_from) %>
<%= f.govuk_error_summary %> <%= f.govuk_error_summary %>
<%= f.govuk_radio_buttons_fieldset date_type_question(action), <%= f.govuk_radio_buttons_fieldset date_type_question(action),
legend: { text: I18n.t("questions.location.toggle_active.apply_from") }, legend: { text: I18n.t("questions.location.toggle_active.apply_from") },
caption: { text: title }, caption: { text: title },
hint: { text: I18n.t("hints.location.toggle_active", date: collection_start_date.to_formatted_s(:govuk_date)) } do %> hint: { text: I18n.t("hints.location.toggle_active", date: start_date.to_formatted_s(:govuk_date)) } do %>
<%= govuk_warning_text text: I18n.t("warnings.location.#{action}.existing_logs") %> <%= govuk_warning_text text: I18n.t("warnings.location.#{action}.existing_logs") %>
<%= f.govuk_radio_button date_type_question(action), <%= f.govuk_radio_button date_type_question(action),
"default", "default",
label: { text: "From the start of the current collection period (#{collection_start_date.to_formatted_s(:govuk_date)})" } %> label: { text: "From the start of the open collection period (#{start_date.to_formatted_s(:govuk_date)})" } %>
<%= f.govuk_radio_button date_type_question(action), <%= f.govuk_radio_button date_type_question(action),
"other", "other",

6
app/views/schemes/toggle_active.html.erb

@ -11,16 +11,16 @@
<%= form_with model: @scheme_deactivation_period, url: toggle_scheme_form_path(action, @scheme), method: "patch", local: true do |f| %> <%= form_with model: @scheme_deactivation_period, url: toggle_scheme_form_path(action, @scheme), method: "patch", local: true do |f| %>
<div class="govuk-grid-row"> <div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds"> <div class="govuk-grid-column-two-thirds">
<% collection_start_date = FormHandler.instance.current_collection_start_date %> <% start_date = FormHandler.instance.start_date_of_earliest_open_collection_period %>
<%= f.govuk_error_summary %> <%= f.govuk_error_summary %>
<%= f.govuk_radio_buttons_fieldset date_type_question(action), <%= f.govuk_radio_buttons_fieldset date_type_question(action),
legend: { text: I18n.t("questions.scheme.toggle_active.apply_from") }, legend: { text: I18n.t("questions.scheme.toggle_active.apply_from") },
caption: { text: title }, caption: { text: title },
hint: { text: I18n.t("hints.scheme.toggle_active", date: collection_start_date.to_formatted_s(:govuk_date)) } do %> hint: { text: I18n.t("hints.scheme.toggle_active", date: start_date.to_formatted_s(:govuk_date)) } do %>
<%= govuk_warning_text text: I18n.t("warnings.scheme.#{action}.existing_logs") %> <%= govuk_warning_text text: I18n.t("warnings.scheme.#{action}.existing_logs") %>
<%= f.govuk_radio_button date_type_question(action), <%= f.govuk_radio_button date_type_question(action),
"default", "default",
label: { text: "From the start of the current collection period (#{collection_start_date.to_formatted_s(:govuk_date)})" } %> label: { text: "From the start of the open collection period (#{start_date.to_formatted_s(:govuk_date)})" } %>
<%= f.govuk_radio_button date_type_question(action), <%= f.govuk_radio_button date_type_question(action),
"other", "other",
label: { text: "For tenancies starting after a certain date" }, label: { text: "For tenancies starting after a certain date" },

24
config/locales/en.yml

@ -248,15 +248,15 @@ en:
benefits: benefits:
part_or_full_time: "Answer cannot be ‘all’ for income from Universal Credit, state pensions or benefits if the tenant or their partner works part-time or full-time" part_or_full_time: "Answer cannot be ‘all’ for income from Universal Credit, state pensions or benefits if the tenant or their partner works part-time or full-time"
earnings: earnings:
over_hard_max: "Net income cannot be greater than £%{hard_max} per week given the tenant’s working situation" over_hard_max: "Net income cannot be greater than %{hard_max} per week given the tenant’s working situation"
under_hard_min: "Net income cannot be less than £%{hard_min} per week given the tenant’s working situation" under_hard_min: "Net income cannot be less than %{hard_min} per week given the tenant’s working situation"
freq_missing: "Select how often the household receives income" freq_missing: "Select how often the household receives income"
earnings_missing: "Enter how much income the household has in total" earnings_missing: "Enter how much income the household has in total"
income: income:
over_hard_max_for_london: "Income must not exceed £90,000 for properties within London local authorities" over_hard_max_for_london: "Income must not exceed £90,000.00 for properties within London local authorities"
over_hard_max_for_outside_london: "Income must not exceed £80,000 for properties outside London local authorities" over_hard_max_for_outside_london: "Income must not exceed £80,000.00 for properties outside London local authorities"
combined_over_hard_max_for_london: "Combined income must not exceed £90,000 for properties within London local authorities" combined_over_hard_max_for_london: "Combined income must not exceed £90,000.00 for properties within London local authorities"
combined_over_hard_max_for_outside_london: "Combined income must not exceed £80,000 for properties outside London local authorities" combined_over_hard_max_for_outside_london: "Combined income must not exceed £80,000.00 for properties outside London local authorities"
child_has_income: "Child's income must be £0" child_has_income: "Child's income must be £0"
negative_currency: "Enter an amount above 0" negative_currency: "Enter an amount above 0"
rent: rent:
@ -389,6 +389,8 @@ en:
housingneeds_type: housingneeds_type:
only_one_option_permitted: "Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected" only_one_option_permitted: "Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected"
housingneeds: housingneeds:
invalid:
If somebody in the household has disabled access needs, they must have the access needs listed, or other access needs
no_disabled_needs_conjunction: "No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs" no_disabled_needs_conjunction: "No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs"
dont_know_disabled_needs_conjunction: "Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs" dont_know_disabled_needs_conjunction: "Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs"
no_and_dont_know_disabled_needs_conjunction: "No disabled access needs and don’t know disabled access needs cannot be selected together" no_and_dont_know_disabled_needs_conjunction: "No disabled access needs and don’t know disabled access needs cannot be selected together"
@ -456,6 +458,7 @@ en:
toggle_date: toggle_date:
not_selected: "Select one of the options" not_selected: "Select one of the options"
invalid: "Enter a valid day, month and year" invalid: "Enter a valid day, month and year"
before_creation: "The scheme cannot be deactivated before %{date}, the start of the collection year when it was created"
out_of_range: "The date must be on or after the %{date}" out_of_range: "The date must be on or after the %{date}"
reactivation: reactivation:
before_deactivation: "This scheme was deactivated on %{date}. The reactivation date must be on or after deactivation date" before_deactivation: "This scheme was deactivated on %{date}. The reactivation date must be on or after deactivation date"
@ -474,6 +477,7 @@ en:
toggle_date: toggle_date:
not_selected: "Select one of the options" not_selected: "Select one of the options"
invalid: "Enter a valid day, month and year" invalid: "Enter a valid day, month and year"
before_creation: "The location cannot be deactivated before %{date}, the date when it was first available"
out_of_range: "The date must be on or after the %{date}" out_of_range: "The date must be on or after the %{date}"
reactivation: reactivation:
before_deactivation: "This location was deactivated on %{date}. The reactivation date must be on or after deactivation date" before_deactivation: "This location was deactivated on %{date}. The reactivation date must be on or after deactivation date"
@ -496,9 +500,9 @@ en:
must_be_after_hodate: "Sale completion date must be after practical completion or handover date" must_be_after_hodate: "Sale completion date must be after practical completion or handover date"
previous_property_type: previous_property_type:
property_type_bedsit: "A bedsit cannot have more than 1 bedroom" property_type_bedsit: "A bedsit cannot have more than 1 bedroom"
discounted_ownership_value: "Mortgage, deposit, and grant total must equal £%{value_with_discount}" discounted_ownership_value: "Mortgage, deposit, and grant total must equal %{value_with_discount}"
monthly_rent: monthly_rent:
higher_than_expected: "Basic monthly rent must be between £0 and £9,999" higher_than_expected: "Basic monthly rent must be between £0.00 and £9,999.00"
soft_validations: soft_validations:
net_income: net_income:
@ -594,10 +598,10 @@ en:
postcode: "For example, SW1P 4DF." postcode: "For example, SW1P 4DF."
name: "This is how you refer to this location within your organisation" name: "This is how you refer to this location within your organisation"
units: "A unit is the space being let. For example, the property might be a block of flats and the unit would be the specific flat being let. A unit can also be a bedroom in a shared house or flat. Do not include spaces used for staff." units: "A unit is the space being let. For example, the property might be a block of flats and the unit would be the specific flat being let. A unit can also be a bedroom in a shared house or flat. Do not include spaces used for staff."
toggle_active: "If the date is before %{date}, select ‘From the start of the current collection period’ because the previous period has now closed." toggle_active: "If the date is before %{date}, select ‘From the start of the open collection period’ because the previous period has now closed."
startdate: "For example, 27 3 2021" startdate: "For example, 27 3 2021"
scheme: scheme:
toggle_active: "If the date is before %{date}, select ‘From the start of the current collection period’ because the previous period has now closed." toggle_active: "If the date is before %{date}, select ‘From the start of the open collection period’ because the previous period has now closed."
bulk_upload: bulk_upload:
needstype: "General needs housing includes both self-contained and shared housing without support or specific adaptations. Supported housing can include direct access hostels, group homes, residential care and nursing homes." needstype: "General needs housing includes both self-contained and shared housing without support or specific adaptations. Supported housing can include direct access hostels, group homes, residential care and nursing homes."
offered: "Do not include the offer that led to this letting. This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0." offered: "Do not include the offer that led to this letting. This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0."

2
config/routes.rb

@ -164,6 +164,7 @@ Rails.application.routes.draw do
FormHandler.instance.lettings_forms.each do |_key, form| FormHandler.instance.lettings_forms.each do |_key, form|
form.pages.map do |page| form.pages.map do |page|
get page.id.to_s.dasherize, to: "form#show_page" get page.id.to_s.dasherize, to: "form#show_page"
post page.id.to_s.dasherize, to: "form#submit_form"
end end
form.subsections.map do |subsection| form.subsections.map do |subsection|
@ -190,6 +191,7 @@ Rails.application.routes.draw do
FormHandler.instance.sales_forms.each do |_key, form| FormHandler.instance.sales_forms.each do |_key, form|
form.pages.map do |page| form.pages.map do |page|
get page.id.to_s.dasherize, to: "form#show_page" get page.id.to_s.dasherize, to: "form#show_page"
post page.id.to_s.dasherize, to: "form#submit_form"
end end
form.subsections.map do |subsection| form.subsections.map do |subsection|

29
spec/components/bulk_upload_error_row_component_spec.rb

@ -5,6 +5,7 @@ RSpec.describe BulkUploadErrorRowComponent, type: :component do
let(:row) { rand(9_999) } let(:row) { rand(9_999) }
let(:tenant_code) { SecureRandom.hex(4) } let(:tenant_code) { SecureRandom.hex(4) }
let(:property_ref) { SecureRandom.hex(4) } let(:property_ref) { SecureRandom.hex(4) }
let(:purchaser_code) { nil }
let(:field) { :field_134 } let(:field) { :field_134 }
let(:error) { "some error" } let(:error) { "some error" }
let(:bulk_upload) { create(:bulk_upload, :lettings) } let(:bulk_upload) { create(:bulk_upload, :lettings) }
@ -16,6 +17,7 @@ RSpec.describe BulkUploadErrorRowComponent, type: :component do
row:, row:,
tenant_code:, tenant_code:,
property_ref:, property_ref:,
purchaser_code:,
field:, field:,
error:, error:,
), ),
@ -49,6 +51,33 @@ RSpec.describe BulkUploadErrorRowComponent, type: :component do
expect(result).to have_content(expected) expect(result).to have_content(expected)
end end
context "when tenant_code not present" do
let(:tenant_code) { nil }
it "does not render tenant code label" do
result = render_inline(described_class.new(bulk_upload_errors:))
expect(result).not_to have_content("Tenant code")
end
end
context "when property_ref not present" do
let(:property_ref) { nil }
it "does not render the property_ref label" do
result = render_inline(described_class.new(bulk_upload_errors:))
expect(result).not_to have_content("Property reference")
end
end
context "when purchaser_code not present" do
let(:bulk_upload) { create(:bulk_upload, :sales) }
it "does not render the purchaser_code label" do
result = render_inline(described_class.new(bulk_upload_errors:))
expect(result).not_to have_content("Purchaser code")
end
end
context "when multiple errors for a row" do context "when multiple errors for a row" do
subject(:component) { described_class.new(bulk_upload_errors:) } subject(:component) { described_class.new(bulk_upload_errors:) }

27
spec/components/bulk_upload_error_summary_table_component_spec.rb

@ -30,6 +30,25 @@ RSpec.describe BulkUploadErrorSummaryTableComponent, type: :component do
end end
end end
context "when on threshold" do
before do
stub_const("BulkUploadErrorSummaryTableComponent::DISPLAY_THRESHOLD", 1)
create(:bulk_upload_error, bulk_upload:, col: "A", row: 1)
end
it "renders tables" do
result = render_inline(component)
expect(result).to have_selector("table", count: 1)
end
it "renders intro with threshold" do
result = render_inline(component)
expect(result).to have_content("This summary shows questions that have more than 0 errors. See full error report for more details.")
end
end
context "when there are 2 independent errors" do context "when there are 2 independent errors" do
let!(:error_2) { create(:bulk_upload_error, bulk_upload:, col: "B", row: 2) } 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) } let!(:error_1) { create(:bulk_upload_error, bulk_upload:, col: "A", row: 1) }
@ -99,6 +118,8 @@ RSpec.describe BulkUploadErrorSummaryTableComponent, type: :component do
before do before do
create(:bulk_upload_error, bulk_upload:, col: "B", row: 2, category: nil) create(:bulk_upload_error, bulk_upload:, col: "B", row: 2, category: nil)
stub_const("BulkUploadErrorSummaryTableComponent::DISPLAY_THRESHOLD", 16)
end end
it "only returns the setup errors" do it "only returns the setup errors" do
@ -117,6 +138,12 @@ RSpec.describe BulkUploadErrorSummaryTableComponent, type: :component do
"1 error", "1 error",
]) ])
end end
it "renders intro with setup errors" do
result = render_inline(component)
expect(result).to have_content("This summary shows important questions that have errors. See full error report for more details.")
end
end end
end end

4
spec/features/form/page_routing_spec.rb

@ -85,11 +85,11 @@ RSpec.describe "Form Page Routing" do
context "when answer is invalid" do context "when answer is invalid" do
it "shows error with invalid value in the field" do it "shows error with invalid value in the field" do
visit("/lettings-logs/#{id}/property-postcode") visit("/lettings-logs/#{id}/property-postcode")
fill_in("lettings-log-postcode-full-field", with: "fake_postcode") fill_in("lettings-log-postcode-full-field", with: "FAKE_POSTCODE")
click_button("Save and continue") click_button("Save and continue")
expect(page).to have_current_path("/lettings-logs/#{id}/property-postcode") expect(page).to have_current_path("/lettings-logs/#{id}/property-postcode")
expect(find("#lettings-log-postcode-full-field-error").value).to eq("fake_postcode") expect(find("#lettings-log-postcode-full-field-error").value).to eq("FAKE_POSTCODE")
end end
it "does not reset the displayed date" do it "does not reset the displayed date" do

4
spec/features/schemes_spec.rb

@ -742,7 +742,7 @@ RSpec.describe "Schemes scheme Features" do
expect(page).to have_current_path("/schemes/#{scheme.id}/locations") expect(page).to have_current_path("/schemes/#{scheme.id}/locations")
end end
context "when location is incative" do context "when location is inactive" do
context "and I click to view the location" do context "and I click to view the location" do
before do before do
click_link(deactivated_location.postcode) click_link(deactivated_location.postcode)
@ -766,7 +766,7 @@ RSpec.describe "Schemes scheme Features" do
expect(page).to have_current_path("/schemes/#{scheme.id}/locations/#{deactivated_location.id}/new-reactivation") expect(page).to have_current_path("/schemes/#{scheme.id}/locations/#{deactivated_location.id}/new-reactivation")
expect(page).to have_content("Reactivate #{deactivated_location.name}") expect(page).to have_content("Reactivate #{deactivated_location.name}")
expect(page).to have_content("You’ll be able to add logs with this location if their tenancy start date is on or after the date you enter.") expect(page).to have_content("You’ll be able to add logs with this location if their tenancy start date is on or after the date you enter.")
expect(page).to have_content("If the date is before 1 April 2022, select ‘From the start of the current collection period’ because the previous period has now closed.") expect(page).to have_content("If the date is before 1 April 2022, select ‘From the start of the open collection period’ because the previous period has now closed.")
end end
context "when I press the back button" do context "when I press the back button" do

41
spec/helpers/money_formatting_helper_spec.rb

@ -0,0 +1,41 @@
require "rails_helper"
RSpec.describe MoneyFormattingHelper do
describe "#format_money_input" do
let!(:log) { create(:lettings_log, :completed, brent: 1000) }
let(:question) { instance_double(Form::Question, id: "brent", prefix:) }
context "with £ prefix" do
let(:prefix) { "£" }
it "returns formatted input" do
expect(format_money_input(log:, question:)).to eq("1000.00")
end
end
context "with other prefix" do
let(:prefix) { "other" }
it "does not format the input" do
expect(format_money_input(log:, question:)).to eq(BigDecimal(1000))
end
end
context "without prefix" do
let(:prefix) { nil }
it "does not format the input" do
expect(format_money_input(log:, question:)).to eq(BigDecimal(1000))
end
end
context "when value is nil" do
let(:prefix) { "£" }
let(:log) { create(:lettings_log, brent: nil) }
it "does not format the input" do
expect(format_money_input(log:, question:)).to be_nil
end
end
end
end

38
spec/mailers/bulk_upload_mailer_spec.rb

@ -19,13 +19,6 @@ RSpec.describe BulkUploadMailer do
create(:bulk_upload_error, bulk_upload:, col: "F", field: "field_5") create(:bulk_upload_error, bulk_upload:, col: "F", field: "field_5")
end end
let(:expected_errors) do
[
"- What is the letting type? (Column A)",
"- Management group code (Column E)",
]
end
it "sends correctly formed email" do it "sends correctly formed email" do
expect(notify_client).to receive(:send_email).with( expect(notify_client).to receive(:send_email).with(
email_address: bulk_upload.user.email, email_address: bulk_upload.user.email,
@ -35,8 +28,7 @@ RSpec.describe BulkUploadMailer do
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
lettings_or_sales: bulk_upload.log_type, lettings_or_sales: bulk_upload.log_type,
year_combo: bulk_upload.year_combo, year_combo: bulk_upload.year_combo,
errors_list: expected_errors.join("\n"), bulk_upload_link: summary_bulk_upload_lettings_result_url(bulk_upload),
bulk_upload_link: start_bulk_upload_lettings_logs_url,
}, },
) )
@ -81,34 +73,6 @@ RSpec.describe BulkUploadMailer do
end end
end end
context "when bulk upload has log which is not completed" do
before do
create(:lettings_log, :in_progress, bulk_upload:)
end
describe "#send_bulk_upload_with_errors_mail" do
let(:error_description) do
"We created logs from your 2022/23 lettings data. There was a problem with 1 of the logs. Click the below link to fix these logs."
end
it "sends correctly formed email" do
expect(notify_client).to receive(:send_email).with(
email_address: bulk_upload.user.email,
template_id: described_class::WITH_ERRORS_TEMPLATE_ID,
personalisation: {
title: "We found 1 log with errors",
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
error_description:,
summary_report_link: "http://localhost:3000/lettings-logs/bulk-upload-results/#{bulk_upload.id}/resume",
},
)
mailer.send_bulk_upload_with_errors_mail(bulk_upload:)
end
end
end
describe "#send_correct_and_upload_again_mail" do describe "#send_correct_and_upload_again_mail" do
context "when 2 columns with errors" do context "when 2 columns with errors" do
before do before do

8
spec/models/lettings_log_spec.rb

@ -2604,24 +2604,24 @@ RSpec.describe LettingsLog do
context "when period is weekly for 52 weeks" do context "when period is weekly for 52 weeks" do
it "returns weekly soft min for 52 weeks" do it "returns weekly soft min for 52 weeks" do
lettings_log.period = 1 lettings_log.period = 1
expect(lettings_log.soft_min_for_period).to eq("100.0 every week") expect(lettings_log.soft_min_for_period).to eq("£100.00 every week")
end end
it "returns weekly soft max for 52 weeks" do it "returns weekly soft max for 52 weeks" do
lettings_log.period = 1 lettings_log.period = 1
expect(lettings_log.soft_max_for_period).to eq("400.0 every week") expect(lettings_log.soft_max_for_period).to eq("£400.00 every week")
end end
end end
context "when period is weekly for 47 weeks" do context "when period is weekly for 47 weeks" do
it "returns weekly soft min for 47 weeks" do it "returns weekly soft min for 47 weeks" do
lettings_log.period = 8 lettings_log.period = 8
expect(lettings_log.soft_min_for_period).to eq("110.64 every week") expect(lettings_log.soft_min_for_period).to eq("£110.64 every week")
end end
it "returns weekly soft max for 47 weeks" do it "returns weekly soft max for 47 weeks" do
lettings_log.period = 8 lettings_log.period = 8
expect(lettings_log.soft_max_for_period).to eq("442.55 every week") expect(lettings_log.soft_max_for_period).to eq("£442.55 every week")
end end
end end
end end

83
spec/models/location_deactivation_period_spec.rb

@ -0,0 +1,83 @@
require "rails_helper"
RSpec.describe LocationDeactivationPeriod do
let(:validator) { LocationDeactivationPeriodValidator.new }
let(:location) { FactoryBot.create(:location, startdate: now - 2.years) }
let(:record) { FactoryBot.create(:location_deactivation_period, deactivation_date: now, location:) }
describe "#validate" do
around do |example|
Timecop.freeze(now) do
example.run
end
end
context "when not in a crossover period" do
let(:now) { Time.utc(2023, 3, 1) }
context "with a deactivation date before the current collection period" do
it "adds an error" do
record.deactivation_date = now - 1.year
location.location_deactivation_periods.clear
validator.validate(record)
expect(record.errors[:deactivation_date]).to include "The date must be on or after the 1 April 2022"
end
end
context "with a deactivation date in the current collection period" do
it "does not add an error" do
record.deactivation_date = now - 1.day
location.location_deactivation_periods.clear
validator.validate(record)
expect(record.errors).to be_empty
end
end
end
context "when in a crossover period" do
let(:now) { Time.utc(2023, 5, 1) }
context "with a deactivation date before the previous collection period" do
it "does not add an error" do
record.deactivation_date = now - 2.years
location.location_deactivation_periods.clear
validator.validate(record)
expect(record.errors[:deactivation_date]).to include "The date must be on or after the 1 April 2022"
end
end
context "with a deactivation date in the previous collection period" do
it "does not add an error" do
record.deactivation_date = now - 1.year
location.location_deactivation_periods.clear
validator.validate(record)
expect(record.errors).to be_empty
end
end
context "with a deactivation date in the current collection period" do
it "does not add an error" do
record.deactivation_date = now - 1.day
location.location_deactivation_periods.clear
validator.validate(record)
expect(record.errors).to be_empty
end
end
context "but the location was created in the current collection period" do
let(:location) { FactoryBot.create(:location, startdate:) }
let(:startdate) { now - 2.days }
context "with a deactivation date in the previous collection period" do
it "adds an error" do
record.deactivation_date = now - 1.year
location.location_deactivation_periods.clear
validator.validate(record)
start_date = startdate.to_formatted_s(:govuk_date)
expect(record.errors[:deactivation_date]).to include "The location cannot be deactivated before #{start_date}, the date when it was first available"
end
end
end
end
end
end

2
spec/models/sales_log_spec.rb

@ -498,7 +498,7 @@ RSpec.describe SalesLog, type: :model do
let!(:completed_sales_log) { create(:sales_log, :completed, ownershipsch: 1, type: 2, value: 1000, equity: 50) } 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 it "is set to completed for a completed sales log" do
expect(completed_sales_log.expected_shared_ownership_deposit_value).to eq(500) expect(completed_sales_log.expected_shared_ownership_deposit_value).to eq("£500.00")
end end
end end

68
spec/models/scheme_deactivation_period_spec.rb

@ -0,0 +1,68 @@
require "rails_helper"
RSpec.describe SchemeDeactivationPeriod do
let(:validator) { SchemeDeactivationPeriodValidator.new }
let(:scheme) { FactoryBot.create(:scheme, created_at: now - 2.years) }
let(:record) { FactoryBot.create(:scheme_deactivation_period, deactivation_date: now, scheme:) }
describe "#validate" do
around do |example|
Timecop.freeze(now) do
example.run
end
end
context "when not in a crossover period" do
let(:now) { Time.utc(2023, 3, 1) }
context "with a deactivation date before the current collection period" do
it "adds an error" do
record.deactivation_date = now - 1.year
scheme.scheme_deactivation_periods.clear
validator.validate(record)
expect(record.errors[:deactivation_date]).to include("The date must be on or after the 1 April 2022")
end
end
context "with a deactivation date in the current collection period" do
it "does not add an error" do
record.deactivation_date = now - 1.day
scheme.scheme_deactivation_periods.clear
validator.validate(record)
expect(record.errors[:deactivation_date]).to be_empty
end
end
end
context "when in a crossover period" do
let(:now) { Time.utc(2023, 5, 1) }
context "with a deactivation date before the previous collection period" do
it "does not add an error" do
record.deactivation_date = now - 2.years
scheme.scheme_deactivation_periods.clear
validator.validate(record)
expect(record.errors[:deactivation_date]).to include("The date must be on or after the 1 April 2022")
end
end
context "with a deactivation date in the previous collection period" do
it "does not add an error" do
record.deactivation_date = now - 1.year
scheme.scheme_deactivation_periods.clear
validator.validate(record)
expect(record.errors[:deactivation_date]).to be_empty
end
end
context "with a deactivation date in the current collection period" do
it "does not add an error" do
record.deactivation_date = now - 1.day
scheme.scheme_deactivation_periods.clear
validator.validate(record)
expect(record.errors[:deactivation_date]).to be_empty
end
end
end
end
end

40
spec/models/validations/financial_validations_spec.rb

@ -202,7 +202,7 @@ RSpec.describe Validations::FinancialValidations do
record.ecstat1 = 1 record.ecstat1 = 1
financial_validator.validate_net_income(record) financial_validator.validate_net_income(record)
expect(record.errors["earnings"]) expect(record.errors["earnings"])
.to include(match I18n.t("validations.financial.earnings.over_hard_max", hard_max: 1230)) .to eq(["Net income cannot be greater than £1,230.00 per week given the tenant’s working situation"])
end end
end end
@ -213,7 +213,7 @@ RSpec.describe Validations::FinancialValidations do
record.ecstat1 = 1 record.ecstat1 = 1
financial_validator.validate_net_income(record) financial_validator.validate_net_income(record)
expect(record.errors["earnings"]) expect(record.errors["earnings"])
.to include(match I18n.t("validations.financial.earnings.under_hard_min", hard_min: 90)) .to eq(["Net income cannot be less than £90.00 per week given the tenant’s working situation"])
end end
end end
end end
@ -913,15 +913,15 @@ RSpec.describe Validations::FinancialValidations do
record.is_carehome = 1 record.is_carehome = 1
end end
context "and charges are over the valid limit (£1000 per week)" do context "and charges are over the valid limit (£1,000 per week)" do
it "validates charge when period is weekly for 52 weeks" do it "validates charge when period is weekly for 52 weeks" do
record.period = 1 record.period = 1
record.chcharge = 1001 record.chcharge = 1001
financial_validator.validate_care_home_charges(record) financial_validator.validate_care_home_charges(record)
expect(record.errors["chcharge"]) expect(record.errors["chcharge"])
.to include(match I18n.t("validations.financial.carehome.out_of_range", min_chcharge: 10, period: "weekly for 52 weeks", max_chcharge: 1000)) .to include("Household rent and other charges must be between £10.00 and £1,000.00 if paying weekly for 52 weeks")
expect(record.errors["period"]) expect(record.errors["period"])
.to include(match I18n.t("validations.financial.carehome.out_of_range", min_chcharge: 10, period: "weekly for 52 weeks", max_chcharge: 1000)) .to include("Household rent and other charges must be between £10.00 and £1,000.00 if paying weekly for 52 weeks")
end end
it "validates charge when period is monthly" do it "validates charge when period is monthly" do
@ -929,9 +929,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 4334 record.chcharge = 4334
financial_validator.validate_care_home_charges(record) financial_validator.validate_care_home_charges(record)
expect(record.errors["chcharge"]) expect(record.errors["chcharge"])
.to include(match I18n.t("validations.financial.carehome.out_of_range", min_chcharge: 43, period: "every calendar month", max_chcharge: 4333)) .to include("Household rent and other charges must be between £43.00 and £4,333.00 if paying every calendar month")
expect(record.errors["period"]) expect(record.errors["period"])
.to include(match I18n.t("validations.financial.carehome.out_of_range", min_chcharge: 43, period: "every calendar month", max_chcharge: 4333)) .to include("Household rent and other charges must be between £43.00 and £4,333.00 if paying every calendar month")
end end
it "validates charge when period is every 2 weeks" do it "validates charge when period is every 2 weeks" do
@ -939,9 +939,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 2001 record.chcharge = 2001
financial_validator.validate_care_home_charges(record) financial_validator.validate_care_home_charges(record)
expect(record.errors["chcharge"]) expect(record.errors["chcharge"])
.to include(match I18n.t("validations.financial.carehome.out_of_range", min_chcharge: 20, period: "every 2 weeks", max_chcharge: 2000)) .to include("Household rent and other charges must be between £20.00 and £2,000.00 if paying every 2 weeks")
expect(record.errors["period"]) expect(record.errors["period"])
.to include(match I18n.t("validations.financial.carehome.out_of_range", min_chcharge: 20, period: "every 2 weeks", max_chcharge: 2000)) .to include("Household rent and other charges must be between £20.00 and £2,000.00 if paying every 2 weeks")
end end
it "validates charge when period is every 4 weeks" do it "validates charge when period is every 4 weeks" do
@ -949,13 +949,13 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 4001 record.chcharge = 4001
financial_validator.validate_care_home_charges(record) financial_validator.validate_care_home_charges(record)
expect(record.errors["chcharge"]) expect(record.errors["chcharge"])
.to include(match I18n.t("validations.financial.carehome.out_of_range", min_chcharge: 40, period: "every 4 weeks", max_chcharge: 4000)) .to include("Household rent and other charges must be between £40.00 and £4,000.00 if paying every 4 weeks")
expect(record.errors["period"]) expect(record.errors["period"])
.to include(match I18n.t("validations.financial.carehome.out_of_range", min_chcharge: 40, period: "every 4 weeks", max_chcharge: 4000)) .to include("Household rent and other charges must be between £40.00 and £4,000.00 if paying every 4 weeks")
end end
end end
context "and charges are within the valid limit (£1000 per week)" do context "and charges are within the valid limit (£1,000 per week)" do
it "does not throw error when period is weekly for 52 weeks" do it "does not throw error when period is weekly for 52 weeks" do
record.period = 1 record.period = 1
record.chcharge = 999 record.chcharge = 999
@ -1007,9 +1007,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 9 record.chcharge = 9
financial_validator.validate_care_home_charges(record) financial_validator.validate_care_home_charges(record)
expect(record.errors["chcharge"]) expect(record.errors["chcharge"])
.to include(match I18n.t("validations.financial.carehome.out_of_range", min_chcharge: 10, period: "weekly for 52 weeks", max_chcharge: 1000)) .to include("Household rent and other charges must be between £10.00 and £1,000.00 if paying weekly for 52 weeks")
expect(record.errors["period"]) expect(record.errors["period"])
.to include(match I18n.t("validations.financial.carehome.out_of_range", min_chcharge: 10, period: "weekly for 52 weeks", max_chcharge: 1000)) .to include("Household rent and other charges must be between £10.00 and £1,000.00 if paying weekly for 52 weeks")
end end
it "validates charge when period is monthly" do it "validates charge when period is monthly" do
@ -1017,9 +1017,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 42 record.chcharge = 42
financial_validator.validate_care_home_charges(record) financial_validator.validate_care_home_charges(record)
expect(record.errors["chcharge"]) expect(record.errors["chcharge"])
.to include(match I18n.t("validations.financial.carehome.out_of_range", min_chcharge: 43, period: "every calendar month", max_chcharge: 4333)) .to include("Household rent and other charges must be between £43.00 and £4,333.00 if paying every calendar month")
expect(record.errors["period"]) expect(record.errors["period"])
.to include(match I18n.t("validations.financial.carehome.out_of_range", min_chcharge: 43, period: "every calendar month", max_chcharge: 4333)) .to include("Household rent and other charges must be between £43.00 and £4,333.00 if paying every calendar month")
end end
it "validates charge when period is every 2 weeks" do it "validates charge when period is every 2 weeks" do
@ -1027,9 +1027,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 19 record.chcharge = 19
financial_validator.validate_care_home_charges(record) financial_validator.validate_care_home_charges(record)
expect(record.errors["chcharge"]) expect(record.errors["chcharge"])
.to include(match I18n.t("validations.financial.carehome.out_of_range", min_chcharge: 20, period: "every 2 weeks", max_chcharge: 2000)) .to include("Household rent and other charges must be between £20.00 and £2,000.00 if paying every 2 weeks")
expect(record.errors["period"]) expect(record.errors["period"])
.to include(match I18n.t("validations.financial.carehome.out_of_range", min_chcharge: 20, period: "every 2 weeks", max_chcharge: 2000)) .to include("Household rent and other charges must be between £20.00 and £2,000.00 if paying every 2 weeks")
end end
it "validates charge when period is every 4 weeks" do it "validates charge when period is every 4 weeks" do
@ -1037,9 +1037,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 39 record.chcharge = 39
financial_validator.validate_care_home_charges(record) financial_validator.validate_care_home_charges(record)
expect(record.errors["chcharge"]) expect(record.errors["chcharge"])
.to include(match I18n.t("validations.financial.carehome.out_of_range", min_chcharge: 40, period: "every 4 weeks", max_chcharge: 4000)) .to include("Household rent and other charges must be between £40.00 and £4,000.00 if paying every 4 weeks")
expect(record.errors["period"]) expect(record.errors["period"])
.to include(match I18n.t("validations.financial.carehome.out_of_range", min_chcharge: 40, period: "every 4 weeks", max_chcharge: 4000)) .to include("Household rent and other charges must be between £40.00 and £4,000.00 if paying every 4 weeks")
end end
end end
end end

16
spec/models/validations/household_validations_spec.rb

@ -637,4 +637,20 @@ RSpec.describe Validations::HouseholdValidations do
end end
end end
end end
describe "housing needs validations" do
it "is invalid when a combination of housingneeds == 1 (yes) && housingneeds_type == 3 (none of listed) && housingneeds_other == 0 (no)" do
record.housingneeds = 1
record.housingneeds_type = 3
record.housingneeds_other = 0
household_validator.validate_combination_of_housing_needs_responses(record)
error_message = ["If somebody in the household has disabled access needs, they must have the access needs listed, or other access needs"]
expect(record.errors["housingneeds"]).to eq(error_message)
expect(record.errors["housingneeds_type"]).to eq(error_message)
expect(record.errors["housingneeds_other"]).to eq(error_message)
end
end
end end

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

@ -246,11 +246,13 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
it "adds an error if mortgage, deposit and grant total does not equal market value" do it "adds an error if mortgage, deposit and grant total does not equal market value" do
record.grant = 3_000 record.grant = 3_000
sale_information_validator.validate_discounted_ownership_value(record) sale_information_validator.validate_discounted_ownership_value(record)
expect(record.errors[:mortgage]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) expected_message = ["Mortgage, deposit, and grant total must equal £30,000.00"]
expect(record.errors[:deposit]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00"))
expect(record.errors[:grant]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) expect(record.errors[:mortgage]).to eq(expected_message)
expect(record.errors[:value]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) expect(record.errors[:deposit]).to eq(expected_message)
expect(record.errors[:discount]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) expect(record.errors[:grant]).to eq(expected_message)
expect(record.errors[:value]).to eq(expected_message)
expect(record.errors[:discount]).to eq(expected_message)
end end
it "does not add an error if mortgage, deposit and grant total equals market value" do it "does not add an error if mortgage, deposit and grant total equals market value" do
@ -280,11 +282,14 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
it "adds an error if mortgage and deposit total does not equal market value - discount" do it "adds an error if mortgage and deposit total does not equal market value - discount" do
record.discount = 10 record.discount = 10
sale_information_validator.validate_discounted_ownership_value(record) sale_information_validator.validate_discounted_ownership_value(record)
expect(record.errors[:mortgage]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "27000.00"))
expect(record.errors[:deposit]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "27000.00")) expected_message = ["Mortgage, deposit, and grant total must equal £27,000.00"]
expect(record.errors[:grant]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "27000.00"))
expect(record.errors[:value]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "27000.00")) expect(record.errors[:mortgage]).to eq(expected_message)
expect(record.errors[:discount]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "27000.00")) expect(record.errors[:deposit]).to eq(expected_message)
expect(record.errors[:grant]).to eq(expected_message)
expect(record.errors[:value]).to eq(expected_message)
expect(record.errors[:discount]).to eq(expected_message)
end end
it "does not add an error if mortgage and deposit total equals market value - discount" do it "does not add an error if mortgage and deposit total equals market value - discount" do
@ -301,11 +306,14 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
it "adds an error if mortgage and deposit total does not equal market value" do it "adds an error if mortgage and deposit total does not equal market value" do
record.deposit = 2_000 record.deposit = 2_000
sale_information_validator.validate_discounted_ownership_value(record) sale_information_validator.validate_discounted_ownership_value(record)
expect(record.errors[:mortgage]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00"))
expect(record.errors[:deposit]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) expected_message = ["Mortgage, deposit, and grant total must equal £30,000.00"]
expect(record.errors[:grant]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00"))
expect(record.errors[:value]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) expect(record.errors[:mortgage]).to eq(expected_message)
expect(record.errors[:discount]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00")) expect(record.errors[:deposit]).to eq(expected_message)
expect(record.errors[:grant]).to eq(expected_message)
expect(record.errors[:value]).to eq(expected_message)
expect(record.errors[:discount]).to eq(expected_message)
end end
it "does not add an error if mortgage and deposit total equals market value" do it "does not add an error if mortgage and deposit total equals market value" do
@ -334,11 +342,14 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
it "adds an error if mortgage, grant and deposit total does not equal market value - discount" do it "adds an error if mortgage, grant and deposit total does not equal market value - discount" do
record.mortgage = 10 record.mortgage = 10
sale_information_validator.validate_discounted_ownership_value(record) sale_information_validator.validate_discounted_ownership_value(record)
expect(record.errors[:mortgage]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00"))
expect(record.errors[:deposit]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) expected_message = ["Mortgage, deposit, and grant total must equal £18,000.00"]
expect(record.errors[:grant]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00"))
expect(record.errors[:value]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) expect(record.errors[:mortgage]).to eq(expected_message)
expect(record.errors[:discount]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) expect(record.errors[:deposit]).to eq(expected_message)
expect(record.errors[:grant]).to eq(expected_message)
expect(record.errors[:value]).to eq(expected_message)
expect(record.errors[:discount]).to eq(expected_message)
end end
it "does not add an error if mortgage, grant and deposit total equals market value - discount" do it "does not add an error if mortgage, grant and deposit total equals market value - discount" do
@ -354,11 +365,13 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
it "adds an error if grant and deposit total does not equal market value - discount" do it "adds an error if grant and deposit total does not equal market value - discount" do
sale_information_validator.validate_discounted_ownership_value(record) sale_information_validator.validate_discounted_ownership_value(record)
expect(record.errors[:mortgage]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00"))
expect(record.errors[:deposit]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) expected_message = ["Mortgage, deposit, and grant total must equal £18,000.00"]
expect(record.errors[:grant]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) expect(record.errors[:mortgage]).to eq(expected_message)
expect(record.errors[:value]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) expect(record.errors[:deposit]).to eq(expected_message)
expect(record.errors[:discount]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00")) expect(record.errors[:grant]).to eq(expected_message)
expect(record.errors[:value]).to eq(expected_message)
expect(record.errors[:discount]).to eq(expected_message)
end end
it "does not add an error if mortgage, grant and deposit total equals market value - discount" do it "does not add an error if mortgage, grant and deposit total equals market value - discount" do

51
spec/requests/form_controller_spec.rb

@ -57,7 +57,7 @@ RSpec.describe FormController, type: :request do
describe "POST" do describe "POST" do
it "does not let you post form answers to lettings logs you don't have access to" do it "does not let you post form answers to lettings logs you don't have access to" do
post "/lettings-logs/#{lettings_log.id}/form", params: {} post "/lettings-logs/#{lettings_log.id}/net-income", params: {}
expect(response).to redirect_to("/account/sign-in") expect(response).to redirect_to("/account/sign-in")
end end
end end
@ -102,7 +102,7 @@ RSpec.describe FormController, type: :request do
end end
it "resets created by and renders the next page" do it "resets created by and renders the next page" do
post "/lettings-logs/#{lettings_log.id}/form", params: params post "/lettings-logs/#{lettings_log.id}/net-income", params: params
expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/created-by") expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/created-by")
follow_redirect! follow_redirect!
lettings_log.reload lettings_log.reload
@ -127,7 +127,7 @@ RSpec.describe FormController, type: :request do
end end
it "does not reset created by" do it "does not reset created by" do
post "/lettings-logs/#{lettings_log.id}/form", params: params post "/lettings-logs/#{lettings_log.id}/net-income", params: params
expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/created-by") expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/created-by")
follow_redirect! follow_redirect!
lettings_log.reload lettings_log.reload
@ -152,7 +152,7 @@ RSpec.describe FormController, type: :request do
end end
it "does not reset created by" do it "does not reset created by" do
post "/lettings-logs/#{lettings_log.id}/form", params: params post "/lettings-logs/#{lettings_log.id}/stock-owner", params: params
expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/managing-organisation") expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/managing-organisation")
follow_redirect! follow_redirect!
lettings_log.reload lettings_log.reload
@ -177,7 +177,7 @@ RSpec.describe FormController, type: :request do
end end
it "does not reset created by" do it "does not reset created by" do
post "/lettings-logs/#{lettings_log.id}/form", params: params post "/lettings-logs/#{lettings_log.id}/stock-owner", params: params
expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/managing-organisation") expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/managing-organisation")
follow_redirect! follow_redirect!
lettings_log.reload lettings_log.reload
@ -400,22 +400,21 @@ RSpec.describe FormController, type: :request do
end end
it "re-renders the same page with errors if validation fails" do it "re-renders the same page with errors if validation fails" do
post "/lettings-logs/#{lettings_log.id}/form", params: params post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params
expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}")
follow_redirect!
expect(page).to have_content("There is a problem") expect(page).to have_content("There is a problem")
expect(page).to have_content("Error: What is the tenant’s age?")
end end
it "resets errors when fixed" do it "resets errors when fixed" do
post "/lettings-logs/#{lettings_log.id}/form", params: params post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params
post "/lettings-logs/#{lettings_log.id}/form", params: valid_params post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: valid_params
get "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}" get "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}"
expect(page).not_to have_content("There is a problem") expect(page).not_to have_content("There is a problem")
end end
it "logs that validation was triggered" do it "logs that validation was triggered" do
expect(Rails.logger).to receive(:info).with("User triggered validation(s) on: age1").once expect(Rails.logger).to receive(:info).with("User triggered validation(s) on: age1").once
post "/lettings-logs/#{lettings_log.id}/form", params: post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params:
end end
context "when the number of days is too high for the month" do context "when the number of days is too high for the month" do
@ -433,8 +432,7 @@ RSpec.describe FormController, type: :request do
end end
it "validates the date correctly" do it "validates the date correctly" do
post "/lettings-logs/#{lettings_log.id}/form", params: params post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params
follow_redirect!
expect(page).to have_content("There is a problem") expect(page).to have_content("There is a problem")
end end
end end
@ -465,10 +463,9 @@ RSpec.describe FormController, type: :request do
end end
it "re-renders the same page with errors if validation fails" do it "re-renders the same page with errors if validation fails" do
post "/lettings-logs/#{lettings_log.id}/form", params: params post "/lettings-logs/#{lettings_log.id}/managing-organisation", params: params
expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/managing-organisation")
follow_redirect!
expect(page).to have_content("There is a problem") expect(page).to have_content("There is a problem")
expect(page).to have_content("Error: Which organisation manages this letting?")
end end
end end
@ -486,7 +483,7 @@ RSpec.describe FormController, type: :request do
end end
before do before do
post "/lettings-logs/#{lettings_log.id}/form", params: post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params:
end end
it "re-renders the same page with errors if validation fails" do it "re-renders the same page with errors if validation fails" do
@ -524,7 +521,7 @@ RSpec.describe FormController, type: :request do
before do before do
lettings_log.update!(postcode_known: 1, postcode_full: "NW1 8RR") lettings_log.update!(postcode_known: 1, postcode_full: "NW1 8RR")
post "/lettings-logs/#{lettings_log.id}/form", params: valid_params post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: valid_params
end end
it "does not require you to answer that question" do it "does not require you to answer that question" do
@ -558,14 +555,14 @@ RSpec.describe FormController, type: :request do
end end
it "sets checked items to true" do it "sets checked items to true" do
post "/lettings-logs/#{lettings_log.id}/form", params: lettings_log_form_params post "/lettings-logs/#{lettings_log.id}/accessibility-requirements", params: lettings_log_form_params
lettings_log.reload lettings_log.reload
expect(lettings_log.housingneeds_b).to eq(1) expect(lettings_log.housingneeds_b).to eq(1)
end end
it "sets previously submitted items to false when resubmitted with new values" do it "sets previously submitted items to false when resubmitted with new values" do
post "/lettings-logs/#{lettings_log.id}/form", params: new_lettings_log_form_params post "/lettings-logs/#{lettings_log.id}/accessibility-requirements", params: new_lettings_log_form_params
lettings_log.reload lettings_log.reload
expect(lettings_log.housingneeds_b).to eq(0) expect(lettings_log.housingneeds_b).to eq(0)
@ -609,7 +606,7 @@ RSpec.describe FormController, type: :request do
it "updates both question fields" do it "updates both question fields" do
allow(page).to receive(:questions).and_return(questions_for_page) allow(page).to receive(:questions).and_return(questions_for_page)
post "/lettings-logs/#{lettings_log.id}/form", params: lettings_log_form_params post "/lettings-logs/#{lettings_log.id}/#{page.id.dasherize}", params: lettings_log_form_params
lettings_log.reload lettings_log.reload
expect(lettings_log.housingneeds_a).to eq(1) expect(lettings_log.housingneeds_a).to eq(1)
@ -650,16 +647,16 @@ RSpec.describe FormController, type: :request do
end end
it "routes to the appropriate conditional page based on the question answer of the current page" do it "routes to the appropriate conditional page based on the question answer of the current page" do
post "/lettings-logs/#{lettings_log.id}/form", params: lettings_log_form_conditional_question_yes_params post "/lettings-logs/#{lettings_log.id}/property-wheelchair-accessible", params: lettings_log_form_conditional_question_yes_params
expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/conditional-question-yes-page") expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/conditional-question-yes-page")
post "/lettings-logs/#{lettings_log.id}/form", params: lettings_log_form_conditional_question_no_params post "/lettings-logs/#{lettings_log.id}/property-wheelchair-accessible", params: lettings_log_form_conditional_question_no_params
expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/conditional-question-no-page") expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/conditional-question-no-page")
end end
it "routes to the page if at least one of the condition sets is met" do it "routes to the page if at least one of the condition sets is met" do
post "/lettings-logs/#{lettings_log.id}/form", params: lettings_log_form_conditional_question_wchair_yes_params post "/lettings-logs/#{lettings_log.id}/property-wheelchair-accessible", params: lettings_log_form_conditional_question_wchair_yes_params
post "/lettings-logs/#{lettings_log.id}/form", params: lettings_log_form_conditional_question_no_params post "/lettings-logs/#{lettings_log.id}/property-wheelchair-accessible", params: lettings_log_form_conditional_question_no_params
expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/conditional-question-yes-page") expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/conditional-question-yes-page")
end end
end end
@ -690,7 +687,7 @@ RSpec.describe FormController, type: :request do
completed_lettings_log.update!(ecstat1: 1, earnings: 130, hhmemb: 1) # we're not routing to that page, so it gets cleared? completed_lettings_log.update!(ecstat1: 1, earnings: 130, hhmemb: 1) # we're not routing to that page, so it gets cleared?
allow(completed_lettings_log).to receive(:net_income_soft_validation_triggered?).and_return(true) allow(completed_lettings_log).to receive(:net_income_soft_validation_triggered?).and_return(true)
allow(completed_lettings_log.form).to receive(:end_date).and_return(Time.zone.today + 1.day) allow(completed_lettings_log.form).to receive(:end_date).and_return(Time.zone.today + 1.day)
post "/lettings-logs/#{completed_lettings_log.id}/form", params: interrupt_params, headers: headers.merge({ "HTTP_REFERER" => referrer }) post "/lettings-logs/#{completed_lettings_log.id}/net-income-value-check", params: interrupt_params, headers: headers.merge({ "HTTP_REFERER" => referrer })
end end
context "when yes is answered" do context "when yes is answered" do
@ -722,7 +719,7 @@ RSpec.describe FormController, type: :request do
end end
before do before do
post "/lettings-logs/#{unauthorized_lettings_log.id}/form", params: {} post "/lettings-logs/#{unauthorized_lettings_log.id}/net-income", params: {}
end end
it "does not let you post form answers to lettings logs you don't have access to" do it "does not let you post form answers to lettings logs you don't have access to" do

8
spec/services/imports/lettings_logs_import_service_spec.rb

@ -223,8 +223,8 @@ RSpec.describe Imports::LettingsLogsImportService do
end end
it "intercepts the relevant validation error" do it "intercepts the relevant validation error" do
expect(logger).to receive(:warn).with(/Removing earnings with error: Net income cannot be less than £10 per week given the tenant’s working situation/) expect(logger).to receive(:warn).with(/Removing earnings with error: Net income cannot be less than £10.00 per week given the tenant’s working situation/)
expect(logger).to receive(:warn).with(/Removing incfreq with error: Net income cannot be less than £10 per week given the tenant’s working situation/) expect(logger).to receive(:warn).with(/Removing incfreq with error: Net income cannot be less than £10.00 per week given the tenant’s working situation/)
expect { lettings_log_service.send(:create_log, lettings_log_xml) } expect { lettings_log_service.send(:create_log, lettings_log_xml) }
.not_to raise_error .not_to raise_error
end end
@ -428,7 +428,7 @@ RSpec.describe Imports::LettingsLogsImportService do
end end
it "intercepts the relevant validation error" do it "intercepts the relevant validation error" do
expect(logger).to receive(:warn).with(/Removing ecstat1 with error: Net income cannot be greater than £890 per week given the tenant’s working situation/) expect(logger).to receive(:warn).with(/Removing ecstat1 with error: Net income cannot be greater than £890.00 per week given the tenant’s working situation/)
expect { lettings_log_service.send(:create_log, lettings_log_xml) } expect { lettings_log_service.send(:create_log, lettings_log_xml) }
.not_to raise_error .not_to raise_error
end end
@ -762,7 +762,7 @@ RSpec.describe Imports::LettingsLogsImportService do
end end
it "intercepts the relevant validation error" do it "intercepts the relevant validation error" do
expect(logger).to receive(:warn).with(/Removing chcharge with error: Household rent and other charges must be between £10 and £1000 if paying weekly for 52 weeks/) expect(logger).to receive(:warn).with(/Removing chcharge with error: Household rent and other charges must be between £10.00 and £1,000.00 if paying weekly for 52 weeks/)
expect { lettings_log_service.send(:create_log, lettings_log_xml) } expect { lettings_log_service.send(:create_log, lettings_log_xml) }
.not_to raise_error .not_to raise_error
end end

Loading…
Cancel
Save