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| %>
<%= govuk_table do |table| %>
<% table.head do |head| %>

11
app/components/bulk_upload_error_summary_table_component.rb

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

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

4
app/controllers/lettings_logs_controller.rb

@ -15,7 +15,7 @@ class LettingsLogsController < LogsController
def index
respond_to do |format|
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)
@search_term = search_term
@ -140,7 +140,7 @@ private
end
def find_resource
@log = LettingsLog.find_by(id: params[:id])
@log = LettingsLog.visible.find_by(id: params[:id])
end
def post_create_redirect_url(log)

6
app/controllers/locations_controller.rb

@ -158,7 +158,7 @@ class LocationsController < ApplicationController
end
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?
deactivate
else
@ -260,7 +260,7 @@ private
end
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
end
@ -269,7 +269,7 @@ private
if params[:location_deactivation_period].blank?
return
elsif params[:location_deactivation_period]["#{key}_type".to_sym] == "default"
return FormHandler.instance.current_collection_start_date
return FormHandler.instance.start_date_of_earliest_open_collection_period
elsif params[:location_deactivation_period][key.to_sym].present?
return params[:location_deactivation_period][key.to_sym]
end

4
app/controllers/organisations_controller.rb

@ -90,7 +90,7 @@ class OrganisationsController < ApplicationController
end
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)
respond_to do |format|
@ -105,7 +105,7 @@ class OrganisationsController < ApplicationController
end
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)
codes_only = params.require(:codes_only) == "true"

4
app/controllers/sales_logs_controller.rb

@ -9,7 +9,7 @@ class SalesLogsController < LogsController
def index
respond_to do |format|
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)
@search_term = search_term
@ -28,7 +28,7 @@ class SalesLogsController < LogsController
end
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
render "logs/edit", locals: { current_user: }
else

6
app/controllers/schemes_controller.rb

@ -39,7 +39,7 @@ class SchemesController < ApplicationController
end
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?
deactivate
else
@ -296,7 +296,7 @@ private
if params[:scheme_deactivation_period].blank?
return
elsif params[:scheme_deactivation_period]["#{key}_type".to_sym] == "default"
return FormHandler.instance.current_collection_start_date
return FormHandler.instance.start_date_of_earliest_open_collection_period
elsif params[:scheme_deactivation_period][key.to_sym].present?
return params[:scheme_deactivation_period][key.to_sym]
end
@ -310,7 +310,7 @@ private
end
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
end

8
app/helpers/filters_helper.rb

