Browse Source

Merge branch 'main' into 1985-error-message-persistence-bug

pull/1515/head
natdeanlewissoftwire 3 years ago
parent
commit
e3c2977b9b
  1. 4
      app/components/bulk_upload_error_summary_table_component.html.erb
  2. 11
      app/components/bulk_upload_error_summary_table_component.rb
  3. 42
      app/controllers/bulk_upload_lettings_resume_controller.rb
  4. 37
      app/controllers/form_controller.rb
  5. 4
      app/controllers/lettings_logs_controller.rb
  6. 6
      app/controllers/locations_controller.rb
  7. 4
      app/controllers/organisations_controller.rb
  8. 4
      app/controllers/sales_logs_controller.rb
  9. 6
      app/controllers/schemes_controller.rb
  10. 8
      app/helpers/filters_helper.rb
  11. 23
      app/helpers/money_formatting_helper.rb
  12. 2
      app/jobs/email_csv_job.rb
  13. 78
      app/mailers/bulk_upload_mailer.rb
  14. 8
      app/models/bulk_upload.rb
  15. 4
      app/models/form_handler.rb
  16. 30
      app/models/forms/bulk_upload_lettings_resume/confirm.rb
  17. 53
      app/models/forms/bulk_upload_lettings_resume/fix_choice.rb
  18. 17
      app/models/lettings_log.rb
  19. 12
      app/models/location_deactivation_period.rb
  20. 36
      app/models/log.rb
  21. 8
      app/models/organisation.rb
  22. 11
      app/models/sales_log.rb
  23. 10
      app/models/scheme_deactivation_period.rb
  24. 8
      app/models/user.rb
  25. 29
      app/models/validations/financial_validations.rb
  26. 9
      app/models/validations/sales/sale_information_validations.rb
  27. 13
      app/models/validations/shared_validations.rb
  28. 3
      app/services/bulk_upload/lettings/log_creator.rb
  29. 1
      app/services/bulk_upload/lettings/validator.rb
  30. 20
      app/services/bulk_upload/lettings/year2022/row_parser.rb
  31. 20
      app/services/bulk_upload/lettings/year2023/row_parser.rb
  32. 33
      app/services/bulk_upload/processor.rb
  33. 2
      app/services/csv/lettings_log_csv_service.rb
  34. 5
      app/services/exports/lettings_log_export_service.rb
  35. 2
      app/services/imports/lettings_logs_import_service.rb
  36. 1
      app/views/bulk_upload_lettings_logs/forms/needstype.erb
  37. 4
      app/views/bulk_upload_lettings_results/summary.html.erb
  38. 22
      app/views/bulk_upload_lettings_resume/confirm.html.erb
  39. 36
      app/views/bulk_upload_lettings_resume/fix_choice.html.erb
  40. 3
      app/views/form/_numeric_output_question.html.erb
  41. 11
      app/views/form/_numeric_question.html.erb
  42. 2
      app/views/form/page.html.erb
  43. 6
      app/views/locations/toggle_active.html.erb
  44. 6
      app/views/schemes/toggle_active.html.erb
  45. 4
      config/initializers/feature_toggle.rb
  46. 27
      config/locales/en.yml
  47. 11
      config/routes.rb
  48. 5
      db/migrate/20230331094840_add_status_cache_to_lettings_log.rb
  49. 3
      db/schema.rb
  50. 27
      spec/components/bulk_upload_error_summary_table_component_spec.rb
  51. 4
      spec/features/form/page_routing_spec.rb
  52. 4
      spec/features/schemes_spec.rb
  53. 4
      spec/fixtures/files/lettings_logs_download.csv
  54. 4
      spec/fixtures/files/lettings_logs_download_codes_only.csv
  55. 41
      spec/helpers/money_formatting_helper_spec.rb
  56. 1
      spec/jobs/email_csv_job_spec.rb
  57. 46
      spec/mailers/bulk_upload_mailer_spec.rb
  58. 8
      spec/models/lettings_log_spec.rb
  59. 83
      spec/models/location_deactivation_period_spec.rb
  60. 5
      spec/models/organisation_spec.rb
  61. 2
      spec/models/sales_log_spec.rb
  62. 68
      spec/models/scheme_deactivation_period_spec.rb
  63. 5
      spec/models/user_spec.rb
  64. 40
      spec/models/validations/financial_validations_spec.rb
  65. 63
      spec/models/validations/sales/sale_information_validations_spec.rb
  66. 30
      spec/models/validations/shared_validations_spec.rb
  67. 84
      spec/requests/bulk_upload_lettings_resume_controller_spec.rb
  68. 68
      spec/requests/form_controller_spec.rb
  69. 33
      spec/requests/lettings_logs_controller_spec.rb
  70. 5
      spec/requests/organisations_controller_spec.rb
  71. 16
      spec/requests/sales_logs_controller_spec.rb
  72. 22
      spec/services/bulk_upload/lettings/log_creator_spec.rb
  73. 53
      spec/services/bulk_upload/lettings/validator_spec.rb
  74. 10
      spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb
  75. 34
      spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb
  76. 167
      spec/services/bulk_upload/processor_spec.rb
  77. 1
      spec/services/csv/lettings_log_csv_service_spec.rb
  78. 29
      spec/services/exports/lettings_log_export_service_spec.rb
  79. 28
      spec/services/imports/lettings_logs_import_service_spec.rb
  80. 2
      spec/support/bulk_upload/log_to_csv.rb

4
app/components/bulk_upload_error_summary_table_component.html.erb

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

11
app/components/bulk_upload_error_summary_table_component.rb

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

42
app/controllers/bulk_upload_lettings_resume_controller.rb

@ -0,0 +1,42 @@
class BulkUploadLettingsResumeController < ApplicationController
before_action :authenticate_user!
def start
@bulk_upload = current_user.bulk_uploads.find(params[:id])
redirect_to page_bulk_upload_lettings_resume_path(@bulk_upload, page: "fix-choice")
end
def show
@bulk_upload = current_user.bulk_uploads.find(params[:id])
render form.view_path
end
def update
@bulk_upload = current_user.bulk_uploads.find(params[:id])
if form.valid? && form.save!
redirect_to form.next_path
else
render form.view_path
end
end
private
def form
@form ||= case params[:page]
when "fix-choice"
Forms::BulkUploadLettingsResume::FixChoice.new(form_params.merge(bulk_upload: @bulk_upload))
when "confirm"
Forms::BulkUploadLettingsResume::Confirm.new(form_params.merge(bulk_upload: @bulk_upload))
else
raise "invalid form"
end
end
def form_params
params.fetch(:form, {}).permit(:choice)
end
end

37
app/controllers/form_controller.rb

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

4
app/controllers/lettings_logs_controller.rb

@ -15,7 +15,7 @@ class LettingsLogsController < LogsController
def index def index
respond_to do |format| respond_to do |format|
format.html do format.html do
all_logs = current_user.lettings_logs all_logs = current_user.lettings_logs.visible
unpaginated_filtered_logs = filtered_logs(all_logs, search_term, @session_filters) unpaginated_filtered_logs = filtered_logs(all_logs, search_term, @session_filters)
@search_term = search_term @search_term = search_term
@ -140,7 +140,7 @@ private
end end
def find_resource def find_resource
@log = LettingsLog.find_by(id: params[:id]) @log = LettingsLog.visible.find_by(id: params[:id])
end end
def post_create_redirect_url(log) def post_create_redirect_url(log)

6
app/controllers/locations_controller.rb

@ -158,7 +158,7 @@ class LocationsController < ApplicationController
end end
def deactivate_confirm def deactivate_confirm
@affected_logs = @location.lettings_logs.filter_by_before_startdate(params[:deactivation_date]) @affected_logs = @location.lettings_logs.visible.filter_by_before_startdate(params[:deactivation_date])
if @affected_logs.count.zero? if @affected_logs.count.zero?
deactivate deactivate
else else
@ -260,7 +260,7 @@ private
end end
def reset_location_and_scheme_for_logs! def reset_location_and_scheme_for_logs!
logs = @location.lettings_logs.filter_by_before_startdate(params[:deactivation_date].to_time) logs = @location.lettings_logs.visible.filter_by_before_startdate(params[:deactivation_date].to_time)
logs.update!(location: nil, scheme: nil, unresolved: true) logs.update!(location: nil, scheme: nil, unresolved: true)
logs logs
end end
@ -269,7 +269,7 @@ private
if params[:location_deactivation_period].blank? if params[:location_deactivation_period].blank?
return return
elsif params[:location_deactivation_period]["#{key}_type".to_sym] == "default" elsif params[:location_deactivation_period]["#{key}_type".to_sym] == "default"
return FormHandler.instance.current_collection_start_date return FormHandler.instance.start_date_of_earliest_open_collection_period
elsif params[:location_deactivation_period][key.to_sym].present? elsif params[:location_deactivation_period][key.to_sym].present?
return params[:location_deactivation_period][key.to_sym] return params[:location_deactivation_period][key.to_sym]
end end

4
app/controllers/organisations_controller.rb

@ -90,7 +90,7 @@ class OrganisationsController < ApplicationController
end end
def lettings_logs def lettings_logs
organisation_logs = LettingsLog.where(owning_organisation_id: @organisation.id) organisation_logs = LettingsLog.visible.where(owning_organisation_id: @organisation.id)
unpaginated_filtered_logs = filtered_logs(organisation_logs, search_term, @session_filters) unpaginated_filtered_logs = filtered_logs(organisation_logs, search_term, @session_filters)
respond_to do |format| respond_to do |format|
@ -105,7 +105,7 @@ class OrganisationsController < ApplicationController
end end
def download_csv def download_csv
organisation_logs = LettingsLog.all.where(owning_organisation_id: @organisation.id) organisation_logs = LettingsLog.visible.where(owning_organisation_id: @organisation.id)
unpaginated_filtered_logs = filtered_logs(organisation_logs, search_term, @session_filters) unpaginated_filtered_logs = filtered_logs(organisation_logs, search_term, @session_filters)
codes_only = params.require(:codes_only) == "true" codes_only = params.require(:codes_only) == "true"

4
app/controllers/sales_logs_controller.rb

@ -9,7 +9,7 @@ class SalesLogsController < LogsController
def index def index
respond_to do |format| respond_to do |format|
format.html do format.html do
all_logs = current_user.sales_logs all_logs = current_user.sales_logs.visible
unpaginated_filtered_logs = filtered_logs(all_logs, search_term, @session_filters) unpaginated_filtered_logs = filtered_logs(all_logs, search_term, @session_filters)
@search_term = search_term @search_term = search_term
@ -28,7 +28,7 @@ class SalesLogsController < LogsController
end end
def edit def edit
@log = current_user.sales_logs.find_by(id: params[:id]) @log = current_user.sales_logs.visible.find_by(id: params[:id])
if @log if @log
render "logs/edit", locals: { current_user: } render "logs/edit", locals: { current_user: }
else else

6
app/controllers/schemes_controller.rb

@ -39,7 +39,7 @@ class SchemesController < ApplicationController
end end
def deactivate_confirm def deactivate_confirm
@affected_logs = @scheme.lettings_logs.filter_by_before_startdate(params[:deactivation_date]) @affected_logs = @scheme.lettings_logs.visible.filter_by_before_startdate(params[:deactivation_date])
if @affected_logs.count.zero? if @affected_logs.count.zero?
deactivate deactivate
else else
@ -296,7 +296,7 @@ private
if params[:scheme_deactivation_period].blank? if params[:scheme_deactivation_period].blank?
return return
elsif params[:scheme_deactivation_period]["#{key}_type".to_sym] == "default" elsif params[:scheme_deactivation_period]["#{key}_type".to_sym] == "default"
return FormHandler.instance.current_collection_start_date return FormHandler.instance.start_date_of_earliest_open_collection_period
elsif params[:scheme_deactivation_period][key.to_sym].present? elsif params[:scheme_deactivation_period][key.to_sym].present?
return params[:scheme_deactivation_period][key.to_sym] return params[:scheme_deactivation_period][key.to_sym]
end end
@ -310,7 +310,7 @@ private
end end
def reset_location_and_scheme_for_logs! def reset_location_and_scheme_for_logs!
logs = @scheme.lettings_logs.filter_by_before_startdate(params[:deactivation_date].to_time) logs = @scheme.lettings_logs.visible.filter_by_before_startdate(params[:deactivation_date].to_time)
logs.update!(location: nil, scheme: nil, unresolved: true) logs.update!(location: nil, scheme: nil, unresolved: true)
logs logs
end end

