Browse Source

Merge branch 'main' into CLDC-2010-discounted-income-soft-max

# Conflicts:
#	db/schema.rb
pull/1518/head
natdeanlewissoftwire 3 years ago
parent
commit
3bc011a83d
  1. 6
      Gemfile.lock
  2. 4
      app/components/bulk_upload_error_row_component.html.erb
  3. 24
      app/components/bulk_upload_error_row_component.rb
  4. 4
      app/components/bulk_upload_error_summary_table_component.html.erb
  5. 11
      app/components/bulk_upload_error_summary_table_component.rb
  6. 29
      app/controllers/form_controller.rb
  7. 2
      app/controllers/locations_controller.rb
  8. 2
      app/controllers/schemes_controller.rb
  9. 23
      app/helpers/money_formatting_helper.rb
  10. 2
      app/helpers/tasklist_helper.rb
  11. 10
      app/jobs/data_export_csv_job.rb
  12. 43
      app/mailers/bulk_upload_mailer.rb
  13. 2
      app/models/derived_variables/sales_log_variables.rb
  14. 2
      app/models/form/sales/pages/leasehold_charges.rb
  15. 10
      app/models/form/sales/questions/has_leasehold_charges.rb
  16. 2
      app/models/form/sales/subsections/discounted_ownership_scheme.rb
  17. 2
      app/models/form/sales/subsections/outright_sale.rb
  18. 2
      app/models/form/sales/subsections/shared_ownership_scheme.rb
  19. 4
      app/models/form_handler.rb
  20. 5
      app/models/lettings_log.rb
  21. 12
      app/models/location_deactivation_period.rb
  22. 4
      app/models/log.rb
  23. 7
      app/models/sales_log.rb
  24. 10
      app/models/scheme_deactivation_period.rb
  25. 29
      app/models/validations/financial_validations.rb
  26. 8
      app/models/validations/household_validations.rb
  27. 9
      app/models/validations/sales/sale_information_validations.rb
  28. 10
      app/services/bulk_upload/lettings/year2022/row_parser.rb
  29. 10
      app/services/bulk_upload/lettings/year2023/row_parser.rb
  30. 6
      app/services/bulk_upload/processor.rb
  31. 69
      app/services/exports/lettings_log_export_service.rb
  32. 3
      app/services/imports/sales_logs_import_service.rb
  33. 4
      app/views/bulk_upload_lettings_results/summary.html.erb
  34. 3
      app/views/form/_numeric_output_question.html.erb
  35. 11
      app/views/form/_numeric_question.html.erb
  36. 2
      app/views/form/page.html.erb
  37. 6
      app/views/locations/toggle_active.html.erb
  38. 2
      app/views/logs/edit.html.erb
  39. 6
      app/views/schemes/toggle_active.html.erb
  40. 24
      config/locales/en.yml
  41. 2
      config/routes.rb
  42. 5
      db/migrate/20230330084455_add_collection_to_logs_export.rb
  43. 5
      db/migrate/20230412143245_rename_mscharge_known.rb
  44. 18
      db/schema.rb
  45. 8
      docs/Gemfile.lock
  46. 29
      spec/components/bulk_upload_error_row_component_spec.rb
  47. 27
      spec/components/bulk_upload_error_summary_table_component_spec.rb
  48. 2
      spec/factories/lettings_log.rb
  49. 2
      spec/factories/sales_log.rb
  50. 4
      spec/features/form/page_routing_spec.rb
  51. 12
      spec/features/form/tasklist_page_spec.rb
  52. 2
      spec/features/lettings_log_spec.rb
  53. 4
      spec/features/schemes_spec.rb
  54. 41
      spec/helpers/money_formatting_helper_spec.rb
  55. 51
      spec/helpers/tasklist_helper_spec.rb
  56. 19
      spec/jobs/data_export_csv_job_spec.rb
  57. 8
      spec/lib/tasks/data_export_spec.rb
  58. 38
      spec/mailers/bulk_upload_mailer_spec.rb
  59. 2
      spec/models/form/sales/pages/leasehold_charges_spec.rb
  60. 10
      spec/models/form/sales/questions/has_leasehold_charges_spec.rb
  61. 8
      spec/models/lettings_log_spec.rb
  62. 83
      spec/models/location_deactivation_period_spec.rb
  63. 2
      spec/models/sales_log_spec.rb
  64. 68
      spec/models/scheme_deactivation_period_spec.rb
  65. 42
      spec/models/validations/financial_validations_spec.rb
  66. 16
      spec/models/validations/household_validations_spec.rb
  67. 63
      spec/models/validations/sales/sale_information_validations_spec.rb
  68. 55
      spec/requests/form_controller_spec.rb
  69. 36
      spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb
  70. 42
      spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb
  71. 28
      spec/services/exports/lettings_log_export_service_spec.rb
  72. 8
      spec/services/imports/lettings_logs_import_service_spec.rb
  73. 34
      spec/services/imports/sales_logs_import_service_spec.rb

6
Gemfile.lock

@ -233,11 +233,11 @@ GEM
net-smtp (0.3.3)
net-protocol
nio4r (2.5.8)
nokogiri (1.14.2-arm64-darwin)
nokogiri (1.14.3-arm64-darwin)
racc (~> 1.4)
nokogiri (1.14.2-x86_64-darwin)
nokogiri (1.14.3-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.14.2-x86_64-linux)
nokogiri (1.14.3-x86_64-linux)
racc (~> 1.4)
notifications-ruby-client (5.4.0)
jwt (>= 1.5, < 3)

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__header">
<% 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 %>
<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 %>
</div>

24
app/components/bulk_upload_error_row_component.rb

@ -15,14 +15,38 @@ class BulkUploadErrorRowComponent < ViewComponent::Base
bulk_upload_errors.first.tenant_code
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
bulk_upload_errors.first.purchaser_code
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
bulk_upload_errors.first.property_ref
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)
bulk_upload.prefix_namespace::RowParser.question_for_field(field.to_sym)
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| %>
<%= govuk_table do |table| %>
<% 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
.bulk_upload_errors
.group(:col, :field, :error)
.having("count(*) > ?", display_threshold)
.having("count(*) >= ?", display_threshold)
.count
.sort_by { |el| el[0][0].rjust(3, "0") }
end
@ -24,6 +24,14 @@ class BulkUploadErrorSummaryTableComponent < ViewComponent::Base
sorted_errors.present?
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
def setup_errors
@ -31,7 +39,6 @@ private
.bulk_upload_errors
.where(category: "setup")
.group(:col, :field, :error)
.having("count(*) > ?", display_threshold)
.count
.sort_by { |el| el[0][0].rjust(3, "0") }
end

29
app/controllers/form_controller.rb

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

2
app/controllers/locations_controller.rb

@ -269,7 +269,7 @@ private
if params[:location_deactivation_period].blank?
return
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?
return params[:location_deactivation_period][key.to_sym]
end

2
app/controllers/schemes_controller.rb

@ -296,7 +296,7 @@ private
if params[:scheme_deactivation_period].blank?
return
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?
return params[:scheme_deactivation_period][key.to_sym]
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

2
app/helpers/tasklist_helper.rb

