Browse Source

CLDC-2135 Bulk upload resume with how fix (#1503)

* add first page for bulk upload resume journey

* bulk upload resume handles upload again

* add confirm page to bulk upload resume journey

* replace placeholder count with correct value

* apply recommendation for bulk upload resume choice

* add how to fix bulk upload mailer

* integrate new bulk upload approve journey

* add missing bulk upload error mappings

* remove test

* prevent approve being called multiple times

* bulk upload creates invisible logs ahead of time

* work invisible logs into bulk upload flow

* sort errors so deterministic

* remove unused ensure

* remove expected_log_count and processed

- these fields are no longer used or needed

* introduce pending status

* swap visible for pending logs

* only show visible lettings logs

* hard code status filters

* remove unused model methods

* only show visible sales logs

* form controller ignores hidden logs

* locations and schemes only affect visible logs
pull/1522/head
Phil Lee 2 years ago committed by GitHub
parent
commit
454df8389e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 42
      app/controllers/bulk_upload_lettings_resume_controller.rb
  2. 8
      app/controllers/form_controller.rb
  3. 4
      app/controllers/lettings_logs_controller.rb
  4. 4
      app/controllers/locations_controller.rb
  5. 4
      app/controllers/organisations_controller.rb
  6. 4
      app/controllers/sales_logs_controller.rb
  7. 4
      app/controllers/schemes_controller.rb
  8. 8
      app/helpers/filters_helper.rb
  9. 2
      app/jobs/email_csv_job.rb
  10. 39
      app/mailers/bulk_upload_mailer.rb
  11. 8
      app/models/bulk_upload.rb
  12. 30
      app/models/forms/bulk_upload_lettings_resume/confirm.rb
  13. 53
      app/models/forms/bulk_upload_lettings_resume/fix_choice.rb
  14. 12
      app/models/lettings_log.rb
  15. 32
      app/models/log.rb
  16. 8
      app/models/organisation.rb
  17. 8
      app/models/sales_log.rb
  18. 8
      app/models/user.rb
  19. 3
      app/services/bulk_upload/lettings/log_creator.rb
  20. 1
      app/services/bulk_upload/lettings/validator.rb
  21. 1
      app/services/bulk_upload/lettings/year2022/row_parser.rb
  22. 1
      app/services/bulk_upload/lettings/year2023/row_parser.rb
  23. 27
      app/services/bulk_upload/processor.rb
  24. 2
      app/services/csv/lettings_log_csv_service.rb
  25. 5
      app/services/exports/lettings_log_export_service.rb
  26. 2
      app/services/imports/lettings_logs_import_service.rb
  27. 1
      app/views/bulk_upload_lettings_logs/forms/needstype.erb
  28. 22
      app/views/bulk_upload_lettings_resume/confirm.html.erb
  29. 36
      app/views/bulk_upload_lettings_resume/fix_choice.html.erb
  30. 5
      config/locales/en.yml
  31. 9
      config/routes.rb
  32. 5
      db/migrate/20230331094840_add_status_cache_to_lettings_log.rb
  33. 3
      db/schema.rb
  34. 4
      spec/fixtures/files/lettings_logs_download.csv
  35. 4
      spec/fixtures/files/lettings_logs_download_codes_only.csv
  36. 1
      spec/jobs/email_csv_job_spec.rb
  37. 10
      spec/mailers/bulk_upload_mailer_spec.rb
  38. 5
      spec/models/organisation_spec.rb
  39. 5
      spec/models/user_spec.rb
  40. 84
      spec/requests/bulk_upload_lettings_resume_controller_spec.rb
  41. 17
      spec/requests/form_controller_spec.rb
  42. 33
      spec/requests/lettings_logs_controller_spec.rb
  43. 5
      spec/requests/organisations_controller_spec.rb
  44. 16
      spec/requests/sales_logs_controller_spec.rb
  45. 22
      spec/services/bulk_upload/lettings/log_creator_spec.rb
  46. 53
      spec/services/bulk_upload/lettings/validator_spec.rb
  47. 167
      spec/services/bulk_upload/processor_spec.rb
  48. 1
      spec/services/csv/lettings_log_csv_service_spec.rb
  49. 29
      spec/services/exports/lettings_log_export_service_spec.rb
  50. 20
      spec/services/imports/lettings_logs_import_service_spec.rb
  51. 2
      spec/support/bulk_upload/log_to_csv.rb

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

8
app/controllers/form_controller.rb

@ -108,17 +108,17 @@ private
def find_resource def find_resource
@log = if params.key?("sales_log") @log = if params.key?("sales_log")
current_user.sales_logs.find_by(id: params[:id]) current_user.sales_logs.visible.find_by(id: params[:id])
else else
current_user.lettings_logs.find_by(id: params[:id]) current_user.lettings_logs.visible.find_by(id: params[:id])
end end
end end
def find_resource_by_named_id def find_resource_by_named_id
@log = if params[:sales_log_id].present? @log = if params[:sales_log_id].present?
current_user.sales_logs.find_by(id: params[:sales_log_id]) current_user.sales_logs.visible.find_by(id: params[:sales_log_id])
else else
current_user.lettings_logs.find_by(id: params[:lettings_log_id]) current_user.lettings_logs.visible.find_by(id: params[:lettings_log_id])
end end
end end

4
app/controllers/lettings_logs_controller.rb

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

4
app/controllers/locations_controller.rb

@ -158,7 +158,7 @@ class LocationsController < ApplicationController
end end
def deactivate_confirm def deactivate_confirm
@affected_logs = @location.lettings_logs.filter_by_before_startdate(params[:deactivation_date]) @affected_logs = @location.lettings_logs.visible.filter_by_before_startdate(params[:deactivation_date])
if @affected_logs.count.zero? if @affected_logs.count.zero?
deactivate deactivate
else else
@ -260,7 +260,7 @@ private
end end
def reset_location_and_scheme_for_logs! def reset_location_and_scheme_for_logs!
logs = @location.lettings_logs.filter_by_before_startdate(params[:deactivation_date].to_time) logs = @location.lettings_logs.visible.filter_by_before_startdate(params[:deactivation_date].to_time)
logs.update!(location: nil, scheme: nil, unresolved: true) logs.update!(location: nil, scheme: nil, unresolved: true)
logs logs
end end

4
app/controllers/organisations_controller.rb

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

4
app/controllers/sales_logs_controller.rb

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

4
app/controllers/schemes_controller.rb

@ -39,7 +39,7 @@ class SchemesController < ApplicationController
end end
def deactivate_confirm def deactivate_confirm
@affected_logs = @scheme.lettings_logs.filter_by_before_startdate(params[:deactivation_date]) @affected_logs = @scheme.lettings_logs.visible.filter_by_before_startdate(params[:deactivation_date])
if @affected_logs.count.zero? if @affected_logs.count.zero?
deactivate deactivate
else else
@ -310,7 +310,7 @@ private
end end
def reset_location_and_scheme_for_logs! def reset_location_and_scheme_for_logs!
logs = @scheme.lettings_logs.filter_by_before_startdate(params[:deactivation_date].to_time) logs = @scheme.lettings_logs.visible.filter_by_before_startdate(params[:deactivation_date].to_time)
logs.update!(location: nil, scheme: nil, unresolved: true) logs.update!(location: nil, scheme: nil, unresolved: true)
logs logs
end end

8
app/helpers/filters_helper.rb

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

2
app/jobs/email_csv_job.rb

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

39
app/mailers/bulk_upload_mailer.rb

@ -1,11 +1,30 @@
class BulkUploadMailer < NotifyMailer class BulkUploadMailer < NotifyMailer
include ActionView::Helpers::TextHelper include ActionView::Helpers::TextHelper
BULK_UPLOAD_COMPLETE_TEMPLATE_ID = "83279578-c890-4168-838b-33c9cf0dc9f0".freeze COMPLETE_TEMPLATE_ID = "83279578-c890-4168-838b-33c9cf0dc9f0".freeze
BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID = "e27abcd4-5295-48c2-b127-e9ee4b781b75".freeze FAILED_CSV_ERRORS_TEMPLATE_ID = "e27abcd4-5295-48c2-b127-e9ee4b781b75".freeze
BULK_UPLOAD_FAILED_FILE_SETUP_ERROR_TEMPLATE_ID = "24c9f4c7-96ad-470a-ba31-eb51b7cbafd9".freeze FAILED_FILE_SETUP_ERROR_TEMPLATE_ID = "24c9f4c7-96ad-470a-ba31-eb51b7cbafd9".freeze
BULK_UPLOAD_FAILED_SERVICE_ERROR_TEMPLATE_ID = "c3f6288c-7a74-4e77-99ee-6c4a0f6e125a".freeze FAILED_SERVICE_ERROR_TEMPLATE_ID = "c3f6288c-7a74-4e77-99ee-6c4a0f6e125a".freeze
BULK_UPLOAD_WITH_ERRORS_TEMPLATE_ID = "eb539005-6234-404e-812d-167728cf4274".freeze WITH_ERRORS_TEMPLATE_ID = "eb539005-6234-404e-812d-167728cf4274".freeze
HOW_FIX_UPLOAD_TEMPLATE_ID = "21a07b26-f625-4846-9f4d-39e30937aa24".freeze
def send_how_fix_upload_mail(bulk_upload:)
title = "We found #{pluralize(bulk_upload.bulk_upload_errors.count, 'error')} in your bulk upload"
description = "There was a problem with your #{bulk_upload.year_combo} #{bulk_upload.log_type} data. Check the error report below to fix these errors."
cta_link = start_bulk_upload_lettings_resume_url(bulk_upload)
send_email(
bulk_upload.user.email,
HOW_FIX_UPLOAD_TEMPLATE_ID,
{
title:,
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
description:,
cta_link:,
},
)
end
def send_bulk_upload_complete_mail(user:, bulk_upload:) def send_bulk_upload_complete_mail(user:, bulk_upload:)
url = if bulk_upload.lettings? url = if bulk_upload.lettings?
@ -22,7 +41,7 @@ class BulkUploadMailer < NotifyMailer
send_email( send_email(
user.email, user.email,
BULK_UPLOAD_COMPLETE_TEMPLATE_ID, COMPLETE_TEMPLATE_ID,
{ {
title:, title:,
filename: bulk_upload.filename, filename: bulk_upload.filename,
@ -42,7 +61,7 @@ class BulkUploadMailer < NotifyMailer
send_email( send_email(
bulk_upload.user.email, bulk_upload.user.email,
BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID, FAILED_CSV_ERRORS_TEMPLATE_ID,
{ {
filename: bulk_upload.filename, filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
@ -75,7 +94,7 @@ class BulkUploadMailer < NotifyMailer
send_email( send_email(
bulk_upload.user.email, bulk_upload.user.email,
BULK_UPLOAD_FAILED_FILE_SETUP_ERROR_TEMPLATE_ID, FAILED_FILE_SETUP_ERROR_TEMPLATE_ID,
{ {
filename: bulk_upload.filename, filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
@ -96,7 +115,7 @@ class BulkUploadMailer < NotifyMailer
send_email( send_email(
bulk_upload.user.email, bulk_upload.user.email,
BULK_UPLOAD_FAILED_SERVICE_ERROR_TEMPLATE_ID, FAILED_SERVICE_ERROR_TEMPLATE_ID,
{ {
filename: bulk_upload.filename, filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
@ -119,7 +138,7 @@ class BulkUploadMailer < NotifyMailer
send_email( send_email(
bulk_upload.user.email, bulk_upload.user.email,
BULK_UPLOAD_WITH_ERRORS_TEMPLATE_ID, WITH_ERRORS_TEMPLATE_ID,
{ {
title:, title:,
filename: bulk_upload.filename, filename: bulk_upload.filename,

8
app/models/bulk_upload.rb

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

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

12
app/models/lettings_log.rb

@ -115,18 +115,6 @@ class LettingsLog < Log
end end
end end
def completed?
status == "completed"
end
def not_started?
status == "not_started"
end
def in_progress?
status == "in_progress"
end
def weekly_net_income def weekly_net_income
return unless earnings && incfreq return unless earnings && incfreq

32
app/models/log.rb

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

8
app/models/organisation.rb

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

8
app/models/sales_log.rb

@ -104,14 +104,6 @@ class SalesLog < Log
collection_start_year < 2023 collection_start_year < 2023
end end
def not_started?
status == "not_started"
end
def completed?
status == "completed"
end
def setup_completed? def setup_completed?
form.setup_sections.all? { |sections| sections.subsections.all? { |subsection| subsection.status(self) == :completed } } form.setup_sections.all? { |sections| sections.subsections.all? { |subsection| subsection.status(self) == :completed } }
end end

8
app/models/user.rb

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

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

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

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

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

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

@ -789,6 +789,7 @@ private
cbl: %i[field_75], cbl: %i[field_75],
chr: %i[field_76], chr: %i[field_76],
cap: %i[field_77], cap: %i[field_77],
letting_allocation: %i[field_75 field_76 field_77],
referral: %i[field_78], referral: %i[field_78],

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

@ -767,6 +767,7 @@ private
cbl: %i[field_116], cbl: %i[field_116],
chr: %i[field_118], chr: %i[field_118],
cap: %i[field_117], cap: %i[field_117],
letting_allocation: %i[field_116 field_117 field_118],
referral: %i[field_119], referral: %i[field_119],

27
app/services/bulk_upload/processor.rb

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

2
app/services/csv/lettings_log_csv_service.rb

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

5
app/services/exports/lettings_log_export_service.rb

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

2
app/services/imports/lettings_logs_import_service.rb

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

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

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

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>

5
config/locales/en.yml

@ -68,6 +68,11 @@ en:
attributes: attributes:
needstype: needstype:
blank: You must answer needs type blank: You must answer needs type
forms/bulk_upload_lettings_resume/fix_choice:
attributes:
choice:
blank: You must select how would you like to fix errors
inclusion: You must select one of the following options for how would like to fix errors
activerecord: activerecord:
errors: errors:

9
config/routes.rb

@ -144,6 +144,15 @@ Rails.application.routes.draw do
end end
end end
resources :bulk_upload_lettings_resume, path: "bulk-upload-resume", only: %i[show update] do
member do
get :start
get "*page", to: "bulk_upload_lettings_resume#show", as: "page"
patch "*page", to: "bulk_upload_lettings_resume#update"
end
end
get "update-logs", to: "lettings_logs#update_logs" get "update-logs", to: "lettings_logs#update_logs"
end end

5
db/migrate/20230331094840_add_status_cache_to_lettings_log.rb

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

3
db/schema.rb

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

4
spec/fixtures/files/lettings_logs_download.csv vendored

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

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

4
spec/fixtures/files/lettings_logs_download_codes_only.csv vendored

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

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

1
spec/jobs/email_csv_job_spec.rb

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

10
spec/mailers/bulk_upload_mailer_spec.rb

@ -29,7 +29,7 @@ RSpec.describe BulkUploadMailer do
it "sends correctly formed email" do it "sends correctly formed email" do
expect(notify_client).to receive(:send_email).with( expect(notify_client).to receive(:send_email).with(
email_address: bulk_upload.user.email, email_address: bulk_upload.user.email,
template_id: described_class::BULK_UPLOAD_FAILED_FILE_SETUP_ERROR_TEMPLATE_ID, template_id: described_class::FAILED_FILE_SETUP_ERROR_TEMPLATE_ID,
personalisation: { personalisation: {
filename: bulk_upload.filename, filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
@ -48,7 +48,7 @@ RSpec.describe BulkUploadMailer do
it "sends correctly formed email" do it "sends correctly formed email" do
expect(notify_client).to receive(:send_email).with( expect(notify_client).to receive(:send_email).with(
email_address: user.email, email_address: user.email,
template_id: described_class::BULK_UPLOAD_COMPLETE_TEMPLATE_ID, template_id: described_class::COMPLETE_TEMPLATE_ID,
personalisation: { personalisation: {
title: "You’ve successfully uploaded 0 logs", title: "You’ve successfully uploaded 0 logs",
filename: bulk_upload.filename, filename: bulk_upload.filename,
@ -66,7 +66,7 @@ RSpec.describe BulkUploadMailer do
it "sends correctly formed email" do it "sends correctly formed email" do
expect(notify_client).to receive(:send_email).with( expect(notify_client).to receive(:send_email).with(
email_address: user.email, email_address: user.email,
template_id: described_class::BULK_UPLOAD_FAILED_SERVICE_ERROR_TEMPLATE_ID, template_id: described_class::FAILED_SERVICE_ERROR_TEMPLATE_ID,
personalisation: { personalisation: {
filename: bulk_upload.filename, filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
@ -94,7 +94,7 @@ RSpec.describe BulkUploadMailer do
it "sends correctly formed email" do it "sends correctly formed email" do
expect(notify_client).to receive(:send_email).with( expect(notify_client).to receive(:send_email).with(
email_address: bulk_upload.user.email, email_address: bulk_upload.user.email,
template_id: described_class::BULK_UPLOAD_WITH_ERRORS_TEMPLATE_ID, template_id: described_class::WITH_ERRORS_TEMPLATE_ID,
personalisation: { personalisation: {
title: "We found 1 log with errors", title: "We found 1 log with errors",
filename: bulk_upload.filename, filename: bulk_upload.filename,
@ -119,7 +119,7 @@ RSpec.describe BulkUploadMailer do
it "sends correctly formed email" do it "sends correctly formed email" do
expect(notify_client).to receive(:send_email).with( expect(notify_client).to receive(:send_email).with(
email_address: user.email, email_address: user.email,
template_id: described_class::BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID, template_id: described_class::FAILED_CSV_ERRORS_TEMPLATE_ID,
personalisation: { personalisation: {
filename: bulk_upload.filename, filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),

5
spec/models/organisation_spec.rb

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

5
spec/models/user_spec.rb

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

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

17
spec/requests/form_controller_spec.rb

@ -321,6 +321,23 @@ RSpec.describe FormController, type: :request do
get "/sales-logs/#{log.id}/review", headers: headers, params: { sales_log: true } get "/sales-logs/#{log.id}/review", headers: headers, params: { sales_log: true }
expect(response.body).to match("Review sales log") expect(response.body).to match("Review sales log")
end end
context "when log is pending" do
let(:pending_log) do
create(
:lettings_log,
owning_organisation: organisation,
created_by: user,
status: "pending",
skip_update_status: true,
)
end
it "does not render pending log and returns 404" do
get "/lettings-logs/#{pending_log.id}/review", headers: headers, params: {}
expect(response).to be_not_found
end
end
end end
context "when viewing a user dependent page" do context "when viewing a user dependent page" do

33
spec/requests/lettings_logs_controller_spec.rb

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

5
spec/requests/organisations_controller_spec.rb

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

16
spec/requests/sales_logs_controller_spec.rb

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

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

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

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

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

167
spec/services/bulk_upload/processor_spec.rb

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

1
spec/services/csv/lettings_log_csv_service_spec.rb

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

29
spec/services/exports/lettings_log_export_service_spec.rb

@ -59,6 +59,35 @@ RSpec.describe Exports::LettingsLogExportService do
end end
end end
context "when one pending lettings log exists" do
before do
FactoryBot.create(
:lettings_log,
:completed,
status: "pending",
skip_update_status: true,
propcode: "123",
ppostcode_full: "SE2 6RT",
postcode_full: "NW1 5TY",
tenancycode: "BZ737",
startdate: Time.zone.local(2022, 2, 2, 10, 36, 49),
voiddate: Time.zone.local(2019, 11, 3),
mrcdate: Time.zone.local(2020, 5, 5, 10, 36, 49),
tenancylength: 5,
underoccupation_benefitcap: 4,
)
end
it "generates a master manifest with CSV headers but no data" do
actual_content = nil
expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\n"
allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
export_service.export_xml_lettings_logs
expect(actual_content).to eq(expected_content)
end
end
context "and one lettings log is available for export" do context "and one lettings log is available for export" do
let!(:lettings_log) { FactoryBot.create(:lettings_log, :completed, propcode: "123", ppostcode_full: "SE2 6RT", postcode_full: "NW1 5TY", tenancycode: "BZ737", startdate: Time.zone.local(2022, 2, 2, 10, 36, 49), voiddate: Time.zone.local(2019, 11, 3), mrcdate: Time.zone.local(2020, 5, 5, 10, 36, 49), tenancylength: 5, underoccupation_benefitcap: 4) } let!(:lettings_log) { FactoryBot.create(:lettings_log, :completed, propcode: "123", ppostcode_full: "SE2 6RT", postcode_full: "NW1 5TY", tenancycode: "BZ737", startdate: Time.zone.local(2022, 2, 2, 10, 36, 49), voiddate: Time.zone.local(2019, 11, 3), mrcdate: Time.zone.local(2020, 5, 5, 10, 36, 49), tenancylength: 5, underoccupation_benefitcap: 4) }

20
spec/services/imports/lettings_logs_import_service_spec.rb

@ -532,11 +532,11 @@ RSpec.describe Imports::LettingsLogsImportService do
end end
it "intercepts the relevant validation error" do it "intercepts the relevant validation error" do
expect(logger).to receive(:warn).with(/Removing brent with error: Enter a total charge that is at least £10 per week, Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?/) expect(logger).to receive(:warn).with("Log 0b4a68df-30cc-474a-93c0-a56ce8fdad3b: Removing brent with error: Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?’, Enter a total charge that is at least £10 per week")
expect(logger).to receive(:warn).with(/Removing scharge with error: Enter a total charge that is at least £10 per week, Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?/) expect(logger).to receive(:warn).with("Log 0b4a68df-30cc-474a-93c0-a56ce8fdad3b: Removing scharge with error: Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?’, Enter a total charge that is at least £10 per week")
expect(logger).to receive(:warn).with(/Removing pscharge with error: Enter a total charge that is at least £10 per week, Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?/) expect(logger).to receive(:warn).with("Log 0b4a68df-30cc-474a-93c0-a56ce8fdad3b: Removing pscharge with error: Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?’, Enter a total charge that is at least £10 per week")
expect(logger).to receive(:warn).with(/Removing supcharg with error: Enter a total charge that is at least £10 per week, Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?/) expect(logger).to receive(:warn).with("Log 0b4a68df-30cc-474a-93c0-a56ce8fdad3b: Removing supcharg with error: Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?’, Enter a total charge that is at least £10 per week")
expect(logger).to receive(:warn).with(/Removing tcharge with error: Enter a total charge that is at least £10 per week, Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?/) expect(logger).to receive(:warn).with("Log 0b4a68df-30cc-474a-93c0-a56ce8fdad3b: Removing tcharge with error: Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?’, Enter a total charge that is at least £10 per week")
expect { lettings_log_service.send(:create_log, lettings_log_xml) } expect { lettings_log_service.send(:create_log, lettings_log_xml) }
.not_to raise_error .not_to raise_error
end end
@ -560,11 +560,11 @@ RSpec.describe Imports::LettingsLogsImportService do
end end
it "intercepts the relevant validation error" do it "intercepts the relevant validation error" do
expect(logger).to receive(:warn).with(/Removing brent with error: Enter an amount above 0, Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Service charge must be at least £0 every week/) expect(logger).to receive(:warn).with(/Removing brent with error: Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Enter an amount above 0, Service charge must be at least £0 every week/)
expect(logger).to receive(:warn).with(/Removing scharge with error: Enter an amount above 0, Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Service charge must be at least £0 every week/) expect(logger).to receive(:warn).with(/Removing scharge with error: Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Enter an amount above 0, Service charge must be at least £0 every week/)
expect(logger).to receive(:warn).with(/Removing pscharge with error: Enter an amount above 0, Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Service charge must be at least £0 every week/) expect(logger).to receive(:warn).with(/Removing pscharge with error: Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Enter an amount above 0, Service charge must be at least £0 every week/)
expect(logger).to receive(:warn).with(/Removing supcharg with error: Enter an amount above 0, Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Service charge must be at least £0 every week/) expect(logger).to receive(:warn).with(/Removing supcharg with error: Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Enter an amount above 0, Service charge must be at least £0 every week/)
expect(logger).to receive(:warn).with(/Removing tcharge with error: Enter an amount above 0, Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Service charge must be at least £0 every week/) expect(logger).to receive(:warn).with(/Removing tcharge with error: Enter a value for the service charge between £0 and £480 per week if the landlord is a private registered provider and it is a supported housing letting, Enter an amount above 0, Service charge must be at least £0 every week/)
expect { lettings_log_service.send(:create_log, lettings_log_xml) } expect { lettings_log_service.send(:create_log, lettings_log_xml) }
.not_to raise_error .not_to raise_error
end end

2
spec/support/bulk_upload/log_to_csv.rb

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

Loading…
Cancel
Save