8
app/helpers/filters_helper.rb

@ -12,9 +12,11 @@ module FiltersHelper
end end
def status_filters def status_filters
statuses = {} {
LettingsLog.statuses.keys.map { |status| statuses[status] = status.humanize } "not_started" => "Not started",
statuses "in_progress" => "In progress",
"completed" => "Completed",
}.freeze
end end
def selected_option(filter) def selected_option(filter)

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/jobs/email_csv_job.rb

@ -6,7 +6,7 @@ class EmailCsvJob < ApplicationJob
EXPIRATION_TIME = 3.hours.to_i EXPIRATION_TIME = 3.hours.to_i
def perform(user, search_term = nil, filters = {}, all_orgs = false, organisation = nil, codes_only_export = false) # rubocop:disable Style/OptionalBooleanParameter - sidekiq can't serialise named params def perform(user, search_term = nil, filters = {}, all_orgs = false, organisation = nil, codes_only_export = false) # rubocop:disable Style/OptionalBooleanParameter - sidekiq can't serialise named params
unfiltered_logs = organisation.present? && user.support? ? LettingsLog.where(owning_organisation_id: organisation.id) : user.lettings_logs unfiltered_logs = organisation.present? && user.support? ? LettingsLog.visible.where(owning_organisation_id: organisation.id) : user.lettings_logs.visible
filtered_logs = FilterService.filter_logs(unfiltered_logs, search_term, filters, all_orgs, user) filtered_logs = FilterService.filter_logs(unfiltered_logs, search_term, filters, all_orgs, user)
filename = organisation.present? ? "logs-#{organisation.name}-#{Time.zone.now}.csv" : "logs-#{Time.zone.now}.csv" filename = organisation.present? ? "logs-#{organisation.name}-#{Time.zone.now}.csv" : "logs-#{Time.zone.now}.csv"

78
app/mailers/bulk_upload_mailer.rb