@ -6,7 +6,7 @@ module TasklistHelper
end
def get_subsections_count(log, status = :all)
return log.form.subsections.count { |subsection| subsection.applicable_questions(log).count.positive? } if status == :all
return log.form.subsections.count { |subsection| subsection.displayed_in_tasklist?(log) } if status == :all
log.form.subsections.count { |subsection| subsection.status(log) == status && subsection.applicable_questions(log).count.positive? }
end

10
app/jobs/data_export_csv_job.rb

@ -1,10 +0,0 @@
class DataExportCsvJob < ApplicationJob
queue_as :default
def perform
storage_service = Storage::S3Service.new(Configuration::PaasConfigurationService.new, ENV["EXPORT_PAAS_INSTANCE"])
export_service = Exports::LettingsLogExportService.new(storage_service)
export_service.export_csv_lettings_logs
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_FILE_SETUP_ERROR_TEMPLATE_ID = "24c9f4c7-96ad-470a-ba31-eb51b7cbafd9".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
def send_how_fix_upload_mail(bulk_upload:)
@ -73,23 +72,10 @@ class BulkUploadMailer < NotifyMailer
end
def send_bulk_upload_failed_file_setup_error_mail(bulk_upload:)
bulk_upload_link = if bulk_upload.lettings?
start_bulk_upload_lettings_logs_url
bulk_upload_link = if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors?
summary_bulk_upload_lettings_result_url(bulk_upload)
else
start_bulk_upload_sales_logs_url
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})"
bulk_upload_lettings_result_url(bulk_upload)
end
send_email(
@ -100,7 +86,6 @@ class BulkUploadMailer < NotifyMailer
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
lettings_or_sales: bulk_upload.log_type,
year_combo: bulk_upload.year_combo,
errors_list: errors.join("\n"),
bulk_upload_link:,
},
)
@ -126,26 +111,4 @@ class BulkUploadMailer < NotifyMailer
},
)
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

2
app/models/derived_variables/sales_log_variables.rb

@ -1,7 +1,7 @@
module DerivedVariables::SalesLogVariables
def set_derived_fields!
self.ethnic = 17 if ethnic_refused?
self.mscharge = nil if monthly_leasehold_charges_unknown?
self.mscharge = nil if no_monthly_leasehold_charges?
if exdate.present?
self.exday = exdate.day
self.exmonth = exdate.month

2
app/models/form/sales/pages/leasehold_charges.rb

@ -6,7 +6,7 @@ class Form::Sales::Pages::LeaseholdCharges < ::Form::Page
def questions
@questions ||= [
Form::Sales::Questions::LeaseholdChargesKnown.new(nil, nil, self, ownershipsch: @ownershipsch),
Form::Sales::Questions::HasLeaseholdCharges.new(nil, nil, self, ownershipsch: @ownershipsch),
Form::Sales::Questions::LeaseholdCharges.new(nil, nil, self, ownershipsch: @ownershipsch),
]
end

10
app/models/form/sales/questions/leasehold_charges_known.rb → app/models/form/sales/questions/has_leasehold_charges.rb

@ -1,9 +1,9 @@
class Form::Sales::Questions::LeaseholdChargesKnown < ::Form::Question
class Form::Sales::Questions::HasLeaseholdCharges < ::Form::Question
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@id = "mscharge_known"
@check_answer_label = "Monthly leasehold charges known?"
@header = "Do you know the monthly leasehold charges for the property?"
@id = "has_mscharge"
@check_answer_label = "Does the property have any monthly leasehold charges?"
@header = "Does the property have any monthly leasehold charges?"
@hint_text = "For example, service and management charges"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@ -13,7 +13,7 @@ class Form::Sales::Questions::LeaseholdChargesKnown < ::Form::Question
@hidden_in_check_answers = {
"depends_on" => [
{
"mscharge_known" => 1,
"has_mscharge" => 1,
},
],
}

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

@ -41,6 +41,6 @@ class Form::Sales::Subsections::DiscountedOwnershipScheme < ::Form::Subsection
end
def displayed_in_tasklist?(log)
log.ownershipsch == 2
log.ownershipsch.nil? || log.ownershipsch == 2
end
end

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

@ -26,7 +26,7 @@ class Form::Sales::Subsections::OutrightSale < ::Form::Subsection
end
def displayed_in_tasklist?(log)
log.ownershipsch == 3
log.ownershipsch.nil? || log.ownershipsch == 3
end
def leasehold_charge_pages

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

@ -46,6 +46,6 @@ class Form::Sales::Subsections::SharedOwnershipScheme < ::Form::Subsection
end
def displayed_in_tasklist?(log)
log.ownershipsch == 1
log.ownershipsch.nil? || log.ownershipsch == 1
end
end

4
app/models/form_handler.rb

@ -77,6 +77,10 @@ class FormHandler
form_mappings[current_collection_start_year - year]
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)
lettings_in_crossover_period?(now:) || sales_in_crossover_period?(now:)
end

5
app/models/lettings_log.rb

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

12
app/models/location_deactivation_period.rb

@ -1,4 +1,6 @@
class LocationDeactivationPeriodValidator < ActiveModel::Validator
include CollectionTimeHelper
def validate(record)
location = record.location
recent_deactivation = location.location_deactivation_periods.deactivations_without_reactivation.first
@ -16,7 +18,7 @@ class LocationDeactivationPeriodValidator < ActiveModel::Validator
elsif record.reactivation_date_type == "other"
record.errors.add(:reactivation_date, message: I18n.t("validations.location.toggle_date.invalid"))
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)))
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)))
@ -32,10 +34,10 @@ class LocationDeactivationPeriodValidator < ActiveModel::Validator
end
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"))
else
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: location.available_from.to_formatted_s(:govuk_date)))
end
elsif record.deactivation_date.before? FormHandler.instance.start_date_of_earliest_open_collection_period
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)))
elsif record.deactivation_date.before? location.available_from
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

4
app/models/log.rb

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

7
app/models/sales_log.rb

@ -18,6 +18,7 @@ class SalesLog < Log
include DerivedVariables::SalesLogVariables
include Validations::Sales::SoftValidations
include Validations::SoftValidations
include MoneyFormattingHelper
self.inheritance_column = :_type_disabled
@ -215,7 +216,7 @@ class SalesLog < Log
def expected_shared_ownership_deposit_value
return unless value && equity
(value * equity / 100).round(2)
format_as_currency(value * equity / 100)
end
def process_postcode(postcode, postcode_known_key, la_inferred_key, la_key)
@ -291,8 +292,8 @@ class SalesLog < Log
companybuy == 1
end
def monthly_leasehold_charges_unknown?
mscharge_known&.zero?
def no_monthly_leasehold_charges?
has_mscharge&.zero?
end
def buyers_age_for_old_persons_shared_ownership_invalid?

10
app/models/scheme_deactivation_period.rb

@ -1,4 +1,6 @@
class SchemeDeactivationPeriodValidator < ActiveModel::Validator
include CollectionTimeHelper
def validate(record)
scheme = record.scheme
recent_deactivation = scheme.scheme_deactivation_periods.deactivations_without_reactivation.first
@ -32,10 +34,10 @@ class SchemeDeactivationPeriodValidator < ActiveModel::Validator
end
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"))
else
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: scheme.available_from.to_formatted_s(:govuk_date)))
end
elsif record.deactivation_date.before? FormHandler.instance.start_date_of_earliest_open_collection_period
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)))
elsif record.deactivation_date.before? scheme.available_from
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