@ -12,9 +12,11 @@ module FiltersHelper
end
def status_filters
statuses = {}
LettingsLog.statuses.keys.map { |status| statuses[status] = status.humanize }
statuses
{
"not_started" => "Not started",
"in_progress" => "In progress",
"completed" => "Completed",
}.freeze
end
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
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)
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
include ActionView::Helpers::TextHelper
BULK_UPLOAD_COMPLETE_TEMPLATE_ID = "83279578-c890-4168-838b-33c9cf0dc9f0".freeze
BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID = "e27abcd4-5295-48c2-b127-e9ee4b781b75".freeze
BULK_UPLOAD_FAILED_FILE_SETUP_ERROR_TEMPLATE_ID = "24c9f4c7-96ad-470a-ba31-eb51b7cbafd9".freeze
BULK_UPLOAD_FAILED_SERVICE_ERROR_TEMPLATE_ID = "c3f6288c-7a74-4e77-99ee-6c4a0f6e125a".freeze
BULK_UPLOAD_WITH_ERRORS_TEMPLATE_ID = "eb539005-6234-404e-812d-167728cf4274".freeze
COMPLETE_TEMPLATE_ID = "83279578-c890-4168-838b-33c9cf0dc9f0".freeze
FAILED_CSV_ERRORS_TEMPLATE_ID = "e27abcd4-5295-48c2-b127-e9ee4b781b75".freeze
FAILED_FILE_SETUP_ERROR_TEMPLATE_ID = "24c9f4c7-96ad-470a-ba31-eb51b7cbafd9".freeze
FAILED_SERVICE_ERROR_TEMPLATE_ID = "c3f6288c-7a74-4e77-99ee-6c4a0f6e125a".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:)
url = if bulk_upload.lettings?
@ -22,7 +40,7 @@ class BulkUploadMailer < NotifyMailer
send_email(
user.email,
BULK_UPLOAD_COMPLETE_TEMPLATE_ID,
COMPLETE_TEMPLATE_ID,
{
title:,
filename: bulk_upload.filename,
@ -42,7 +60,7 @@ class BulkUploadMailer < NotifyMailer
send_email(
bulk_upload.user.email,
BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID,
FAILED_CSV_ERRORS_TEMPLATE_ID,
{
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
@ -54,34 +72,20 @@ class BulkUploadMailer < NotifyMailer
end
def send_bulk_upload_failed_file_setup_error_mail(bulk_upload:)
bulk_upload_link = if bulk_upload.lettings?
start_bulk_upload_lettings_logs_url
bulk_upload_link = if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors?
summary_bulk_upload_lettings_result_url(bulk_upload)
else
start_bulk_upload_sales_logs_url
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
send_email(
bulk_upload.user.email,
BULK_UPLOAD_FAILED_FILE_SETUP_ERROR_TEMPLATE_ID,
FAILED_FILE_SETUP_ERROR_TEMPLATE_ID,
{
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
lettings_or_sales: bulk_upload.log_type,
year_combo: bulk_upload.year_combo,
errors_list: errors.join("\n"),
bulk_upload_link:,
},
)
@ -96,7 +100,7 @@ class BulkUploadMailer < NotifyMailer
send_email(
bulk_upload.user.email,
BULK_UPLOAD_FAILED_SERVICE_ERROR_TEMPLATE_ID,
FAILED_SERVICE_ERROR_TEMPLATE_ID,
{
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
@ -107,26 +111,4 @@ class BulkUploadMailer < NotifyMailer
},
)
end
def send_bulk_upload_with_errors_mail(bulk_upload:)
count = bulk_upload.logs.where.not(status: %w[completed]).count
n_logs = pluralize(count, "log")
title = "We found #{n_logs} with errors"
error_description = "We created logs from your #{bulk_upload.year_combo} #{bulk_upload.log_type} data. There was a problem with #{count} of the logs. Click the below link to fix these logs."
send_email(
bulk_upload.user.email,
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

8
app/models/bulk_upload.rb

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

4
app/models/form_handler.rb

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

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

12
app/models/location_deactivation_period.rb

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

36
app/models/log.rb

@ -8,8 +8,16 @@ class Log < ApplicationRecord
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_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_years, lambda { |years, _user = nil|
@ -31,6 +39,8 @@ class Log < ApplicationRecord
}
scope :created_by, ->(user) { where(created_by: user) }
attr_accessor :skip_update_status
def process_uprn_change!
if uprn.present?
service = UprnClient.new(uprn)
@ -106,6 +116,16 @@ class Log < ApplicationRecord
end
end
def calculate_status
if all_fields_completed? && errors.empty?
"completed"
elsif all_fields_nil?
"not_started"
else
"in_progress"
end
end
private
def plural_gender_for_person(person_num)
@ -120,13 +140,9 @@ private
end
def update_status!
self.status = if all_fields_completed? && errors.empty?
"completed"
elsif all_fields_nil?
"not_started"
else
"in_progress"
end
return if skip_update_status
self.status = calculate_status
end
def all_fields_completed?
@ -201,8 +217,4 @@ private
self[is_inferred_key] = false
self[postcode_key] = nil
end
def format_as_currency(num_string)
ActionController::Base.helpers.number_to_currency(num_string, unit: "£")
end
end

8
app/models/organisation.rb

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

11
app/models/sales_log.rb

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

10
app/models/scheme_deactivation_period.rb

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

8
app/models/user.rb

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

29
app/models/validations/financial_validations.rb

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

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

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

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) }
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)
partner_numbers = (2..max_people).select { |n| person_is_partner?(record["relat#{n}"]) }
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.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
row_parser.log.save!

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

@ -42,7 +42,6 @@ class BulkUpload::Lettings::Validator
def create_logs?
return false if any_setup_errors?
return false if over_column_error_threshold?
return false if row_parsers.any?(&:block_log_creation?)
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_rent_type
validate :validate_valid_radio_option
def self.question_for_field(field)
QUESTIONS[field]
end
@ -386,6 +388,21 @@ class BulkUpload::Lettings::Year2022::RowParser
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
return if field_112.blank?
@ -772,6 +789,7 @@ private
cbl: %i[field_75],
chr: %i[field_76],
cap: %i[field_77],
letting_allocation: %i[field_75 field_76 field_77],
referral: %i[field_78],
@ -1254,7 +1272,7 @@ private
2
elsif field_60 == 1
3
elsif field_59&.zero?
elsif field_59.blank? || field_59&.zero?
1
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_related
validate :validate_valid_radio_option
def self.question_for_field(field)
QUESTIONS[field]
end
@ -388,6 +390,21 @@ class BulkUpload::Lettings::Year2023::RowParser
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
return if field_3.blank?
@ -750,6 +767,7 @@ private
cbl: %i[field_116],
chr: %i[field_118],
cap: %i[field_117],
letting_allocation: %i[field_116 field_117 field_118],
referral: %i[field_119],
@ -1177,7 +1195,7 @@ private
2
elsif field_88 == 1
3
elsif field_87&.zero?
elsif field_87.blank? || field_87&.zero?
1
end
end

33
app/services/bulk_upload/processor.rb

@ -16,10 +16,17 @@ class BulkUpload::Processor
send_setup_errors_mail
elsif validator.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
send_correct_and_upload_again_mail
send_correct_and_upload_again_mail # summary/full report
end
rescue StandardError => e
Sentry.capture_exception(e)
@ -28,23 +35,27 @@ class BulkUpload::Processor
downloader.delete_local_file!
end
def approve
bulk_upload.unpend
end
private
def send_setup_errors_mail
def send_how_fix_upload_mail
BulkUploadMailer
.send_bulk_upload_failed_file_setup_error_mail(bulk_upload:)
.send_how_fix_upload_mail(bulk_upload:)
.deliver_later
end
def send_correct_and_upload_again_mail
def send_setup_errors_mail
BulkUploadMailer
.send_correct_and_upload_again_mail(bulk_upload:)
.send_bulk_upload_failed_file_setup_error_mail(bulk_upload:)
.deliver_later
end
def send_fix_errors_mail
def send_correct_and_upload_again_mail
BulkUploadMailer
.send_bulk_upload_with_errors_mail(bulk_upload:)
.send_correct_and_upload_again_mail(bulk_upload:)
.deliver_later
end
@ -55,11 +66,11 @@ private
end
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
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
def send_failure_mail(errors: [])

2
app/services/csv/lettings_log_csv_service.rb

@ -1,6 +1,6 @@
module Csv
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:)
@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)
recent_export = LogsExport.order("started_at").last
if !full_update && recent_export
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
params = { to: start_time }
LettingsLog.where("updated_at <= :to", params)
LettingsLog.visible.where("updated_at <= :to", params)
end
end