@ -1,11 +1,29 @@
class BulkUploadMailer < NotifyMailer class BulkUploadMailer < NotifyMailer
include ActionView::Helpers::TextHelper include ActionView::Helpers::TextHelper
BULK_UPLOAD_COMPLETE_TEMPLATE_ID = "83279578-c890-4168-838b-33c9cf0dc9f0".freeze COMPLETE_TEMPLATE_ID = "83279578-c890-4168-838b-33c9cf0dc9f0".freeze
BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID = "e27abcd4-5295-48c2-b127-e9ee4b781b75".freeze FAILED_CSV_ERRORS_TEMPLATE_ID = "e27abcd4-5295-48c2-b127-e9ee4b781b75".freeze
BULK_UPLOAD_FAILED_FILE_SETUP_ERROR_TEMPLATE_ID = "24c9f4c7-96ad-470a-ba31-eb51b7cbafd9".freeze FAILED_FILE_SETUP_ERROR_TEMPLATE_ID = "24c9f4c7-96ad-470a-ba31-eb51b7cbafd9".freeze
BULK_UPLOAD_FAILED_SERVICE_ERROR_TEMPLATE_ID = "c3f6288c-7a74-4e77-99ee-6c4a0f6e125a".freeze FAILED_SERVICE_ERROR_TEMPLATE_ID = "c3f6288c-7a74-4e77-99ee-6c4a0f6e125a".freeze
BULK_UPLOAD_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:)
title = "We found #{pluralize(bulk_upload.bulk_upload_errors.count, 'error')} in your bulk upload"
description = "There was a problem with your #{bulk_upload.year_combo} #{bulk_upload.log_type} data. Check the error report below to fix these errors."
cta_link = start_bulk_upload_lettings_resume_url(bulk_upload)
send_email(
bulk_upload.user.email,
HOW_FIX_UPLOAD_TEMPLATE_ID,
{
title:,
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
description:,
cta_link:,
},
)
end
def send_bulk_upload_complete_mail(user:, bulk_upload:) def send_bulk_upload_complete_mail(user:, bulk_upload:)
url = if bulk_upload.lettings? url = if bulk_upload.lettings?
@ -22,7 +40,7 @@ class BulkUploadMailer < NotifyMailer
send_email( send_email(
user.email, user.email,
BULK_UPLOAD_COMPLETE_TEMPLATE_ID, COMPLETE_TEMPLATE_ID,
{ {
title:, title:,
filename: bulk_upload.filename, filename: bulk_upload.filename,
@ -42,7 +60,7 @@ class BulkUploadMailer < NotifyMailer
send_email( send_email(
bulk_upload.user.email, bulk_upload.user.email,
BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID, FAILED_CSV_ERRORS_TEMPLATE_ID,
{ {
filename: bulk_upload.filename, filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
@ -54,34 +72,20 @@ class BulkUploadMailer < NotifyMailer
end end
def send_bulk_upload_failed_file_setup_error_mail(bulk_upload:) def send_bulk_upload_failed_file_setup_error_mail(bulk_upload:)
bulk_upload_link = if bulk_upload.lettings? bulk_upload_link = if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors?
start_bulk_upload_lettings_logs_url summary_bulk_upload_lettings_result_url(bulk_upload)
else else
start_bulk_upload_sales_logs_url bulk_upload_lettings_result_url(bulk_upload)
end
row_parser_class = bulk_upload.prefix_namespace::RowParser
errors = bulk_upload
.bulk_upload_errors
.where(category: "setup")
.group(:col, :field)
.count
.keys
.sort_by { |_col, field| field }
.map do |col, field|
"- #{row_parser_class.question_for_field(field.to_sym)} (Column #{col})"
end end
send_email( send_email(
bulk_upload.user.email, bulk_upload.user.email,
BULK_UPLOAD_FAILED_FILE_SETUP_ERROR_TEMPLATE_ID, FAILED_FILE_SETUP_ERROR_TEMPLATE_ID,
{ {
filename: bulk_upload.filename, filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
lettings_or_sales: bulk_upload.log_type, lettings_or_sales: bulk_upload.log_type,
year_combo: bulk_upload.year_combo, year_combo: bulk_upload.year_combo,
errors_list: errors.join("\n"),
bulk_upload_link:, bulk_upload_link:,
}, },
) )
@ -96,7 +100,7 @@ class BulkUploadMailer < NotifyMailer
send_email( send_email(
bulk_upload.user.email, bulk_upload.user.email,
BULK_UPLOAD_FAILED_SERVICE_ERROR_TEMPLATE_ID, FAILED_SERVICE_ERROR_TEMPLATE_ID,
{ {
filename: bulk_upload.filename, filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
@ -107,26 +111,4 @@ class BulkUploadMailer < NotifyMailer
}, },
) )
end end
def send_bulk_upload_with_errors_mail(bulk_upload:)
count = bulk_upload.logs.where.not(status: %w[completed]).count
n_logs = pluralize(count, "log")
title = "We found #{n_logs} with errors"
error_description = "We created logs from your #{bulk_upload.year_combo} #{bulk_upload.log_type} data. There was a problem with #{count} of the logs. Click the below link to fix these logs."
send_email(
bulk_upload.user.email,
BULK_UPLOAD_WITH_ERRORS_TEMPLATE_ID,
{
title:,
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
error_description:,
summary_report_link: resume_bulk_upload_lettings_result_url(bulk_upload),
},
)
end
end end

8
app/models/bulk_upload.rb

@ -60,6 +60,14 @@ class BulkUpload < ApplicationRecord
"BulkUpload::#{type_class}::#{year_class}".constantize "BulkUpload::#{type_class}::#{year_class}".constantize
end end
def unpend
logs.find_each do |log|
log.skip_update_status = true
log.status = log.status_cache
log.save!
end
end
private private
def generate_identifier def generate_identifier

4
app/models/form_handler.rb

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

30
app/models/forms/bulk_upload_lettings_resume/confirm.rb

@ -0,0 +1,30 @@
module Forms
module BulkUploadLettingsResume
class Confirm
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :bulk_upload
def view_path
"bulk_upload_lettings_resume/confirm"
end
def back_path
page_bulk_upload_lettings_resume_path(bulk_upload, page: "fix-choice")
end
def next_path
resume_bulk_upload_lettings_result_path(bulk_upload)
end
def save!
processor = BulkUpload::Processor.new(bulk_upload:)
processor.approve
true
end
end
end
end

53
app/models/forms/bulk_upload_lettings_resume/fix_choice.rb

@ -0,0 +1,53 @@
module Forms
module BulkUploadLettingsResume
class FixChoice
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :bulk_upload
attribute :choice, :string
validates :choice, presence: true,
inclusion: { in: %w[create-fix-inline upload-again] }
def options
[
OpenStruct.new(id: "create-fix-inline", name: "Upload these logs and fix errors on CORE site"),
OpenStruct.new(id: "upload-again", name: "Fix errors in the CSV and re-upload"),
]
end
def view_path
"bulk_upload_lettings_resume/fix_choice"
end
def next_path
case choice
when "create-fix-inline"
page_bulk_upload_lettings_resume_path(bulk_upload, page: "confirm")
when "upload-again"
if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors?
summary_bulk_upload_lettings_result_path(bulk_upload)
else
bulk_upload_lettings_result_path(bulk_upload)
end
else
raise "invalid choice"
end
end
def recommendation
if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors?
"For this many errors we recommend to fix errors in the CSV and re-upload as you may be able to edit many fields at once in a CSV."
else
"For this many errors we recommend to upload logs and fix errors on site as you can easily see the questions and select the appropriate answer."
end
end
def save!
true
end
end
end
end

17
app/models/lettings_log.rb

@ -20,6 +20,7 @@ class LettingsLog < Log
include DerivedVariables::LettingsLogVariables include DerivedVariables::LettingsLogVariables
include Validations::DateValidations include Validations::DateValidations
include Validations::FinancialValidations include Validations::FinancialValidations
include MoneyFormattingHelper
has_paper_trail has_paper_trail
@ -115,18 +116,6 @@ class LettingsLog < Log
end end
end end
def completed?
status == "completed"
end
def not_started?
status == "not_started"
end
def in_progress?
status == "in_progress"
end
def weekly_net_income def weekly_net_income
return unless earnings && incfreq return unless earnings && incfreq
@ -149,7 +138,7 @@ class LettingsLog < Log
def weekly_to_value_per_period(field_value) def weekly_to_value_per_period(field_value)
num_of_weeks = NUM_OF_WEEKS_FROM_PERIOD[period] num_of_weeks = NUM_OF_WEEKS_FROM_PERIOD[period]
((field_value * 52) / num_of_weeks).round(2) format_as_currency((field_value * 52) / num_of_weeks)
end end
def applicable_income_range def applicable_income_range
@ -650,7 +639,7 @@ private
num_of_weeks = NUM_OF_WEEKS_FROM_PERIOD[period] num_of_weeks = NUM_OF_WEEKS_FROM_PERIOD[period]
return "" unless value && num_of_weeks return "" unless value && num_of_weeks
(value * 52 / num_of_weeks).round(2) format_as_currency((value * 52 / num_of_weeks))
end end
def fully_wheelchair_accessible? def fully_wheelchair_accessible?

12
app/models/location_deactivation_period.rb

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

36
app/models/log.rb

@ -8,8 +8,16 @@ class Log < ApplicationRecord
before_save :update_status! before_save :update_status!
STATUS = { "not_started" => 0, "in_progress" => 1, "completed" => 2 }.freeze STATUS = {
"not_started" => 0,
"in_progress" => 1,
"completed" => 2,
"pending" => 3,
}.freeze
enum status: STATUS enum status: STATUS
enum status_cache: STATUS, _prefix: true
scope :visible, -> { where(status: %w[not_started in_progress completed]) }
scope :filter_by_status, ->(status, _user = nil) { where status: } scope :filter_by_status, ->(status, _user = nil) { where status: }
scope :filter_by_years, lambda { |years, _user = nil| scope :filter_by_years, lambda { |years, _user = nil|
@ -31,6 +39,8 @@ class Log < ApplicationRecord
} }
scope :created_by, ->(user) { where(created_by: user) } scope :created_by, ->(user) { where(created_by: user) }
attr_accessor :skip_update_status
def process_uprn_change! def process_uprn_change!
if uprn.present? if uprn.present?
service = UprnClient.new(uprn) service = UprnClient.new(uprn)
@ -106,6 +116,16 @@ class Log < ApplicationRecord
end end
end end
def calculate_status
if all_fields_completed? && errors.empty?
"completed"
elsif all_fields_nil?
"not_started"
else
"in_progress"
end
end
private private
def plural_gender_for_person(person_num) def plural_gender_for_person(person_num)
@ -120,13 +140,9 @@ private
end end
def update_status! def update_status!
self.status = if all_fields_completed? && errors.empty? return if skip_update_status
"completed"
elsif all_fields_nil? self.status = calculate_status
"not_started"
else
"in_progress"
end
end end
def all_fields_completed? def all_fields_completed?
@ -201,8 +217,4 @@ private
self[is_inferred_key] = false self[is_inferred_key] = false
self[postcode_key] = nil self[postcode_key] = nil
end end
def format_as_currency(num_string)
ActionController::Base.helpers.number_to_currency(num_string, unit: "£")
end
end end

8
app/models/organisation.rb

@ -70,14 +70,6 @@ class Organisation < ApplicationRecord
SalesLog.filter_by_organisation(self) SalesLog.filter_by_organisation(self)
end end
def completed_lettings_logs
lettings_logs.completed
end
def not_completed_lettings_logs
lettings_logs.not_completed
end
def address_string def address_string
%i[address_line1 address_line2 postcode].map { |field| public_send(field) }.join("\n") %i[address_line1 address_line2 postcode].map { |field| public_send(field) }.join("\n")
end end

11
app/models/sales_log.rb

@ -18,6 +18,7 @@ class SalesLog < Log
include DerivedVariables::SalesLogVariables include DerivedVariables::SalesLogVariables
include Validations::Sales::SoftValidations include Validations::Sales::SoftValidations
include Validations::SoftValidations include Validations::SoftValidations
include MoneyFormattingHelper
self.inheritance_column = :_type_disabled self.inheritance_column = :_type_disabled
@ -104,14 +105,6 @@ class SalesLog < Log
collection_start_year < 2023 collection_start_year < 2023
end end
def not_started?
status == "not_started"
end
def completed?
status == "completed"
end
def setup_completed? def setup_completed?
form.setup_sections.all? { |sections| sections.subsections.all? { |subsection| subsection.status(self) == :completed } } form.setup_sections.all? { |sections| sections.subsections.all? { |subsection| subsection.status(self) == :completed } }
end end
@ -223,7 +216,7 @@ class SalesLog < Log
def expected_shared_ownership_deposit_value def expected_shared_ownership_deposit_value
return unless value && equity return unless value && equity
(value * equity / 100).round(2) format_as_currency(value * equity / 100)
end end
def process_postcode(postcode, postcode_known_key, la_inferred_key, la_key) def process_postcode(postcode, postcode_known_key, la_inferred_key, la_key)

10
app/models/scheme_deactivation_period.rb

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

8
app/models/user.rb

@ -69,14 +69,6 @@ class User < ApplicationRecord
end end
end end
def completed_lettings_logs
lettings_logs.completed
end
def not_completed_lettings_logs
lettings_logs.not_completed
end
def is_key_contact? def is_key_contact?
is_key_contact is_key_contact
end end

29
app/models/validations/financial_validations.rb

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

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

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

13
app/models/validations/shared_validations.rb

@ -77,19 +77,6 @@ module Validations::SharedValidations
{ scope: status, date: date&.to_formatted_s(:govuk_date), deactivation_date: closest_reactivation&.deactivation_date&.to_formatted_s(:govuk_date) } { scope: status, date: date&.to_formatted_s(:govuk_date), deactivation_date: closest_reactivation&.deactivation_date&.to_formatted_s(:govuk_date) }
end end
def validate_valid_radio_option(record)
return unless FeatureToggle.validate_valid_radio_options?
record.attributes.each do |question_id, _v|
question = record.form.get_question(question_id, record)
next unless question&.type == "radio"
next unless record[question_id].present? && !question.answer_options.key?(record[question_id].to_s) && question.page.routed_to?(record, nil)
record.errors.add(question_id, I18n.t("validations.invalid_option", question: question.check_answer_label&.downcase))
end
end
def shared_validate_partner_count(record, max_people) def shared_validate_partner_count(record, max_people)
partner_numbers = (2..max_people).select { |n| person_is_partner?(record["relat#{n}"]) } partner_numbers = (2..max_people).select { |n| person_is_partner?(record["relat#{n}"]) }
if partner_numbers.count > 1 if partner_numbers.count > 1

3
app/services/bulk_upload/lettings/log_creator.rb

@ -14,6 +14,9 @@ class BulkUpload::Lettings::LogCreator
row_parser.log.blank_invalid_non_setup_fields! row_parser.log.blank_invalid_non_setup_fields!
row_parser.log.bulk_upload = bulk_upload row_parser.log.bulk_upload = bulk_upload
row_parser.log.skip_update_status = true
row_parser.log.status = "pending"
row_parser.log.status_cache = row_parser.log.calculate_status
begin begin
row_parser.log.save! row_parser.log.save!

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

@ -42,7 +42,6 @@ class BulkUpload::Lettings::Validator
def create_logs? def create_logs?
return false if any_setup_errors? return false if any_setup_errors?
return false if over_column_error_threshold?
return false if row_parsers.any?(&:block_log_creation?) return false if row_parsers.any?(&:block_log_creation?)
row_parsers.all? { |row_parser| row_parser.log.valid? } row_parsers.all? { |row_parser| row_parser.log.valid? }

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

@ -329,6 +329,8 @@ class BulkUpload::Lettings::Year2022::RowParser
validate :validate_created_by_related validate :validate_created_by_related
validate :validate_rent_type validate :validate_rent_type
validate :validate_valid_radio_option
def self.question_for_field(field) def self.question_for_field(field)
QUESTIONS[field] QUESTIONS[field]
end end
@ -386,6 +388,21 @@ class BulkUpload::Lettings::Year2022::RowParser
private private
def validate_valid_radio_option
log.attributes.each do |question_id, _v|
question = log.form.get_question(question_id, log)
next unless question&.type == "radio"
next if log[question_id].blank? || question.answer_options.key?(log[question_id].to_s) || !question.page.routed_to?(log, nil)
fields = field_mapping_for_errors[question_id.to_sym] || []
fields.each do |field|
errors.add(field, I18n.t("validations.invalid_option", question: QUESTIONS[field]))
end
end
end
def validate_created_by_exists def validate_created_by_exists
return if field_112.blank? return if field_112.blank?
@ -772,6 +789,7 @@ private
cbl: %i[field_75], cbl: %i[field_75],
chr: %i[field_76], chr: %i[field_76],
cap: %i[field_77], cap: %i[field_77],
letting_allocation: %i[field_75 field_76 field_77],
referral: %i[field_78], referral: %i[field_78],
@ -1254,7 +1272,7 @@ private
2 2
elsif field_60 == 1 elsif field_60 == 1
3 3
elsif field_59&.zero? elsif field_59.blank? || field_59&.zero?
1 1
end end
end end

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

@ -331,6 +331,8 @@ class BulkUpload::Lettings::Year2023::RowParser
validate :validate_created_by_exists validate :validate_created_by_exists
validate :validate_created_by_related validate :validate_created_by_related
validate :validate_valid_radio_option
def self.question_for_field(field) def self.question_for_field(field)
QUESTIONS[field] QUESTIONS[field]
end end
@ -388,6 +390,21 @@ class BulkUpload::Lettings::Year2023::RowParser
private private
def validate_valid_radio_option
log.attributes.each do |question_id, _v|
question = log.form.get_question(question_id, log)
next unless question&.type == "radio"
next if log[question_id].blank? || question.answer_options.key?(log[question_id].to_s) || !question.page.routed_to?(log, nil)
fields = field_mapping_for_errors[question_id.to_sym] || []
fields.each do |field|
errors.add(field, I18n.t("validations.invalid_option", question: QUESTIONS[field]))
end
end
end
def validate_created_by_exists def validate_created_by_exists
return if field_3.blank? return if field_3.blank?
@ -750,6 +767,7 @@ private
cbl: %i[field_116], cbl: %i[field_116],
chr: %i[field_118], chr: %i[field_118],
cap: %i[field_117], cap: %i[field_117],
letting_allocation: %i[field_116 field_117 field_118],
referral: %i[field_119], referral: %i[field_119],
@ -1177,7 +1195,7 @@ private
2 2
elsif field_88 == 1 elsif field_88 == 1
3 3
elsif field_87&.zero? elsif field_87.blank? || field_87&.zero?
1 1
end end
end end

33
app/services/bulk_upload/processor.rb

@ -16,10 +16,17 @@ class BulkUpload::Processor
send_setup_errors_mail send_setup_errors_mail
elsif validator.create_logs? elsif validator.create_logs?
create_logs create_logs
send_fix_errors_mail if created_logs_but_incompleted?
send_success_mail if created_logs_and_all_completed? if created_logs_but_incompleted?
send_how_fix_upload_mail
end
if created_logs_and_all_completed?
bulk_upload.unpend
send_success_mail
end
else else
send_correct_and_upload_again_mail send_correct_and_upload_again_mail # summary/full report
end end
rescue StandardError => e rescue StandardError => e
Sentry.capture_exception(e) Sentry.capture_exception(e)
@ -28,23 +35,27 @@ class BulkUpload::Processor
downloader.delete_local_file! downloader.delete_local_file!
end end
def approve
bulk_upload.unpend
end
private private
def send_setup_errors_mail def send_how_fix_upload_mail
BulkUploadMailer BulkUploadMailer
.send_bulk_upload_failed_file_setup_error_mail(bulk_upload:) .send_how_fix_upload_mail(bulk_upload:)
.deliver_later .deliver_later
end end
def send_correct_and_upload_again_mail def send_setup_errors_mail
BulkUploadMailer BulkUploadMailer
.send_correct_and_upload_again_mail(bulk_upload:) .send_bulk_upload_failed_file_setup_error_mail(bulk_upload:)
.deliver_later .deliver_later
end end
def send_fix_errors_mail def send_correct_and_upload_again_mail
BulkUploadMailer BulkUploadMailer
.send_bulk_upload_with_errors_mail(bulk_upload:) .send_correct_and_upload_again_mail(bulk_upload:)
.deliver_later .deliver_later
end end
@ -55,11 +66,11 @@ private
end end
def created_logs_but_incompleted? def created_logs_but_incompleted?
validator.create_logs? && bulk_upload.logs.where.not(status: %w[completed]).count.positive? bulk_upload.logs.where.not(status_cache: %w[completed]).count.positive?
end end
def created_logs_and_all_completed? def created_logs_and_all_completed?
validator.create_logs? && bulk_upload.logs.group(:status).count.keys == %w[completed] bulk_upload.logs.group(:status_cache).count.keys == %w[completed]
end end
def send_failure_mail(errors: []) def send_failure_mail(errors: [])

2
app/services/csv/lettings_log_csv_service.rb

@ -1,6 +1,6 @@
module Csv module Csv
class LettingsLogCsvService class LettingsLogCsvService
CSV_FIELDS_TO_OMIT = %w[hhmemb net_income_value_check first_time_property_let_as_social_housing renttype needstype postcode_known is_la_inferred totchild totelder totadult net_income_known is_carehome previous_la_known is_previous_la_inferred age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known letting_allocation_unknown details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 rent_type_detail wrent wscharge wpschrge wsupchrg wtcharge wtshortfall rent_value_check old_form_id old_id retirement_value_check tshortfall_known pregnancy_value_check hhtype new_old vacdays la prevloc unresolved updated_by_id bulk_upload_id uprn_confirmed].freeze CSV_FIELDS_TO_OMIT = %w[hhmemb net_income_value_check first_time_property_let_as_social_housing renttype needstype postcode_known is_la_inferred totchild totelder totadult net_income_known is_carehome previous_la_known is_previous_la_inferred age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known letting_allocation_unknown details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 rent_type_detail wrent wscharge wpschrge wsupchrg wtcharge wtshortfall rent_value_check old_form_id old_id retirement_value_check tshortfall_known pregnancy_value_check hhtype new_old vacdays la prevloc unresolved updated_by_id bulk_upload_id uprn_confirmed status_cache].freeze
def initialize(user, export_type:) def initialize(user, export_type:)
@user = user @user = user

5
app/services/exports/lettings_log_export_service.rb

@ -115,12 +115,13 @@ module Exports
def retrieve_lettings_logs(start_time, full_update) def retrieve_lettings_logs(start_time, full_update)
recent_export = LogsExport.order("started_at").last recent_export = LogsExport.order("started_at").last
if !full_update && recent_export if !full_update && recent_export
params = { from: recent_export.started_at, to: start_time } params = { from: recent_export.started_at, to: start_time }
LettingsLog.where("updated_at >= :from and updated_at <= :to", params) LettingsLog.visible.where("updated_at >= :from and updated_at <= :to", params)
else else
params = { to: start_time } params = { to: start_time }
LettingsLog.where("updated_at <= :to", params) LettingsLog.visible.where("updated_at <= :to", params)
end end
end end

2
app/services/imports/lettings_logs_import_service.rb

@ -313,7 +313,7 @@ module Imports
attribute, _type = error attribute, _type = error
fields.each do |field| fields.each do |field|
@logger.warn("Log #{lettings_log.old_id}: Removing #{field} with error: #{lettings_log.errors[attribute].join(', ')}") @logger.warn("Log #{lettings_log.old_id}: Removing #{field} with error: #{lettings_log.errors[attribute].sort.join(', ')}")
attributes.delete(field) attributes.delete(field)
end end
@logs_overridden << lettings_log.old_id @logs_overridden << lettings_log.old_id

1
app/views/bulk_upload_lettings_logs/forms/needstype.erb

@ -1,6 +1,7 @@
<% content_for :before_content do %> <% content_for :before_content do %>
<%= govuk_back_link href: @form.back_path %> <%= govuk_back_link href: @form.back_path %>
<% end %> <% end %>
<div class="govuk-grid-row"> <div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop"> <div class="govuk-grid-column-two-thirds-from-desktop">
<%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "needstype"), method: :patch do |f| %> <%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "needstype"), method: :patch do |f| %>

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

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

22
app/views/bulk_upload_lettings_resume/confirm.html.erb

@ -0,0 +1,22 @@
<% content_for :before_content do %>
<%= govuk_back_link href: @form.back_path %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<span class="govuk-caption-l">Bulk upload for lettings (<%= @bulk_upload.year_combo %>)</span>
<h1 class="govuk-heading-l">Are you sure you want to upload all logs from this bulk upload?</h1>
<p class="govuk-body">There are <%= pluralize(@bulk_upload.logs.count, "log") %> in this bulk upload with <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> that still need to be fixed after upload.</p>
<%= govuk_warning_text(icon_fallback_text: "Danger") do %>
You can not delete logs once you create them
<% end %>
<%= form_with model: @form, scope: :form, url: page_bulk_upload_lettings_resume_path(@bulk_upload, page: "confirm"), method: :patch do |f| %>
<%= f.govuk_submit %>
<%= govuk_button_link_to "Cancel", @form.back_path, secondary: true %>
<% end %>
</div>
</div>

36
app/views/bulk_upload_lettings_resume/fix_choice.html.erb

@ -0,0 +1,36 @@
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= form_with model: @form, scope: :form, url: page_bulk_upload_lettings_resume_path(@bulk_upload, page: "fix-choice"), method: :patch do |f| %>
<%= f.govuk_error_summary %>
<span class="govuk-caption-l">Bulk upload for lettings (<%= @bulk_upload.year_combo %>)</span>
<h1 class="govuk-heading-l">How would you like to fix <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %>?</h1>
<div class="govuk-body-l">
<%= @bulk_upload.filename %>
</div>
<div class="govuk-body">
<%= @form.recommendation %>
</div>
<%= govuk_details(summary_text: "How to choose between fixing errors on the CORE site or in the CSV") do %>
<p class="govuk-body">When it comes to fixing errors, there are pros and cons to doing it on a CSV versus doing it on a website.</p>
<p class="govuk-body">Fixing errors on a CSV file can be beneficial because it allows you to easily make changes to multiple records at once, and you can use tools like Excel to quickly identify and correct errors. However, if the CSV file is not properly formatted, it can be difficult to identify which records contain errors.</p>
<p class="govuk-body">Fixing errors on a website can be convenient because you can see the data in context and make changes in real-time. However, this approach can be time-consuming if you need to make changes to multiple records, and it may be more difficult to identify errors in a large dataset.</p>
<p class="govuk-body">Ultimately, the best approach will depend on the specific situation and the nature of the errors that need to be fixed.</p>
<% end %>
<%= f.govuk_collection_radio_buttons :choice,
@form.options,
:id,
:name,
legend: { hidden: true } %>
<%= f.govuk_submit %>
<% end %>
</div>
</div>

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

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

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

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

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

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

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

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

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

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

4
config/initializers/feature_toggle.rb

@ -50,10 +50,6 @@ class FeatureToggle
!Rails.env.production? !Rails.env.production?
end end
def self.validate_valid_radio_options?
!(Rails.env.production? || Rails.env.staging?)
end
def self.collection_2023_2024_year_enabled? def self.collection_2023_2024_year_enabled?
true true
end end

27
config/locales/en.yml

@ -68,6 +68,11 @@ en:
attributes: attributes:
needstype: needstype:
blank: You must answer needs type blank: You must answer needs type
forms/bulk_upload_lettings_resume/fix_choice:
attributes:
choice:
blank: You must select how would you like to fix errors
inclusion: You must select one of the following options for how would like to fix errors
activerecord: activerecord:
errors: errors:
@ -243,15 +248,15 @@ en:
benefits: benefits:
part_or_full_time: "Answer cannot be ‘all’ for income from Universal Credit, state pensions or benefits if the tenant or their partner works part-time or full-time" part_or_full_time: "Answer cannot be ‘all’ for income from Universal Credit, state pensions or benefits if the tenant or their partner works part-time or full-time"
earnings: earnings:
over_hard_max: "Net income cannot be greater than £%{hard_max} per week given the tenant’s working situation" over_hard_max: "Net income cannot be greater than %{hard_max} per week given the tenant’s working situation"
under_hard_min: "Net income cannot be less than £%{hard_min} per week given the tenant’s working situation" under_hard_min: "Net income cannot be less than %{hard_min} per week given the tenant’s working situation"
freq_missing: "Select how often the household receives income" freq_missing: "Select how often the household receives income"
earnings_missing: "Enter how much income the household has in total" earnings_missing: "Enter how much income the household has in total"
income: income:
over_hard_max_for_london: "Income must not exceed £90,000 for properties within London local authorities" over_hard_max_for_london: "Income must not exceed £90,000.00 for properties within London local authorities"
over_hard_max_for_outside_london: "Income must not exceed £80,000 for properties outside London local authorities" over_hard_max_for_outside_london: "Income must not exceed £80,000.00 for properties outside London local authorities"
combined_over_hard_max_for_london: "Combined income must not exceed £90,000 for properties within London local authorities" combined_over_hard_max_for_london: "Combined income must not exceed £90,000.00 for properties within London local authorities"
combined_over_hard_max_for_outside_london: "Combined income must not exceed £80,000 for properties outside London local authorities" combined_over_hard_max_for_outside_london: "Combined income must not exceed £80,000.00 for properties outside London local authorities"
child_has_income: "Child's income must be £0" child_has_income: "Child's income must be £0"
negative_currency: "Enter an amount above 0" negative_currency: "Enter an amount above 0"
rent: rent:
@ -451,6 +456,7 @@ en:
toggle_date: toggle_date:
not_selected: "Select one of the options" not_selected: "Select one of the options"
invalid: "Enter a valid day, month and year" invalid: "Enter a valid day, month and year"
before_creation: "The scheme cannot be deactivated before %{date}, the start of the collection year when it was created"
out_of_range: "The date must be on or after the %{date}" out_of_range: "The date must be on or after the %{date}"
reactivation: reactivation:
before_deactivation: "This scheme was deactivated on %{date}. The reactivation date must be on or after deactivation date" before_deactivation: "This scheme was deactivated on %{date}. The reactivation date must be on or after deactivation date"
@ -469,6 +475,7 @@ en:
toggle_date: toggle_date:
not_selected: "Select one of the options" not_selected: "Select one of the options"
invalid: "Enter a valid day, month and year" invalid: "Enter a valid day, month and year"
before_creation: "The location cannot be deactivated before %{date}, the date when it was first available"
out_of_range: "The date must be on or after the %{date}" out_of_range: "The date must be on or after the %{date}"
reactivation: reactivation:
before_deactivation: "This location was deactivated on %{date}. The reactivation date must be on or after deactivation date" before_deactivation: "This location was deactivated on %{date}. The reactivation date must be on or after deactivation date"
@ -491,9 +498,9 @@ en:
must_be_after_hodate: "Sale completion date must be after practical completion or handover date" must_be_after_hodate: "Sale completion date must be after practical completion or handover date"
previous_property_type: previous_property_type:
property_type_bedsit: "A bedsit cannot have more than 1 bedroom" property_type_bedsit: "A bedsit cannot have more than 1 bedroom"
discounted_ownership_value: "Mortgage, deposit, and grant total must equal £%{value_with_discount}" discounted_ownership_value: "Mortgage, deposit, and grant total must equal %{value_with_discount}"
monthly_rent: monthly_rent:
higher_than_expected: "Basic monthly rent must be between £0 and £9,999" higher_than_expected: "Basic monthly rent must be between £0.00 and £9,999.00"
soft_validations: soft_validations:
net_income: net_income:
@ -589,10 +596,10 @@ en:
postcode: "For example, SW1P 4DF." postcode: "For example, SW1P 4DF."
name: "This is how you refer to this location within your organisation" name: "This is how you refer to this location within your organisation"
units: "A unit is the space being let. For example, the property might be a block of flats and the unit would be the specific flat being let. A unit can also be a bedroom in a shared house or flat. Do not include spaces used for staff." units: "A unit is the space being let. For example, the property might be a block of flats and the unit would be the specific flat being let. A unit can also be a bedroom in a shared house or flat. Do not include spaces used for staff."
toggle_active: "If the date is before %{date}, select ‘From the start of the current collection period’ because the previous period has now closed." toggle_active: "If the date is before %{date}, select ‘From the start of the open collection period’ because the previous period has now closed."
startdate: "For example, 27 3 2021" startdate: "For example, 27 3 2021"
scheme: scheme:
toggle_active: "If the date is before %{date}, select ‘From the start of the current collection period’ because the previous period has now closed." toggle_active: "If the date is before %{date}, select ‘From the start of the open collection period’ because the previous period has now closed."
bulk_upload: bulk_upload:
needstype: "General needs housing includes both self-contained and shared housing without support or specific adaptations. Supported housing can include direct access hostels, group homes, residential care and nursing homes." needstype: "General needs housing includes both self-contained and shared housing without support or specific adaptations. Supported housing can include direct access hostels, group homes, residential care and nursing homes."
offered: "Do not include the offer that led to this letting. This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0." offered: "Do not include the offer that led to this letting. This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0."

11
config/routes.rb

@ -144,6 +144,15 @@ Rails.application.routes.draw do
end end
end end
resources :bulk_upload_lettings_resume, path: "bulk-upload-resume", only: %i[show update] do
member do
get :start
get "*page", to: "bulk_upload_lettings_resume#show", as: "page"
patch "*page", to: "bulk_upload_lettings_resume#update"
end
end
get "update-logs", to: "lettings_logs#update_logs" get "update-logs", to: "lettings_logs#update_logs"
end end
@ -155,6 +164,7 @@ Rails.application.routes.draw do
FormHandler.instance.lettings_forms.each do |_key, form| FormHandler.instance.lettings_forms.each do |_key, form|
form.pages.map do |page| form.pages.map do |page|
get page.id.to_s.dasherize, to: "form#show_page" get page.id.to_s.dasherize, to: "form#show_page"
post page.id.to_s.dasherize, to: "form#submit_form"
end end
form.subsections.map do |subsection| form.subsections.map do |subsection|
@ -181,6 +191,7 @@ Rails.application.routes.draw do
FormHandler.instance.sales_forms.each do |_key, form| FormHandler.instance.sales_forms.each do |_key, form|
form.pages.map do |page| form.pages.map do |page|
get page.id.to_s.dasherize, to: "form#show_page" get page.id.to_s.dasherize, to: "form#show_page"
post page.id.to_s.dasherize, to: "form#submit_form"
end end
form.subsections.map do |subsection| form.subsections.map do |subsection|

5
db/migrate/20230331094840_add_status_cache_to_lettings_log.rb

@ -0,0 +1,5 @@
class AddStatusCacheToLettingsLog < ActiveRecord::Migration[7.0]
def change
add_column :lettings_logs, :status_cache, :integer, null: false, default: 0
end
end

3
db/schema.rb

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_03_20_084057) do ActiveRecord::Schema[7.0].define(version: 2023_03_31_094840) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -287,6 +287,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_20_084057) do
t.string "town_or_city" t.string "town_or_city"
t.string "county" t.string "county"
t.integer "carehome_charges_value_check" t.integer "carehome_charges_value_check"
t.integer "status_cache", default: 0, null: false
t.index ["bulk_upload_id"], name: "index_lettings_logs_on_bulk_upload_id" t.index ["bulk_upload_id"], name: "index_lettings_logs_on_bulk_upload_id"
t.index ["created_by_id"], name: "index_lettings_logs_on_created_by_id" t.index ["created_by_id"], name: "index_lettings_logs_on_created_by_id"
t.index ["location_id"], name: "index_lettings_logs_on_location_id" t.index ["location_id"], name: "index_lettings_logs_on_location_id"

27
spec/components/bulk_upload_error_summary_table_component_spec.rb

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

4
spec/features/form/page_routing_spec.rb

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

4
spec/features/schemes_spec.rb

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

4
spec/fixtures/files/lettings_logs_download.csv vendored

@ -1,2 +1,2 @@
id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,needstype,renewal,startdate,rent_type_detail,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,hhmemb,relat2,age2,sex2,retirement_value_check,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,is_previous_la_inferred,prevloc_label,prevloc,illness_type_1,illness_type_2,is_la_inferred,la_label,la,postcode_known,postcode_full,previous_la_known,wchair,preg_occ,cbl,earnings,incfreq,net_income_value_check,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,lettype,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,tshortfall,chcharge,ppcodenk,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,has_benefits,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,rent_value_check,old_form_id,lar,irproduct,old_id,joint,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,unresolved,updated_by_id,uprn,uprn_known,uprn_confirmed,address_line1,address_line2,town_or_city,county,carehome_charges_value_check,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,needstype,renewal,startdate,rent_type_detail,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,hhmemb,relat2,age2,sex2,retirement_value_check,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,is_previous_la_inferred,prevloc_label,prevloc,illness_type_1,illness_type_2,is_la_inferred,la_label,la,postcode_known,postcode_full,previous_la_known,wchair,preg_occ,cbl,earnings,incfreq,net_income_value_check,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,lettype,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,tshortfall,chcharge,ppcodenk,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,has_benefits,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,rent_value_check,old_form_id,lar,irproduct,old_id,joint,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,unresolved,updated_by_id,uprn,uprn_known,uprn_confirmed,address_line1,address_line2,town_or_city,county,carehome_charges_value_check,status_cache,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate
{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,No,DLUHC,DLUHC,2021,Supported housing,,2 October 2021,London Affordable Rent,,,,,,,,,,,,,,,,,,,,No,,,,,No,Westminster,E09000033,,SE1 1TE,,No,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,8,0,0,0,,0,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,0,,,,,,,,,,,,,,,,,,,9,1,,,,,,,,,,,,,,,,6,{scheme_code},{scheme_service_name},{scheme_sensitive},Missing,No,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,Bungalow,Fitted with equipment and adaptations,Westminster,{location_startdate} {id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,No,DLUHC,DLUHC,2021,Supported housing,,2 October 2021,London Affordable Rent,,,,,,,,,,,,,,,,,,,,No,,,,,No,Westminster,E09000033,,SE1 1TE,,No,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,8,0,0,0,,0,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,0,,,,,,,,,,,,,,,,,,,9,1,,,,,,,,,,,,,,,,not_started,6,{scheme_code},{scheme_service_name},{scheme_sensitive},Missing,No,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,Bungalow,Fitted with equipment and adaptations,Westminster,{location_startdate}

1 id status created_at updated_at created_by_name is_dpo owning_organisation_name managing_organisation_name collection_start_year needstype renewal startdate rent_type_detail irproduct_other tenancycode propcode age1 sex1 ecstat1 hhmemb relat2 age2 sex2 retirement_value_check ecstat2 armedforces leftreg illness housingneeds_a housingneeds_b housingneeds_c housingneeds_h is_previous_la_inferred prevloc_label prevloc illness_type_1 illness_type_2 is_la_inferred la_label la postcode_known postcode_full previous_la_known wchair preg_occ cbl earnings incfreq net_income_value_check benefits hb period brent scharge pscharge supcharg tcharge offered layear ppostcode_full mrcdate declaration ethnic national prevten age3 sex3 ecstat3 age4 sex4 ecstat4 age5 sex5 ecstat5 age6 sex6 ecstat6 age7 sex7 ecstat7 age8 sex8 ecstat8 homeless underoccupation_benefitcap reservist startertenancy tenancylength tenancy rsnvac unittype_gn beds waityear reasonpref chr cap reasonother housingneeds_f housingneeds_g illness_type_3 illness_type_4 illness_type_8 illness_type_5 illness_type_6 illness_type_7 illness_type_9 illness_type_10 rp_homeless rp_insan_unsat rp_medwel rp_hardship rp_dontknow tenancyother property_owner_organisation property_manager_organisation purchaser_code reason majorrepairs hbrentshortfall property_relet incref first_time_property_let_as_social_housing unitletas builtype voiddate renttype lettype totchild totelder totadult net_income_known nocharge is_carehome household_charge referral tshortfall chcharge ppcodenk age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known ethnic_group letting_allocation_unknown details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 has_benefits wrent wscharge wpschrge wsupchrg wtcharge wtshortfall refused housingneeds wchchrg newprop relat3 relat4 relat5 relat6 relat7 relat8 rent_value_check old_form_id lar irproduct old_id joint tshortfall_known sheltered pregnancy_value_check hhtype new_old vacdays major_repairs_date_value_check void_date_value_check housingneeds_type housingneeds_other unresolved updated_by_id uprn uprn_known uprn_confirmed address_line1 address_line2 town_or_city county carehome_charges_value_check status_cache unittype_sh scheme_code scheme_service_name scheme_sensitive scheme_type scheme_registered_under_care_act scheme_owning_organisation_name scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at location_code location_postcode location_name location_units location_type_of_unit location_mobility_type location_admin_district location_startdate
2 {id} in_progress 2022-02-08 16:52:15 +0000 2022-02-08 16:52:15 +0000 Danny Rojas No DLUHC DLUHC 2021 Supported housing 2 October 2021 London Affordable Rent No No Westminster E09000033 SE1 1TE No 2 8 0 0 0 0 0 0 9 1 not_started 6 {scheme_code} {scheme_service_name} {scheme_sensitive} Missing No DLUHC {scheme_primary_client_group} {scheme_secondary_client_group} {scheme_support_type} {scheme_intended_stay} 2021-04-01 00:00:00 +0100 {location_code} SE1 1TE Downing Street 20 Bungalow Fitted with equipment and adaptations Westminster {location_startdate}

4
spec/fixtures/files/lettings_logs_download_codes_only.csv vendored

@ -1,2 +1,2 @@
id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,needstype,renewal,startdate,rent_type_detail,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,hhmemb,relat2,age2,sex2,retirement_value_check,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,is_previous_la_inferred,prevloc_label,prevloc,illness_type_1,illness_type_2,is_la_inferred,la_label,la,postcode_known,postcode_full,previous_la_known,wchair,preg_occ,cbl,earnings,incfreq,net_income_value_check,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,lettype,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,tshortfall,chcharge,ppcodenk,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,has_benefits,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,rent_value_check,old_form_id,lar,irproduct,old_id,joint,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,unresolved,updated_by_id,uprn,uprn_known,uprn_confirmed,address_line1,address_line2,town_or_city,county,carehome_charges_value_check,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,needstype,renewal,startdate,rent_type_detail,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,hhmemb,relat2,age2,sex2,retirement_value_check,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,is_previous_la_inferred,prevloc_label,prevloc,illness_type_1,illness_type_2,is_la_inferred,la_label,la,postcode_known,postcode_full,previous_la_known,wchair,preg_occ,cbl,earnings,incfreq,net_income_value_check,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,lettype,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,tshortfall,chcharge,ppcodenk,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,has_benefits,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,rent_value_check,old_form_id,lar,irproduct,old_id,joint,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,unresolved,updated_by_id,uprn,uprn_known,uprn_confirmed,address_line1,address_line2,town_or_city,county,carehome_charges_value_check,status_cache,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate
{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,false,DLUHC,DLUHC,2021,2,,2 October 2021,2,,,,,,,,,,,,,,,,,,,,false,,,,,false,Westminster,E09000033,,SE1 1TE,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,8,0,0,0,,0,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,0,,,,,,,,,,,,,,,,,,,9,1,,,,,,,,,,,,,,,,6,{scheme_code},{scheme_service_name},{scheme_sensitive},0,1,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,6,A,Westminster,{location_startdate} {id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,false,DLUHC,DLUHC,2021,2,,2 October 2021,2,,,,,,,,,,,,,,,,,,,,false,,,,,false,Westminster,E09000033,,SE1 1TE,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,8,0,0,0,,0,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,0,,,,,,,,,,,,,,,,,,,9,1,,,,,,,,,,,,,,,,not_started,6,{scheme_code},{scheme_service_name},{scheme_sensitive},0,1,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,6,A,Westminster,{location_startdate}

1 id status created_at updated_at created_by_name is_dpo owning_organisation_name managing_organisation_name collection_start_year needstype renewal startdate rent_type_detail irproduct_other tenancycode propcode age1 sex1 ecstat1 hhmemb relat2 age2 sex2 retirement_value_check ecstat2 armedforces leftreg illness housingneeds_a housingneeds_b housingneeds_c housingneeds_h is_previous_la_inferred prevloc_label prevloc illness_type_1 illness_type_2 is_la_inferred la_label la postcode_known postcode_full previous_la_known wchair preg_occ cbl earnings incfreq net_income_value_check benefits hb period brent scharge pscharge supcharg tcharge offered layear ppostcode_full mrcdate declaration ethnic national prevten age3 sex3 ecstat3 age4 sex4 ecstat4 age5 sex5 ecstat5 age6 sex6 ecstat6 age7 sex7 ecstat7 age8 sex8 ecstat8 homeless underoccupation_benefitcap reservist startertenancy tenancylength tenancy rsnvac unittype_gn beds waityear reasonpref chr cap reasonother housingneeds_f housingneeds_g illness_type_3 illness_type_4 illness_type_8 illness_type_5 illness_type_6 illness_type_7 illness_type_9 illness_type_10 rp_homeless rp_insan_unsat rp_medwel rp_hardship rp_dontknow tenancyother property_owner_organisation property_manager_organisation purchaser_code reason majorrepairs hbrentshortfall property_relet incref first_time_property_let_as_social_housing unitletas builtype voiddate renttype lettype totchild totelder totadult net_income_known nocharge is_carehome household_charge referral tshortfall chcharge ppcodenk age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known ethnic_group letting_allocation_unknown details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 has_benefits wrent wscharge wpschrge wsupchrg wtcharge wtshortfall refused housingneeds wchchrg newprop relat3 relat4 relat5 relat6 relat7 relat8 rent_value_check old_form_id lar irproduct old_id joint tshortfall_known sheltered pregnancy_value_check hhtype new_old vacdays major_repairs_date_value_check void_date_value_check housingneeds_type housingneeds_other unresolved updated_by_id uprn uprn_known uprn_confirmed address_line1 address_line2 town_or_city county carehome_charges_value_check status_cache unittype_sh scheme_code scheme_service_name scheme_sensitive scheme_type scheme_registered_under_care_act scheme_owning_organisation_name scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at location_code location_postcode location_name location_units location_type_of_unit location_mobility_type location_admin_district location_startdate
2 {id} in_progress 2022-02-08 16:52:15 +0000 2022-02-08 16:52:15 +0000 Danny Rojas false DLUHC DLUHC 2021 2 2 October 2021 2 false false Westminster E09000033 SE1 1TE 2 2 8 0 0 0 0 0 0 9 1 not_started 6 {scheme_code} {scheme_service_name} {scheme_sensitive} 0 1 DLUHC {scheme_primary_client_group} {scheme_secondary_client_group} {scheme_support_type} {scheme_intended_stay} 2021-04-01 00:00:00 +0100 {location_code} SE1 1TE Downing Street 20 6 A Westminster {location_startdate}

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

1
spec/jobs/email_csv_job_spec.rb

@ -61,6 +61,7 @@ describe EmailCsvJob do
context "when writing to S3" do context "when writing to S3" do
before do before do
FactoryBot.create_list(:lettings_log, 4, owning_organisation: other_organisation) FactoryBot.create_list(:lettings_log, 4, owning_organisation: other_organisation)
FactoryBot.create(:lettings_log, owning_organisation: other_organisation, status: "pending", skip_update_status: true)
end end
def expect_csv def expect_csv

46
spec/mailers/bulk_upload_mailer_spec.rb

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

8
spec/models/lettings_log_spec.rb

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

83
spec/models/location_deactivation_period_spec.rb

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

5
spec/models/organisation_spec.rb

@ -169,11 +169,6 @@ RSpec.describe Organisation, type: :model do
it "has lettings logs" do it "has lettings logs" do
expect(organisation.lettings_logs.to_a).to match_array([owned_lettings_log, managed_lettings_log]) expect(organisation.lettings_logs.to_a).to match_array([owned_lettings_log, managed_lettings_log])
end end
it "has lettings log status helper methods" do
expect(organisation.completed_lettings_logs.to_a).to eq([owned_lettings_log])
expect(organisation.not_completed_lettings_logs.to_a).to eq([managed_lettings_log])
end
end end
end end

2
spec/models/sales_log_spec.rb

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

68
spec/models/scheme_deactivation_period_spec.rb

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

5
spec/models/user_spec.rb

@ -36,11 +36,6 @@ RSpec.describe User, type: :model do
expect(user.lettings_logs.to_a).to match_array([owned_lettings_log, managed_lettings_log]) expect(user.lettings_logs.to_a).to match_array([owned_lettings_log, managed_lettings_log])
end end
it "has lettings log status helper methods" do
expect(user.completed_lettings_logs.to_a).to match_array([owned_lettings_log])
expect(user.not_completed_lettings_logs.to_a).to match_array([managed_lettings_log])
end
it "has a role" do it "has a role" do
expect(user.role).to eq("data_provider") expect(user.role).to eq("data_provider")
expect(user.data_provider?).to be true expect(user.data_provider?).to be true

40
spec/models/validations/financial_validations_spec.rb

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

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

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

30
spec/models/validations/shared_validations_spec.rb

@ -113,34 +113,4 @@ RSpec.describe Validations::SharedValidations do
end end
end end
end end
describe "radio options validations" do
it "allows only possible values" do
record.needstype = 1
shared_validator.validate_valid_radio_option(record)
expect(record.errors["needstype"]).to be_empty
end
it "denies impossible values" do
record.needstype = 3
shared_validator.validate_valid_radio_option(record)
expect(record.errors["needstype"]).to be_present
expect(record.errors["needstype"]).to eql(["Enter a valid value for needs type"])
end
context "when feature is toggled off" do
before do
allow(FeatureToggle).to receive(:validate_valid_radio_options?).and_return(false)
end
it "allows any values" do
record.needstype = 3
shared_validator.validate_valid_radio_option(record)
expect(record.errors["needstype"]).to be_empty
end
end
end
end end

84
spec/requests/bulk_upload_lettings_resume_controller_spec.rb

@ -0,0 +1,84 @@
require "rails_helper"
RSpec.describe BulkUploadLettingsResumeController, type: :request do
let(:user) { create(:user) }
let(:bulk_upload) { create(:bulk_upload, :lettings, user:, bulk_upload_errors:) }
let(:bulk_upload_errors) { create_list(:bulk_upload_error, 2) }
before do
sign_in user
end
describe "GET /lettings-logs/bulk-upload-resume/:ID/start" do
it "redirects to choice page" do
get "/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/start"
expect(response).to redirect_to("/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice")
end
end
describe "GET /lettings-logs/bulk-upload-resume/:ID/fix-choice" do
it "renders the page correctly" do
get "/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice"
expect(response).to be_successful
expect(response.body).to include("Bulk upload for lettings")
expect(response.body).to include("2022/23")
expect(response.body).to include("How would you like to fix 2 errors?")
expect(response.body).to include(bulk_upload.filename)
end
end
describe "PATCH /lettings-logs/bulk-upload-resume/:ID/fix-choice" do
context "when no option selected" do
it "renders error message" do
patch "/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice"
expect(response).to be_successful
expect(response.body).to include("You must select")
end
end
context "when upload again selected" do
it "sends them to relevant report" do
patch "/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice", params: { form: { choice: "upload-again" } }
expect(response).to redirect_to("/lettings-logs/bulk-upload-results/#{bulk_upload.id}")
end
end
context "when fix inline selected" do
it "sends them to confirm choice" do
patch "/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice", params: { form: { choice: "create-fix-inline" } }
expect(response).to redirect_to("/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/confirm")
end
end
end
describe "GET /lettings-logs/bulk-upload-resume/:ID/confirm" do
it "renders page" do
get "/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/confirm"
expect(response).to be_successful
expect(response.body).to include("Are you sure")
end
end
describe "PATCH /lettings-logs/bulk-upload-resume/:ID/confirm" do
let(:mock_processor) { instance_double(BulkUpload::Processor, approve: nil) }
it "approves logs for creation" do
allow(BulkUpload::Processor).to receive(:new).with(bulk_upload:).and_return(mock_processor)
patch "/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/confirm"
expect(mock_processor).to have_received(:approve)
expect(response).to redirect_to("/lettings-logs/bulk-upload-results/#{bulk_upload.id}/resume")
end
end
end

68
spec/requests/form_controller_spec.rb

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

33
spec/requests/lettings_logs_controller_spec.rb

@ -224,6 +224,15 @@ RSpec.describe LettingsLogsController, type: :request do
tenancycode: "UA984", tenancycode: "UA984",
) )
end end
let!(:pending_lettings_log) do
FactoryBot.create(
:lettings_log,
created_by: user,
tenancycode: "LC999",
status: "pending",
skip_update_status: true,
)
end
context "when displaying a collection of logs" do context "when displaying a collection of logs" do
let(:headers) { { "Accept" => "text/html" } } let(:headers) { { "Accept" => "text/html" } }
@ -261,6 +270,7 @@ RSpec.describe LettingsLogsController, type: :request do
get "/lettings-logs", headers:, params: {} get "/lettings-logs", headers:, params: {}
expect(page).to have_content("LC783") expect(page).to have_content("LC783")
expect(page).to have_content("UA984") expect(page).to have_content("UA984")
expect(page).not_to have_content(pending_lettings_log.tenancycode)
end end
it "displays CSV download links with the correct paths" do it "displays CSV download links with the correct paths" do
@ -841,6 +851,24 @@ RSpec.describe LettingsLogsController, type: :request do
end end
end end
context "when viewing a pending log" do
let(:completed_lettings_log) do
FactoryBot.create(
:lettings_log,
:completed,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
created_by: user,
status: "pending",
skip_update_status: true,
)
end
it "returns 404" do
expect(response).to have_http_status(:not_found)
end
end
context "when editing a lettings log" do context "when editing a lettings log" do
let(:headers) { { "Accept" => "text/html" } } let(:headers) { { "Accept" => "text/html" } }
@ -1319,9 +1347,12 @@ RSpec.describe LettingsLogsController, type: :request do
end end
context "when a lettings log deletion fails" do context "when a lettings log deletion fails" do
let(:mock_scope) { instance_double("LettingsLog::ActiveRecord_Relation", find_by: lettings_log) }
before do before do
allow(LettingsLog).to receive(:find_by).and_return(lettings_log) allow(LettingsLog).to receive(:visible).and_return(mock_scope)
allow(lettings_log).to receive(:delete).and_return(false) allow(lettings_log).to receive(:delete).and_return(false)
delete "/lettings-logs/#{id}", headers: delete "/lettings-logs/#{id}", headers:
end end

5
spec/requests/organisations_controller_spec.rb

@ -591,6 +591,7 @@ RSpec.describe OrganisationsController, type: :request do
before do before do
FactoryBot.create_list(:lettings_log, number_of_org1_lettings_logs, created_by: user) FactoryBot.create_list(:lettings_log, number_of_org1_lettings_logs, created_by: user)
FactoryBot.create(:lettings_log, created_by: user, status: "pending", skip_update_status: true)
FactoryBot.create_list(:lettings_log, number_of_org2_lettings_logs, created_by: nil, owning_organisation_id: unauthorised_organisation.id, managing_organisation_id: unauthorised_organisation.id) FactoryBot.create_list(:lettings_log, number_of_org2_lettings_logs, created_by: nil, owning_organisation_id: unauthorised_organisation.id, managing_organisation_id: unauthorised_organisation.id)
get "/organisations/#{organisation.id}/lettings-logs", headers:, params: {} get "/organisations/#{organisation.id}/lettings-logs", headers:, params: {}
@ -598,7 +599,8 @@ RSpec.describe OrganisationsController, type: :request do
it "only shows logs for that organisation" do it "only shows logs for that organisation" do
expect(page).to have_content("#{number_of_org1_lettings_logs} total logs") expect(page).to have_content("#{number_of_org1_lettings_logs} total logs")
organisation.lettings_logs.map(&:id).each do |lettings_log_id|
organisation.lettings_logs.visible.map(&:id).each do |lettings_log_id|
expect(page).to have_link lettings_log_id.to_s, href: "/lettings-logs/#{lettings_log_id}" expect(page).to have_link lettings_log_id.to_s, href: "/lettings-logs/#{lettings_log_id}"
end end
@ -1155,6 +1157,7 @@ RSpec.describe OrganisationsController, type: :request do
before do before do
FactoryBot.create_list(:lettings_log, 2, owning_organisation: organisation) FactoryBot.create_list(:lettings_log, 2, owning_organisation: organisation)
FactoryBot.create(:lettings_log, owning_organisation: organisation, status: "pending", skip_update_status: true)
FactoryBot.create_list(:lettings_log, 2, owning_organisation: other_organisation) FactoryBot.create_list(:lettings_log, 2, owning_organisation: other_organisation)
end end

16
spec/requests/sales_logs_controller_spec.rb

@ -159,6 +159,22 @@ RSpec.describe SalesLogsController, type: :request do
end end
end end
context "when there is a pending log" do
let!(:invisible_log) do
FactoryBot.create(
:sales_log,
owning_organisation: organisation,
status: "pending",
skip_update_status: true,
)
end
it "does not render pending logs" do
get "/sales-logs", headers: headers, params: {}
expect(page).not_to have_content(invisible_log.id)
end
end
context "when filtering" do context "when filtering" do
context "with status filter" do context "with status filter" do
let(:organisation_2) { FactoryBot.create(:organisation) } let(:organisation_2) { FactoryBot.create(:organisation) }

22
spec/services/bulk_upload/lettings/log_creator_spec.rb

@ -15,6 +15,11 @@ RSpec.describe BulkUpload::Lettings::LogCreator do
expect { service.call }.to change(LettingsLog, :count) expect { service.call }.to change(LettingsLog, :count)
end end
it "create a log with pending status" do
service.call
expect(LettingsLog.last.status).to eql("pending")
end
it "associates log with bulk upload" do it "associates log with bulk upload" do
service.call service.call
@ -77,6 +82,23 @@ RSpec.describe BulkUpload::Lettings::LogCreator do
end end
end end
context "when pre-creating logs" do
subject(:service) { described_class.new(bulk_upload:, path:) }
it "creates a new log" do
expect { service.call }.to change(LettingsLog, :count)
end
it "creates a log with correct states" do
service.call
last_log = LettingsLog.last
expect(last_log.status).to eql("pending")
expect(last_log.status_cache).to eql("completed")
end
end
context "when valid csv with existing log" do context "when valid csv with existing log" do
xit "what should happen?" xit "what should happen?"
end end

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

@ -273,58 +273,5 @@ RSpec.describe BulkUpload::Lettings::Validator do
end end
end end
end end
context "when a column has error rate above absolute threshold" do
before do
stub_const("BulkUpload::Lettings::Validator::COLUMN_ABSOLUTE_ERROR_THRESHOLD", 1)
end
context "when a column is over 60% error threshold" do
let(:log_1) { build(:lettings_log, :completed, renttype: 1, created_by: user) }
let(:log_2) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
let(:log_3) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
let(:log_4) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
let(:log_5) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
before do
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.close
end
it "returns false" do
validator.call
expect(validator).not_to be_create_logs
end
end
context "when a column is under 60% error threshold" do
let(:log_1) { build(:lettings_log, :completed, renttype: 1, created_by: user) }
let(:log_2) { build(:lettings_log, :completed, renttype: 1, created_by: user) }
let(:log_3) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
let(:log_4) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
let(:log_5) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
before do
overrides = { age1: 50, age2: "R", age3: "R", age4: "4", age5: "R", age6: "R", age7: "R", age8: "R" }
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0, overrides:).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0, overrides:).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0, overrides:).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0, overrides:).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0, overrides:).to_2022_csv_row)
file.close
end
it "returns true" do
validator.call
expect(validator).to be_create_logs
end
end
end
end end
end end

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