29
app/models/validations/financial_validations.rb

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

8
app/models/validations/household_validations.rb

@ -121,6 +121,14 @@ module Validations::HouseholdValidations
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
def household_no_illness?(record)

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

@ -1,5 +1,6 @@
module Validations::Sales::SaleInformationValidations
include CollectionTimeHelper
include MoneyFormattingHelper
def validate_practical_completion_date_before_saledate(record)
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?
%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

10
app/services/bulk_upload/lettings/year2022/row_parser.rb

@ -480,7 +480,7 @@ private
block_log_creation!
if errors[:field_113].blank?
errors.add(:field_113, "This managing organisation does not have a relationship with the owning organisation")
errors.add(:field_113, "This managing organisation does not have a relationship with the owning organisation", category: :setup)
end
end
end
@ -490,7 +490,7 @@ private
block_log_creation!
if errors[:field_113].blank?
errors.add(:field_113, "The managing organisation code is incorrect")
errors.add(:field_113, "The managing organisation code is incorrect", category: :setup)
end
end
end
@ -507,7 +507,7 @@ private
block_log_creation!
if errors[:field_111].blank?
errors.add(:field_111, "The owning organisation code provided is for an organisation that does not own stock")
errors.add(:field_111, "The owning organisation code provided is for an organisation that does not own stock", category: :setup)
end
end
end
@ -517,7 +517,7 @@ private
block_log_creation!
if errors[:field_111].blank?
errors.add(:field_111, "The owning organisation code is incorrect")
errors.add(:field_111, "The owning organisation code is incorrect", category: :setup)
end
end
end
@ -537,7 +537,7 @@ private
block_log_creation!
if errors[:field_111].blank?
errors.add(:field_111, "You do not have permission to add logs for this owning organisation")
errors.add(:field_111, "You do not have permission to add logs for this owning organisation", category: :setup)
end
end
end

10
app/services/bulk_upload/lettings/year2023/row_parser.rb

@ -598,7 +598,7 @@ private
block_log_creation!
if errors[:field_2].blank?
errors.add(:field_2, "This managing organisation does not have a relationship with the owning organisation")
errors.add(:field_2, "This managing organisation does not have a relationship with the owning organisation", category: :setup)
end
end
end
@ -608,7 +608,7 @@ private
block_log_creation!
if errors[:field_2].blank?
errors.add(:field_2, "The managing organisation code is incorrect")
errors.add(:field_2, "The managing organisation code is incorrect", category: :setup)
end
end
end
@ -625,7 +625,7 @@ private
block_log_creation!
if errors[:field_1].blank?
errors.add(:field_1, "The owning organisation code provided is for an organisation that does not own stock")
errors.add(:field_1, "The owning organisation code provided is for an organisation that does not own stock", category: :setup)
end
end
end
@ -635,7 +635,7 @@ private
block_log_creation!
if errors[:field_1].blank?
errors.add(:field_1, "The owning organisation code is incorrect")
errors.add(:field_1, "The owning organisation code is incorrect", category: :setup)
end
end
end
@ -652,7 +652,7 @@ private
block_log_creation!
if errors[:field_1].blank?
errors.add(:field_1, "You do not have permission to add logs for this owning organisation")
errors.add(:field_1, "You do not have permission to add logs for this owning organisation", category: :setup)
end
end
end

6
app/services/bulk_upload/processor.rb

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

69
app/services/exports/lettings_log_export_service.rb

@ -8,49 +8,49 @@ module Exports
@logger = logger
end
def export_csv_lettings_logs
time_str = Time.zone.now.strftime("%F").underscore
lettings_logs = retrieve_lettings_logs(Time.zone.now, true)
csv_io = build_export_csv(lettings_logs)
@storage_service.write_file("export_#{time_str}.csv", csv_io)
end
def export_xml_lettings_logs(full_update: false)
start_time = Time.zone.now
lettings_logs = retrieve_lettings_logs(start_time, full_update)
export = build_export_run(start_time, full_update)
daily_run = get_daily_run_number
archive_datetimes = write_export_archive(export, lettings_logs)
export.empty_export = archive_datetimes.empty?
write_master_manifest(daily_run, archive_datetimes)
logs_by_collection = retrieve_lettings_logs(start_time, full_update).group_by(&:collection_start_year)
daily_run_number = get_daily_run_number
archives_for_manifest = []
base_number = LogsExport.where(empty_export: false).maximum(:base_number) || 1
available_collection_years.each do |collection|
lettings_logs = logs_by_collection.fetch(collection, LettingsLog.none)
export = build_export_run(collection, start_time, base_number, full_update)
archives = write_export_archive(export, lettings_logs)
archives_for_manifest << archives if archives.any?
export.empty_export = archives.empty?
export.save!
end
write_master_manifest(daily_run_number, archives_for_manifest.flatten)
end
private
def get_daily_run_number
today = Time.zone.today
LogsExport.where(created_at: today.beginning_of_day..today.end_of_day).count + 1
LogsExport.where(created_at: today.beginning_of_day..today.end_of_day).select(:started_at).distinct.count + 1
end
def build_export_run(current_time, full_update)
previous_exports_with_data = LogsExport.where(empty_export: false)
if previous_exports_with_data.empty?
return LogsExport.new(started_at: current_time)
end
def build_export_run(collection, current_time, base_number, full_update)
previous_exports_with_data = LogsExport.where(collection:, empty_export: false)
base_number = previous_exports_with_data.maximum(:base_number)
increment_number = previous_exports_with_data.where(base_number:).maximum(:increment_number)
increment_number = previous_exports_with_data.where(base_number:).maximum(:increment_number) || 1
if full_update
base_number += 1
base_number += 1 if LogsExport.any? # Only increment when it's not the first run
increment_number = 1
else
increment_number += 1
end
LogsExport.new(started_at: current_time, base_number:, increment_number:)
if previous_exports_with_data.empty?
return LogsExport.new(collection:, base_number:, started_at: current_time)
end
LogsExport.new(collection:, started_at: current_time, base_number:, increment_number:)
end
def write_master_manifest(daily_run, archive_datetimes)
@ -237,23 +237,6 @@ module Exports
!EXPORT_FIELDS.include?(field_name)
end
def build_export_csv(lettings_logs)
csv_io = CSV.generate do |csv|
attribute_keys = nil
lettings_logs.each do |lettings_log|
attribute_hash = apply_cds_transformation(lettings_log, EXPORT_MODE[:csv])
if attribute_keys.nil?
attribute_keys = attribute_hash.keys
filter_keys!(attribute_keys)
csv << attribute_keys
end
csv << attribute_keys.map { |attribute_key| attribute_hash[attribute_key] }
end
end
StringIO.new(csv_io)
end
def build_export_xml(lettings_logs)
doc = Nokogiri::XML("<forms/>")
@ -273,5 +256,9 @@ module Exports
xml_doc_to_temp_file(doc)
end
def available_collection_years
FormHandler.instance.lettings_forms.values.map { |f| f.start_date.year }.uniq
end
end
end

3
app/services/imports/sales_logs_import_service.rb