2
app/services/imports/lettings_logs_import_service.rb

@ -313,7 +313,7 @@ module Imports
attribute, _type = error
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)
end
@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 %>
<%= govuk_back_link href: @form.back_path %>
<% end %>
<div class="govuk-grid-row">
<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| %>

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

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

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

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

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

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

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

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

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

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

4
config/initializers/feature_toggle.rb

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

27
config/locales/en.yml

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

11
config/routes.rb

@ -144,6 +144,15 @@ Rails.application.routes.draw do
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"
end
@ -155,6 +164,7 @@ Rails.application.routes.draw do
FormHandler.instance.lettings_forms.each do |_key, form|
form.pages.map do |page|
get page.id.to_s.dasherize, to: "form#show_page"
post page.id.to_s.dasherize, to: "form#submit_form"
end
form.subsections.map do |subsection|
@ -181,6 +191,7 @@ Rails.application.routes.draw do
FormHandler.instance.sales_forms.each do |_key, form|
form.pages.map do |page|
get page.id.to_s.dasherize, to: "form#show_page"
post page.id.to_s.dasherize, to: "form#submit_form"
end
form.subsections.map do |subsection|

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

4
spec/features/form/page_routing_spec.rb

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

4
spec/features/schemes_spec.rb

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

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},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,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,,,,,,,,,,,,,,,,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},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,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,,,,,,,,,,,,,,,,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
before do
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
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")
end
let(:expected_errors) do
[
"- What is the letting type? (Column A)",
"- Management group code (Column E)",
]
end
it "sends correctly formed email" do
expect(notify_client).to receive(:send_email).with(
email_address: bulk_upload.user.email,
template_id: described_class::BULK_UPLOAD_FAILED_FILE_SETUP_ERROR_TEMPLATE_ID,
template_id: described_class::FAILED_FILE_SETUP_ERROR_TEMPLATE_ID,
personalisation: {
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
lettings_or_sales: bulk_upload.log_type,
year_combo: bulk_upload.year_combo,
errors_list: expected_errors.join("\n"),
bulk_upload_link: start_bulk_upload_lettings_logs_url,
bulk_upload_link: summary_bulk_upload_lettings_result_url(bulk_upload),
},
)
@ -48,7 +40,7 @@ RSpec.describe BulkUploadMailer do
it "sends correctly formed email" do
expect(notify_client).to receive(:send_email).with(
email_address: user.email,
template_id: described_class::BULK_UPLOAD_COMPLETE_TEMPLATE_ID,
template_id: described_class::COMPLETE_TEMPLATE_ID,
personalisation: {
title: "You’ve successfully uploaded 0 logs",
filename: bulk_upload.filename,
@ -66,7 +58,7 @@ RSpec.describe BulkUploadMailer do
it "sends correctly formed email" do
expect(notify_client).to receive(:send_email).with(
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: {
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
@ -81,34 +73,6 @@ RSpec.describe BulkUploadMailer do
end
end
context "when bulk upload has log which is not completed" do
before do
create(:lettings_log, :in_progress, bulk_upload:)
end
describe "#send_bulk_upload_with_errors_mail" do
let(:error_description) do
"We created logs from your 2022/23 lettings data. There was a problem with 1 of the logs. Click the below link to fix these logs."
end
it "sends correctly formed email" do
expect(notify_client).to receive(:send_email).with(
email_address: bulk_upload.user.email,
template_id: described_class::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
context "when 2 columns with errors" do
before do
@ -119,7 +83,7 @@ RSpec.describe BulkUploadMailer do
it "sends correctly formed email" do
expect(notify_client).to receive(:send_email).with(
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: {
filename: bulk_upload.filename,
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
it "returns weekly soft min for 52 weeks" do
lettings_log.period = 1
expect(lettings_log.soft_min_for_period).to eq("100.0 every week")
expect(lettings_log.soft_min_for_period).to eq("£100.00 every week")
end
it "returns weekly soft max for 52 weeks" do
lettings_log.period = 1
expect(lettings_log.soft_max_for_period).to eq("400.0 every week")
expect(lettings_log.soft_max_for_period).to eq("£400.00 every week")
end
end
context "when period is weekly for 47 weeks" do
it "returns weekly soft min for 47 weeks" do
lettings_log.period = 8
expect(lettings_log.soft_min_for_period).to eq("110.64 every week")
expect(lettings_log.soft_min_for_period).to eq("£110.64 every week")
end
it "returns weekly soft max for 47 weeks" do
lettings_log.period = 8
expect(lettings_log.soft_max_for_period).to eq("442.55 every week")
expect(lettings_log.soft_max_for_period).to eq("£442.55 every week")
end
end
end

83
spec/models/location_deactivation_period_spec.rb

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

5
spec/models/organisation_spec.rb

@ -169,11 +169,6 @@ RSpec.describe Organisation, type: :model do
it "has lettings logs" do
expect(organisation.lettings_logs.to_a).to match_array([owned_lettings_log, managed_lettings_log])
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

2
spec/models/sales_log_spec.rb

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

68
spec/models/scheme_deactivation_period_spec.rb

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

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

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

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

30
spec/models/validations/shared_validations_spec.rb

@ -113,34 +113,4 @@ RSpec.describe Validations::SharedValidations do
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

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

33
spec/requests/lettings_logs_controller_spec.rb

@ -224,6 +224,15 @@ RSpec.describe LettingsLogsController, type: :request do
tenancycode: "UA984",
)
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
let(:headers) { { "Accept" => "text/html" } }
@ -261,6 +270,7 @@ RSpec.describe LettingsLogsController, type: :request do
get "/lettings-logs", headers:, params: {}
expect(page).to have_content("LC783")
expect(page).to have_content("UA984")
expect(page).not_to have_content(pending_lettings_log.tenancycode)
end
it "displays CSV download links with the correct paths" do
@ -841,6 +851,24 @@ RSpec.describe LettingsLogsController, type: :request do
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
let(:headers) { { "Accept" => "text/html" } }
@ -1319,9 +1347,12 @@ RSpec.describe LettingsLogsController, type: :request do
end
context "when a lettings log deletion fails" do
let(:mock_scope) { instance_double("LettingsLog::ActiveRecord_Relation", find_by: lettings_log) }
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)
delete "/lettings-logs/#{id}", headers:
end

5
spec/requests/organisations_controller_spec.rb

@ -591,6 +591,7 @@ RSpec.describe OrganisationsController, type: :request do
before do
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)
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
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}"
end
@ -1155,6 +1157,7 @@ RSpec.describe OrganisationsController, type: :request do
before do
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)
end

16
spec/requests/sales_logs_controller_spec.rb

@ -159,6 +159,22 @@ RSpec.describe SalesLogsController, type: :request do
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 "with status filter" do
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)
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
service.call
@ -77,6 +82,23 @@ RSpec.describe BulkUpload::Lettings::LogCreator do
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
xit "what should happen?"
end

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

@ -273,58 +273,5 @@ RSpec.describe BulkUpload::Lettings::Validator do
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

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

@ -1472,6 +1472,16 @@ RSpec.describe BulkUpload::Lettings::Year2022::RowParser do
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
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
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(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: nil) }
@ -223,11 +223,11 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
}
end
it "returns true" do
xit "returns true" do
expect(parser).to be_valid
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|
parser.send(:log).optional_fields.include?(q.id) || q.completed?(parser.send(:log))
end
@ -504,6 +504,14 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
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
describe "#field_83, #field_84, #field_85" do
@ -790,6 +798,16 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
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
context "when null" do
let(:attributes) { setup_section_params.merge({ field_30: nil }) }
@ -1468,6 +1486,16 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
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
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
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
instance_double(
BulkUpload::Downloader,
call: nil,
path: file_fixture("2022_23_lettings_bulk_upload.csv"),
path:,
delete_local_file!: nil,
)
end
let(:mock_validator) do
instance_double(
BulkUpload::Lettings::Validator,
invalid?: false,
call: nil,
any_setup_errors?: false,
create_logs?: true,
let(:file) { Tempfile.new }
let(:path) { file.path }
let(:log) do
build(
:lettings_log,
: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
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::Lettings::Validator).to receive(:new).and_return(mock_validator)
end
it "deletes the local file afterwards" do
processor.call
expect(mock_downloader).to have_received(:delete_local_file!)
it "creates logs as not pending" do
expect { processor.call }.to change(LettingsLog.completed, :count).by(1)
end
it "sends fix errors email" do
it "sends success email" do
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
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)
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
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
instance_double(
BulkUpload::Downloader,
call: nil,
path: file_fixture("2022_23_lettings_bulk_upload.csv"),
path:,
delete_local_file!: nil,
)
end
let(:mock_validator) do
instance_double(
BulkUpload::Lettings::Validator,
invalid?: false,
call: nil,
any_setup_errors?: false,
create_logs?: false,
let(:file) { Tempfile.new }
let(:path) { file.path }
let(:log) do
LettingsLog.new(
lettype: 2,
renttype: 3,
owning_organisation: owning_org,
managing_organisation: owning_org,
startdate: Time.zone.local(2022, 10, 1),
renewal: 2,
)
end
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::Lettings::Validator).to receive(:new).and_return(mock_validator)
end
it "deletes the local file afterwards" do
processor.call
expect(mock_downloader).to have_received(:delete_local_file!)
it "creates pending log" do
expect { processor.call }.to change(LettingsLog.pending, :count).by(1)
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)
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
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)
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
context "when processing a bulk with perfect data" do
let(:file) { Tempfile.new }
let(:path) { file.path }
context "when upload has no setup errors something blocks log creation" do
let(:mock_downloader) do
instance_double(
BulkUpload::Downloader,
@ -253,24 +244,20 @@ RSpec.describe BulkUpload::Processor do
)
end
let(:file) { Tempfile.new }
let(:path) { file.path }
let(:other_user) { create(:user) }
let(:log) do
build(
:lettings_log,
:completed,
LettingsLog.new(
lettype: 2,
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,
startdate: Time.zone.local(2022, 10, 1),
renewal: 2,
created_by: other_user, # unaffiliated user
)
end
@ -281,28 +268,26 @@ RSpec.describe BulkUpload::Processor do
allow(BulkUpload::Downloader).to receive(:new).with(bulk_upload:).and_return(mock_downloader)
end
it "creates logs" 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
it "sends correct_and_upload_again_mail" do
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
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)
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

1
spec/services/csv/lettings_log_csv_service_spec.rb

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

29
spec/services/exports/lettings_log_export_service_spec.rb

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

2
spec/support/bulk_upload/log_to_csv.rb

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

Loading…
Cancel
Save