@ -1472,6 +1472,16 @@ RSpec.describe BulkUpload::Lettings::Year2022::RowParser do
end end
end end
context "when housingneeds are given and field_59 is nil" do
let(:attributes) { { bulk_upload:, field_57: "1", field_58: "1", field_59: nil } }
it "sets correct housingneeds" do
expect(parser.log.housingneeds).to eq(1)
expect(parser.log.housingneeds_type).to eq(2)
expect(parser.log.housingneeds_other).to eq(1)
end
end
context "when housingneeds a and b are selected" do context "when housingneeds a and b are selected" do
let(:attributes) { { bulk_upload:, field_55: "1", field_56: "1" } } let(:attributes) { { bulk_upload:, field_55: "1", field_56: "1" } }

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

@ -3,7 +3,7 @@ require "rails_helper"
RSpec.describe BulkUpload::Lettings::Year2023::RowParser do RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
subject(:parser) { described_class.new(attributes) } subject(:parser) { described_class.new(attributes) }
let(:now) { Time.zone.parse("01/03/2023") } let(:now) { Time.zone.now.beginning_of_day }
let(:attributes) { { bulk_upload: } } let(:attributes) { { bulk_upload: } }
let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: nil) } let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: nil) }
@ -223,11 +223,11 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
} }
end end
it "returns true" do xit "returns true" do
expect(parser).to be_valid expect(parser).to be_valid
end end
it "instantiates a log with everything completed", aggregate_failures: true do xit "instantiates a log with everything completed", aggregate_failures: true do
questions = parser.send(:questions).reject do |q| questions = parser.send(:questions).reject do |q|
parser.send(:log).optional_fields.include?(q.id) || q.completed?(parser.send(:log)) parser.send(:log).optional_fields.include?(q.id) || q.completed?(parser.send(:log))
end end
@ -504,6 +504,14 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
end end
end end
end end
context "when no longer a valid option from previous year" do
let(:attributes) { setup_section_params.merge({ field_102: "7" }) }
it "returns an error" do
expect(parser.errors[:field_102]).to be_present
end
end
end end
describe "#field_83, #field_84, #field_85" do describe "#field_83, #field_84, #field_85" do
@ -790,6 +798,16 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
end end
end end
describe "#field_26" do # unitletas
context "when no longer a valid option from previous year" do
let(:attributes) { setup_section_params.merge({ field_26: "4" }) }
it "returns an error" do
expect(parser.errors[:field_26]).to be_present
end
end
end
describe "#field_30" do describe "#field_30" do
context "when null" do context "when null" do
let(:attributes) { setup_section_params.merge({ field_30: nil }) } let(:attributes) { setup_section_params.merge({ field_30: nil }) }
@ -1468,6 +1486,16 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
end end
end end
context "when housingneeds are given and field_86 is nil" do
let(:attributes) { { bulk_upload:, field_87: nil, field_85: "1", field_86: "1" } }
it "sets correct housingneeds" do
expect(parser.log.housingneeds).to eq(1)
expect(parser.log.housingneeds_type).to eq(2)
expect(parser.log.housingneeds_other).to eq(1)
end
end
context "when housingneeds a and b are selected" do context "when housingneeds a and b are selected" do
let(:attributes) { { bulk_upload:, field_83: "1", field_84: "1" } } let(:attributes) { { bulk_upload:, field_83: "1", field_84: "1" } }