@ -107,7 +107,8 @@ module Imports
attributes["hhregresstill"] = still_serving(xml_doc)
attributes["proplen"] = safe_string_as_integer(xml_doc, "Q16aProplen2") || safe_string_as_integer(xml_doc, "Q16aProplensec2")
attributes["mscharge"] = monthly_charges(xml_doc, attributes)
attributes["mscharge_known"] = 1 if attributes["mscharge"].present?
attributes["has_mscharge"] = 1 if attributes["mscharge"]&.positive?
attributes["has_mscharge"] = 0 if attributes["mscharge"].present? && attributes["mscharge"] <= 0
attributes["prevten"] = unsafe_string_as_integer(xml_doc, "Q6PrevTenure")
attributes["mortlen"] = mortgage_length(xml_doc, attributes)
attributes["extrabor"] = borrowing(xml_doc, attributes)

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

@ -16,10 +16,6 @@
<div class="govuk-grid-row">
<%= govuk_tabs(title: "Error reports") do |c| %>
<% 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) %>
<% end %>

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

@ -16,7 +16,8 @@
type="number"
name="lettings_log[tcharge]"
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>
</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? %>
<%= f.govuk_number_field question.id.to_sym,
<%= f.govuk_number_field(
question.id.to_sym,
caption: caption(caption_text, page_header, conditional),
label: legend(question, page_header, conditional),
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,
readonly: question.read_only?,
prefix_text: question.prefix.to_s,
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? %>

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

@ -5,7 +5,7 @@
<% end %>
<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-column-<%= @page.questions[0].type == "interruption_screen" ? "full-from-desktop" : "two-thirds-from-desktop" %>">
<% 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| %>
<div class="govuk-grid-row">
<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_radio_buttons_fieldset date_type_question(action),
legend: { text: I18n.t("questions.location.toggle_active.apply_from") },
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") %>
<%= f.govuk_radio_button date_type_question(action),
"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),
"other",

2
app/views/logs/edit.html.erb

@ -11,7 +11,7 @@
</h1>
<% if @log.status == "in_progress" %>
<p class="govuk-body govuk-!-margin-bottom-7"><%= get_subsections_count(@log, :completed) %> of <%= get_subsections_count(@log, :all) %> sections completed.</p>
<p class="govuk-body govuk-!-margin-bottom-7"><%= get_subsections_count(@log, :completed) %> of <%= get_subsections_count(@log) %> subsections completed.</p>
<p class="govuk-body govuk-!-margin-bottom-2">
<% next_incomplete_section = get_next_incomplete_section(@log) %>
</p>

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| %>
<div class="govuk-grid-row">
<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_radio_buttons_fieldset date_type_question(action),
legend: { text: I18n.t("questions.scheme.toggle_active.apply_from") },
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") %>
<%= f.govuk_radio_button date_type_question(action),
"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),
"other",
label: { text: "For tenancies starting after a certain date" },

24
config/locales/en.yml

@ -248,15 +248,15 @@ en:
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"
earnings:
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"
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"
freq_missing: "Select how often the household receives income"
earnings_missing: "Enter how much income the household has in total"
income:
over_hard_max_for_london: "Income must not exceed £90,000 for properties within London local authorities"
over_hard_max_for_outside_london: "Income must not exceed £80,000 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_outside_london: "Combined income must not exceed £80,000 for properties outside 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.00 for properties outside 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.00 for properties outside London local authorities"
child_has_income: "Child's income must be £0"
negative_currency: "Enter an amount above 0"
rent:
@ -389,6 +389,8 @@ en:
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"
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"
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"
@ -456,6 +458,7 @@ en:
toggle_date:
not_selected: "Select one of the options"
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}"
reactivation:
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:
not_selected: "Select one of the options"
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}"
reactivation:
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"
previous_property_type:
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:
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:
net_income:
@ -596,10 +600,10 @@ en:
postcode: "For example, SW1P 4DF."
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."
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"
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:
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."

2
config/routes.rb

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

5
db/migrate/20230330084455_add_collection_to_logs_export.rb

@ -0,0 +1,5 @@
class AddCollectionToLogsExport < ActiveRecord::Migration[7.0]
def change
add_column :logs_exports, :collection, :string
end
end

5
db/migrate/20230412143245_rename_mscharge_known.rb

@ -0,0 +1,5 @@
class RenameMschargeKnown < ActiveRecord::Migration[7.0]
def change
rename_column :sales_logs, :mscharge_known, :has_mscharge
end
end

18
db/schema.rb

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_04_05_132300) do
ActiveRecord::Schema[7.0].define(version: 2023_04_12_143245) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -351,6 +351,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_132300) do
t.integer "base_number", default: 1, null: false
t.integer "increment_number", default: 1, null: false
t.boolean "empty_export", default: false, null: false
t.string "collection"
end
create_table "organisation_relationships", force: :cascade do |t|
@ -513,12 +514,11 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_132300) do
t.integer "hhregres"
t.integer "hhregresstill"
t.integer "proplen"
t.integer "mscharge_known"
t.integer "has_mscharge"
t.decimal "mscharge", precision: 10, scale: 2
t.integer "prevten"
t.integer "mortgageused"
t.integer "wchair"
t.integer "income2_value_check"
t.integer "armedforcesspouse"
t.datetime "hodate", precision: nil
t.integer "hoday"
@ -543,13 +543,14 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_132300) do
t.integer "retirement_value_check"
t.integer "hodate_check"
t.integer "extrabor_value_check"
t.integer "grant_value_check"
t.integer "staircase_bought_value_check"
t.integer "deposit_and_mortgage_value_check"
t.integer "shared_ownership_deposit_value_check"
t.integer "grant_value_check"
t.integer "value_value_check"
t.integer "old_persons_shared_ownership_value_check"
t.integer "staircase_bought_value_check"
t.integer "income2_value_check"
t.integer "monthly_charges_value_check"
t.integer "value_value_check"
t.integer "details_known_5"
t.integer "details_known_6"
t.integer "saledate_check"
@ -559,9 +560,10 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_132300) do
t.integer "ethnicbuy2"
t.integer "proplen_asked"
t.string "old_id"
t.integer "pregblank"
t.integer "buy2living"
t.integer "prevtenbuy2"
t.integer "pregblank"
t.integer "nationalbuy2"
t.string "uprn"
t.integer "uprn_known"
t.integer "uprn_confirmed"
@ -569,10 +571,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_05_132300) do
t.string "address_line2"
t.string "town_or_city"
t.string "county"
t.integer "nationalbuy2"
t.integer "discounted_sale_value_check"
t.integer "student_not_child_value_check"
t.integer "combined_income_value_check"
t.index ["bulk_upload_id"], name: "index_sales_logs_on_bulk_upload_id"
t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id"
t.index ["old_id"], name: "index_sales_logs_on_old_id", unique: true

8
docs/Gemfile.lock

@ -13,7 +13,7 @@ GEM
execjs
coffee-script-source (1.11.1)
colorator (1.1.0)
commonmarker (0.23.8)
commonmarker (0.23.9)
concurrent-ruby (1.2.2)
dnsruby (1.61.9)
simpleidn (~> 0.1)
@ -210,11 +210,11 @@ GEM
jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1)
minitest (5.18.0)
nokogiri (1.14.2-arm64-darwin)
nokogiri (1.14.3-arm64-darwin)
racc (~> 1.4)
nokogiri (1.14.2-x86_64-darwin)
nokogiri (1.14.3-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.14.2-x86_64-linux)
nokogiri (1.14.3-x86_64-linux)
racc (~> 1.4)
octokit (4.25.1)
faraday (>= 1, < 3)

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(:tenant_code) { SecureRandom.hex(4) }
let(:property_ref) { SecureRandom.hex(4) }
let(:purchaser_code) { nil }
let(:field) { :field_134 }
let(:error) { "some error" }
let(:bulk_upload) { create(:bulk_upload, :lettings) }
@ -16,6 +17,7 @@ RSpec.describe BulkUploadErrorRowComponent, type: :component do
row:,
tenant_code:,
property_ref:,
purchaser_code:,
field:,
error:,
),
@ -49,6 +51,33 @@ RSpec.describe BulkUploadErrorRowComponent, type: :component do
expect(result).to have_content(expected)
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
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
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
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) }
@ -99,6 +118,8 @@ RSpec.describe BulkUploadErrorSummaryTableComponent, type: :component do
before do
create(:bulk_upload_error, bulk_upload:, col: "B", row: 2, category: nil)
stub_const("BulkUploadErrorSummaryTableComponent::DISPLAY_THRESHOLD", 16)
end
it "only returns the setup errors" do
@ -117,6 +138,12 @@ RSpec.describe BulkUploadErrorSummaryTableComponent, type: :component do
"1 error",
])
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

2
spec/factories/lettings_log.rb

@ -3,7 +3,7 @@ FactoryBot.define do
created_by { FactoryBot.create(:user) }
owning_organisation { created_by.organisation }
managing_organisation { created_by.organisation }
trait :about_completed do
trait :setup_completed do
renewal { 0 }
needstype { 1 }
rent_type { 1 }

2
spec/factories/sales_log.rb

@ -92,7 +92,7 @@ FactoryBot.define do
mortgageused { 1 }
wchair { 1 }
armedforcesspouse { 5 }
mscharge_known { 1 }
has_mscharge { 1 }
mscharge { 100 }
mortlen { 10 }
pcodenk { 1 }

4
spec/features/form/page_routing_spec.rb

@ -85,11 +85,11 @@ RSpec.describe "Form Page Routing" do
context "when answer is invalid" do
it "shows error with invalid value in the field" do
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")
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
it "does not reset the displayed date" do

12
spec/features/form/tasklist_page_spec.rb

@ -33,7 +33,7 @@ RSpec.describe "Task List" do
let(:setup_completed_log) do
FactoryBot.create(
:lettings_log,
:about_completed,
:setup_completed,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
created_by: user,
@ -67,9 +67,17 @@ RSpec.describe "Task List" do
expect(page).to have_content("This log has not been started.")
end
context "when testing completed subsection count" do
let(:real_2021_2022_form) { Form.new("config/forms/2021_2022.json") }
before do
allow(FormHandler.instance).to receive(:get_form).and_return(real_2021_2022_form)
end
it "shows number of completed sections if one section is completed" do
visit("/lettings-logs/#{setup_completed_log.id}")
expect(page).to have_content("1 of 8 sections completed.")
expect(page).to have_content("1 of 7 subsections completed.")
end
end
it "show skip link for next incomplete section" do

2
spec/features/lettings_log_spec.rb

@ -95,7 +95,7 @@ RSpec.describe "Lettings Log Features" do
end
context "when visiting a subsection check answers page" do
let(:lettings_log) { FactoryBot.create(:lettings_log, :about_completed) }
let(:lettings_log) { FactoryBot.create(:lettings_log, :setup_completed) }
it "has the correct breadcrumbs with the correct links" do
visit lettings_log_setup_check_answers_path(lettings_log)

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")
end
context "when location is incative" do
context "when location is inactive" do
context "and I click to view the location" do
before do
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_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("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
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

51
spec/helpers/tasklist_helper_spec.rb

@ -27,26 +27,30 @@ RSpec.describe TasklistHelper do
end
describe "get sections count" do
let(:fake_2021_2022_form) { Form.new("spec/fixtures/forms/2021_2022.json") }
let(:real_2021_2022_form) { Form.new("config/forms/2021_2022.json") }
before do
allow(FormHandler.instance).to receive(:get_form).and_return(fake_2021_2022_form)
allow(FormHandler.instance).to receive(:get_form).and_return(real_2021_2022_form)
end
it "returns the total of sections if no status is given" do
expect(get_subsections_count(empty_lettings_log)).to eq(8)
context "with an empty lettings log" do
it "returns the total displayed subsections count if no status is given" do
expect(get_subsections_count(empty_lettings_log)).to eq(7)
end
it "returns 0 sections for completed sections if no sections are completed" do
expect(get_subsections_count(empty_lettings_log, :completed)).to eq(0)
end
end
it "returns the number of not started sections" do
expect(get_subsections_count(empty_lettings_log, :not_started)).to eq(8)
context "with a partially complete lettings log" do
it "returns the total displayed subsections count if no status is given" do
expect(get_subsections_count(lettings_log)).to eq(7)
end
it "returns the number of sections in progress" do
expect(get_subsections_count(lettings_log, :in_progress)).to eq(2)
it "returns the completed sections count" do
expect(get_subsections_count(lettings_log, :completed)).to eq(1)
end
end
it "returns 0 for invalid state" do
@ -77,6 +81,37 @@ RSpec.describe TasklistHelper do
end
end
describe "with sales" do
let(:empty_sales_log) { create(:sales_log, owning_organisation: nil) }
let(:completed_sales_log) { build(:sales_log, :completed) }
describe "get sections count" do
context "with an empty sales log" do
it "returns the total displayed subsections count if no status is given (includes all 3 sale information subsections)" do
expect(get_subsections_count(empty_sales_log)).to eq(9)
end
it "returns 0 sections for completed sections if no sections are completed" do
expect(get_subsections_count(empty_sales_log, :completed)).to eq(0)
end
end
context "with a completed sales log" do
it "returns the total displayed subsections count if no status is given (includes only the 1 relevant sale information subsection)" do
expect(get_subsections_count(completed_sales_log)).to eq(7)
end
it "returns the completed sections count" do
expect(get_subsections_count(completed_sales_log, :completed)).to eq(7)
end
end
it "returns 0 for invalid state" do
expect(get_subsections_count(completed_sales_log, :fake)).to eq(0)
end
end
end
describe "#review_log_text" do
context "with sales log" do
context "when collection_period_open? == true" do

19
spec/jobs/data_export_csv_job_spec.rb

@ -1,19 +0,0 @@
require "rails_helper"
describe DataExportCsvJob do
let(:storage_service) { instance_double(Storage::S3Service) }
let(:paas_config_service) { instance_double(Configuration::PaasConfigurationService) }
let(:export_service) { instance_double(Exports::LettingsLogExportService) }
before do
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(Configuration::PaasConfigurationService).to receive(:new).and_return(paas_config_service)
allow(Exports::LettingsLogExportService).to receive(:new).and_return(export_service)
end
it "calls the export service" do
expect(export_service).to receive(:export_csv_lettings_logs)
described_class.perform_now
end
end

8
spec/lib/tasks/data_export_spec.rb

@ -26,12 +26,4 @@ describe "rake core:data_export", type: task do
expect { task.invoke }.to enqueue_job(DataExportXmlJob)
end
end
context "when exporting lettings logs with CSV format" do
let(:task) { Rake::Task["core:data_export_csv"] }
it "starts the CSV export process" do
expect { task.invoke }.to enqueue_job(DataExportCsvJob)
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")
end
let(:expected_errors) do
[
"- What is the letting type? (Column A)",
"- Management group code (Column E)",
]
end
it "sends correctly formed email" do
expect(notify_client).to receive(:send_email).with(
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),
lettings_or_sales: bulk_upload.log_type,
year_combo: bulk_upload.year_combo,
errors_list: expected_errors.join("\n"),
bulk_upload_link: start_bulk_upload_lettings_logs_url,
bulk_upload_link: summary_bulk_upload_lettings_result_url(bulk_upload),
},
)
@ -81,34 +73,6 @@ RSpec.describe BulkUploadMailer do
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
context "when 2 columns with errors" do
before do

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