167
spec/services/bulk_upload/processor_spec.rb

@ -130,120 +130,111 @@ RSpec.describe BulkUpload::Processor do
end end
end end
context "when processing a bulk upload with errors but below threshold (therefore creates logs)" do context "when processing a bulk with perfect data" do
let(:mock_downloader) do let(:mock_downloader) do
instance_double( instance_double(
BulkUpload::Downloader, BulkUpload::Downloader,
call: nil, call: nil,
path: file_fixture("2022_23_lettings_bulk_upload.csv"), path:,
delete_local_file!: nil, delete_local_file!: nil,
) )
end end
let(:mock_validator) do let(:file) { Tempfile.new }
instance_double( let(:path) { file.path }
BulkUpload::Lettings::Validator,
invalid?: false, let(:log) do
call: nil, build(
any_setup_errors?: false, :lettings_log,
create_logs?: true, :completed,
renttype: 3,
age1: 20,
owning_organisation: owning_org,
managing_organisation: owning_org,
created_by: nil,
national: 18,
waityear: 9,
joint: 2,
tenancy: 9,
ppcodenk: 0,
voiddate: nil,
mrcdate: nil,
startdate: Date.new(2022, 10, 1),
tenancylength: nil,
) )
end end
before do before do
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
file.rewind
allow(BulkUpload::Downloader).to receive(:new).with(bulk_upload:).and_return(mock_downloader) allow(BulkUpload::Downloader).to receive(:new).with(bulk_upload:).and_return(mock_downloader)
allow(BulkUpload::Lettings::Validator).to receive(:new).and_return(mock_validator)
end end
it "deletes the local file afterwards" do it "creates logs as not pending" do
processor.call expect { processor.call }.to change(LettingsLog.completed, :count).by(1)
expect(mock_downloader).to have_received(:delete_local_file!)
end end
it "sends fix errors email" do it "sends success email" do
mail_double = instance_double("ActionMailer::MessageDelivery", deliver_later: nil) mail_double = instance_double("ActionMailer::MessageDelivery", deliver_later: nil)
allow(BulkUploadMailer).to receive(:send_bulk_upload_with_errors_mail).and_return(mail_double) allow(BulkUploadMailer).to receive(:send_bulk_upload_complete_mail).and_return(mail_double)
processor.call processor.call
expect(BulkUploadMailer).to have_received(:send_bulk_upload_with_errors_mail) expect(BulkUploadMailer).to have_received(:send_bulk_upload_complete_mail)
expect(mail_double).to have_received(:deliver_later) expect(mail_double).to have_received(:deliver_later)
end end
it "does not send success email" do
allow(BulkUploadMailer).to receive(:send_bulk_upload_complete_mail).and_call_original
processor.call
expect(BulkUploadMailer).not_to have_received(:send_bulk_upload_complete_mail)
end
end end
context "when processing a bulk upload with errors but above threshold (therefore does not create logs)" do context "when a bulk upload has an in progress log" do
let(:mock_downloader) do let(:mock_downloader) do
instance_double( instance_double(
BulkUpload::Downloader, BulkUpload::Downloader,
call: nil, call: nil,
path: file_fixture("2022_23_lettings_bulk_upload.csv"), path:,
delete_local_file!: nil, delete_local_file!: nil,
) )
end end
let(:mock_validator) do let(:file) { Tempfile.new }
instance_double( let(:path) { file.path }
BulkUpload::Lettings::Validator,
invalid?: false, let(:log) do
call: nil, LettingsLog.new(
any_setup_errors?: false, lettype: 2,
create_logs?: false, renttype: 3,
owning_organisation: owning_org,
managing_organisation: owning_org,
startdate: Time.zone.local(2022, 10, 1),
renewal: 2,
) )
end end
before do before do
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
file.rewind
allow(BulkUpload::Downloader).to receive(:new).with(bulk_upload:).and_return(mock_downloader) allow(BulkUpload::Downloader).to receive(:new).with(bulk_upload:).and_return(mock_downloader)
allow(BulkUpload::Lettings::Validator).to receive(:new).and_return(mock_validator)
end end
it "deletes the local file afterwards" do it "creates pending log" do
processor.call expect { processor.call }.to change(LettingsLog.pending, :count).by(1)
expect(mock_downloader).to have_received(:delete_local_file!)
end end
it "sends correct and upload again mail" do it "sends how_fix_upload_mail" do
mail_double = instance_double("ActionMailer::MessageDelivery", deliver_later: nil) mail_double = instance_double("ActionMailer::MessageDelivery", deliver_later: nil)
allow(BulkUploadMailer).to receive(:send_correct_and_upload_again_mail).and_return(mail_double) allow(BulkUploadMailer).to receive(:send_how_fix_upload_mail).and_return(mail_double)
processor.call processor.call
expect(BulkUploadMailer).to have_received(:send_correct_and_upload_again_mail) expect(BulkUploadMailer).to have_received(:send_how_fix_upload_mail)
expect(mail_double).to have_received(:deliver_later) expect(mail_double).to have_received(:deliver_later)
end end
it "does not send fix errors email" do
allow(BulkUploadMailer).to receive(:send_bulk_upload_with_errors_mail).and_call_original
processor.call
expect(BulkUploadMailer).not_to have_received(:send_bulk_upload_with_errors_mail)
end
it "does not send success email" do
allow(BulkUploadMailer).to receive(:send_bulk_upload_complete_mail).and_call_original
processor.call
expect(BulkUploadMailer).not_to have_received(:send_bulk_upload_complete_mail)
end
end end
context "when processing a bulk with perfect data" do context "when upload has no setup errors something blocks log creation" do
let(:file) { Tempfile.new }
let(:path) { file.path }
let(:mock_downloader) do let(:mock_downloader) do
instance_double( instance_double(
BulkUpload::Downloader, BulkUpload::Downloader,
@ -253,24 +244,20 @@ RSpec.describe BulkUpload::Processor do
) )
end end
let(:file) { Tempfile.new }
let(:path) { file.path }
let(:other_user) { create(:user) }
let(:log) do let(:log) do
build( LettingsLog.new(
:lettings_log, lettype: 2,
:completed,
renttype: 3, renttype: 3,
age1: 20,
owning_organisation: owning_org, owning_organisation: owning_org,
managing_organisation: owning_org, managing_organisation: owning_org,
created_by: nil, startdate: Time.zone.local(2022, 10, 1),
national: 18, renewal: 2,
waityear: 9, created_by: other_user, # unaffiliated user
joint: 2,
tenancy: 9,
ppcodenk: 0,
voiddate: nil,
mrcdate: nil,
startdate: Date.new(2022, 10, 1),
tenancylength: nil,
) )
end end
@ -281,28 +268,26 @@ RSpec.describe BulkUpload::Processor do
allow(BulkUpload::Downloader).to receive(:new).with(bulk_upload:).and_return(mock_downloader) allow(BulkUpload::Downloader).to receive(:new).with(bulk_upload:).and_return(mock_downloader)
end end
it "creates logs" do it "sends correct_and_upload_again_mail" do
expect { processor.call }.to change(LettingsLog, :count).by(1)
end
it "does not send fix errors email" do
allow(BulkUploadMailer).to receive(:send_bulk_upload_with_errors_mail).and_call_original
processor.call
expect(BulkUploadMailer).not_to have_received(:send_bulk_upload_with_errors_mail)
end
it "sends success email" do
mail_double = instance_double("ActionMailer::MessageDelivery", deliver_later: nil) mail_double = instance_double("ActionMailer::MessageDelivery", deliver_later: nil)
allow(BulkUploadMailer).to receive(:send_bulk_upload_complete_mail).and_return(mail_double) allow(BulkUploadMailer).to receive(:send_correct_and_upload_again_mail).and_return(mail_double)
processor.call processor.call
expect(BulkUploadMailer).to have_received(:send_bulk_upload_complete_mail) expect(BulkUploadMailer).to have_received(:send_correct_and_upload_again_mail)
expect(mail_double).to have_received(:deliver_later) expect(mail_double).to have_received(:deliver_later)
end end
end end
end end
describe "#approve" do
let!(:log) { create(:lettings_log, bulk_upload:, status: "pending", skip_update_status: true, status_cache: "not_started") }
it "makes pending logs no longer pending" do
expect(log.status).to eql("pending")
processor.approve
expect(log.reload.status).to eql("not_started")
end
end
end end