@ -12,7 +12,7 @@ RSpec.describe Form::Sales::Pages::LeaseholdCharges, type: :model do
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[mscharge_known mscharge])
expect(page.questions.map(&:id)).to eq(%w[has_mscharge mscharge])
end
it "has the correct id" do

10
spec/models/form/sales/questions/leasehold_charges_known_spec.rb → spec/models/form/sales/questions/has_leasehold_charges_spec.rb

@ -1,6 +1,6 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::LeaseholdChargesKnown, type: :model do
RSpec.describe Form::Sales::Questions::HasLeaseholdCharges, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page, ownershipsch: 1) }
let(:question_id) { nil }
@ -12,15 +12,15 @@ RSpec.describe Form::Sales::Questions::LeaseholdChargesKnown, type: :model do
end
it "has the correct id" do
expect(question.id).to eq("mscharge_known")
expect(question.id).to eq("has_mscharge")
end
it "has the correct header" do
expect(question.header).to eq("Do you know the monthly leasehold charges for the property?")
expect(question.header).to eq("Does the property have any monthly leasehold charges?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Monthly leasehold charges known?")
expect(question.check_answer_label).to eq("Does the property have any monthly leasehold charges?")
end
it "has the correct type" do
@ -52,7 +52,7 @@ RSpec.describe Form::Sales::Questions::LeaseholdChargesKnown, type: :model do
expect(question.hidden_in_check_answers).to eq({
"depends_on" => [
{
"mscharge_known" => 1,
"has_mscharge" => 1,
},
],
})

8
spec/models/lettings_log_spec.rb

@ -2605,24 +2605,24 @@ RSpec.describe LettingsLog do
context "when period is weekly for 52 weeks" do
it "returns weekly soft min for 52 weeks" do
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
it "returns weekly soft max for 52 weeks" do
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
context "when period is weekly for 47 weeks" do
it "returns weekly soft min for 47 weeks" do
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
it "returns weekly soft max for 47 weeks" do
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

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

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

42
spec/models/validations/financial_validations_spec.rb

@ -86,7 +86,7 @@ RSpec.describe Validations::FinancialValidations do
end
context "when outstanding rent or charges is yes" do
let(:record) { FactoryBot.create(:lettings_log, :about_completed, startdate: Time.zone.now) }
let(:record) { FactoryBot.create(:lettings_log, :setup_completed, startdate: Time.zone.now) }
it "expects that a shortfall is provided" do
record.hbrentshortfall = 1
@ -202,7 +202,7 @@ RSpec.describe Validations::FinancialValidations do
record.ecstat1 = 1
financial_validator.validate_net_income(record)
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
@ -213,7 +213,7 @@ RSpec.describe Validations::FinancialValidations do
record.ecstat1 = 1
financial_validator.validate_net_income(record)
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
@ -913,15 +913,15 @@ RSpec.describe Validations::FinancialValidations do
record.is_carehome = 1
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
record.period = 1
record.chcharge = 1001
financial_validator.validate_care_home_charges(record)
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"])
.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
it "validates charge when period is monthly" do
@ -929,9 +929,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 4334
financial_validator.validate_care_home_charges(record)
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"])
.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
it "validates charge when period is every 2 weeks" do
@ -939,9 +939,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 2001
financial_validator.validate_care_home_charges(record)
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"])
.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
it "validates charge when period is every 4 weeks" do
@ -949,13 +949,13 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 4001
financial_validator.validate_care_home_charges(record)
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"])
.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
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
record.period = 1
record.chcharge = 999
@ -1007,9 +1007,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 9
financial_validator.validate_care_home_charges(record)
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"])
.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
it "validates charge when period is monthly" do
@ -1017,9 +1017,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 42
financial_validator.validate_care_home_charges(record)
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"])
.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
it "validates charge when period is every 2 weeks" do
@ -1027,9 +1027,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 19
financial_validator.validate_care_home_charges(record)
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"])
.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
it "validates charge when period is every 4 weeks" do
@ -1037,9 +1037,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 39
financial_validator.validate_care_home_charges(record)
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"])
.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

16
spec/models/validations/household_validations_spec.rb

@ -637,4 +637,20 @@ RSpec.describe Validations::HouseholdValidations do
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

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
record.grant = 3_000
sale_information_validator.validate_discounted_ownership_value(record)
expect(record.errors[:mortgage]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00"))
expect(record.errors[:deposit]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00"))
expect(record.errors[:grant]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00"))
expect(record.errors[:value]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00"))
expect(record.errors[:discount]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00"))
expected_message = ["Mortgage, deposit, and grant total must equal £30,000.00"]
expect(record.errors[:mortgage]).to eq(expected_message)
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
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
record.discount = 10
sale_information_validator.validate_discounted_ownership_value(record)
expect(record.errors[:mortgage]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "27000.00"))
expect(record.errors[:deposit]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "27000.00"))
expect(record.errors[:grant]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "27000.00"))
expect(record.errors[:value]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "27000.00"))
expect(record.errors[:discount]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "27000.00"))
expected_message = ["Mortgage, deposit, and grant total must equal £27,000.00"]
expect(record.errors[:mortgage]).to eq(expected_message)
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
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
record.deposit = 2_000
sale_information_validator.validate_discounted_ownership_value(record)
expect(record.errors[:mortgage]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00"))
expect(record.errors[:deposit]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00"))
expect(record.errors[:grant]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00"))
expect(record.errors[:value]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00"))
expect(record.errors[:discount]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "30000.00"))
expected_message = ["Mortgage, deposit, and grant total must equal £30,000.00"]
expect(record.errors[:mortgage]).to eq(expected_message)
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
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
record.mortgage = 10
sale_information_validator.validate_discounted_ownership_value(record)
expect(record.errors[:mortgage]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00"))
expect(record.errors[:deposit]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00"))
expect(record.errors[:grant]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00"))
expect(record.errors[:value]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00"))
expect(record.errors[:discount]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00"))
expected_message = ["Mortgage, deposit, and grant total must equal £18,000.00"]
expect(record.errors[:mortgage]).to eq(expected_message)
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
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
sale_information_validator.validate_discounted_ownership_value(record)
expect(record.errors[:mortgage]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00"))
expect(record.errors[:deposit]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00"))
expect(record.errors[:grant]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00"))
expect(record.errors[:value]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00"))
expect(record.errors[:discount]).to include(I18n.t("validations.sale_information.discounted_ownership_value", value_with_discount: "18000.00"))
expected_message = ["Mortgage, deposit, and grant total must equal £18,000.00"]
expect(record.errors[:mortgage]).to eq(expected_message)
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
it "does not add an error if mortgage, grant and deposit total equals market value - discount" do

55
spec/requests/form_controller_spec.rb

@ -15,7 +15,7 @@ RSpec.describe FormController, type: :request do
let(:setup_complete_lettings_log) do
create(
:lettings_log,
:about_completed,
:setup_completed,
status: 1,
created_by: user,
)
@ -57,7 +57,7 @@ RSpec.describe FormController, type: :request do
describe "POST" 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")
end
end
@ -102,7 +102,7 @@ RSpec.describe FormController, type: :request do
end
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")
follow_redirect!
lettings_log.reload
@ -127,7 +127,7 @@ RSpec.describe FormController, type: :request do
end
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")
follow_redirect!
lettings_log.reload
@ -152,7 +152,7 @@ RSpec.describe FormController, type: :request do
end
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")
follow_redirect!
lettings_log.reload
@ -177,7 +177,7 @@ RSpec.describe FormController, type: :request do
end
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")
follow_redirect!
lettings_log.reload
@ -203,7 +203,7 @@ RSpec.describe FormController, type: :request do
context "with form pages" do
context "when forms exist for multiple years" do
let(:lettings_log_year_1) { create(:lettings_log, owning_organisation: organisation, created_by: user) }
let(:lettings_log_year_2) { create(:lettings_log, :about_completed, startdate: Time.zone.local(2022, 5, 1), owning_organisation: organisation, created_by: user) }
let(:lettings_log_year_2) { create(:lettings_log, :setup_completed, startdate: Time.zone.local(2022, 5, 1), owning_organisation: organisation, created_by: user) }
before do
Timecop.freeze(Time.zone.local(2021, 5, 1))
@ -400,22 +400,21 @@ RSpec.describe FormController, type: :request do
end
it "re-renders the same page with errors if validation fails" do
post "/lettings-logs/#{lettings_log.id}/form", params: params
expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}")
follow_redirect!
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params
expect(page).to have_content("There is a problem")
expect(page).to have_content("Error: What is the tenant’s age?")
end
it "resets errors when fixed" do
post "/lettings-logs/#{lettings_log.id}/form", params: params
post "/lettings-logs/#{lettings_log.id}/form", params: valid_params
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: valid_params
get "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}"
expect(page).not_to have_content("There is a problem")
end
it "logs that validation was triggered" do
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
context "when the number of days is too high for the month" do
@ -433,8 +432,7 @@ RSpec.describe FormController, type: :request do
end
it "validates the date correctly" do
post "/lettings-logs/#{lettings_log.id}/form", params: params
follow_redirect!
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params
expect(page).to have_content("There is a problem")
end
end
@ -465,10 +463,9 @@ RSpec.describe FormController, type: :request do
end
it "re-renders the same page with errors if validation fails" do
post "/lettings-logs/#{lettings_log.id}/form", params: params
expect(response).to redirect_to("/lettings-logs/#{lettings_log.id}/managing-organisation")
follow_redirect!
post "/lettings-logs/#{lettings_log.id}/managing-organisation", params: params
expect(page).to have_content("There is a problem")
expect(page).to have_content("Error: Which organisation manages this letting?")
end
end
@ -486,7 +483,7 @@ RSpec.describe FormController, type: :request do
end
before do
post "/lettings-logs/#{lettings_log.id}/form", params:
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params:
end
it "re-renders the same page with errors if validation fails" do
@ -524,7 +521,7 @@ RSpec.describe FormController, type: :request do
before do
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
it "does not require you to answer that question" do
@ -558,14 +555,14 @@ RSpec.describe FormController, type: :request do
end
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
expect(lettings_log.housingneeds_b).to eq(1)
end
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
expect(lettings_log.housingneeds_b).to eq(0)
@ -609,7 +606,7 @@ RSpec.describe FormController, type: :request do
it "updates both question fields" do
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
expect(lettings_log.housingneeds_a).to eq(1)
@ -650,16 +647,16 @@ RSpec.describe FormController, type: :request do
end
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")
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")
end
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}/form", params: lettings_log_form_conditional_question_no_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}/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")
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?
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)
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
context "when yes is answered" do
@ -722,7 +719,7 @@ RSpec.describe FormController, type: :request do
end
before do
post "/lettings-logs/#{unauthorized_lettings_log.id}/form", params: {}
post "/lettings-logs/#{unauthorized_lettings_log.id}/net-income", params: {}
end
it "does not let you post form answers to lettings logs you don't have access to" do

36
spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb

@ -710,8 +710,10 @@ RSpec.describe BulkUpload::Lettings::Year2022::RowParser do
context "when cannot find owning org" do
let(:attributes) { { bulk_upload:, field_111: "donotexist" } }
it "is not permitted" do
expect(parser.errors[:field_111]).to eql(["The owning organisation code is incorrect"])
it "is not permitted as a setup error" do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
expect(setup_errors.find { |e| e.attribute == :field_111 }.message).to eql("The owning organisation code is incorrect")
end
it "blocks log creation" do
@ -724,8 +726,10 @@ RSpec.describe BulkUpload::Lettings::Year2022::RowParser do
let(:attributes) { { bulk_upload:, field_111: owning_org.old_visible_id } }
it "is not permitted" do
expect(parser.errors[:field_111]).to eql(["The owning organisation code provided is for an organisation that does not own stock"])
it "is not permitted as a setup error" do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
expect(setup_errors.find { |e| e.attribute == :field_111 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock")
end
it "blocks log creation" do
@ -738,8 +742,10 @@ RSpec.describe BulkUpload::Lettings::Year2022::RowParser do
let(:attributes) { { bulk_upload:, field_111: unaffiliated_org.old_visible_id } }
it "is not permitted" do
expect(parser.errors[:field_111]).to eql(["You do not have permission to add logs for this owning organisation"])
it "is not permitted as setup error" do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
expect(setup_errors.find { |e| e.attribute == :field_111 }.message).to eql("You do not have permission to add logs for this owning organisation")
end
it "blocks log creation" do
@ -804,8 +810,10 @@ RSpec.describe BulkUpload::Lettings::Year2022::RowParser do
context "when blank" do
let(:attributes) { { bulk_upload:, field_113: "" } }
it "is not permitted" do
expect(parser.errors[:field_113]).to eql(["The managing organisation code is incorrect"])
it "is not permitted as setup error" do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
expect(setup_errors.find { |e| e.attribute == :field_113 }.message).to eql("The managing organisation code is incorrect")
end
it "blocks log creation" do
@ -816,8 +824,10 @@ RSpec.describe BulkUpload::Lettings::Year2022::RowParser do
context "when cannot find managing org" do
let(:attributes) { { bulk_upload:, field_113: "donotexist" } }
it "is not permitted" do
expect(parser.errors[:field_113]).to eql(["The managing organisation code is incorrect"])
it "is not permitted as setup error" do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
expect(setup_errors.find { |e| e.attribute == :field_113 }.message).to eql("The managing organisation code is incorrect")
end
it "blocks log creation" do
@ -830,8 +840,10 @@ RSpec.describe BulkUpload::Lettings::Year2022::RowParser do
let(:attributes) { { bulk_upload:, field_111: owning_org.old_visible_id, field_113: unaffiliated_org.old_visible_id } }
it "is not permitted" do
expect(parser.errors[:field_113]).to eql(["This managing organisation does not have a relationship with the owning organisation"])
it "is not permitted as setup error" do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
expect(setup_errors.find { |e| e.attribute == :field_113 }.message).to eql("This managing organisation does not have a relationship with the owning organisation")
end
it "blocks log creation" do

42
spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb

@ -674,8 +674,10 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
context "when blank" do
let(:attributes) { { bulk_upload:, field_1: "" } }
it "is not permitted" do
expect(parser.errors[:field_1]).to eql(["The owning organisation code is incorrect"])
it "is not permitted as setup error" do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code is incorrect")
end
it "blocks log creation" do
@ -686,8 +688,10 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
context "when cannot find owning org" do
let(:attributes) { { bulk_upload:, field_1: "donotexist" } }
it "is not permitted" do
expect(parser.errors[:field_1]).to eql(["The owning organisation code is incorrect"])
it "is not permitted as setup error" do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code is incorrect")
end
it "blocks log creation" do
@ -700,8 +704,10 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
let(:attributes) { { bulk_upload:, field_1: owning_org.old_visible_id } }
it "is not permitted" do
expect(parser.errors[:field_1]).to eql(["The owning organisation code provided is for an organisation that does not own stock"])
it "is not permitted as setup error" do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock")
end
it "blocks log creation" do
@ -714,8 +720,10 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
let(:attributes) { { bulk_upload:, field_1: unaffiliated_org.old_visible_id } }
it "is not permitted" do
expect(parser.errors[:field_1]).to eql(["You do not have permission to add logs for this owning organisation"])
it "is not permitted as setup error" do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("You do not have permission to add logs for this owning organisation")
end
it "blocks log creation" do
@ -728,8 +736,10 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
context "when blank" do
let(:attributes) { { bulk_upload:, field_2: "" } }
it "is not permitted" do
expect(parser.errors[:field_2]).to eql(["The managing organisation code is incorrect"])
it "is not permitted as setup error" do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("The managing organisation code is incorrect")
end
it "blocks log creation" do
@ -740,8 +750,10 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
context "when cannot find managing org" do
let(:attributes) { { bulk_upload:, field_2: "donotexist" } }
it "is not permitted" do
expect(parser.errors[:field_2]).to eql(["The managing organisation code is incorrect"])
it "is not permitted as setup error" do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("The managing organisation code is incorrect")
end
it "blocks log creation" do
@ -754,8 +766,10 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
let(:attributes) { { bulk_upload:, field_1: owning_org.old_visible_id, field_2: unaffiliated_org.old_visible_id } }
it "is not permitted" do
expect(parser.errors[:field_2]).to eql(["This managing organisation does not have a relationship with the owning organisation"])
it "is not permitted as setup error" do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This managing organisation does not have a relationship with the owning organisation")
end
it "blocks log creation" do

28
spec/services/exports/lettings_log_export_service_spec.rb

@ -217,7 +217,7 @@ RSpec.describe Exports::LettingsLogExportService do
it "creates a logs export record in a database with correct time" do
expect { export_service.export_xml_lettings_logs }
.to change(LogsExport, :count).by(1)
.to change(LogsExport, :count).by(3)
expect(LogsExport.last.started_at).to eq(start_time)
end
@ -294,32 +294,6 @@ RSpec.describe Exports::LettingsLogExportService do
expect(LogsExport.last.increment_number).to eq(1)
end
end
context "and the export has an error" do
before { allow(storage_service).to receive(:write_file).and_raise(StandardError.new("This is an exception")) }
it "does not save a record in the database" do
expect { export_service.export_xml_lettings_logs }
.to raise_error(StandardError)
.and(change(LogsExport, :count).by(0))
end
end
end
context "when exporting a general needs lettings logs in CSV" do
let(:csv_export_file) { File.open("spec/fixtures/exports/general_needs_log.csv", "r:UTF-8") }
let(:expected_csv_filename) { "export_2022_05_01.csv" }
let(:lettings_log) { FactoryBot.create(:lettings_log, :completed, propcode: "123", ppostcode_full: "SE2 6RT", postcode_full: "NW1 5TY", tenancycode: "BZ737", startdate: Time.zone.local(2022, 2, 2, 10, 36, 49), voiddate: Time.zone.local(2019, 11, 3), mrcdate: Time.zone.local(2020, 5, 5, 10, 36, 49), tenancylength: 5, underoccupation_benefitcap: 4) }
it "generates an CSV export file with the expected content" do
expected_content = replace_entity_ids(lettings_log, csv_export_file.read)
expect(storage_service).to receive(:write_file).with(expected_csv_filename, any_args) do |_, content|
expect(content).not_to be_nil
expect(content.read).to eq(expected_content)
end
export_service.export_csv_lettings_logs
end
end
context "when exporting a supporting housing lettings logs in XML" do

8
spec/services/imports/lettings_logs_import_service_spec.rb

@ -223,8 +223,8 @@ RSpec.describe Imports::LettingsLogsImportService do
end
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 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 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.00 per week given the tenant’s working situation/)
expect { lettings_log_service.send(:create_log, lettings_log_xml) }
.not_to raise_error
end
@ -428,7 +428,7 @@ RSpec.describe Imports::LettingsLogsImportService do
end
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) }
.not_to raise_error
end
@ -762,7 +762,7 @@ RSpec.describe Imports::LettingsLogsImportService do
end
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) }
.not_to raise_error
end

34
spec/services/imports/sales_logs_import_service_spec.rb

@ -1478,6 +1478,40 @@ RSpec.describe Imports::SalesLogsImportService do
expect(sales_log&.jointmore).to eq(3)
end
end
context "when mscharge is 0" do
let(:sales_log_id) { "shared_ownership_sales_log" }
before do
sales_log_xml.at_xpath("//xmlns:Q29MonthlyCharges").content = "0"
allow(logger).to receive(:warn).and_return(nil)
end
it "sets has_mscharge to no" do
sales_log_service.send(:create_log, sales_log_xml)
sales_log = SalesLog.find_by(old_id: sales_log_id)
expect(sales_log&.has_mscharge).to be(0)
expect(sales_log&.mscharge).to be(nil)
end
end
context "when mscharge is more than 0" do
let(:sales_log_id) { "shared_ownership_sales_log" }
before do
sales_log_xml.at_xpath("//xmlns:Q29MonthlyCharges").content = "100"
allow(logger).to receive(:warn).and_return(nil)
end
it "sets has_mscharge to yes" do
sales_log_service.send(:create_log, sales_log_xml)
sales_log = SalesLog.find_by(old_id: sales_log_id)
expect(sales_log&.has_mscharge).to be(1)
expect(sales_log&.mscharge).to eq(100)
end
end
end
end
end

Loading…
Cancel
Save