1
spec/services/csv/lettings_log_csv_service_spec.rb

@ -209,6 +209,7 @@ RSpec.describe Csv::LettingsLogCsvService do
address_line2 address_line2
town_or_city town_or_city
county county
status_cache
unittype_sh unittype_sh
scheme_code scheme_code
scheme_service_name scheme_service_name

29
spec/services/exports/lettings_log_export_service_spec.rb

@ -59,6 +59,35 @@ RSpec.describe Exports::LettingsLogExportService do
end end
end end
context "when one pending lettings log exists" do
before do
FactoryBot.create(
:lettings_log,
:completed,
status: "pending",
skip_update_status: true,
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,
)
end
it "generates a master manifest with CSV headers but no data" do
actual_content = nil
expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\n"
allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
export_service.export_xml_lettings_logs
expect(actual_content).to eq(expected_content)
end
end
context "and one lettings log is available for export" do context "and one lettings log is available for export" do
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) } 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) }

28
spec/services/imports/lettings_logs_import_service_spec.rb

@ -223,8 +223,8 @@ RSpec.describe Imports::LettingsLogsImportService do
end end
it "intercepts the relevant validation error" do it "intercepts the relevant validation error" do
expect(logger).to receive(:warn).with(/Removing earnings with error: Net income cannot be less than £10 per week given the tenant’s working situation/) expect(logger).to receive(:warn).with(/Removing earnings with error: Net income cannot be less than £10.00 per week given the tenant’s working situation/)
expect(logger).to receive(:warn).with(/Removing incfreq with error: Net income cannot be less than £10 per week given the tenant’s working situation/) expect(logger).to receive(:warn).with(/Removing incfreq with error: Net income cannot be less than £10.00 per week given the tenant’s working situation/)
expect { lettings_log_service.send(:create_log, lettings_log_xml) } expect { lettings_log_service.send(:create_log, lettings_log_xml) }
.not_to raise_error .not_to raise_error
end end
@ -428,7 +428,7 @@ RSpec.describe Imports::LettingsLogsImportService do
end end
it "intercepts the relevant validation error" do it "intercepts the relevant validation error" do
expect(logger).to receive(:warn).with(/Removing ecstat1 with error: Net income cannot be greater than £890 per week given the tenant’s working situation/) expect(logger).to receive(:warn).with(/Removing ecstat1 with error: Net income cannot be greater than £890.00 per week given the tenant’s working situation/)
expect { lettings_log_service.send(:create_log, lettings_log_xml) } expect { lettings_log_service.send(:create_log, lettings_log_xml) }
.not_to raise_error .not_to raise_error
end end
@ -532,11 +532,11 @@ RSpec.describe Imports::LettingsLogsImportService do
end end
it "intercepts the relevant validation error" do it "intercepts the relevant validation error" do
expect(logger).to receive(:warn).with(/Removing brent with error: Enter a total charge that is at least £10 per week, Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?/) expect(logger).to receive(:warn).with("Log 0b4a68df-30cc-474a-93c0-a56ce8fdad3b: Removing brent with error: Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?’, Enter a total charge that is at least £10 per week")
expect(logger).to receive(:warn).with(/Removing scharge with error: Enter a total charge that is at least £10 per week, Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?/) expect(logger).to receive(:warn).with("Log 0b4a68df-30cc-474a-93c0-a56ce8fdad3b: Removing scharge with error: Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?’, Enter a total charge that is at least £10 per week")
expect(logger).to receive(:warn).with(/Removing pscharge with error: Enter a total charge that is at least £10 per week, Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?/) expect(logger).to receive(:warn).with("Log 0b4a68df-30cc-474a-93c0-a56ce8fdad3b: Removing pscharge with error: Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?’, Enter a total charge that is at least £10 per week")
expect(logger).to receive(:warn).with(/Removing supcharg with error: Enter a total charge that is at least £10 per week, Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?/) expect(logger).to receive(:warn).with("Log 0b4a68df-30cc-474a-93c0-a56ce8fdad3b: Removing supcharg with error: Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?’, Enter a total charge that is at least £10 per week")
expect(logger).to receive(:warn).with(/Removing tcharge with error: Enter a total charge that is at least £10 per week, Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?/) expect(logger).to receive(:warn).with("Log 0b4a68df-30cc-474a-93c0-a56ce8fdad3b: Removing tcharge with error: Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?’, Enter a total charge that is at least £10 per week")
expect { lettings_log_service.send(:create_log, lettings_log_xml) } expect { lettings_log_service.send(:create_log, lettings_log_xml) }
.not_to raise_error .not_to raise_error
end end
@ -560,11 +560,11 @@ RSpec.describe Imports::LettingsLogsImportService do
end end
it "intercepts the relevant validation error" do it "intercepts the relevant validation error" do
expect(logger).to receive(:warn).with(/Removing brent with error: Enter an amount above 0, Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Service charge must be at least £0 every week/) expect(logger).to receive(:warn).with(/Removing brent with error: Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Enter an amount above 0, Service charge must be at least £0 every week/)
expect(logger).to receive(:warn).with(/Removing scharge with error: Enter an amount above 0, Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Service charge must be at least £0 every week/) expect(logger).to receive(:warn).with(/Removing scharge with error: Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Enter an amount above 0, Service charge must be at least £0 every week/)
expect(logger).to receive(:warn).with(/Removing pscharge with error: Enter an amount above 0, Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Service charge must be at least £0 every week/) expect(logger).to receive(:warn).with(/Removing pscharge with error: Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Enter an amount above 0, Service charge must be at least £0 every week/)
expect(logger).to receive(:warn).with(/Removing supcharg with error: Enter an amount above 0, Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Service charge must be at least £0 every week/) expect(logger).to receive(:warn).with(/Removing supcharg with error: Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Enter an amount above 0, Service charge must be at least £0 every week/)
expect(logger).to receive(:warn).with(/Removing tcharge with error: Enter an amount above 0, Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Service charge must be at least £0 every week/) expect(logger).to receive(:warn).with(/Removing tcharge with error: Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Enter an amount above 0, Service charge must be at least £0 every week/)
expect { lettings_log_service.send(:create_log, lettings_log_xml) } expect { lettings_log_service.send(:create_log, lettings_log_xml) }
.not_to raise_error .not_to raise_error
end end
@ -762,7 +762,7 @@ RSpec.describe Imports::LettingsLogsImportService do
end end
it "intercepts the relevant validation error" do it "intercepts the relevant validation error" do
expect(logger).to receive(:warn).with(/Removing chcharge with error: Household rent and other charges must be between £10 and £1000 if paying weekly for 52 weeks/) expect(logger).to receive(:warn).with(/Removing chcharge with error: Household rent and other charges must be between £10.00 and £1,000.00 if paying weekly for 52 weeks/)
expect { lettings_log_service.send(:create_log, lettings_log_xml) } expect { lettings_log_service.send(:create_log, lettings_log_xml) }
.not_to raise_error .not_to raise_error
end end

2
spec/support/bulk_upload/log_to_csv.rb

@ -170,7 +170,7 @@ class BulkUpload::LogToCsv
nil, # 110 nil, # 110
log.owning_organisation&.old_visible_id, log.owning_organisation&.old_visible_id,
nil, log.created_by&.email,
log.managing_organisation&.old_visible_id, log.managing_organisation&.old_visible_id,
leftreg, leftreg,
nil, nil,

Loading…
Cancel
Save