Browse Source

Merge branch 'update-dockerfile' into CLDC-3787-Autocomplete-address-search

pull/2902/head
Manny Dinssa 3 weeks ago
parent
commit
37b4c3afd9
  1. 2
      Dockerfile
  2. 5
      app/components/create_log_actions_component.html.erb
  3. 4
      app/components/create_log_actions_component.rb
  4. 10
      app/controllers/bulk_upload_lettings_logs_controller.rb
  5. 8
      app/controllers/bulk_upload_lettings_resume_controller.rb
  6. 6
      app/controllers/bulk_upload_lettings_soft_validations_check_controller.rb
  7. 10
      app/controllers/bulk_upload_sales_logs_controller.rb
  8. 8
      app/controllers/bulk_upload_sales_resume_controller.rb
  9. 6
      app/controllers/bulk_upload_sales_soft_validations_check_controller.rb
  10. 1
      app/controllers/form_controller.rb
  11. 14
      app/controllers/lettings_logs_controller.rb
  12. 14
      app/controllers/sales_logs_controller.rb
  13. 68
      app/controllers/test_data_controller.rb
  14. 34
      app/frontend/styles/_custom-rails-admin.scss
  15. 1
      app/frontend/styles/application.scss
  16. 14
      app/helpers/bulk_upload/lettings_log_to_csv.rb
  17. 12
      app/helpers/bulk_upload/sales_log_to_csv.rb
  18. 2
      app/helpers/tag_helper.rb
  19. 4
      app/models/bulk_upload.rb
  20. 2
      app/models/derived_variables/lettings_log_variables.rb
  21. 2
      app/models/derived_variables/sales_log_variables.rb
  22. 12
      app/models/form/lettings/pages/person_lead_partner.rb
  23. 19
      app/models/form/lettings/pages/rent_4_weekly.rb
  24. 19
      app/models/form/lettings/pages/rent_bi_weekly.rb
  25. 19
      app/models/form/lettings/pages/rent_monthly.rb
  26. 19
      app/models/form/lettings/pages/rent_weekly.rb
  27. 30
      app/models/form/lettings/questions/person_partner.rb
  28. 22
      app/models/form/lettings/subsections/household_characteristics.rb
  29. 22
      app/models/form/lettings/subsections/income_and_benefits.rb
  30. 2
      app/models/form/question.rb
  31. 8
      app/models/form_handler.rb
  32. 9
      app/models/forms/bulk_upload_form/checking_file.rb
  33. 13
      app/models/forms/bulk_upload_form/guidance.rb
  34. 21
      app/models/forms/bulk_upload_form/prepare_your_file.rb
  35. 13
      app/models/forms/bulk_upload_form/upload_your_file.rb
  36. 17
      app/models/forms/bulk_upload_form/year.rb
  37. 60
      app/models/forms/bulk_upload_lettings/prepare_your_file.rb
  38. 55
      app/models/forms/bulk_upload_lettings_resume/confirm.rb
  39. 28
      app/models/forms/bulk_upload_lettings_resume/deletion_report.rb
  40. 74
      app/models/forms/bulk_upload_lettings_resume/fix_choice.rb
  41. 31
      app/models/forms/bulk_upload_lettings_soft_validations_check/chosen.rb
  42. 9
      app/models/forms/bulk_upload_resume/chosen.rb
  43. 17
      app/models/forms/bulk_upload_resume/confirm.rb
  44. 9
      app/models/forms/bulk_upload_resume/deletion_report.rb
  45. 17
      app/models/forms/bulk_upload_resume/fix_choice.rb
  46. 32
      app/models/forms/bulk_upload_sales/checking_file.rb
  47. 51
      app/models/forms/bulk_upload_sales/guidance.rb
  48. 83
      app/models/forms/bulk_upload_sales/upload_your_file.rb
  49. 50
      app/models/forms/bulk_upload_sales/year.rb
  50. 31
      app/models/forms/bulk_upload_sales_soft_validations_check/chosen.rb
  51. 47
      app/models/forms/bulk_upload_sales_soft_validations_check/confirm.rb
  52. 53
      app/models/forms/bulk_upload_sales_soft_validations_check/confirm_soft_errors.rb
  53. 9
      app/models/forms/bulk_upload_soft_validations_check/chosen.rb
  54. 13
      app/models/forms/bulk_upload_soft_validations_check/confirm.rb
  55. 13
      app/models/forms/bulk_upload_soft_validations_check/confirm_soft_errors.rb
  56. 6
      app/models/lettings_log.rb
  57. 34
      app/models/sales_log.rb
  58. 55
      app/models/validations/date_validations.rb
  59. 2
      app/models/validations/financial_validations.rb
  60. 2
      app/models/validations/household_validations.rb
  61. 2
      app/models/validations/sales/financial_validations.rb
  62. 2
      app/models/validations/sales/household_validations.rb
  63. 4
      app/models/validations/sales/sale_information_validations.rb
  64. 17
      app/models/validations/shared_validations.rb
  65. 4
      app/models/validations/soft_validations.rb
  66. 3
      app/services/bulk_upload/processor.rb
  67. 58
      app/services/documentation_generator.rb
  68. 3
      app/services/exports/organisation_export_constants.rb
  69. 2
      app/services/exports/organisation_export_service.rb
  70. 46
      app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2023.html.erb
  71. 2
      app/views/bulk_upload_lettings_results/show.html.erb
  72. 2
      app/views/bulk_upload_lettings_results/summary.html.erb
  73. 40
      app/views/bulk_upload_sales_logs/forms/prepare_your_file_2023.html.erb
  74. 2
      app/views/bulk_upload_sales_results/show.html.erb
  75. 2
      app/views/bulk_upload_sales_results/summary.html.erb
  76. 21
      app/views/bulk_upload_shared/guidance.html.erb
  77. 9
      app/views/layouts/rails_admin/_navigation.html.erb
  78. 70
      app/views/layouts/rails_admin/application.html.erb
  79. 25
      app/views/rails_admin/main/_submit_buttons.html.erb
  80. 68
      app/views/rails_admin/main/dashboard.html.erb
  81. 21
      app/views/rails_admin/main/delete.html.erb
  82. 187
      app/views/rails_admin/main/index.html.erb
  83. 2
      config/initializers/assets.rb
  84. 29
      config/locales/en.yml
  85. 28
      config/locales/forms/2025/lettings/household_characteristics.en.yml
  86. 4
      config/locales/validations/lettings/date.en.yml
  87. 0
      config/locales/validations/lettings/financial.en.yml
  88. 20
      config/locales/validations/sales/sale_information.en.yml
  89. 2
      config/locales/validations/shared.en.yml
  90. 14
      config/routes.rb
  91. 5
      db/migrate/20250110150609_add_collection_year.rb
  92. 3
      db/schema.rb
  93. 1
      lib/tasks/generate_lettings_documentation.rake
  94. 6
      lib/tasks/set_log_validation_collection_year.rake
  95. 2
      spec/factories/sales_log.rb
  96. 8
      spec/features/bulk_upload_lettings_logs_spec.rb
  97. 22
      spec/features/bulk_upload_sales_logs_spec.rb
  98. 10
      spec/features/organisation_spec.rb
  99. 3
      spec/features/sales_log_spec.rb
  100. 1
      spec/fixtures/exports/organisation.xml
  101. Some files were not shown because too many files have changed in this diff Show More

2
Dockerfile

@ -10,7 +10,7 @@ RUN apk add --update --no-cache tzdata && \
# build-base: compilation tools for bundle # build-base: compilation tools for bundle
# yarn: node package manager # yarn: node package manager
# postgresql-dev: postgres driver and libraries # postgresql-dev: postgres driver and libraries
RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r7 nodejs-current=20.8.1-r0 yarn=1.22.19-r0 postgresql13-dev=13.18-r0 git=2.40.3-r0 bash=5.2.15-r5 RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r7 nodejs-current=20.8.1-r0 yarn=1.22.19-r0 postgresql13-dev=13.18-r0 bash=5.2.15-r5
# Bundler version should be the same version as what the Gemfile.lock was bundled with # Bundler version should be the same version as what the Gemfile.lock was bundled with
RUN gem install bundler:2.3.14 --no-document RUN gem install bundler:2.3.14 --no-document

5
app/components/create_log_actions_component.html.erb

@ -9,8 +9,9 @@
<% end %> <% end %>
<% if FeatureToggle.create_test_logs_enabled? %> <% if FeatureToggle.create_test_logs_enabled? %>
<%= govuk_button_link_to "Create test log", create_test_log_href, secondary: true %> <%= govuk_link_to "Create test log", create_test_log_href %>
<%= govuk_button_link_to "Create test log (setup only)", create_setup_test_log_href, secondary: true %> <%= govuk_link_to "Create test log (setup only)", create_setup_test_log_href %>
<%= govuk_link_to "Get test BU file (2024)", create_2024_test_bulk_upload_href %>
<% end %> <% end %>
<% end %> <% end %>
</div> </div>

4
app/components/create_log_actions_component.rb

@ -42,6 +42,10 @@ class CreateLogActionsComponent < ViewComponent::Base
send("create_setup_test_#{log_type}_log_path") send("create_setup_test_#{log_type}_log_path")
end end
def create_2024_test_bulk_upload_href
send("create_2024_test_#{log_type}_bulk_upload_path")
end
def view_uploads_button_copy def view_uploads_button_copy
"View #{log_type} bulk uploads" "View #{log_type} bulk uploads"
end end

10
app/controllers/bulk_upload_lettings_logs_controller.rb

@ -58,15 +58,15 @@ private
def form def form
@form ||= case params[:id] @form ||= case params[:id]
when "year" when "year"
Forms::BulkUploadLettings::Year.new(form_params) Forms::BulkUploadForm::Year.new(form_params.merge(log_type: "lettings"))
when "prepare-your-file" when "prepare-your-file"
Forms::BulkUploadLettings::PrepareYourFile.new(form_params) Forms::BulkUploadForm::PrepareYourFile.new(form_params.merge(log_type: "lettings"))
when "guidance" when "guidance"
Forms::BulkUploadLettings::Guidance.new(form_params.merge(referrer: params[:referrer])) Forms::BulkUploadForm::Guidance.new(form_params.merge(referrer: params[:referrer], log_type: "lettings"))
when "upload-your-file" when "upload-your-file"
Forms::BulkUploadLettings::UploadYourFile.new(form_params.merge(current_user:)) Forms::BulkUploadForm::UploadYourFile.new(form_params.merge(current_user:, log_type: "lettings"))
when "checking-file" when "checking-file"
Forms::BulkUploadLettings::CheckingFile.new(form_params) Forms::BulkUploadForm::CheckingFile.new(form_params.merge(log_type: "lettings"))
else else
raise "Page not found for path #{params[:id]}" raise "Page not found for path #{params[:id]}"
end end

8
app/controllers/bulk_upload_lettings_resume_controller.rb

@ -42,13 +42,13 @@ private
def form def form
@form ||= case params[:page] @form ||= case params[:page]
when "fix-choice" when "fix-choice"
Forms::BulkUploadLettingsResume::FixChoice.new(form_params.merge(bulk_upload: @bulk_upload)) Forms::BulkUploadResume::FixChoice.new(form_params.merge(bulk_upload: @bulk_upload, log_type: "lettings"))
when "chosen" when "chosen"
Forms::BulkUploadLettingsResume::Chosen.new(form_params.merge(bulk_upload: @bulk_upload)) Forms::BulkUploadResume::Chosen.new(form_params.merge(bulk_upload: @bulk_upload, log_type: "lettings"))
when "confirm" when "confirm"
Forms::BulkUploadLettingsResume::Confirm.new(form_params.merge(bulk_upload: @bulk_upload)) Forms::BulkUploadResume::Confirm.new(form_params.merge(bulk_upload: @bulk_upload, log_type: "lettings"))
when "deletion-report" when "deletion-report"
Forms::BulkUploadLettingsResume::DeletionReport.new(form_params.merge(bulk_upload: @bulk_upload)) Forms::BulkUploadResume::DeletionReport.new(form_params.merge(bulk_upload: @bulk_upload, log_type: "lettings"))
else else
raise "invalid form" raise "invalid form"
end end

6
app/controllers/bulk_upload_lettings_soft_validations_check_controller.rb

@ -36,11 +36,11 @@ private
def form def form
@form ||= case params[:page] @form ||= case params[:page]
when "confirm-soft-errors" when "confirm-soft-errors"
Forms::BulkUploadLettingsSoftValidationsCheck::ConfirmSoftErrors.new(form_params.merge(bulk_upload: @bulk_upload)) Forms::BulkUploadSoftValidationsCheck::ConfirmSoftErrors.new(form_params.merge(bulk_upload: @bulk_upload, log_type: "lettings"))
when "chosen" when "chosen"
Forms::BulkUploadLettingsSoftValidationsCheck::Chosen.new(form_params.merge(bulk_upload: @bulk_upload)) Forms::BulkUploadSoftValidationsCheck::Chosen.new(form_params.merge(bulk_upload: @bulk_upload, log_type: "lettings"))
when "confirm" when "confirm"
Forms::BulkUploadLettingsSoftValidationsCheck::Confirm.new(form_params.merge(bulk_upload: @bulk_upload)) Forms::BulkUploadSoftValidationsCheck::Confirm.new(form_params.merge(bulk_upload: @bulk_upload, log_type: "lettings"))
else else
raise "invalid form" raise "invalid form"
end end

10
app/controllers/bulk_upload_sales_logs_controller.rb

@ -58,15 +58,15 @@ private
def form def form
@form ||= case params[:id] @form ||= case params[:id]
when "year" when "year"
Forms::BulkUploadSales::Year.new(form_params) Forms::BulkUploadForm::Year.new(form_params.merge(log_type: "sales"))
when "prepare-your-file" when "prepare-your-file"
Forms::BulkUploadSales::PrepareYourFile.new(form_params) Forms::BulkUploadForm::PrepareYourFile.new(form_params.merge(log_type: "sales"))
when "guidance" when "guidance"
Forms::BulkUploadSales::Guidance.new(form_params.merge(referrer: params[:referrer])) Forms::BulkUploadForm::Guidance.new(form_params.merge(referrer: params[:referrer], log_type: "sales"))
when "upload-your-file" when "upload-your-file"
Forms::BulkUploadSales::UploadYourFile.new(form_params.merge(current_user:)) Forms::BulkUploadForm::UploadYourFile.new(form_params.merge(current_user:, log_type: "sales"))
when "checking-file" when "checking-file"
Forms::BulkUploadSales::CheckingFile.new(form_params) Forms::BulkUploadForm::CheckingFile.new(form_params.merge(log_type: "sales"))
else else
raise "Page not found for path #{params[:id]}" raise "Page not found for path #{params[:id]}"
end end

8
app/controllers/bulk_upload_sales_resume_controller.rb

@ -42,13 +42,13 @@ private
def form def form
@form ||= case params[:page] @form ||= case params[:page]
when "fix-choice" when "fix-choice"
Forms::BulkUploadSalesResume::FixChoice.new(form_params.merge(bulk_upload: @bulk_upload)) Forms::BulkUploadResume::FixChoice.new(form_params.merge(bulk_upload: @bulk_upload, log_type: "sales"))
when "chosen" when "chosen"
Forms::BulkUploadSalesResume::Chosen.new(form_params.merge(bulk_upload: @bulk_upload)) Forms::BulkUploadResume::Chosen.new(form_params.merge(bulk_upload: @bulk_upload, log_type: "sales"))
when "confirm" when "confirm"
Forms::BulkUploadSalesResume::Confirm.new(form_params.merge(bulk_upload: @bulk_upload)) Forms::BulkUploadResume::Confirm.new(form_params.merge(bulk_upload: @bulk_upload, log_type: "sales"))
when "deletion-report" when "deletion-report"
Forms::BulkUploadSalesResume::DeletionReport.new(form_params.merge(bulk_upload: @bulk_upload)) Forms::BulkUploadResume::DeletionReport.new(form_params.merge(bulk_upload: @bulk_upload, log_type: "sales"))
else else
raise "invalid form" raise "invalid form"
end end

6
app/controllers/bulk_upload_sales_soft_validations_check_controller.rb

@ -36,11 +36,11 @@ private
def form def form
@form ||= case params[:page] @form ||= case params[:page]
when "confirm-soft-errors" when "confirm-soft-errors"
Forms::BulkUploadSalesSoftValidationsCheck::ConfirmSoftErrors.new(form_params.merge(bulk_upload: @bulk_upload)) Forms::BulkUploadSoftValidationsCheck::ConfirmSoftErrors.new(form_params.merge(bulk_upload: @bulk_upload, log_type: "sales"))
when "chosen" when "chosen"
Forms::BulkUploadSalesSoftValidationsCheck::Chosen.new(form_params.merge(bulk_upload: @bulk_upload)) Forms::BulkUploadSoftValidationsCheck::Chosen.new(form_params.merge(bulk_upload: @bulk_upload, log_type: "sales"))
when "confirm" when "confirm"
Forms::BulkUploadSalesSoftValidationsCheck::Confirm.new(form_params.merge(bulk_upload: @bulk_upload)) Forms::BulkUploadSoftValidationsCheck::Confirm.new(form_params.merge(bulk_upload: @bulk_upload, log_type: "sales"))
else else
raise "invalid form" raise "invalid form"
end end

1
app/controllers/form_controller.rb

@ -32,6 +32,7 @@ class FormController < ApplicationController
pages_requiring_update = pages_requiring_update(shown_page_ids_with_unanswered_questions_before_update) pages_requiring_update = pages_requiring_update(shown_page_ids_with_unanswered_questions_before_update)
redirect_to(successful_redirect_path(pages_requiring_update)) redirect_to(successful_redirect_path(pages_requiring_update))
else else
@log.valid? if mandatory_questions_with_no_response.any?
mandatory_questions_with_no_response.map do |question| mandatory_questions_with_no_response.map do |question|
@log.errors.add question.id.to_sym, question.unanswered_error_message, category: :not_answered @log.errors.add question.id.to_sym, question.unanswered_error_message, category: :not_answered
end end

14
app/controllers/lettings_logs_controller.rb

@ -149,20 +149,6 @@ class LettingsLogsController < LogsController
end end
end end
def create_test_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?
log = FactoryBot.create(:lettings_log, :completed, assigned_to: current_user, ppostcode_full: "SW1A 1AA")
redirect_to lettings_log_path(log)
end
def create_setup_test_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?
log = FactoryBot.create(:lettings_log, :setup_completed, assigned_to: current_user)
redirect_to lettings_log_path(log)
end
private private
def session_filters def session_filters

14
app/controllers/sales_logs_controller.rb

@ -119,20 +119,6 @@ class SalesLogsController < LogsController
end end
end end
def create_test_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?
log = FactoryBot.create(:sales_log, :completed, assigned_to: current_user)
redirect_to sales_log_path(log)
end
def create_setup_test_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?
log = FactoryBot.create(:sales_log, :shared_ownership_setup_complete, assigned_to: current_user)
redirect_to sales_log_path(log)
end
private private
def session_filters def session_filters

68
app/controllers/test_data_controller.rb

@ -0,0 +1,68 @@
class TestDataController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
def create_test_lettings_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?
log = FactoryBot.create(:lettings_log, :completed, assigned_to: current_user, ppostcode_full: "SW1A 1AA")
redirect_to lettings_log_path(log)
end
def create_setup_test_lettings_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?
log = FactoryBot.create(:lettings_log, :setup_completed, assigned_to: current_user)
redirect_to lettings_log_path(log)
end
def create_2024_test_lettings_bulk_upload
return render_not_found unless FeatureToggle.create_test_logs_enabled?
file = Tempfile.new("test_lettings_log.csv")
log = FactoryBot.create(:lettings_log, :completed, assigned_to: current_user, ppostcode_full: "SW1A 1AA")
log_to_csv = BulkUpload::LettingsLogToCsv.new(log:, line_ending: "\n", overrides: { organisation_id: "ORG#{log.owning_organisation_id}", managing_organisation_id: "ORG#{log.owning_organisation_id}" })
file.write(log_to_csv.default_field_numbers_row)
file.write(log_to_csv.to_csv_row)
file.rewind
send_file file.path, type: "text/csv",
filename: "test_lettings_log.csv",
disposition: "attachment",
after_send: lambda {
file.close
file.unlink
}
end
def create_test_sales_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?
log = FactoryBot.create(:sales_log, :completed, assigned_to: current_user)
redirect_to sales_log_path(log)
end
def create_setup_test_sales_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?
log = FactoryBot.create(:sales_log, :shared_ownership_setup_complete, assigned_to: current_user)
redirect_to sales_log_path(log)
end
def create_2024_test_sales_bulk_upload
return render_not_found unless FeatureToggle.create_test_logs_enabled?
file = Tempfile.new("test_sales_log.csv")
log = FactoryBot.create(:sales_log, :completed, assigned_to: current_user, value: 180_000, deposit: 150_000)
log_to_csv = BulkUpload::SalesLogToCsv.new(log:, line_ending: "\n", overrides: { organisation_id: "ORG#{log.owning_organisation_id}", managing_organisation_id: "ORG#{log.owning_organisation_id}" })
file.write(log_to_csv.default_field_numbers_row)
file.write(log_to_csv.to_csv_row)
file.rewind
send_file file.path, type: "text/csv",
filename: "test_sales_log.csv",
disposition: "attachment",
after_send: lambda {
file.close
file.unlink
}
end
end

34
app/frontend/styles/_custom-rails-admin.scss

@ -0,0 +1,34 @@
.rails-admin-sidescroll {
overflow-x: scroll;
}
.rails-admin-description_field {
min-width: 500px;
}
.rails-admin-case_field {
min-width: 500px;
}
.rails-admin-error_message_field {
min-width: 500px;
}
.rails-admin-actions {
min-width: 160px;
ul {
float: right;
}
}
.rails-admin-filters-box {
.filter {
// stylelint-disable-next-line declaration-no-important
display: flex !important;
}
button {
min-width: 20%;
}
}

1
app/frontend/styles/application.scss

@ -48,6 +48,7 @@ $govuk-breakpoints: (
@import "search"; @import "search";
@import "sub-navigation"; @import "sub-navigation";
@import "unread-notification"; @import "unread-notification";
@import "custom-rails-admin";
// App utilities // App utilities
.app-\!-colour-muted { .app-\!-colour-muted {

14
spec/support/bulk_upload/lettings_log_to_csv.rb → app/helpers/bulk_upload/lettings_log_to_csv.rb

@ -2,10 +2,12 @@ class BulkUpload::LettingsLogToCsv
attr_reader :log, :line_ending, :col_offset, :overrides attr_reader :log, :line_ending, :col_offset, :overrides
def initialize(log:, line_ending: "\n", col_offset: 1, overrides: {}) def initialize(log:, line_ending: "\n", col_offset: 1, overrides: {})
# rubocop:disable Rails/HelperInstanceVariable
@log = log @log = log
@line_ending = line_ending @line_ending = line_ending
@col_offset = col_offset @col_offset = col_offset
@overrides = overrides @overrides = overrides
# rubocop:enable Rails/HelperInstanceVariable
end end
def row_prefix def row_prefix
@ -145,8 +147,8 @@ class BulkUpload::LettingsLogToCsv
def to_2024_row def to_2024_row
[ [
log.owning_organisation&.old_visible_id, # 1 overrides[:organisation_id] || log.owning_organisation&.old_visible_id, # 1
log.managing_organisation&.old_visible_id, overrides[:managing_organisation_id] || log.managing_organisation&.old_visible_id,
log.assigned_to&.email, log.assigned_to&.email,
log.needstype, log.needstype,
log.scheme&.id ? "S#{log.scheme&.id}" : "", log.scheme&.id ? "S#{log.scheme&.id}" : "",
@ -162,10 +164,10 @@ class BulkUpload::LettingsLogToCsv
log.propcode, log.propcode,
log.declaration, log.declaration,
log.uprn, log.uprn,
log.address_line1, log.address_line1&.tr(",", " "),
log.address_line2, log.address_line2&.tr(",", " "),
log.town_or_city, log.town_or_city&.tr(",", " "),
log.county, # 20 log.county&.tr(",", " "), # 20
((log.postcode_full || "").split(" ") || [""]).first, ((log.postcode_full || "").split(" ") || [""]).first,
((log.postcode_full || "").split(" ") || [""]).last, ((log.postcode_full || "").split(" ") || [""]).last,

12
spec/support/bulk_upload/sales_log_to_csv.rb → app/helpers/bulk_upload/sales_log_to_csv.rb

@ -2,10 +2,12 @@ class BulkUpload::SalesLogToCsv
attr_reader :log, :line_ending, :col_offset, :overrides attr_reader :log, :line_ending, :col_offset, :overrides
def initialize(log:, line_ending: "\n", col_offset: 1, overrides: {}) def initialize(log:, line_ending: "\n", col_offset: 1, overrides: {})
# rubocop:disable Rails/HelperInstanceVariable
@log = log @log = log
@line_ending = line_ending @line_ending = line_ending
@col_offset = col_offset @col_offset = col_offset
@overrides = overrides @overrides = overrides
# rubocop:enable Rails/HelperInstanceVariable
end end
def row_prefix def row_prefix
@ -187,7 +189,7 @@ class BulkUpload::SalesLogToCsv
log.prevloc, # 40 log.prevloc, # 40
((log.ppostcode_full || "").split(" ") || [""]).first, ((log.ppostcode_full || "").split(" ") || [""]).first,
((log.ppostcode_full || "").split(" ") || [""]).last, ((log.ppostcode_full || "").split(" ") || [""]).last,
log.ppcodenk == 0 ? 1 : nil, log.ppcodenk&.zero? ? 1 : nil,
log.pregyrha, log.pregyrha,
log.pregla, log.pregla,
@ -311,10 +313,10 @@ class BulkUpload::SalesLogToCsv
log.builtype, log.builtype,
log.uprn, log.uprn,
log.address_line1, log.address_line1&.tr(",", " "),
log.address_line2, log.address_line2&.tr(",", " "),
log.town_or_city, log.town_or_city&.tr(",", " "),
log.county, log.county&.tr(",", " "),
((log.postcode_full || "").split(" ") || [""]).first, ((log.postcode_full || "").split(" ") || [""]).first,
((log.postcode_full || "").split(" ") || [""]).last, ((log.postcode_full || "").split(" ") || [""]).last,
log.la, log.la,

2
app/helpers/tag_helper.rb

@ -21,6 +21,7 @@ module TagHelper
processing: "Processing", processing: "Processing",
blank_template: "Blank template", blank_template: "Blank template",
wrong_template: "Wrong template used", wrong_template: "Wrong template used",
processing_error: "Error processing CSV",
important_errors: "Errors on important questions in CSV", important_errors: "Errors on important questions in CSV",
critical_errors: "Critical errors in CSV", critical_errors: "Critical errors in CSV",
potential_errors: "Potential errors in CSV", potential_errors: "Potential errors in CSV",
@ -48,6 +49,7 @@ module TagHelper
processing: "yellow", processing: "yellow",
blank_template: "red", blank_template: "red",
wrong_template: "red", wrong_template: "red",
processing_error: "red",
important_errors: "red", important_errors: "red",
critical_errors: "red", critical_errors: "red",
potential_errors: "red", potential_errors: "red",

4
app/models/bulk_upload.rb

@ -1,7 +1,7 @@
class BulkUpload < ApplicationRecord class BulkUpload < ApplicationRecord
enum :log_type, { lettings: "lettings", sales: "sales" } enum :log_type, { lettings: "lettings", sales: "sales" }
enum :rent_type_fix_status, { not_applied: "not_applied", applied: "applied", not_needed: "not_needed" } enum :rent_type_fix_status, { not_applied: "not_applied", applied: "applied", not_needed: "not_needed" }
enum :failure_reason, { blank_template: "blank_template", wrong_template: "wrong_template" } enum :failure_reason, { blank_template: "blank_template", wrong_template: "wrong_template", processing_error: "processing_error" }
belongs_to :user belongs_to :user
@ -43,6 +43,7 @@ class BulkUpload < ApplicationRecord
return :processing if processing return :processing if processing
return :blank_template if failure_reason == "blank_template" return :blank_template if failure_reason == "blank_template"
return :wrong_template if failure_reason == "wrong_template" return :wrong_template if failure_reason == "wrong_template"
return :processing_error if failure_reason == "processing_error"
if logs.visible.exists? if logs.visible.exists?
return :errors_fixed_in_service if completed? && bulk_upload_errors.any? return :errors_fixed_in_service if completed? && bulk_upload_errors.any?
@ -129,6 +130,7 @@ class BulkUpload < ApplicationRecord
def unpend_and_confirm_soft_validations def unpend_and_confirm_soft_validations
logs.find_each do |log| logs.find_each do |log|
fields_to_confirm(log).each { |field| log[field] = 0 } fields_to_confirm(log).each { |field| log[field] = 0 }
log.status = log.status_cache
log.save! log.save!
end end
end end

2
app/models/derived_variables/lettings_log_variables.rb

@ -346,7 +346,7 @@ private
end end
def address_answered_without_uprn? def address_answered_without_uprn?
[address_line1, town_or_city].all?(&:present?) && uprn.nil? && form.start_date.year >= 2023 [address_line1, town_or_city].all?(&:present?) && uprn.nil?
end end
def get_lar def get_lar

2
app/models/derived_variables/sales_log_variables.rb

@ -234,7 +234,7 @@ private
end end
def address_answered_without_uprn? def address_answered_without_uprn?
[address_line1, town_or_city].all?(&:present?) && uprn.nil? && form.start_date.year >= 2023 [address_line1, town_or_city].all?(&:present?) && uprn.nil?
end end
def soctenant_from_prevten_values def soctenant_from_prevten_values

12
app/models/form/lettings/pages/person_lead_partner.rb

@ -0,0 +1,12 @@
class Form::Lettings::Pages::PersonLeadPartner < ::Form::Page
def initialize(id, hsh, subsection, person_index:)
super(id, hsh, subsection)
@id = "person_#{person_index}_lead_partner"
@depends_on = [{ "details_known_#{person_index}" => 0 }]
@person_index = person_index
end
def questions
@questions ||= [Form::Lettings::Questions::PersonPartner.new(nil, nil, self, person_index: @person_index)]
end
end

19
app/models/form/lettings/pages/rent_4_weekly.rb

@ -3,10 +3,7 @@ class Form::Lettings::Pages::Rent4Weekly < ::Form::Page
super super
@id = "rent_4_weekly" @id = "rent_4_weekly"
@copy_key = "lettings.income_and_benefits.rent_and_charges" @copy_key = "lettings.income_and_benefits.rent_and_charges"
@depends_on = [ @depends_on = depends_on
{ "household_charge" => 0, "rent_and_charges_paid_every_4_weeks?" => true, "is_carehome?" => false },
{ "household_charge" => nil, "rent_and_charges_paid_every_4_weeks?" => true, "is_carehome?" => false },
]
end end
def questions def questions
@ -18,4 +15,18 @@ class Form::Lettings::Pages::Rent4Weekly < ::Form::Page
Form::Lettings::Questions::Tcharge4Weekly.new(nil, nil, self), Form::Lettings::Questions::Tcharge4Weekly.new(nil, nil, self),
] ]
end end
def depends_on
if form.start_year_2025_or_later?
[
{ "household_charge" => 0, "rent_and_charges_paid_every_4_weeks?" => true },
{ "household_charge" => nil, "rent_and_charges_paid_every_4_weeks?" => true },
]
else
[
{ "household_charge" => 0, "rent_and_charges_paid_every_4_weeks?" => true, "is_carehome?" => false },
{ "household_charge" => nil, "rent_and_charges_paid_every_4_weeks?" => true, "is_carehome?" => false },
]
end
end
end end

19
app/models/form/lettings/pages/rent_bi_weekly.rb

@ -3,10 +3,7 @@ class Form::Lettings::Pages::RentBiWeekly < ::Form::Page
super super
@id = "rent_bi_weekly" @id = "rent_bi_weekly"
@copy_key = "lettings.income_and_benefits.rent_and_charges" @copy_key = "lettings.income_and_benefits.rent_and_charges"
@depends_on = [ @depends_on = depends_on
{ "household_charge" => nil, "rent_and_charges_paid_every_2_weeks?" => true, "is_carehome?" => false },
{ "household_charge" => 0, "rent_and_charges_paid_every_2_weeks?" => true, "is_carehome?" => false },
]
end end
def questions def questions
@ -18,4 +15,18 @@ class Form::Lettings::Pages::RentBiWeekly < ::Form::Page
Form::Lettings::Questions::TchargeBiWeekly.new(nil, nil, self), Form::Lettings::Questions::TchargeBiWeekly.new(nil, nil, self),
] ]
end end
def depends_on
if form.start_year_2025_or_later?
[
{ "household_charge" => nil, "rent_and_charges_paid_every_2_weeks?" => true },
{ "household_charge" => 0, "rent_and_charges_paid_every_2_weeks?" => true },
]
else
[
{ "household_charge" => nil, "rent_and_charges_paid_every_2_weeks?" => true, "is_carehome?" => false },
{ "household_charge" => 0, "rent_and_charges_paid_every_2_weeks?" => true, "is_carehome?" => false },
]
end
end
end end

19
app/models/form/lettings/pages/rent_monthly.rb

@ -3,10 +3,7 @@ class Form::Lettings::Pages::RentMonthly < ::Form::Page
super super
@id = "rent_monthly" @id = "rent_monthly"
@copy_key = "lettings.income_and_benefits.rent_and_charges" @copy_key = "lettings.income_and_benefits.rent_and_charges"
@depends_on = [ @depends_on = depends_on
{ "household_charge" => nil, "rent_and_charges_paid_monthly?" => true, "is_carehome?" => false },
{ "household_charge" => 0, "rent_and_charges_paid_monthly?" => true, "is_carehome?" => false },
]
end end
def questions def questions
@ -18,4 +15,18 @@ class Form::Lettings::Pages::RentMonthly < ::Form::Page
Form::Lettings::Questions::TchargeMonthly.new(nil, nil, self), Form::Lettings::Questions::TchargeMonthly.new(nil, nil, self),
] ]
end end
def depends_on
if form.start_year_2025_or_later?
[
{ "household_charge" => nil, "rent_and_charges_paid_monthly?" => true },
{ "household_charge" => 0, "rent_and_charges_paid_monthly?" => true },
]
else
[
{ "household_charge" => nil, "rent_and_charges_paid_monthly?" => true, "is_carehome?" => false },
{ "household_charge" => 0, "rent_and_charges_paid_monthly?" => true, "is_carehome?" => false },
]
end
end
end end

19
app/models/form/lettings/pages/rent_weekly.rb

@ -3,10 +3,7 @@ class Form::Lettings::Pages::RentWeekly < ::Form::Page
super super
@id = "rent_weekly" @id = "rent_weekly"
@copy_key = "lettings.income_and_benefits.rent_and_charges" @copy_key = "lettings.income_and_benefits.rent_and_charges"
@depends_on = [ @depends_on = depends_on
{ "rent_and_charges_paid_weekly?" => true, "household_charge" => 0, "is_carehome?" => false },
{ "rent_and_charges_paid_weekly?" => true, "household_charge" => nil, "is_carehome?" => false },
]
end end
def questions def questions
@ -18,4 +15,18 @@ class Form::Lettings::Pages::RentWeekly < ::Form::Page
Form::Lettings::Questions::TchargeWeekly.new(nil, nil, self), Form::Lettings::Questions::TchargeWeekly.new(nil, nil, self),
] ]
end end
def depends_on
if form.start_year_2025_or_later?
[
{ "rent_and_charges_paid_weekly?" => true, "household_charge" => 0 },
{ "rent_and_charges_paid_weekly?" => true, "household_charge" => nil },
]
else
[
{ "rent_and_charges_paid_weekly?" => true, "household_charge" => 0, "is_carehome?" => false },
{ "rent_and_charges_paid_weekly?" => true, "household_charge" => nil, "is_carehome?" => false },
]
end
end
end end

30
app/models/form/lettings/questions/person_partner.rb

@ -0,0 +1,30 @@
class Form::Lettings::Questions::PersonPartner < ::Form::Question
def initialize(id, hsh, page, person_index:)
super(id, hsh, page)
@id = "relat#{person_index}"
@type = "radio"
@check_answers_card_number = person_index
@answer_options = answer_options
@person_index = person_index
@question_number = question_number
end
def answer_options
{
"P" => { "value" => "Yes" },
"X" => { "value" => "No" },
"R" => { "value" => "Tenant prefers not to say" },
}
end
def question_number
base_question_number = case form.start_date.year
when 2023
30
else
29
end
base_question_number + (4 * @person_index)
end
end

22
app/models/form/lettings/subsections/household_characteristics.rb

@ -32,7 +32,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::LeadTenantUnderRetirementValueCheck.new("working_situation_lead_tenant_under_retirement_value_check", nil, self), Form::Lettings::Pages::LeadTenantUnderRetirementValueCheck.new("working_situation_lead_tenant_under_retirement_value_check", nil, self),
Form::Lettings::Pages::LeadTenantOverRetirementValueCheck.new("working_situation_lead_tenant_over_retirement_value_check", nil, self), Form::Lettings::Pages::LeadTenantOverRetirementValueCheck.new("working_situation_lead_tenant_over_retirement_value_check", nil, self),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 2), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 2),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 2), relationship_question(person_index: 2),
(Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_2_partner_under_16_value_check", nil, self, person_index: 2) if form.start_year_2024_or_later?), (Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_2_partner_under_16_value_check", nil, self, person_index: 2) if form.start_year_2024_or_later?),
(Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_2_multiple_partners_value_check", nil, self, person_index: 2) if form.start_year_2024_or_later?), (Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_2_multiple_partners_value_check", nil, self, person_index: 2) if form.start_year_2024_or_later?),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 2), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 2),
@ -52,7 +52,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_2_under_retirement_value_check", nil, self, person_index: 2), Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_2_under_retirement_value_check", nil, self, person_index: 2),
Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_2_over_retirement_value_check", nil, self, person_index: 2), Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_2_over_retirement_value_check", nil, self, person_index: 2),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 3), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 3),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 3), relationship_question(person_index: 3),
(Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_3_partner_under_16_value_check", nil, self, person_index: 3) if form.start_year_2024_or_later?), (Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_3_partner_under_16_value_check", nil, self, person_index: 3) if form.start_year_2024_or_later?),
(Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_3_multiple_partners_value_check", nil, self, person_index: 3) if form.start_year_2024_or_later?), (Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_3_multiple_partners_value_check", nil, self, person_index: 3) if form.start_year_2024_or_later?),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 3), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 3),
@ -72,7 +72,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_3_under_retirement_value_check", nil, self, person_index: 3), Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_3_under_retirement_value_check", nil, self, person_index: 3),
Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_3_over_retirement_value_check", nil, self, person_index: 3), Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_3_over_retirement_value_check", nil, self, person_index: 3),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 4), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 4),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 4), relationship_question(person_index: 4),
(Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_4_partner_under_16_value_check", nil, self, person_index: 4) if form.start_year_2024_or_later?), (Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_4_partner_under_16_value_check", nil, self, person_index: 4) if form.start_year_2024_or_later?),
(Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_4_multiple_partners_value_check", nil, self, person_index: 4) if form.start_year_2024_or_later?), (Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_4_multiple_partners_value_check", nil, self, person_index: 4) if form.start_year_2024_or_later?),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 4), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 4),
@ -92,7 +92,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_4_under_retirement_value_check", nil, self, person_index: 4), Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_4_under_retirement_value_check", nil, self, person_index: 4),
Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_4_over_retirement_value_check", nil, self, person_index: 4), Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_4_over_retirement_value_check", nil, self, person_index: 4),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 5), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 5),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 5), relationship_question(person_index: 5),
(Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_5_partner_under_16_value_check", nil, self, person_index: 5) if form.start_year_2024_or_later?), (Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_5_partner_under_16_value_check", nil, self, person_index: 5) if form.start_year_2024_or_later?),
(Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_5_multiple_partners_value_check", nil, self, person_index: 5) if form.start_year_2024_or_later?), (Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_5_multiple_partners_value_check", nil, self, person_index: 5) if form.start_year_2024_or_later?),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 5), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 5),
@ -112,7 +112,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_5_under_retirement_value_check", nil, self, person_index: 5), Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_5_under_retirement_value_check", nil, self, person_index: 5),
Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_5_over_retirement_value_check", nil, self, person_index: 5), Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_5_over_retirement_value_check", nil, self, person_index: 5),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 6), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 6),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 6), relationship_question(person_index: 6),
(Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_6_partner_under_16_value_check", nil, self, person_index: 6) if form.start_year_2024_or_later?), (Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_6_partner_under_16_value_check", nil, self, person_index: 6) if form.start_year_2024_or_later?),
(Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_6_multiple_partners_value_check", nil, self, person_index: 6) if form.start_year_2024_or_later?), (Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_6_multiple_partners_value_check", nil, self, person_index: 6) if form.start_year_2024_or_later?),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 6), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 6),
@ -132,7 +132,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_6_under_retirement_value_check", nil, self, person_index: 6), Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_6_under_retirement_value_check", nil, self, person_index: 6),
Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_6_over_retirement_value_check", nil, self, person_index: 6), Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_6_over_retirement_value_check", nil, self, person_index: 6),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 7), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 7),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 7), relationship_question(person_index: 7),
(Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_7_partner_under_16_value_check", nil, self, person_index: 7) if form.start_year_2024_or_later?), (Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_7_partner_under_16_value_check", nil, self, person_index: 7) if form.start_year_2024_or_later?),
(Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_7_multiple_partners_value_check", nil, self, person_index: 7) if form.start_year_2024_or_later?), (Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_7_multiple_partners_value_check", nil, self, person_index: 7) if form.start_year_2024_or_later?),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 7), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 7),
@ -152,7 +152,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_7_under_retirement_value_check", nil, self, person_index: 7), Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_7_under_retirement_value_check", nil, self, person_index: 7),
Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_7_over_retirement_value_check", nil, self, person_index: 7), Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_7_over_retirement_value_check", nil, self, person_index: 7),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 8), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 8),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 8), relationship_question(person_index: 8),
(Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_8_partner_under_16_value_check", nil, self, person_index: 8) if form.start_year_2024_or_later?), (Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_8_partner_under_16_value_check", nil, self, person_index: 8) if form.start_year_2024_or_later?),
(Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_8_multiple_partners_value_check", nil, self, person_index: 8) if form.start_year_2024_or_later?), (Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_8_multiple_partners_value_check", nil, self, person_index: 8) if form.start_year_2024_or_later?),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 8), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 8),
@ -173,4 +173,12 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_8_over_retirement_value_check", nil, self, person_index: 8), Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_8_over_retirement_value_check", nil, self, person_index: 8),
].compact ].compact
end end
def relationship_question(person_index:)
if form.start_year_2025_or_later?
Form::Lettings::Pages::PersonLeadPartner.new(nil, nil, self, person_index:)
else
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index:)
end
end
end end

22
app/models/form/lettings/subsections/income_and_benefits.rb

@ -15,11 +15,7 @@ class Form::Lettings::Subsections::IncomeAndBenefits < ::Form::Subsection
Form::Lettings::Pages::BenefitsProportion.new("benefits_proportion", nil, self), Form::Lettings::Pages::BenefitsProportion.new("benefits_proportion", nil, self),
Form::Lettings::Pages::RentOrOtherCharges.new(nil, nil, self), Form::Lettings::Pages::RentOrOtherCharges.new(nil, nil, self),
Form::Lettings::Pages::RentPeriod.new(nil, nil, self), Form::Lettings::Pages::RentPeriod.new(nil, nil, self),
Form::Lettings::Pages::CareHomeWeekly.new(nil, nil, self), carehome_questions,
Form::Lettings::Pages::CareHomeBiWeekly.new(nil, nil, self),
Form::Lettings::Pages::CareHome4Weekly.new(nil, nil, self),
Form::Lettings::Pages::CareHomeMonthly.new(nil, nil, self),
Form::Lettings::Pages::CareHomeChargesValueCheck.new(nil, nil, self),
Form::Lettings::Pages::RentWeekly.new(nil, nil, self), Form::Lettings::Pages::RentWeekly.new(nil, nil, self),
Form::Lettings::Pages::RentBiWeekly.new(nil, nil, self), Form::Lettings::Pages::RentBiWeekly.new(nil, nil, self),
Form::Lettings::Pages::Rent4Weekly.new(nil, nil, self), Form::Lettings::Pages::Rent4Weekly.new(nil, nil, self),
@ -30,6 +26,20 @@ class Form::Lettings::Subsections::IncomeAndBenefits < ::Form::Subsection
Form::Lettings::Pages::SupchargValueCheck.new(nil, nil, self), Form::Lettings::Pages::SupchargValueCheck.new(nil, nil, self),
Form::Lettings::Pages::Outstanding.new(nil, nil, self), Form::Lettings::Pages::Outstanding.new(nil, nil, self),
Form::Lettings::Pages::OutstandingAmount.new(nil, nil, self), Form::Lettings::Pages::OutstandingAmount.new(nil, nil, self),
].compact ].flatten.compact
end
private
def carehome_questions
return [] if form.start_year_2025_or_later?
[
Form::Lettings::Pages::CareHomeWeekly.new(nil, nil, self),
Form::Lettings::Pages::CareHomeBiWeekly.new(nil, nil, self),
Form::Lettings::Pages::CareHome4Weekly.new(nil, nil, self),
Form::Lettings::Pages::CareHomeMonthly.new(nil, nil, self),
Form::Lettings::Pages::CareHomeChargesValueCheck.new(nil, nil, self),
]
end end
end end

2
app/models/form/question.rb

@ -276,7 +276,7 @@ class Form::Question
end end
def question_number_string(hidden: false) def question_number_string(hidden: false)
if @question_number && !hidden && form.start_date.year >= 2023 if @question_number && !hidden
"Q#{@question_number}" "Q#{@question_number}"
end end
end end

8
app/models/form_handler.rb

@ -36,6 +36,10 @@ class FormHandler
forms["next_lettings"] forms["next_lettings"]
end end
def archived_lettings_form
forms["archived_lettings"]
end
def current_sales_form def current_sales_form
forms["current_sales"] forms["current_sales"]
end end
@ -44,6 +48,10 @@ class FormHandler
forms["previous_sales"] forms["previous_sales"]
end end
def archived_sales_form
forms["archived_sales"]
end
def next_sales_form def next_sales_form
forms["next_sales"] forms["next_sales"]
end end

9
app/models/forms/bulk_upload_lettings/checking_file.rb → app/models/forms/bulk_upload_form/checking_file.rb

@ -1,22 +1,23 @@
module Forms module Forms
module BulkUploadLettings module BulkUploadForm
class CheckingFile class CheckingFile
include ActiveModel::Model include ActiveModel::Model
include ActiveModel::Attributes include ActiveModel::Attributes
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
attribute :log_type
attribute :year, :integer attribute :year, :integer
attribute :organisation_id, :integer attribute :organisation_id, :integer
def view_path def view_path
"bulk_upload_lettings_logs/forms/checking_file" "bulk_upload_#{log_type}_logs/forms/checking_file"
end end
def back_path def back_path
if organisation_id.present? if organisation_id.present?
lettings_logs_organisation_path(organisation_id) send("#{log_type}_logs_organisation_path", organisation_id)
else else
bulk_upload_lettings_log_path(id: "start") send("bulk_upload_#{log_type}_log_path", id: "start")
end end
end end

13
app/models/forms/bulk_upload_lettings/guidance.rb → app/models/forms/bulk_upload_form/guidance.rb

@ -1,11 +1,12 @@
module Forms module Forms
module BulkUploadLettings module BulkUploadForm
class Guidance class Guidance
include ActiveModel::Model include ActiveModel::Model
include ActiveModel::Attributes include ActiveModel::Attributes
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
include CollectionTimeHelper include CollectionTimeHelper
attribute :log_type
attribute :year, :integer attribute :year, :integer
attribute :referrer attribute :referrer
attribute :organisation_id, :integer attribute :organisation_id, :integer
@ -23,7 +24,7 @@ module Forms
def back_path def back_path
case referrer case referrer
when "prepare-your-file" when "prepare-your-file"
bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year:, organisation_id: }.compact) send("bulk_upload_#{log_type}_log_path", id: "prepare-your-file", form: { year:, organisation_id: }.compact)
when "home" when "home"
root_path root_path
else else
@ -32,19 +33,19 @@ module Forms
end end
def lettings_template_path def lettings_template_path
Forms::BulkUploadLettings::PrepareYourFile.new(year:).template_path Forms::BulkUploadForm::PrepareYourFile.new(year:, log_type: "lettings").template_path
end end
def lettings_specification_path def lettings_specification_path
Forms::BulkUploadLettings::PrepareYourFile.new(year:).specification_path Forms::BulkUploadForm::PrepareYourFile.new(year:, log_type: "lettings").specification_path
end end
def sales_template_path def sales_template_path
Forms::BulkUploadSales::PrepareYourFile.new(year:).template_path Forms::BulkUploadForm::PrepareYourFile.new(year:, log_type: "sales").template_path
end end
def sales_specification_path def sales_specification_path
Forms::BulkUploadSales::PrepareYourFile.new(year:).specification_path Forms::BulkUploadForm::PrepareYourFile.new(year:, log_type: "sales").specification_path
end end
end end
end end

21
app/models/forms/bulk_upload_sales/prepare_your_file.rb → app/models/forms/bulk_upload_form/prepare_your_file.rb

@ -1,42 +1,41 @@
module Forms module Forms
module BulkUploadSales module BulkUploadForm
class PrepareYourFile class PrepareYourFile
include ActiveModel::Model include ActiveModel::Model
include ActiveModel::Attributes include ActiveModel::Attributes
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
attribute :log_type
attribute :year, :integer attribute :year, :integer
attribute :organisation_id, :integer attribute :organisation_id, :integer
def view_path def view_path
case year case year
when 2023
"bulk_upload_sales_logs/forms/prepare_your_file_2023"
when 2024 when 2024
"bulk_upload_sales_logs/forms/prepare_your_file_2024" "bulk_upload_#{log_type}_logs/forms/prepare_your_file_2024"
end end
end end
def back_path def back_path
if have_choice_of_year? if have_choice_of_year?
Rails.application.routes.url_helpers.bulk_upload_sales_log_path(id: "year", form: { year: }.compact) Rails.application.routes.url_helpers.send("bulk_upload_#{log_type}_log_path", id: "year", form: { year: }.compact)
elsif organisation_id.present? elsif organisation_id.present?
sales_logs_organisation_path(organisation_id) send("#{log_type}_logs_organisation_path", organisation_id)
else else
Rails.application.routes.url_helpers.sales_logs_path Rails.application.routes.url_helpers.send("#{log_type}_logs_path")
end end
end end
def next_path def next_path
bulk_upload_sales_log_path(id: "upload-your-file", form: { year:, organisation_id: }.compact) send("bulk_upload_#{log_type}_log_path", id: "upload-your-file", form: { year:, organisation_id: }.compact)
end end
def template_path def template_path
download_mandatory_collection_resource_path(year:, log_type: "sales", resource_type: "bulk_upload_template") download_mandatory_collection_resource_path(year:, log_type:, resource_type: "bulk_upload_template")
end end
def specification_path def specification_path
download_mandatory_collection_resource_path(year:, log_type: "sales", resource_type: "bulk_upload_specification") download_mandatory_collection_resource_path(year:, log_type:, resource_type: "bulk_upload_specification")
end end
def year_combo def year_combo
@ -52,7 +51,7 @@ module Forms
def have_choice_of_year? def have_choice_of_year?
return true if FeatureToggle.allow_future_form_use? return true if FeatureToggle.allow_future_form_use?
FormHandler.instance.sales_in_crossover_period? FormHandler.instance.send("#{log_type}_in_crossover_period?")
end end
end end
end end

13
app/models/forms/bulk_upload_lettings/upload_your_file.rb → app/models/forms/bulk_upload_form/upload_your_file.rb

@ -1,14 +1,14 @@
require "shellwords" require "shellwords"
module Forms module Forms
module BulkUploadLettings module BulkUploadForm
class UploadYourFile class UploadYourFile
include ActiveModel::Model include ActiveModel::Model
include ActiveModel::Attributes include ActiveModel::Attributes
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
attribute :log_type
attribute :year, :integer attribute :year, :integer
attribute :needstype, :integer
attribute :file attribute :file
attribute :current_user attribute :current_user
attribute :organisation_id, :integer attribute :organisation_id, :integer
@ -18,11 +18,11 @@ module Forms
validate :validate_file_size validate :validate_file_size
def view_path def view_path
"bulk_upload_lettings_logs/forms/upload_your_file" "bulk_upload_#{log_type}_logs/forms/upload_your_file"
end end
def back_path def back_path
bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year:, needstype:, organisation_id: }.compact) send("bulk_upload_#{log_type}_log_path", id: "prepare-your-file", form: { year:, organisation_id: }.compact)
end end
def year_combo def year_combo
@ -30,15 +30,14 @@ module Forms
end end
def next_path def next_path
bulk_upload_lettings_log_path(id: "checking-file", form: { year:, organisation_id: }.compact) send("bulk_upload_#{log_type}_log_path", id: "checking-file", form: { year:, organisation_id: }.compact)
end end
def save! def save!
bulk_upload = BulkUpload.create!( bulk_upload = BulkUpload.create!(
user: current_user, user: current_user,
log_type: BulkUpload.log_types[:lettings], log_type: BulkUpload.log_types[log_type.to_sym],
year:, year:,
needstype:,
filename: file.original_filename, filename: file.original_filename,
organisation_id: (organisation_id if current_user.support?) || current_user.organisation_id, organisation_id: (organisation_id if current_user.support?) || current_user.organisation_id,
) )

17
app/models/forms/bulk_upload_lettings/year.rb → app/models/forms/bulk_upload_form/year.rb

@ -1,17 +1,18 @@
module Forms module Forms
module BulkUploadLettings module BulkUploadForm
class Year class Year
include ActiveModel::Model include ActiveModel::Model
include ActiveModel::Attributes include ActiveModel::Attributes
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
attribute :log_type
attribute :year, :integer attribute :year, :integer
attribute :organisation_id, :integer attribute :organisation_id, :integer
validates :year, presence: true validates :year, presence: true
def view_path def view_path
"bulk_upload_lettings_logs/forms/year" "bulk_upload_#{log_type}_logs/forms/year"
end end
def options def options
@ -22,14 +23,14 @@ module Forms
def back_path def back_path
if organisation_id.present? if organisation_id.present?
lettings_logs_organisation_path(organisation_id) send("#{log_type}_logs_organisation_path", organisation_id)
else else
lettings_logs_path send("#{log_type}_logs_path")
end end
end end
def next_path def next_path
bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year:, organisation_id: }.compact) send("bulk_upload_#{log_type}_log_path", id: "prepare-your-file", form: { year:, organisation_id: }.compact)
end end
def save! def save!
@ -40,9 +41,9 @@ module Forms
def possible_years def possible_years
[ [
FormHandler.instance.lettings_forms["current_lettings"].start_date.year, FormHandler.instance.send("#{log_type}_forms")["current_#{log_type}"].start_date.year,
(FormHandler.instance.previous_lettings_form.start_date.year if FormHandler.instance.lettings_in_crossover_period?), (FormHandler.instance.send("previous_#{log_type}_form").start_date.year if FormHandler.instance.send("#{log_type}_in_crossover_period?")),
(FormHandler.instance.next_lettings_form.start_date.year if FeatureToggle.allow_future_form_use?), (FormHandler.instance.send("next_#{log_type}_form").start_date.year if FeatureToggle.allow_future_form_use?),
].compact ].compact
end end
end end

60
app/models/forms/bulk_upload_lettings/prepare_your_file.rb

@ -1,60 +0,0 @@
module Forms
module BulkUploadLettings
class PrepareYourFile
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :year, :integer
attribute :needstype, :integer
attribute :organisation_id, :integer
def view_path
case year
when 2023
"bulk_upload_lettings_logs/forms/prepare_your_file_2023"
when 2024
"bulk_upload_lettings_logs/forms/prepare_your_file_2024"
end
end
def back_path
if have_choice_of_year?
Rails.application.routes.url_helpers.bulk_upload_lettings_log_path(id: "year", form: { year:, organisation_id: }.compact)
elsif organisation_id.present?
lettings_logs_organisation_path(organisation_id)
else
Rails.application.routes.url_helpers.lettings_logs_path
end
end
def next_path
bulk_upload_lettings_log_path(id: "upload-your-file", form: { year:, needstype:, organisation_id: }.compact)
end
def template_path
download_mandatory_collection_resource_path(year:, log_type: "lettings", resource_type: "bulk_upload_template")
end
def specification_path
download_mandatory_collection_resource_path(year:, log_type: "lettings", resource_type: "bulk_upload_specification")
end
def year_combo
"#{year} to #{year + 1}"
end
def save!
true
end
private
def have_choice_of_year?
return true if FeatureToggle.allow_future_form_use?
FormHandler.instance.lettings_in_crossover_period?
end
end
end
end

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

@ -1,55 +0,0 @@
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 error_report_path
if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors?
summary_bulk_upload_lettings_result_path(bulk_upload)
else
bulk_upload_lettings_result_path(bulk_upload)
end
end
def save!
ApplicationRecord.transaction do
processor = BulkUpload::Processor.new(bulk_upload:)
processor.approve
bulk_upload.update!(choice: "create-fix-inline")
end
true
end
def preflight_valid?
bulk_upload.choice != "create-fix-inline" && bulk_upload.choice != "bulk-confirm-soft-validations"
end
def preflight_redirect
case bulk_upload.choice
when "create-fix-inline"
page_bulk_upload_lettings_resume_path(bulk_upload, :chosen)
when "bulk-confirm-soft-validations"
page_bulk_upload_lettings_soft_validations_check_path(bulk_upload, :chosen)
end
end
end
end
end

28
app/models/forms/bulk_upload_lettings_resume/deletion_report.rb

@ -1,28 +0,0 @@
module Forms
module BulkUploadLettingsResume
class DeletionReport
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :bulk_upload
def view_path
"bulk_upload_lettings_resume/deletion_report"
end
def preflight_valid?
bulk_upload.choice != "create-fix-inline" && bulk_upload.choice != "bulk-confirm-soft-validations"
end
def preflight_redirect
case bulk_upload.choice
when "create-fix-inline"
page_bulk_upload_lettings_resume_path(bulk_upload, :chosen)
when "bulk-confirm-soft-validations"
page_bulk_upload_lettings_soft_validations_check_path(bulk_upload, :chosen)
end
end
end
end
end

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

@ -1,74 +0,0 @@
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 upload the file again"),
]
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"
error_report_path
else
raise "invalid choice"
end
end
def error_report_path
if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors?
summary_bulk_upload_lettings_result_path(bulk_upload)
else
bulk_upload_lettings_result_path(bulk_upload)
end
end
def recommendation
if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors?
"We recommend fixing these errors in the CSV, as you may be able to edit multiple fields at once. However, you can also upload these logs and fix the errors on the CORE site."
else
"We recommend uploading logs and fixing errors on site as you can easily see the questions and select the appropriate answer. However, you can also fix these errors in the CSV."
end
end
def save!
bulk_upload.update!(choice:) if choice == "upload-again"
true
end
def preflight_valid?
bulk_upload.choice.blank?
end
def preflight_redirect
case bulk_upload.choice
when "create-fix-inline"
page_bulk_upload_lettings_resume_path(bulk_upload, :chosen)
when "bulk-confirm-soft-validations"
page_bulk_upload_lettings_soft_validations_check_path(bulk_upload, :chosen)
else
bulk_upload_lettings_result_path(bulk_upload)
end
end
end
end
end

31
app/models/forms/bulk_upload_lettings_soft_validations_check/chosen.rb

@ -1,31 +0,0 @@
module Forms
module BulkUploadLettingsSoftValidationsCheck
class Chosen
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :bulk_upload
def view_path
"bulk_upload_lettings_soft_validations_check/chosen"
end
def back_path
lettings_logs_path
end
def next_path
lettings_logs_path
end
def save!
true
end
def preflight_valid?
true
end
end
end
end

9
app/models/forms/bulk_upload_lettings_resume/chosen.rb → app/models/forms/bulk_upload_resume/chosen.rb

@ -1,22 +1,23 @@
module Forms module Forms
module BulkUploadLettingsResume module BulkUploadResume
class Chosen class Chosen
include ActiveModel::Model include ActiveModel::Model
include ActiveModel::Attributes include ActiveModel::Attributes
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
attribute :log_type
attribute :bulk_upload attribute :bulk_upload
def view_path def view_path
bulk_upload.completed? ? "bulk_upload_lettings_resume/completed" : "bulk_upload_lettings_resume/chosen" bulk_upload.completed? ? "bulk_upload_#{log_type}_resume/completed" : "bulk_upload_#{log_type}_resume/chosen"
end end
def back_path def back_path
lettings_logs_path send("#{log_type}_logs_path")
end end
def next_path def next_path
lettings_logs_path send("#{log_type}_logs_path")
end end
def save! def save!

17
app/models/forms/bulk_upload_sales_resume/confirm.rb → app/models/forms/bulk_upload_resume/confirm.rb

@ -1,29 +1,30 @@
module Forms module Forms
module BulkUploadSalesResume module BulkUploadResume
class Confirm class Confirm
include ActiveModel::Model include ActiveModel::Model
include ActiveModel::Attributes include ActiveModel::Attributes
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
attribute :log_type
attribute :bulk_upload attribute :bulk_upload
def view_path def view_path
"bulk_upload_sales_resume/confirm" "bulk_upload_#{log_type}_resume/confirm"
end end
def back_path def back_path
page_bulk_upload_sales_resume_path(bulk_upload, page: "fix-choice") send("page_bulk_upload_#{log_type}_resume_path", bulk_upload, page: "fix-choice")
end end
def next_path def next_path
resume_bulk_upload_sales_result_path(bulk_upload) send("resume_bulk_upload_#{log_type}_result_path", bulk_upload)
end end
def error_report_path def error_report_path
if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors? if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors?
summary_bulk_upload_sales_result_path(bulk_upload) send("summary_bulk_upload_#{log_type}_result_path", bulk_upload)
else else
bulk_upload_sales_result_path(bulk_upload) send("bulk_upload_#{log_type}_result_path", bulk_upload)
end end
end end
@ -45,9 +46,9 @@ module Forms
def preflight_redirect def preflight_redirect
case bulk_upload.choice case bulk_upload.choice
when "create-fix-inline" when "create-fix-inline"
page_bulk_upload_sales_resume_path(bulk_upload, :chosen) send("page_bulk_upload_#{log_type}_resume_path", bulk_upload, :chosen)
when "bulk-confirm-soft-validations" when "bulk-confirm-soft-validations"
page_bulk_upload_sales_soft_validations_check_path(bulk_upload, :chosen) send("page_bulk_upload_#{log_type}_soft_validations_check_path", bulk_upload, :chosen)
end end
end end
end end

9
app/models/forms/bulk_upload_sales_resume/deletion_report.rb → app/models/forms/bulk_upload_resume/deletion_report.rb

@ -1,14 +1,15 @@
module Forms module Forms
module BulkUploadSalesResume module BulkUploadResume
class DeletionReport class DeletionReport
include ActiveModel::Model include ActiveModel::Model
include ActiveModel::Attributes include ActiveModel::Attributes
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
attribute :log_type
attribute :bulk_upload attribute :bulk_upload
def view_path def view_path
"bulk_upload_sales_resume/deletion_report" "bulk_upload_#{log_type}_resume/deletion_report"
end end
def preflight_valid? def preflight_valid?
@ -18,9 +19,9 @@ module Forms
def preflight_redirect def preflight_redirect
case bulk_upload.choice case bulk_upload.choice
when "create-fix-inline" when "create-fix-inline"
page_bulk_upload_sales_resume_path(bulk_upload, :chosen) send("page_bulk_upload_#{log_type}_resume_path", bulk_upload, :chosen)
when "bulk-confirm-soft-validations" when "bulk-confirm-soft-validations"
page_bulk_upload_sales_soft_validations_check_path(bulk_upload, :chosen) send("page_bulk_upload_#{log_type}_soft_validations_check_path", bulk_upload, :chosen)
end end
end end
end end

17
app/models/forms/bulk_upload_sales_resume/fix_choice.rb → app/models/forms/bulk_upload_resume/fix_choice.rb

@ -1,10 +1,11 @@
module Forms module Forms
module BulkUploadSalesResume module BulkUploadResume
class FixChoice class FixChoice
include ActiveModel::Model include ActiveModel::Model
include ActiveModel::Attributes include ActiveModel::Attributes
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
attribute :log_type
attribute :bulk_upload attribute :bulk_upload
attribute :choice, :string attribute :choice, :string
@ -19,13 +20,13 @@ module Forms
end end
def view_path def view_path
"bulk_upload_sales_resume/fix_choice" "bulk_upload_#{log_type}_resume/fix_choice"
end end
def next_path def next_path
case choice case choice
when "create-fix-inline" when "create-fix-inline"
page_bulk_upload_sales_resume_path(bulk_upload, page: "confirm") send("page_bulk_upload_#{log_type}_resume_path", bulk_upload, page: "confirm")
when "upload-again" when "upload-again"
error_report_path error_report_path
else else
@ -35,9 +36,9 @@ module Forms
def error_report_path def error_report_path
if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors? if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors?
summary_bulk_upload_sales_result_path(bulk_upload) send("summary_bulk_upload_#{log_type}_result_path", bulk_upload)
else else
bulk_upload_sales_result_path(bulk_upload) send("bulk_upload_#{log_type}_result_path", bulk_upload)
end end
end end
@ -62,11 +63,11 @@ module Forms
def preflight_redirect def preflight_redirect
case bulk_upload.choice case bulk_upload.choice
when "create-fix-inline" when "create-fix-inline"
page_bulk_upload_sales_resume_path(bulk_upload, :chosen) send("page_bulk_upload_#{log_type}_resume_path", bulk_upload, :chosen)
when "bulk-confirm-soft-validations" when "bulk-confirm-soft-validations"
page_bulk_upload_sales_soft_validations_check_path(bulk_upload, :chosen) send("page_bulk_upload_#{log_type}_soft_validations_check_path", bulk_upload, :chosen)
else else
bulk_upload_sales_result_path(bulk_upload) send("bulk_upload_#{log_type}_result_path", bulk_upload)
end end
end end
end end

32
app/models/forms/bulk_upload_sales/checking_file.rb

@ -1,32 +0,0 @@
module Forms
module BulkUploadSales
class CheckingFile
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :year, :integer
attribute :organisation_id, :integer
def view_path
"bulk_upload_sales_logs/forms/checking_file"
end
def back_path
if organisation_id.present?
sales_logs_organisation_path(organisation_id)
else
bulk_upload_sales_log_path(id: "start")
end
end
def year_combo
"#{year} to #{year + 1}"
end
def save!
true
end
end
end
end

51
app/models/forms/bulk_upload_sales/guidance.rb

@ -1,51 +0,0 @@
module Forms
module BulkUploadSales
class Guidance
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
include CollectionTimeHelper
attribute :year, :integer
attribute :referrer
attribute :organisation_id, :integer
def initialize(params)
super(params)
self.year = current_collection_start_year if year.nil?
end
def view_path
"bulk_upload_shared/guidance"
end
def back_path
case referrer
when "prepare-your-file"
bulk_upload_sales_log_path(id: "prepare-your-file", form: { year:, organisation_id: }.compact)
when "home"
root_path
else
guidance_path
end
end
def lettings_template_path
Forms::BulkUploadLettings::PrepareYourFile.new(year:).template_path
end
def lettings_specification_path
Forms::BulkUploadLettings::PrepareYourFile.new(year:).specification_path
end
def sales_template_path
Forms::BulkUploadSales::PrepareYourFile.new(year:).template_path
end
def sales_specification_path
Forms::BulkUploadSales::PrepareYourFile.new(year:).specification_path
end
end
end
end

83
app/models/forms/bulk_upload_sales/upload_your_file.rb

@ -1,83 +0,0 @@
require "shellwords"
module Forms
module BulkUploadSales
class UploadYourFile
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :year, :integer
attribute :file
attribute :current_user
attribute :organisation_id, :integer
validates :file, presence: true
validate :validate_file_is_csv
validate :validate_file_size
def view_path
"bulk_upload_sales_logs/forms/upload_your_file"
end
def back_path
bulk_upload_sales_log_path(id: "prepare-your-file", form: { year:, organisation_id: }.compact)
end
def year_combo
"#{year} to #{year + 1}"
end
def next_path
bulk_upload_sales_log_path(id: "checking-file", form: { year:, organisation_id: }.compact)
end
def save!
bulk_upload = BulkUpload.create!(
user: current_user,
log_type: BulkUpload.log_types[:sales],
year:,
filename: file.original_filename,
organisation_id: (organisation_id if current_user.support?) || current_user.organisation_id,
)
storage_service.write_file(bulk_upload.identifier, File.read(file.path))
ProcessBulkUploadJob.perform_later(bulk_upload:)
true
end
private
def storage_service
@storage_service ||= if FeatureToggle.upload_enabled?
Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
else
Storage::LocalDiskService.new
end
end
def validate_file_is_csv
return unless file
argv = %W[file --brief --mime-type -- #{file.path}]
output = `#{argv.shelljoin}`
unless output.match?(/text\/csv|text\/plain/)
errors.add(:file, :not_csv)
end
end
MAX_FILE_SIZE = 10.megabytes
def validate_file_size
return unless file
if file.size > MAX_FILE_SIZE
errors.add(:file, :file_too_large)
end
end
end
end
end

50
app/models/forms/bulk_upload_sales/year.rb

@ -1,50 +0,0 @@
module Forms
module BulkUploadSales
class Year
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :year, :integer
attribute :organisation_id, :integer
validates :year, presence: true
def view_path
"bulk_upload_sales_logs/forms/year"
end
def options
possible_years.map do |year|
OpenStruct.new(id: year, name: "#{year} to #{year + 1}")
end
end
def back_path
if organisation_id.present?
sales_logs_organisation_path(organisation_id)
else
sales_logs_path
end
end
def next_path
bulk_upload_sales_log_path(id: "prepare-your-file", form: { year:, organisation_id: }.compact)
end
def save!
true
end
private
def possible_years
[
FormHandler.instance.current_sales_form.start_date.year,
(FormHandler.instance.previous_sales_form.start_date.year if FormHandler.instance.sales_in_crossover_period?),
(FormHandler.instance.next_sales_form.start_date.year if FeatureToggle.allow_future_form_use?),
].compact
end
end
end
end

31
app/models/forms/bulk_upload_sales_soft_validations_check/chosen.rb

@ -1,31 +0,0 @@
module Forms
module BulkUploadSalesSoftValidationsCheck
class Chosen
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :bulk_upload
def view_path
"bulk_upload_sales_soft_validations_check/chosen"
end
def back_path
sales_logs_path
end
def next_path
sales_logs_path
end
def save!
true
end
def preflight_valid?
true
end
end
end
end

47
app/models/forms/bulk_upload_sales_soft_validations_check/confirm.rb

@ -1,47 +0,0 @@
module Forms
module BulkUploadSalesSoftValidationsCheck
class Confirm
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :bulk_upload
def view_path
"bulk_upload_sales_soft_validations_check/confirm"
end
def back_path
page_bulk_upload_sales_soft_validations_check_path(bulk_upload, page: "confirm-soft-errors")
end
def next_path
sales_logs_path
end
def save!
ApplicationRecord.transaction do
processor = BulkUpload::Processor.new(bulk_upload:)
processor.approve_and_confirm_soft_validations
bulk_upload.update!(choice: "bulk-confirm-soft-validations")
end
true
end
def preflight_valid?
bulk_upload.choice != "bulk-confirm-soft-validations" && bulk_upload.choice != "create-fix-inline"
end
def preflight_redirect
case bulk_upload.choice
when "bulk-confirm-soft-validations"
page_bulk_upload_sales_soft_validations_check_path(bulk_upload, :chosen)
when "create-fix-inline"
page_bulk_upload_sales_resume_path(bulk_upload, :chosen)
end
end
end
end
end

53
app/models/forms/bulk_upload_sales_soft_validations_check/confirm_soft_errors.rb

@ -1,53 +0,0 @@
module Forms
module BulkUploadSalesSoftValidationsCheck
class ConfirmSoftErrors
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :bulk_upload
attribute :confirm_soft_errors, :string
validates :confirm_soft_errors, presence: true
def options
[
OpenStruct.new(id: "yes", name: "Yes, these fields are correct"),
OpenStruct.new(id: "no", name: "No, there are errors"),
]
end
def view_path
"bulk_upload_sales_soft_validations_check/confirm_soft_errors"
end
def next_path
case confirm_soft_errors
when "no"
page_bulk_upload_sales_resume_path(bulk_upload, page: "fix-choice", soft_errors_only: true)
when "yes"
page_bulk_upload_sales_soft_validations_check_path(bulk_upload, page: "confirm")
else
raise "invalid choice"
end
end
def save!
true
end
def preflight_valid?
bulk_upload.choice != "bulk-confirm-soft-validations" && bulk_upload.choice != "create-fix-inline"
end
def preflight_redirect
case bulk_upload.choice
when "bulk-confirm-soft-validations"
page_bulk_upload_sales_soft_validations_check_path(bulk_upload, :chosen)
when "create-fix-inline"
page_bulk_upload_sales_resume_path(bulk_upload, :chosen)
end
end
end
end
end

9
app/models/forms/bulk_upload_sales_resume/chosen.rb → app/models/forms/bulk_upload_soft_validations_check/chosen.rb

@ -1,22 +1,23 @@
module Forms module Forms
module BulkUploadSalesResume module BulkUploadSoftValidationsCheck
class Chosen class Chosen
include ActiveModel::Model include ActiveModel::Model
include ActiveModel::Attributes include ActiveModel::Attributes
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
attribute :log_type
attribute :bulk_upload attribute :bulk_upload
def view_path def view_path
bulk_upload.completed? ? "bulk_upload_sales_resume/completed" : "bulk_upload_sales_resume/chosen" "bulk_upload_#{log_type}_soft_validations_check/chosen"
end end
def back_path def back_path
sales_logs_path send("#{log_type}_logs_path")
end end
def next_path def next_path
sales_logs_path send("#{log_type}_logs_path")
end end
def save! def save!

13
app/models/forms/bulk_upload_lettings_soft_validations_check/confirm.rb → app/models/forms/bulk_upload_soft_validations_check/confirm.rb

@ -1,22 +1,23 @@
module Forms module Forms
module BulkUploadLettingsSoftValidationsCheck module BulkUploadSoftValidationsCheck
class Confirm class Confirm
include ActiveModel::Model include ActiveModel::Model
include ActiveModel::Attributes include ActiveModel::Attributes
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
attribute :log_type
attribute :bulk_upload attribute :bulk_upload
def view_path def view_path
"bulk_upload_lettings_soft_validations_check/confirm" "bulk_upload_#{log_type}_soft_validations_check/confirm"
end end
def back_path def back_path
page_bulk_upload_lettings_soft_validations_check_path(bulk_upload, page: "confirm-soft-errors") send("page_bulk_upload_#{log_type}_soft_validations_check_path", bulk_upload, page: "confirm-soft-errors")
end end
def next_path def next_path
lettings_logs_path send("#{log_type}_logs_path")
end end
def save! def save!
@ -37,9 +38,9 @@ module Forms
def preflight_redirect def preflight_redirect
case bulk_upload.choice case bulk_upload.choice
when "bulk-confirm-soft-validations" when "bulk-confirm-soft-validations"
page_bulk_upload_lettings_soft_validations_check_path(bulk_upload, :chosen) send("page_bulk_upload_#{log_type}_soft_validations_check_path", bulk_upload, :chosen)
when "create-fix-inline" when "create-fix-inline"
page_bulk_upload_lettings_resume_path(bulk_upload, :chosen) send("page_bulk_upload_#{log_type}_resume_path", bulk_upload, :chosen)
end end
end end
end end

13
app/models/forms/bulk_upload_lettings_soft_validations_check/confirm_soft_errors.rb → app/models/forms/bulk_upload_soft_validations_check/confirm_soft_errors.rb

@ -1,10 +1,11 @@
module Forms module Forms
module BulkUploadLettingsSoftValidationsCheck module BulkUploadSoftValidationsCheck
class ConfirmSoftErrors class ConfirmSoftErrors
include ActiveModel::Model include ActiveModel::Model
include ActiveModel::Attributes include ActiveModel::Attributes
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
attribute :log_type
attribute :bulk_upload attribute :bulk_upload
attribute :confirm_soft_errors, :string attribute :confirm_soft_errors, :string
@ -18,15 +19,15 @@ module Forms
end end
def view_path def view_path
"bulk_upload_lettings_soft_validations_check/confirm_soft_errors" "bulk_upload_#{log_type}_soft_validations_check/confirm_soft_errors"
end end
def next_path def next_path
case confirm_soft_errors case confirm_soft_errors
when "no" when "no"
page_bulk_upload_lettings_resume_path(bulk_upload, page: "fix-choice", soft_errors_only: true) send("page_bulk_upload_#{log_type}_resume_path", bulk_upload, page: "fix-choice", soft_errors_only: true)
when "yes" when "yes"
page_bulk_upload_lettings_soft_validations_check_path(bulk_upload, page: "confirm") send("page_bulk_upload_#{log_type}_soft_validations_check_path", bulk_upload, page: "confirm")
else else
raise "invalid choice" raise "invalid choice"
end end
@ -43,9 +44,9 @@ module Forms
def preflight_redirect def preflight_redirect
case bulk_upload.choice case bulk_upload.choice
when "bulk-confirm-soft-validations" when "bulk-confirm-soft-validations"
page_bulk_upload_lettings_soft_validations_check_path(bulk_upload, :chosen) send("page_bulk_upload_#{log_type}_soft_validations_check_path", bulk_upload, :chosen)
when "create-fix-inline" when "create-fix-inline"
page_bulk_upload_lettings_resume_path(bulk_upload, :chosen) send("page_bulk_upload_#{log_type}_resume_path", bulk_upload, :chosen)
end end
end end
end end

6
app/models/lettings_log.rb

@ -240,7 +240,7 @@ class LettingsLog < Log
end end
def applicable_income_range def applicable_income_range
return unless ecstat1 && hhmemb return unless ecstat1 && hhmemb && ALLOWED_INCOME_RANGES[ecstat1]
range = ALLOWED_INCOME_RANGES[ecstat1].clone range = ALLOWED_INCOME_RANGES[ecstat1].clone
@ -670,8 +670,7 @@ class LettingsLog < Log
["owning_organisation_id", ["owning_organisation_id",
"startdate", "startdate",
"tenancycode", "tenancycode",
form.start_date.year < 2023 || uprn.blank? ? "postcode_full" : nil, uprn.blank? ? "postcode_full" : "uprn",
form.start_date.year >= 2023 && uprn.present? ? "uprn" : nil,
"scheme_id", "scheme_id",
"location_id", "location_id",
"age1", "age1",
@ -884,7 +883,6 @@ private
def should_process_uprn_change? def should_process_uprn_change?
return unless uprn return unless uprn
return unless startdate return unless startdate
return unless collection_start_year_for_date(startdate) >= 2023
uprn_changed? || startdate_changed? uprn_changed? || startdate_changed?
end end

34
app/models/sales_log.rb

@ -61,13 +61,14 @@ class SalesLog < Log
[by_postcode, param_without_spaces, param_without_spaces]) [by_postcode, param_without_spaces, param_without_spaces])
} }
scope :age1_answered, -> { where.not(age1: nil).or(where(age1_known: [1, 2])) } scope :age1_answered, -> { where.not(age1: nil).or(where(age1_known: [1, 2])) }
scope :ecstat1_answered, -> { where.not(ecstat1: nil).or(where("saledate >= ?", Time.zone.local(2025, 4, 1))) }
scope :duplicate_logs, lambda { |log| scope :duplicate_logs, lambda { |log|
visible.where(log.slice(*DUPLICATE_LOG_ATTRIBUTES)) visible.where(log.slice(*DUPLICATE_LOG_ATTRIBUTES))
.where.not(id: log.id) .where.not(id: log.id)
.where.not(saledate: nil) .where.not(saledate: nil)
.where.not(sex1: nil) .where.not(sex1: nil)
.where.not(ecstat1: nil)
.where.not(postcode_full: nil) .where.not(postcode_full: nil)
.ecstat1_answered
.age1_answered .age1_answered
} }
scope :after_date, ->(date) { where("saledate >= ?", date) } scope :after_date, ->(date) { where("saledate >= ?", date) }
@ -77,9 +78,9 @@ class SalesLog < Log
.group(*DUPLICATE_LOG_ATTRIBUTES) .group(*DUPLICATE_LOG_ATTRIBUTES)
.where.not(saledate: nil) .where.not(saledate: nil)
.where.not(sex1: nil) .where.not(sex1: nil)
.where.not(ecstat1: nil)
.where.not(postcode_full: nil) .where.not(postcode_full: nil)
.age1_answered .age1_answered
.ecstat1_answered
.having("COUNT(*) > 1") .having("COUNT(*) > 1")
if assigned_to_id if assigned_to_id
@ -128,33 +129,12 @@ class SalesLog < Log
def dynamically_not_required def dynamically_not_required
not_required = [] not_required = []
not_required << "proplen" if proplen_optional?
not_required << "mortlen" if mortlen_optional?
not_required << "frombeds" if frombeds_optional?
not_required << "deposit" if form.start_year_2024_or_later? && stairowned_100? not_required << "deposit" if form.start_year_2024_or_later? && stairowned_100?
not_required += %w[address_line2 county] not_required += %w[address_line2 county]
not_required not_required
end end
def proplen_optional?
return false unless collection_start_year
collection_start_year < 2023
end
def mortlen_optional?
return false unless collection_start_year
collection_start_year < 2023
end
def frombeds_optional?
return false unless collection_start_year
collection_start_year < 2023
end
def unresolved def unresolved
false false
end end
@ -439,16 +419,15 @@ class SalesLog < Log
def income_soft_min_for_ecstat(ecstat_field) def income_soft_min_for_ecstat(ecstat_field)
economic_status_code = public_send(ecstat_field) economic_status_code = public_send(ecstat_field)
return unless ALLOWED_INCOME_RANGES_SALES return unless ALLOWED_INCOME_RANGES_SALES && ALLOWED_INCOME_RANGES_SALES[economic_status_code]
soft_min = ALLOWED_INCOME_RANGES_SALES[economic_status_code]&.soft_min soft_min = ALLOWED_INCOME_RANGES_SALES[economic_status_code].soft_min
format_as_currency(soft_min) format_as_currency(soft_min)
end end
def should_process_uprn_change? def should_process_uprn_change?
return unless uprn return unless uprn
return unless saledate return unless saledate
return unless collection_start_year_for_date(saledate) >= 2023
uprn_changed? || saledate_changed? uprn_changed? || saledate_changed?
end end
@ -513,8 +492,7 @@ class SalesLog < Log
"age1", "age1",
"sex1", "sex1",
"ecstat1", "ecstat1",
form.start_date.year < 2023 || uprn.blank? ? "postcode_full" : nil, uprn.blank? ? "postcode_full" : "uprn"].compact
form.start_date.year >= 2023 && uprn.present? ? "uprn" : nil].compact
end end
def soctenant_is_inferred? def soctenant_is_inferred?

55
app/models/validations/date_validations.rb

@ -2,53 +2,56 @@ module Validations::DateValidations
include Validations::SharedValidations include Validations::SharedValidations
def validate_property_major_repairs(record) def validate_property_major_repairs(record)
date_valid?("mrcdate", record) return unless record["mrcdate"].present? && date_valid?("mrcdate", record)
if record["startdate"].present? && record["mrcdate"].present? && record["startdate"] < record["mrcdate"]
record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.before_tenancy_start")
end
if is_rsnvac_first_let?(record) && record["mrcdate"].present? if is_rsnvac_first_let?(record) && record["mrcdate"].present?
record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.not_first_let") record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.not_first_let")
end end
return unless record["startdate"].present? && date_valid?("startdate", record)
if record["mrcdate"].present? && record["startdate"].present? && record["startdate"].to_date - record["mrcdate"].to_date > 3650 if record["startdate"].present? && record["mrcdate"].present? && record["startdate"] < record["mrcdate"]
record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.before_tenancy_start")
record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.after_major_repair_date")
end
if record.form.start_year_2025_or_later?
if record["startdate"].to_date - 20.years > record["mrcdate"].to_date
record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.twenty_years_before_tenancy_start")
record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.twenty_years_after_mrc_date")
end
elsif record["startdate"].to_date - 10.years > record["mrcdate"].to_date
record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.ten_years_before_tenancy_start") record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.ten_years_before_tenancy_start")
record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.ten_years_after_mrc_date")
end end
end end
def validate_property_void_date(record) def validate_property_void_date(record)
if record["voiddate"].present? && record["startdate"].present? && record["startdate"].to_date - record["voiddate"].to_date > 3650 return unless record["voiddate"].present? && date_valid?("voiddate", record)
record.errors.add :voiddate, I18n.t("validations.lettings.date.void_date.ten_years_before_tenancy_start")
end
if record["voiddate"].present? && record["startdate"].present? && record["startdate"].to_date < record["voiddate"].to_date
record.errors.add :voiddate, I18n.t("validations.lettings.date.void_date.before_tenancy_start")
end
if record["voiddate"].present? && record["mrcdate"].present? && record["mrcdate"].to_date < record["voiddate"].to_date if record["mrcdate"].present? && record["mrcdate"].to_date < record["voiddate"].to_date
record.errors.add :voiddate, :after_mrcdate, message: I18n.t("validations.lettings.date.void_date.after_mrcdate") record.errors.add :voiddate, :after_mrcdate, message: I18n.t("validations.lettings.date.void_date.after_mrcdate")
record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.before_void_date") record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.before_void_date")
end end
end return unless record["startdate"].present? && date_valid?("startdate", record)
def validate_startdate(record)
return unless record.startdate && date_valid?("startdate", record)
if record["voiddate"].present? && record.startdate < record["voiddate"] if record["startdate"].to_date < record["voiddate"].to_date
record.errors.add :voiddate, I18n.t("validations.lettings.date.void_date.before_tenancy_start")
record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.after_void_date") record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.after_void_date")
end end
if record["mrcdate"].present? && record.startdate < record["mrcdate"] if record.form.start_year_2025_or_later?
record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.after_major_repair_date") if record["startdate"].to_date - 20.years > record["voiddate"].to_date
end record.errors.add :voiddate, I18n.t("validations.lettings.date.void_date.twenty_years_before_tenancy_start")
record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.twenty_years_after_void_date")
if record["voiddate"].present? && record["startdate"].to_date - record["voiddate"].to_date > 3650 end
elsif record["startdate"].to_date - 10.years > record["voiddate"].to_date
record.errors.add :voiddate, I18n.t("validations.lettings.date.void_date.ten_years_before_tenancy_start")
record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.ten_years_after_void_date") record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.ten_years_after_void_date")
end end
end
if record["mrcdate"].present? && record["startdate"].to_date - record["mrcdate"].to_date > 3650 def validate_startdate(record)
record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.ten_years_after_mrc_date") date_valid?("startdate", record)
end
end end
private private

2
app/models/validations/financial_validations.rb

@ -25,7 +25,7 @@ module Validations::FinancialValidations
end end
def validate_net_income(record) def validate_net_income(record)
if record.ecstat1 && record.hhmemb && record.weekly_net_income && record.startdate && record.form.start_date.year >= 2023 if record.ecstat1 && record.hhmemb && record.weekly_net_income && record.startdate
if record.weekly_net_income > record.applicable_income_range.hard_max if record.weekly_net_income > record.applicable_income_range.hard_max
frequency = record.form.get_question("incfreq", record).label_from_value(record.incfreq).downcase frequency = record.form.get_question("incfreq", record).label_from_value(record.incfreq).downcase
hard_max = format_as_currency(record.applicable_income_range.hard_max) hard_max = format_as_currency(record.applicable_income_range.hard_max)

2
app/models/validations/household_validations.rb

@ -201,7 +201,7 @@ module Validations::HouseholdValidations
def validate_layear_and_prevloc(record) def validate_layear_and_prevloc(record)
return unless record.layear && record.la && record.prevloc && record.collection_start_year return unless record.layear && record.la && record.prevloc && record.collection_start_year
if record.la == record.prevloc && record.layear == 1 && record.collection_start_year >= 2023 if record.la == record.prevloc && record.layear == 1
record.errors.add :layear, :renewal_just_moved, message: I18n.t("validations.lettings.household.layear.same_la_just_moved_to_area") record.errors.add :layear, :renewal_just_moved, message: I18n.t("validations.lettings.household.layear.same_la_just_moved_to_area")
record.errors.add :la, :renewal_just_moved, message: I18n.t("validations.lettings.household.la.same_la_just_moved_to_area") record.errors.add :la, :renewal_just_moved, message: I18n.t("validations.lettings.household.la.same_la_just_moved_to_area")
record.errors.add :postcode_full, :renewal_just_moved, message: I18n.t("validations.lettings.household.postcode_full.same_la_just_moved_to_area") record.errors.add :postcode_full, :renewal_just_moved, message: I18n.t("validations.lettings.household.postcode_full.same_la_just_moved_to_area")

2
app/models/validations/sales/financial_validations.rb

@ -82,7 +82,7 @@ module Validations::Sales::FinancialValidations
def validate_child_income(record) def validate_child_income(record)
return unless record.income2 && record.ecstat2 return unless record.income2 && record.ecstat2
if record.income2.positive? && is_economic_status_child?(record.ecstat2) && record.form.start_date.year >= 2023 if record.income2.positive? && is_economic_status_child?(record.ecstat2)
record.errors.add :ecstat2, I18n.t("validations.sales.financial.ecstat2.child_has_income") record.errors.add :ecstat2, I18n.t("validations.sales.financial.ecstat2.child_has_income")
record.errors.add :income2, I18n.t("validations.sales.financial.income2.child_has_income") record.errors.add :income2, I18n.t("validations.sales.financial.income2.child_has_income")
end end

2
app/models/validations/sales/household_validations.rb

@ -13,8 +13,6 @@ module Validations::Sales::HouseholdValidations
end end
def validate_buyers_living_in_property(record) def validate_buyers_living_in_property(record)
return unless record.form.start_date.year >= 2023
if record.buyers_will_live_in? && record.buyer_one_will_not_live_in_property? && record.buyer_two_will_not_live_in_property? if record.buyers_will_live_in? && record.buyer_one_will_not_live_in_property? && record.buyer_two_will_not_live_in_property?
record.errors.add :buylivein, I18n.t("validations.sales.household.buylivein.buyers_will_live_in_property_values_inconsistent") record.errors.add :buylivein, I18n.t("validations.sales.household.buylivein.buyers_will_live_in_property_values_inconsistent")
record.errors.add :buy1livein, I18n.t("validations.sales.household.buy1livein.buyers_will_live_in_property_values_inconsistent") record.errors.add :buy1livein, I18n.t("validations.sales.household.buy1livein.buyers_will_live_in_property_values_inconsistent")

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

@ -144,11 +144,11 @@ module Validations::Sales::SaleInformationValidations
return unless record.saledate && record.form.start_year_2024_or_later? return unless record.saledate && record.form.start_year_2024_or_later?
return unless record.discount && record.value && record.la return unless record.discount && record.value && record.la
if record.london_property? && record.discount_value > 136_400 if record.london_property? && record.discount_value > 137_400
%i[discount value la postcode_full uprn].each do |field| %i[discount value la postcode_full uprn].each do |field|
record.errors.add field, I18n.t("validations.sales.sale_information.#{field}.value_over_discounted_london_max", discount_value: record.field_formatted_as_currency("discount_value")) record.errors.add field, I18n.t("validations.sales.sale_information.#{field}.value_over_discounted_london_max", discount_value: record.field_formatted_as_currency("discount_value"))
end end
elsif record.property_not_in_london? && record.discount_value > 102_400 elsif record.property_not_in_london? && record.discount_value > 103_400
%i[discount value la postcode_full uprn].each do |field| %i[discount value la postcode_full uprn].each do |field|
record.errors.add field, I18n.t("validations.sales.sale_information.#{field}.value_over_discounted_max", discount_value: record.field_formatted_as_currency("discount_value")) record.errors.add field, I18n.t("validations.sales.sale_information.#{field}.value_over_discounted_max", discount_value: record.field_formatted_as_currency("discount_value"))
end end

17
app/models/validations/shared_validations.rb

@ -54,14 +54,15 @@ module Validations::SharedValidations
field = question.check_answer_label || question.id field = question.check_answer_label || question.id
incorrect_accuracy = (value.to_d * 100) % (question.step * 100) != 0 incorrect_accuracy = (value.to_d * 100) % (question.step * 100) != 0
if question.step < 1 && incorrect_accuracy next unless incorrect_accuracy
record.errors.add question.id.to_sym, I18n.t("validations.shared.numeric.nearest_hundredth", field:)
elsif incorrect_accuracy || value.to_d != value.to_i # if the user enters a value in exponent notation (eg '4e1') the to_i method does not convert this to the correct value case question.step
field = question.check_answer_label || question.id when 0.01 then record.errors.add question.id.to_sym, I18n.t("validations.shared.numeric.nearest_hundredth", field:)
case question.step when 0.1 then record.errors.add question.id.to_sym, I18n.t("validations.shared.numeric.nearest_tenth", field:)
when 1 then record.errors.add question.id.to_sym, :not_integer, message: I18n.t("validations.shared.numeric.whole_number", field:) when 1 then record.errors.add question.id.to_sym, :not_integer, message: I18n.t("validations.shared.numeric.whole_number", field:)
when 10 then record.errors.add question.id.to_sym, I18n.t("validations.shared.numeric.nearest_ten", field:) when 10 then record.errors.add question.id.to_sym, I18n.t("validations.shared.numeric.nearest_ten", field:)
end else
record.errors.add question.id.to_sym, I18n.t("validations.shared.numeric.nearest_step", field:, step: question.step)
end end
end end
end end

4
app/models/validations/soft_validations.rb

@ -16,13 +16,13 @@ module Validations::SoftValidations
}.freeze }.freeze
def net_income_in_soft_max_range? def net_income_in_soft_max_range?
return unless weekly_net_income && ecstat1 && hhmemb return unless weekly_net_income && ecstat1 && hhmemb && applicable_income_range
weekly_net_income.between?(applicable_income_range.soft_max, applicable_income_range.hard_max) weekly_net_income.between?(applicable_income_range.soft_max, applicable_income_range.hard_max)
end end
def net_income_in_soft_min_range? def net_income_in_soft_min_range?
return unless weekly_net_income && ecstat1 && hhmemb return unless weekly_net_income && ecstat1 && hhmemb && applicable_income_range
weekly_net_income.between?(applicable_income_range.hard_min, applicable_income_range.soft_min) weekly_net_income.between?(applicable_income_range.hard_min, applicable_income_range.soft_min)
end end

3
app/services/bulk_upload/processor.rb

@ -61,6 +61,7 @@ class BulkUpload::Processor
end end
rescue StandardError => e rescue StandardError => e
Sentry.capture_exception(e) Sentry.capture_exception(e)
@bulk_upload.update!(failure_reason: "processing_error")
send_failure_mail send_failure_mail
ensure ensure
downloader.delete_local_file! downloader.delete_local_file!
@ -184,6 +185,8 @@ private
@bulk_upload.update!(failure_reason: "blank_template") @bulk_upload.update!(failure_reason: "blank_template")
elsif wrong_template_errors.any? { |error| validator.errors.full_messages.include?(error) } elsif wrong_template_errors.any? { |error| validator.errors.full_messages.include?(error) }
@bulk_upload.update!(failure_reason: "wrong_template") @bulk_upload.update!(failure_reason: "wrong_template")
else
@bulk_upload.update!(failure_reason: "processing_error")
end end
send_failure_mail(errors: validator.errors.full_messages) send_failure_mail(errors: validator.errors.full_messages)

58
app/services/documentation_generator.rb

@ -18,14 +18,16 @@ class DocumentationGenerator
next next
end end
validation_source = method(meth).source validation = method(meth)
validation_source = validation.source
file_path = validation.source_location[0]
helper_methods_source = all_helper_methods.map { |helper_method| helper_methods_source = all_helper_methods.map { |helper_method|
if validation_source.include?(helper_method.to_s) if validation_source.include?(helper_method.to_s)
method(helper_method).source method(helper_method).source
end end
}.compact.join("\n") }.compact.join("\n")
response = describe_hard_validation(client, meth, validation_source, helper_methods_source, form) response = describe_hard_validation(client, meth, validation_source, helper_methods_source, form, file_path)
next unless response next unless response
begin begin
@ -41,19 +43,19 @@ class DocumentationGenerator
def describe_bu_validations(client, form, row_parser_class, all_validation_methods, all_helper_methods, field_mapping_for_errors, log_type) def describe_bu_validations(client, form, row_parser_class, all_validation_methods, all_helper_methods, field_mapping_for_errors, log_type)
all_validation_methods.each do |meth| all_validation_methods.each do |meth|
if LogValidation.where(validation_name: meth.to_s, bulk_upload_specific: true, from: form.start_date, log_type:).exists? if LogValidation.where(validation_name: meth.to_s, bulk_upload_specific: true, collection_year: "#{form.start_date.year}/#{form.start_date.year + 1}", log_type:).exists?
Rails.logger.info("Validation #{meth} already exists for #{form.start_date.year}") Rails.logger.info("Validation #{meth} already exists for #{form.start_date.year}")
next next
end end
validation = row_parser_class.instance_method(meth)
validation_source = row_parser_class.instance_method(meth).source validation_source = validation.source
helper_methods_source = all_helper_methods.map { |helper_method| helper_methods_source = all_helper_methods.map { |helper_method|
if validation_source.include?(helper_method.to_s) if validation_source.include?(helper_method.to_s)
row_parser_class.instance_method(helper_method).source row_parser_class.instance_method(helper_method).source
end end
}.compact.join("\n") }.compact.join("\n")
response = describe_hard_validation(client, meth, validation_source, helper_methods_source, form) response = describe_hard_validation(client, meth, validation_source, helper_methods_source, form, validation.source_location[0])
next unless response next unless response
begin begin
@ -69,7 +71,7 @@ class DocumentationGenerator
def describe_soft_validations(client, all_validation_methods, all_helper_methods, log_type) def describe_soft_validations(client, all_validation_methods, all_helper_methods, log_type)
validation_descriptions = {} validation_descriptions = {}
all_validation_methods.each do |meth| all_validation_methods[0..5].each do |meth|
validation_source = method(meth).source validation_source = method(meth).source
helper_methods_source = all_helper_methods.map { |helper_method| helper_methods_source = all_helper_methods.map { |helper_method|
if validation_source.include?(helper_method.to_s) if validation_source.include?(helper_method.to_s)
@ -101,8 +103,8 @@ class DocumentationGenerator
private private
def describe_hard_validation(client, meth, validation_source, helper_methods_source, form) def describe_hard_validation(client, meth, validation_source, helper_methods_source, form, file_path)
en_yml = File.read("./config/locales/en.yml") en_yml = File.read(translation_file_path(form, file_path))
begin begin
client.chat( client.chat(
@ -166,14 +168,6 @@ private
required: %w[error_message field], required: %w[error_message field],
}, },
}, },
from: {
type: :number,
description: "the year from which the validation starts. If this validation runs for logs with a startdate after a certain year, specify that year here, only if it is not specified in the validation method, leave this field blank",
},
to: {
type: :number,
description: "the year in which the validation ends. If this validation runs for logs with a startdate before a certain year, specify that year here, only if it is not specified in the validation method, leave this field blank",
},
validation_type: { validation_type: {
type: :string, type: :string,
enum: %w[presence format minimum maximum range inclusion length other], enum: %w[presence format minimum maximum range inclusion length other],
@ -271,8 +265,7 @@ Look at these helper methods where needed to understand what is being checked in
error_message: error["error_message"], error_message: error["error_message"],
case: case_info["case_description"], case: case_info["case_description"],
section: form.get_question(error["field"], nil)&.subsection&.id, section: form.get_question(error["field"], nil)&.subsection&.id,
from: case_info["from"] || "", collection_year: "#{form.start_date.year}/#{form.start_date.year + 1}",
to: case_info["to"] || "",
validation_type: case_info["validation_type"], validation_type: case_info["validation_type"],
hard_soft: "hard", hard_soft: "hard",
other_validated_models: case_info["other_validated_models"]) other_validated_models: case_info["other_validated_models"])
@ -295,8 +288,7 @@ Look at these helper methods where needed to understand what is being checked in
error_message: error["error_message"], error_message: error["error_message"],
case: case_info["case_description"], case: case_info["case_description"],
section: form.get_question(error_field, nil)&.subsection&.id, section: form.get_question(error_field, nil)&.subsection&.id,
from: form.start_date, collection_year: "#{form.start_date.year}/#{form.start_date.year + 1}",
to: form.start_date + 1.year,
validation_type: case_info["validation_type"], validation_type: case_info["validation_type"],
hard_soft: "hard", hard_soft: "hard",
other_validated_models: case_info["other_validated_models"], other_validated_models: case_info["other_validated_models"],
@ -333,7 +325,7 @@ Look at these helper methods where needed to understand what is being checked in
return return
end end
if LogValidation.where(validation_name: validation_depends_on_hash.keys.first, field: page_the_validation_applied_to.questions.first.id, from: form.start_date, log_type:).exists? if LogValidation.where(validation_name: validation_depends_on_hash.keys.first, field: page_the_validation_applied_to.questions.first.id, collection_year: "#{form.start_date.year}/#{form.start_date.year + 1}", log_type:).exists?
Rails.logger.info("Validation #{validation_depends_on_hash.keys.first} already exists for #{page_the_validation_applied_to.questions.first.id} for start year #{form.start_date.year}") Rails.logger.info("Validation #{validation_depends_on_hash.keys.first} already exists for #{page_the_validation_applied_to.questions.first.id} for start year #{form.start_date.year}")
return return
end end
@ -360,12 +352,30 @@ Look at these helper methods where needed to understand what is being checked in
error_message:, error_message:,
case: case_info, case: case_info,
section: form.get_question(page_the_validation_applied_to.questions.first.id, nil)&.subsection&.id, section: form.get_question(page_the_validation_applied_to.questions.first.id, nil)&.subsection&.id,
from: form.start_date, collection_year: "#{form.start_date.year}/#{form.start_date.year + 1}",
to: form.start_date + 1.year,
validation_type: result["validation_type"], validation_type: result["validation_type"],
hard_soft: "soft", hard_soft: "soft",
other_validated_models: result["other_validated_models"]) other_validated_models: result["other_validated_models"])
Rails.logger.info("******** described #{validation_depends_on_hash.keys.first} for #{page_the_validation_applied_to.questions.first.id} ********") Rails.logger.info("******** described #{validation_depends_on_hash.keys.first} for #{page_the_validation_applied_to.questions.first.id} ********")
end end
TRANSLATION_FILE_MAPPINGS = {
"property" => "property_information",
}.freeze
def translation_file_path(form, file_path)
return "./config/locales/validations/#{form.type}/#{form.start_date.year}/bulk_upload.en.yml" if file_path.include?("bulk_upload")
file_name = file_path.split("/").last.gsub("_validations.rb", "")
translation_file_name = TRANSLATION_FILE_MAPPINGS[file_name] || file_name
file_path = "./config/locales/validations/#{form.type}/#{translation_file_name}.en.yml"
return file_path if File.exist?(file_path)
shared_file_path = "./config/locales/validations/#{translation_file_name}.en.yml"
return shared_file_path if File.exist?(shared_file_path)
"./config/locales/en.yml"
end
end end

3
app/services/exports/organisation_export_constants.rb

@ -22,6 +22,7 @@ module Exports::OrganisationExportConstants
"dsa_signed_at", "dsa_signed_at",
"dpo_email", "dpo_email",
"profit_status", "profit_status",
"group" "group",
"status"
] ]
end end

2
app/services/exports/organisation_export_service.rb

@ -65,6 +65,8 @@ module Exports
attribute_hash["available_from"] = organisation.available_from&.iso8601 attribute_hash["available_from"] = organisation.available_from&.iso8601
attribute_hash["profit_status"] = nil # will need update when we add the field to the org attribute_hash["profit_status"] = nil # will need update when we add the field to the org
attribute_hash["group"] = nil # will need update when we add the field to the org attribute_hash["group"] = nil # will need update when we add the field to the org
attribute_hash["status"] = organisation.status
attribute_hash["active"] = attribute_hash["status"] == :active
attribute_hash attribute_hash
end end

46
app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2023.html.erb

@ -1,46 +0,0 @@
<% 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">
<%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "prepare-your-file"), method: :patch do |f| %>
<%= f.hidden_field :year %>
<span class="govuk-caption-l">Upload lettings logs in bulk (<%= @form.year_combo %>)</span>
<h1 class="govuk-heading-l">Prepare your file</h1>
<p class="govuk-body govuk-!-margin-bottom-2"><%= govuk_link_to "Read the full guidance", bulk_upload_lettings_log_path(id: "guidance", form: { year: @form.year }, referrer: "prepare-your-file") %> before you start if you have not used bulk upload before.</p>
<h2 class="govuk-heading-s">Download template</h2>
<p class="govuk-body govuk-!-margin-bottom-2">Use one of these templates to upload logs for 2023/24:</p>
<ul class="govuk-list govuk-list--bullet">
<li>
<%= govuk_link_to "Download the new template", @form.template_path %>: In this template, the questions are in the same order as the 2023/24 paper form and web form.
</li>
</ul>
<p class="govuk-body govuk-!-margin-bottom-2">There are 7 or 8 rows of content in the templates. These rows are called the ‘headers’. They contain the CORE form questions and guidance about which questions are required and how to format your answers.</p>
<h2 class="govuk-heading-s">Create your file</h2>
<ul class="govuk-list govuk-list--bullet">
<li>Fill in the template with data from your housing management system. Your data should go below the headers, with one row per log. Leave column A blank - the bulk upload fields start in column B.</li>
<li>Make sure each column of your data aligns with the matching headers above. You may need to reorder your data.</li>
<li>Use the <%= govuk_link_to "Lettings #{@form.year_combo} Bulk Upload Specification", @form.specification_path %> to check your data is in the correct format.</li>
<li><strong>Username field:</strong> To assign a log to someone else, enter the email address they use to log into CORE.</li>
<li>If you are using the new template, keep the headers. If you are using the legacy template, you can either keep or remove the headers. If you remove the headers, you should also remove the blank column A.</li>
</ul>
<%= govuk_inset_text(text: "You can upload both general needs and supported housing logs in the same file for 2023 to 2024 data.") %>
<h2 class="govuk-heading-s">Save your file</h2>
<ul class="govuk-list govuk-list--bullet">
<li>Save your file as a CSV.</li>
<li>Your file should now be ready to upload.</li>
</ul>
<%= f.govuk_submit class: "govuk-!-margin-top-7" %>
<% end %>
</div>
</div>

2
app/views/bulk_upload_lettings_results/show.html.erb

@ -10,7 +10,7 @@
<h1 class="govuk-heading-l">We found <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> in your file</h1> <h1 class="govuk-heading-l">We found <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> in your file</h1>
<div class="govuk-body"> <div class="govuk-body">
Here’s a list of everything that you need to fix your spreadsheet. You can download the <%= govuk_link_to "specification", Forms::BulkUploadLettings::PrepareYourFile.new(year: @bulk_upload.year).specification_path, target: "_blank" %> to help you fix the cells in your CSV file. Here’s a list of everything that you need to fix your spreadsheet. You can download the <%= govuk_link_to "specification", Forms::BulkUploadForm::PrepareYourFile.new(year: @bulk_upload.year, log_type: "lettings").specification_path, target: "_blank" %> to help you fix the cells in your CSV file.
</div> </div>
<p class="govuk-!-font-size-19 govuk-!-margin-bottom-2"><strong>File name: </strong><%= @bulk_upload.filename %></p> <p class="govuk-!-font-size-19 govuk-!-margin-bottom-2"><strong>File name: </strong><%= @bulk_upload.filename %></p>

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

@ -6,7 +6,7 @@
<h1 class="govuk-heading-l">Fix <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> and upload file again</h1> <h1 class="govuk-heading-l">Fix <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> and upload file again</h1>
<p class="govuk-body"> <p class="govuk-body">
We could not create logs from your bulk upload because of the following errors. Download the <%= govuk_link_to "specification", Forms::BulkUploadLettings::PrepareYourFile.new(year: @bulk_upload.year).specification_path, target: "_blank" %> to help you fix the cells in your CSV file. We could not create logs from your bulk upload because of the following errors. Download the <%= govuk_link_to "specification", Forms::BulkUploadForm::PrepareYourFile.new(year: @bulk_upload.year, log_type: "lettings").specification_path, target: "_blank" %> to help you fix the cells in your CSV file.
</p> </p>
<p class="govuk-!-font-size-19 govuk-!-margin-bottom-2"><strong>File name: </strong><%= @bulk_upload.filename %></p> <p class="govuk-!-font-size-19 govuk-!-margin-bottom-2"><strong>File name: </strong><%= @bulk_upload.filename %></p>

40
app/views/bulk_upload_sales_logs/forms/prepare_your_file_2023.html.erb

@ -1,40 +0,0 @@
<% 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">
<%= form_with model: @form, scope: :form, url: bulk_upload_sales_log_path(id: "prepare-your-file"), method: :patch do |f| %>
<%= f.hidden_field :year %>
<span class="govuk-caption-l">Upload sales logs in bulk (<%= @form.year_combo %>)</span>
<h1 class="govuk-heading-l">Prepare your file</h1>
<p class="govuk-body govuk-!-margin-bottom-2"><%= govuk_link_to "Read the full guidance", bulk_upload_sales_log_path(id: "guidance", form: { year: @form.year }, referrer: "prepare-your-file") %> before you start if you have not used bulk upload before.</p>
<h2 class="govuk-heading-s">Download template</h2>
<p class="govuk-body govuk-!-margin-bottom-2">Use one of these templates to upload logs for 2023/24:</p>
<ul class="govuk-list govuk-list--bullet">
<li><%= govuk_link_to "Download the new template", @form.template_path %>: In this template, the questions are in the same order as the 2023/24 paper form and web form.</li>
</ul>
<p class="govuk-body govuk-!-margin-bottom-2">There are 7 or 8 rows of content in the templates. These rows are called the ‘headers’. They contain the CORE form questions and guidance about which questions are required and how to format your answers.</p>
<h2 class="govuk-heading-s">Create your file</h2>
<ul class="govuk-list govuk-list--bullet">
<li>Fill in the template with data from your housing management system. Your data should go below the headers, with one row per log. The bulk upload fields start at column B. Leave column A blank.</li>
<li>Make sure each column of your data aligns with the matching headers above. You may need to reorder your data.</li>
<li>Use the <%= govuk_link_to "Sales #{@form.year_combo} Bulk Upload Specification", @form.specification_path %> to check your data is in the correct format.</li>
<li><strong>Username field:</strong> To assign a log to someone else, enter the email address they use to log into CORE.</li>
<li>If you are using the new template, keep the headers. If you are using the legacy template, you can either keep or remove the headers. If you remove the headers, you should also remove the blank column A.</li>
</ul>
<h2 class="govuk-heading-s">Save your file</h2>
<ul class="govuk-list govuk-list--bullet">
<li>Save your file as a CSV.</li>
<li>Your file should now be ready to upload.</li>
</ul>
<%= f.govuk_submit %>
<% end %>
</div>
</div>

2
app/views/bulk_upload_sales_results/show.html.erb

@ -10,7 +10,7 @@
<h1 class="govuk-heading-l">We found <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> in your file</h1> <h1 class="govuk-heading-l">We found <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> in your file</h1>
<div class="govuk-body"> <div class="govuk-body">
Here’s a list of everything that you need to fix your spreadsheet. You can download the <%= govuk_link_to "specification", Forms::BulkUploadSales::PrepareYourFile.new(year: @bulk_upload.year).specification_path, target: "_blank" %> to help you fix the cells in your CSV file. Here’s a list of everything that you need to fix your spreadsheet. You can download the <%= govuk_link_to "specification", Forms::BulkUploadForm::PrepareYourFile.new(year: @bulk_upload.year, log_type: "sales").specification_path, target: "_blank" %> to help you fix the cells in your CSV file.
</div> </div>
<p class="govuk-!-font-size-19 govuk-!-margin-bottom-2"><strong>File name: </strong><%= @bulk_upload.filename %></p> <p class="govuk-!-font-size-19 govuk-!-margin-bottom-2"><strong>File name: </strong><%= @bulk_upload.filename %></p>

2
app/views/bulk_upload_sales_results/summary.html.erb

@ -6,7 +6,7 @@
<h1 class="govuk-heading-l">Fix <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> and upload file again</h1> <h1 class="govuk-heading-l">Fix <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> and upload file again</h1>
<p class="govuk-body"> <p class="govuk-body">
We could not create logs from your bulk upload because of the following errors. Download the <%= govuk_link_to "specification", Forms::BulkUploadSales::PrepareYourFile.new(year: @bulk_upload.year).specification_path, target: "_blank" %> to help you fix the cells in your CSV file. We could not create logs from your bulk upload because of the following errors. Download the <%= govuk_link_to "specification", Forms::BulkUploadForm::PrepareYourFile.new(year: @bulk_upload.year, log_type: "sales").specification_path, target: "_blank" %> to help you fix the cells in your CSV file.
</p> </p>
<p class="govuk-!-font-size-19 govuk-!-margin-bottom-2"><strong>File name: </strong><%= @bulk_upload.filename %></p> <p class="govuk-!-font-size-19 govuk-!-margin-bottom-2"><strong>File name: </strong><%= @bulk_upload.filename %></p>

21
app/views/bulk_upload_shared/guidance.html.erb

@ -28,29 +28,14 @@
<%= accordion.with_section(heading_text: "Using the bulk upload template") do %> <%= accordion.with_section(heading_text: "Using the bulk upload template") do %>
<p class="govuk-body">For each collection year, we publish a bulk upload template and specification.</p> <p class="govuk-body">For each collection year, we publish a bulk upload template and specification.</p>
<% if @form.year == 2023 %> <p class="govuk-body">The bulk upload templates contain 8 rows of ‘headers’ with information about how to fill in the template, including:</p>
<p class="govuk-body">The bulk upload templates contain 7 or 8 rows of ‘headers’ with information about how to fill in the template, including:</p>
<% else %>
<p class="govuk-body">The bulk upload templates contain 8 rows of ‘headers’ with information about how to fill in the template, including:</p>
<% end %>
<%= govuk_list ["the CORE form questions and their field numbers", "each field’s valid responses", "if/when certain fields can be left blank", "which fields are used to check for duplicate logs"], type: :bullet %> <%= govuk_list ["the CORE form questions and their field numbers", "each field’s valid responses", "if/when certain fields can be left blank", "which fields are used to check for duplicate logs"], type: :bullet %>
<p class="govuk-body">You can paste your data below the headers or copy the headers and insert them above the data in your file. The bulk upload fields start at column B. Leave column A blank.</p> <p class="govuk-body">You can paste your data below the headers or copy the headers and insert them above the data in your file. The bulk upload fields start at column B. Leave column A blank.</p>
<p class="govuk-body">Make sure that each column of data aligns with the corresponding question in the headers. We recommend ordering your data to match the headers, but you can also reorder the headers to match your data. When processing the file, we check what each column of data represents based on the headers above.</p> <p class="govuk-body">Make sure that each column of data aligns with the corresponding question in the headers. We recommend ordering your data to match the headers, but you can also reorder the headers to match your data. When processing the file, we check what each column of data represents based on the headers above.</p>
<% if @form.year == 2023 %> <p class="govuk-body"><%= govuk_link_to "Download the lettings bulk upload template (2024 to 2025)", @form.lettings_template_path %></p>
<p class="govuk-body">For 2023/24 uploads, there are 2 templates to choose from, a new template and a legacy template. They outline suggested ways of ordering your data.</p> <p class="govuk-body"><%= govuk_link_to "Download the sales bulk upload template (2024 to 2025)", @form.sales_template_path %></p>
<p class="govuk-body">You must include the headers in your file when you upload, unless your data matches the exact order of the legacy template. If you choose to remove the headers, you must also remove the blank column A.</p>
<p class="govuk-body"><strong>New template</strong>: In this template, the questions are in the same order as the 2023/24 paper form and web form. Use this template if your organisation is new to bulk upload or if your housing management system matches the new column ordering.</p>
<p class="govuk-body"><%= govuk_link_to "Download the lettings bulk upload template (2023 to 2024) – New question ordering", @form.lettings_template_path %></p>
<p class="govuk-body"><%= govuk_link_to "Download the sales bulk upload template (2023 to 2024) – New question ordering", @form.sales_template_path %></p>
<p class="govuk-body"><strong>Legacy template</strong>: In this template, the questions are in the same order as the 2022/23 template, with new questions added on to the end. Use this template if you have not updated your system to match the new template yet.</p>
<% else %>
<p class="govuk-body"><%= govuk_link_to "Download the lettings bulk upload template (2024 to 2025)", @form.lettings_template_path %></p>
<p class="govuk-body"><%= govuk_link_to "Download the sales bulk upload template (2024 to 2025)", @form.sales_template_path %></p>
<% end %>
<% end %> <% end %>
<%= accordion.with_section(heading_text: "Using the bulk upload specification") do %> <%= accordion.with_section(heading_text: "Using the bulk upload specification") do %>

9
app/views/layouts/rails_admin/_navigation.html.erb

@ -0,0 +1,9 @@
<%= govuk_header(
classes: "app-header app-header--orange",
homepage_url: Rails.application.routes.url_helpers.root_path,
navigation_classes: "govuk-header__navigation--end",
) do |component|
component.with_product_name(name: t("service_name"))
component.with_navigation_item(text: "Your account", href: Rails.application.routes.url_helpers.account_path)
component.with_navigation_item(text: "Sign out", href: Rails.application.routes.url_helpers.destroy_user_session_path)
end %>

70
app/views/layouts/rails_admin/application.html.erb

@ -0,0 +1,70 @@
</html><!DOCTYPE html>
<html lang="en" class="govuk-template">
<head>
<title>Admin</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= tag.meta name: "viewport", content: "width=device-width, initial-scale=1" %>
<%= tag.meta property: "og:image", content: asset_path("images/govuk-opengraph-image.png") %>
<%= tag.meta name: "theme-color", content: "#0b0c0c" %>
<%= favicon_link_tag asset_path("images/favicon.ico"), type: nil, sizes: "48x48" %>
<%= favicon_link_tag asset_path("images/favicon.svg"), type: "image/svg+xml", sizes: "any" %>
<%= favicon_link_tag asset_path("images/govuk-icon-mask.svg"), rel: "mask-icon", color: "#0b0c0c", type: nil %>
<%= favicon_link_tag asset_path("images/govuk-icon-180.png"), rel: "apple-touch-icon", type: nil %>
<%= stylesheet_link_tag "application" %>
<%= javascript_include_tag "vendor/html5shiv.min.js" %>
<script>
window.html5.elements = "output";
html5.shivDocument(document);
</script>
<%= javascript_include_tag "vendor/polyfill-output-value.js" %>
<%= javascript_include_tag "vendor/outerHTML.js" %>
<%= javascript_include_tag "application", defer: true %>
<% if content_for?(:head) %>
<%= yield(:head) %>
<% end %>
<%= capybara_lockstep if defined?(Capybara::Lockstep) %>
<% if Rails.env.development? %>
<script>
console.log(<%= session.to_json.html_safe %>)
</script>
<% end %>
<%= render "layouts/rails_admin/head" %>
</head>
<body class="govuk-template__body app-template--wide">
<script>
document.body.className += " js-enabled" + ("noModule" in HTMLScriptElement.prototype ? " govuk-frontend-supported" : "");
</script>
<div data-i18n-options="<%= I18n.t("admin.js").to_json %>" id="admin-js"></div>
<div class="badge bg-warning" id="loading" style="display:none; position:fixed; right:20px; bottom:20px; z-index:100000">
<%= t("admin.loading") %>
</div>
<%= govuk_skip_link %>
<%= render "layouts/rails_admin/navigation" %>
<% feedback_link = govuk_link_to "giving us your feedback (opens in a new tab)", t("feedback_form"), rel: "noreferrer noopener", target: "_blank" %>
<%= govuk_phase_banner(
classes: "#{current_user.present? ? 'no-bottom-border ' : ''}govuk-width-container",
tag: { colour: "orange", text: "Support beta" },
text: "This is a new service – help us improve it by #{feedback_link}".html_safe,
) %>
<div class="govuk-width-container">
<main class="govuk-main-wrapper govuk-main-wrapper--auto-spacing" id="main-content" role="main">
<%= render template: "layouts/rails_admin/content" %>
</main>
</div>
<%= render partial: "layouts/feedback" %>
<%= render partial: "layouts/footer", locals: {
accessibility_statement_path: Rails.application.routes.url_helpers.accessibility_statement_path,
privacy_notice_path: Rails.application.routes.url_helpers.privacy_notice_path,
cookies_path: Rails.application.routes.url_helpers.cookies_path,
} %>
</body>
</html>

25
app/views/rails_admin/main/_submit_buttons.html.erb

@ -0,0 +1,25 @@
<div class="form-actions row justify-content-end my-3">
<div class="col-sm-10">
<input name="return_to" type="<%= :hidden %>" value="<%= (params[:return_to].presence || request.referer) %>">
<button class="govuk-button" data-disable-with="<%= t("admin.form.save") %>" name="_save" type="submit"<%= " disabled" unless @action.enabled? %>>
<i class="fas fa-check"></i>
<%= t("admin.form.save") %>
</button>
<span class="extra_buttons">
<% if @action.enabled? && authorized?(:new, @abstract_model) %>
<button class="govuk-button govuk-button--secondary" data-disable-with="<%= t("admin.form.save_and_add_another") %>" name="_add_another" type="submit">
<%= t("admin.form.save_and_add_another") %>
</button>
<% end %>
<% if @action.enabled? && authorized?(:edit, @abstract_model) %>
<button class="govuk-button govuk-button--secondary" data-disable-with="<%= t("admin.form.save_and_edit") %>" name="_add_edit" type="submit"<%= " disabled" unless @action.enabled? %>>
<%= t("admin.form.save_and_edit") %>
</button>
<% end %>
<button class="govuk-button govuk-button--secondary" data-disable-with="<%= t("admin.form.cancel") %>" formnovalidate="<%= true %>" name="_continue" type="submit">
<i class="fas fa-times"></i>
<%= t("admin.form.cancel") %>
</button>
</span>
</div>
</div>

68
app/views/rails_admin/main/dashboard.html.erb

@ -0,0 +1,68 @@
<% if @abstract_models %>
<table class="table table-condensed table-striped table-hover">
<thead>
<tr>
<th class="shrink model-name">
<%= t "admin.table_headers.model_name" %>
</th>
<th class="shrink last-created">
<%= t "admin.table_headers.last_created" %>
</th>
<th class="records">
<%= t "admin.table_headers.records" %>
</th>
<th class="shrink controls"></th>
</tr>
</thead>
<tbody class="table-group-divider">
<% @abstract_models.each do |abstract_model| %>
<% if authorized? :index, abstract_model %>
<% index_path = index_path(model_name: abstract_model.to_param) %>
<% row_class = "#{cycle('odd', 'even')}#{' link' if index_path} #{abstract_model.param_key}_links" %>
<tr class="<%= row_class %>" data-link="<%= index_path %>">
<% last_created = @most_recent_created[abstract_model.model.name] %>
<% active = last_created.try(:today?) %>
<td>
<span class="show">
<%= link_to abstract_model.config.label_plural, index_path %>
</span>
</td>
<td>
<% if last_created %>
<%= t "admin.misc.time_ago", time: time_ago_in_words(last_created), default: "#{time_ago_in_words(last_created)} #{t('admin.misc.ago')}" %>
<% end %>
</td>
<td>
<% count = @count[abstract_model.model.name] %>
<% percent = if count.positive?
@max <= 1 ? count : ((Math.log(count + 1) * 100.0) / Math.log(@max + 1)).to_i
else
-1
end %>
<div class="<%= active ? "active progress-bar-striped " : "" %>progress" style="margin-bottom:0px">
<div class="bg-<%= get_indicator(percent) %> progress-bar animate-width-to" data-animate-length="<%= ([1.0, percent].max.to_i * 20) %>" data-animate-width-to="<%= [2.0, percent].max.to_i %>%" style="width:2%">
<%= @count[abstract_model.model.name] %>
</div>
</div>
</td>
<td class="last links rails-admin-actions">
<ul class="nav list-inline">
<%= menu_for :collection, abstract_model, nil, true %>
</ul>
</td>
</tr>
<% end %>
<% end %>
</tbody>
</table>
<% end %>
<% if @history && authorized?(:history_index) %>
<div class="block" id="block-tables">
<div class="content">
<h2>
<%= t("admin.actions.history_index.menu") %>
</h2>
<%= render partial: "rails_admin/main/dashboard_history" %>
</div>
</div>
<% end %>

21
app/views/rails_admin/main/delete.html.erb

@ -0,0 +1,21 @@
<h4>
<%= t("admin.form.are_you_sure_you_want_to_delete_the_object", model_name: @abstract_model.pretty_name.downcase) %>
<q><strong><%= @model_config.with(object: @object).object_label %></strong></q>
<%= t("admin.form.all_of_the_following_related_items_will_be_deleted") %>
</h4>
<ul>
<%= render partial: "delete_notice", object: @object %>
</ul>
<%= form_for(@object, url: delete_path(model_name: @abstract_model.to_param, id: @object.id), html: { method: "delete" }) do %>
<input name="return_to" type="<%= :hidden %>" value="<%= (params[:return_to].presence || request.referer) %>">
<div class="form-actions">
<button class="govuk-button govuk-button--warning" data-disable-with="<%= t("admin.form.confirmation") %>" type="submit">
<i class="fas fa-check"></i>
<%= t("admin.form.confirmation") %>
</button>
<button class="govuk-button" data-disable-with="<%= t("admin.form.cancel") %>" name="_continue" type="submit">
<i class="fas fa-times"></i>
<%= t("admin.form.cancel") %>
</button>
</div>
<% end %>

187
app/views/rails_admin/main/index.html.erb

@ -0,0 +1,187 @@
<% query = params[:query] %>
<% params = request.params.except(:authenticity_token, :action, :controller, :utf8, :bulk_export) %>
<% params.delete(:query) if params[:query].blank? %>
<% params.delete(:sort_reverse) unless params[:sort_reverse] == "true" %>
<% sort_reverse = params[:sort_reverse] %>
<% sort = params[:sort] %>
<% params.delete(:sort) if params[:sort] == @model_config.list.sort_by.to_s %>
<% export_action = RailsAdmin::Config::Actions.find(:export, { controller:, abstract_model: @abstract_model }) %>
<% export_action = nil unless export_action && authorized?(export_action.authorization_key, @abstract_model) %>
<% description = RailsAdmin.config(@abstract_model.model_name).description %>
<% properties = @model_config.list.with(controller:, view: self, object: @abstract_model.model.new).fields_for_table %>
<% checkboxes = @model_config.list.checkboxes? %>
<% table_table_header_count = begin
count = checkboxes ? 1 : 0
count += properties.count
end %>
<% content_for :contextual_tabs do %>
<% if filterable_fields.present? %>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#">
<%= t("admin.misc.add_filter") %>
<b class="caret"></b>
</a>
<ul class="dropdown-menu dropdown-menu-end" id="filters">
<% filterable_fields.each do |field| %>
<li>
<a
href="#"
class="dropdown-item"
data-options="<%= field.with(view: self).filter_options.to_json %>">
<%= field.label %>
</a>
</li>
<% end %>
</ul>
</li>
<% end %>
<% if checkboxes %>
<%= bulk_menu %>
<% end %>
<% end %>
<style>
<% properties.select { |p| p.column_width.present? }.each do |property| %>
<%= "#list th.#{property.css_class} { width: #{property.column_width}px; min-width: #{property.column_width}px; }" %>
<%= "#list td.#{property.css_class} { max-width: #{property.column_width}px;}" %>
<% end %>
</style>
<div id="list">
<%= form_tag(index_path(params.except(*%w[page f query])), method: :get) do %>
<div class="card mb-3 p-3 bg-light">
<div class="row rails-admin-filters-box" data-options="<%= ordered_filter_options.to_json %>" id="filters_box"></div>
<hr class="filters_box" style="display:<%= ordered_filters.empty? ? "none" : "block" %>">
<div class="row">
<div class="col-sm-8">
<div class="input-group">
<input class="govuk-input govuk-input--width-20" name="query" placeholder="<%= t("admin.misc.filter") %>" type="search" value="<%= query %>" autocomplete="off">
<div class="govuk-!-margin-left-2">
<button class="govuk-button govuk-!-margin-bottom-0" data-disable-with="<%= "<i class=\"fas fa-sync\"></i>#{t('admin.misc.refresh')}" %>" type="submit">
<i class="fas fa-sync"></i>
<%= t("admin.misc.refresh") %>
</button>
</div>
<div id="remove_filter" title="<%= t("admin.misc.reset_filters") %>">
<button class="govuk-button govuk-button--secondary govuk-!-margin-bottom-0">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<% if @model_config.list.search_help.present? %>
<div class="form-text"><%= @model_config.list.search_help %></div>
<% end %>
</div>
<div class="col-sm-4 text-end">
<% if export_action %>
<%= govuk_button_link_to wording_for(:link, export_action), export_path(params.except("page")), class: "govuk-!-margin-bottom-0" %>
<% end %>
</div>
</div>
</div>
<% end %>
<% unless @model_config.list.scopes.empty? %>
<ul class="nav nav-tabs" id="scope_selector">
<% @model_config.list.scopes.each_with_index do |scope, index| %>
<% scope = "_all" if scope.nil? %>
<li class="nav-item">
<a href="<%= index_path(params.merge(scope:, page: nil)) %>" class="nav-link <%= "active" if scope.to_s == params[:scope] || (params[:scope].blank? && index.zero?) %>">
<%= I18n.t("admin.scopes.#{@abstract_model.to_param}.#{scope}", default: I18n.t("admin.scopes.#{scope}", default: scope.to_s.titleize)) %>
</a>
</li>
<% end %>
</ul>
<% end %>
<%= form_tag bulk_action_path(model_name: @abstract_model.to_param), method: :post, id: "bulk_form", class: %w[form mb-3] do %>
<%= hidden_field_tag :bulk_action %>
<% if description.present? %>
<p>
<strong>
<%= description %>
</strong>
</p>
<% end %>
<div id="sidescroll" class="rails-admin-sidescroll">
<table class="table table-condensed table-striped table-hover">
<thead>
<tr>
<% if checkboxes %>
<th class="shrink sticky">
<input class="toggle" type="checkbox">
</th>
<% end %>
<% properties.each do |property| %>
<% selected = (sort == property.name.to_s) %>
<% if property.sortable %>
<% sort_location = index_path params.except("sort_reverse").except("page").merge(sort: property.name).merge(selected && sort_reverse != "true" ? { sort_reverse: "true" } : {}) %>
<% sort_direction = (if selected
sort_reverse == "true" ? "headerSortUp" : "headerSortDown"
end) %>
<% end %>
<th class="<%= [property.sortable && "header", property.sortable && sort_direction, property.sticky? && "sticky", property.css_class, property.type_css_class].select(&:present?).join(" ") %>" data-href="<%= property.sortable && sort_location %>" rel="tooltip" title="<%= property.hint %>">
<%= property.label %>
</th>
<% end %>
<th class="last shrink"></th>
</tr>
</thead>
<tbody class="table-group-divider">
<% @objects.each do |object| %>
<tr class="<%= @abstract_model.param_key %>_row <%= @model_config.list.with(object:).row_css_class %>">
<% if checkboxes %>
<td class="sticky">
<%= check_box_tag "bulk_ids[]", object.id.to_s, false %>
</td>
<% end %>
<% properties.map { |property| property.bind(:object, object) }.each do |property| %>
<% value = property.pretty_value %>
<%= content_tag(:td, class: [property.sticky? && "sticky", property.css_class, property.type_css_class].select(&:present?).map { |x| "rails-admin-#{x}" }, title: strip_tags(value.to_s)) do %>
<%= value %>
<% end %>
<% end %>
<td class="last links ra-sidescroll-frozen rails-admin-actions">
<ul class="nav list-inline">
<%= menu_for :member, @abstract_model, object, true %>
</ul>
</td>
</tr>
<% end %>
<% if @objects.empty? %>
<tr class="empty_row">
<td colspan="<%= table_table_header_count %>">
<%= I18n.t("admin.actions.index.no_records") %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% if @model_config.list.limited_pagination %>
<div class="row">
<div class="col-md-6">
<%= paginate(@objects, theme: "ra-twitter-bootstrap/without_count", total_pages: Float::INFINITY) %>
</div>
</div>
<% elsif @objects.respond_to?(:total_count) %>
<% total_count = @objects.total_count.to_i %>
<div class="row">
<div class="col-md-6">
<%= paginate(@objects, theme: "ra-twitter-bootstrap") %>
</div>
</div>
<div class="row">
<div class="col-md-6">
<%= link_to(t("admin.misc.show_all"), index_path(params.merge(all: true)), class: "govuk-button govuk-button--secondary") unless total_count > 100 || total_count <= @objects.to_a.size %>
</div>
</div>
<div class="clearfix total-count">
<%= "#{total_count} #{@model_config.pluralize(total_count).downcase}" %>
</div>
<% else %>
<div class="clearfix total-count">
<%= "#{@objects.size} #{@model_config.pluralize(@objects.size).downcase}" %>
</div>
<% end %>
<% end %>
</div>

2
config/initializers/assets.rb

@ -5,3 +5,5 @@ Rails.application.config.assets.version = "1.0"
# Add additional assets to the asset load path. # Add additional assets to the asset load path.
Rails.application.config.assets.paths << Rails.root.join("node_modules/@fortawesome/fontawesome-free/webfonts") Rails.application.config.assets.paths << Rails.root.join("node_modules/@fortawesome/fontawesome-free/webfonts")
Rails.application.config.assets.precompile += %w[rails_admin/rails_admin.css rails_admin/rails_admin.js]

29
config/locales/en.yml

@ -61,45 +61,26 @@ en:
<<: *bulk_upload__row_parser__base <<: *bulk_upload__row_parser__base
bulk_upload/sales/year2023/row_parser: bulk_upload/sales/year2023/row_parser:
<<: *bulk_upload__row_parser__base <<: *bulk_upload__row_parser__base
forms/bulk_upload_lettings/year: forms/bulk_upload_form/year:
attributes: attributes:
year: year:
blank: "You must select a collection period to upload for." blank: "You must select a collection period to upload for."
forms/bulk_upload_sales/year: forms/bulk_upload_form/upload_your_file:
attributes:
year:
blank: "You must select a collection period to upload for."
forms/bulk_upload_lettings/upload_your_file:
attributes:
file:
blank: "Select which file to upload."
not_csv: "Your file must be in CSV format."
file_too_large: "File must be 10MB or less. Check your file and delete data that does not need to be uploaded."
forms/bulk_upload_sales/upload_your_file:
attributes: attributes:
file: file:
blank: "Select which file to upload." blank: "Select which file to upload."
not_csv: "Your file must be in CSV format." not_csv: "Your file must be in CSV format."
file_too_large: "File must be 10MB or less. Check your file and delete data that does not need to be uploaded." file_too_large: "File must be 10MB or less. Check your file and delete data that does not need to be uploaded."
forms/bulk_upload_lettings/needstype: forms/bulk_upload_form/needstype:
attributes: attributes:
needstype: needstype:
blank: "You must answer needs type." blank: "You must answer needs type."
forms/bulk_upload_lettings_resume/fix_choice: forms/bulk_upload_resume/fix_choice:
attributes: attributes:
choice: choice:
blank: "Select how you would like to fix these errors." blank: "Select how you would like to fix these errors."
inclusion: "You must select one of the following options for how you would like to fix these errors." inclusion: "You must select one of the following options for how you would like to fix these errors."
forms/bulk_upload_sales_resume/fix_choice: forms/bulk_upload_soft_validations_check/confirm_soft_errors:
attributes:
choice:
blank: "Select how you would like to fix these errors."
inclusion: "You must select one of the following options for how you would like to fix these errors."
forms/bulk_upload_lettings_soft_validations_check/confirm_soft_errors:
attributes:
confirm_soft_errors:
blank: "You must select if there are errors in these fields."
forms/bulk_upload_sales_soft_validations_check/confirm_soft_errors:
attributes: attributes:
confirm_soft_errors: confirm_soft_errors:
blank: "You must select if there are errors in these fields." blank: "You must select if there are errors in these fields."

28
config/locales/forms/2025/lettings/household_characteristics.en.yml

@ -84,9 +84,9 @@ en:
relat2: relat2:
page_header: "" page_header: ""
check_answer_label: "Person 2’s relationship to the lead tenant" check_answer_label: "Person 2 lead tenant’s partner"
hint_text: "" hint_text: ""
question_text: "What is person 2’s relationship to the lead tenant?" question_text: "Is tenant 2 the partner of tenant 1?"
age2: age2:
page_header: "" page_header: ""
@ -119,9 +119,9 @@ en:
relat3: relat3:
page_header: "" page_header: ""
check_answer_label: "Person 3’s relationship to the lead tenant" check_answer_label: "Person 3 lead tenant’s partner"
hint_text: "" hint_text: ""
question_text: "What is person 3’s relationship to the lead tenant?" question_text: "Is tenant 3 the partner of tenant 1?"
age3: age3:
page_header: "" page_header: ""
@ -154,9 +154,9 @@ en:
relat4: relat4:
page_header: "" page_header: ""
check_answer_label: "Person 4’s relationship to the lead tenant" check_answer_label: "Person 4 lead tenant’s partner"
hint_text: "" hint_text: ""
question_text: "What is person 4’s relationship to the lead tenant?" question_text: "Is tenant 4 the partner of tenant 1?"
age4: age4:
page_header: "" page_header: ""
@ -189,9 +189,9 @@ en:
relat5: relat5:
page_header: "" page_header: ""
check_answer_label: "Person 5’s relationship to the lead tenant" check_answer_label: "Person 5 lead tenant’s partner"
hint_text: "" hint_text: ""
question_text: "What is person 5’s relationship to the lead tenant?" question_text: "Is tenant 5 the partner of tenant 1?"
age5: age5:
page_header: "" page_header: ""
@ -224,9 +224,9 @@ en:
relat6: relat6:
page_header: "" page_header: ""
check_answer_label: "Person 6’s relationship to the lead tenant" check_answer_label: "Person 6 lead tenant’s partner"
hint_text: "" hint_text: ""
question_text: "What is person 6’s relationship to the lead tenant?" question_text: "Is tenant 6 the partner of tenant 1?"
age6: age6:
page_header: "" page_header: ""
@ -259,9 +259,9 @@ en:
relat7: relat7:
page_header: "" page_header: ""
check_answer_label: "Person 7’s relationship to the lead tenant" check_answer_label: "Person 7 lead tenant’s partner"
hint_text: "" hint_text: ""
question_text: "What is person 7’s relationship to the lead tenant?" question_text: "Is tenant 7 the partner of tenant 1?"
age7: age7:
page_header: "" page_header: ""
@ -294,9 +294,9 @@ en:
relat8: relat8:
page_header: "" page_header: ""
check_answer_label: "Person 8’s relationship to the lead tenant" check_answer_label: "Person 8 lead tenant’s partner"
hint_text: "" hint_text: ""
question_text: "What is person 8’s relationship to the lead tenant?" question_text: "Is tenant 8 the partner of tenant 1?"
age8: age8:
page_header: "" page_header: ""

4
config/locales/validations/lettings/date.en.yml

@ -7,14 +7,18 @@ en:
after_major_repair_date: "Enter a tenancy start date that is after the major repair date." after_major_repair_date: "Enter a tenancy start date that is after the major repair date."
ten_years_after_void_date: "Enter a tenancy start date that is no more than 10 years after the void date." ten_years_after_void_date: "Enter a tenancy start date that is no more than 10 years after the void date."
ten_years_after_mrc_date: "Enter a tenancy start date that is no more than 10 years after the major repairs completion date." ten_years_after_mrc_date: "Enter a tenancy start date that is no more than 10 years after the major repairs completion date."
twenty_years_after_void_date: "Enter a tenancy start date that is no more than 20 years after the void date."
twenty_years_after_mrc_date: "Enter a tenancy start date that is no more than 20 years after the major repairs completion date."
mrcdate: mrcdate:
before_tenancy_start: "Enter a major repairs date that is before the tenancy start date." before_tenancy_start: "Enter a major repairs date that is before the tenancy start date."
not_first_let: "Major repairs date must not be completed if the tenancy is a first let." not_first_let: "Major repairs date must not be completed if the tenancy is a first let."
ten_years_before_tenancy_start: "Enter a major repairs completion date that is no more than 10 years before the tenancy start date." ten_years_before_tenancy_start: "Enter a major repairs completion date that is no more than 10 years before the tenancy start date."
twenty_years_before_tenancy_start: "Enter a major repairs completion date that is no more than 20 years before the tenancy start date."
before_void_date: "Major repairs date must be after the void date if provided." before_void_date: "Major repairs date must be after the void date if provided."
void_date: void_date:
ten_years_before_tenancy_start: "Enter a void date no more than 10 years before the tenancy start date." ten_years_before_tenancy_start: "Enter a void date no more than 10 years before the tenancy start date."
twenty_years_before_tenancy_start: "Enter a void date no more than 20 years before the tenancy start date."
before_tenancy_start: "Enter a void date that is before the tenancy start date." before_tenancy_start: "Enter a void date that is before the tenancy start date."
after_mrcdate: "Void date must be before the major repairs date if provided." after_mrcdate: "Void date must be before the major repairs date if provided."

0
config/locales/validations/lettings/financial.yml → config/locales/validations/lettings/financial.en.yml

20
config/locales/validations/sales/sale_information.en.yml

@ -47,8 +47,8 @@ en:
value: value:
discounted_ownership_value: "The mortgage%{mortgage}%{deposit_and_grant_sentence} added together is %{mortgage_deposit_and_grant_total}.</br></br>The full purchase price%{discount_sentence} is %{value_with_discount}.</br></br>These two amounts should be the same." discounted_ownership_value: "The mortgage%{mortgage}%{deposit_and_grant_sentence} added together is %{mortgage_deposit_and_grant_total}.</br></br>The full purchase price%{discount_sentence} is %{value_with_discount}.</br></br>These two amounts should be the same."
outright_sale_value: "The mortgage%{mortgage} and cash deposit (%{deposit}) when added together is %{mortgage_and_deposit_total}.</br></br>The full purchase price is %{value}.</br></br>These two amounts should be the same." outright_sale_value: "The mortgage%{mortgage} and cash deposit (%{deposit}) when added together is %{mortgage_and_deposit_total}.</br></br>The full purchase price is %{value}.</br></br>These two amounts should be the same."
value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London." value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £137,400 for properties in London."
value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London." value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £103,400 for properties outside of London."
non_staircasing_mortgage: non_staircasing_mortgage:
mortgage_used: "The mortgage (%{mortgage}) and cash deposit (%{deposit}) added together is %{mortgage_and_deposit_total}.</br></br>The full purchase price (%{value}) multiplied by the percentage equity stake purchased (%{equity}) is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same." mortgage_used: "The mortgage (%{mortgage}) and cash deposit (%{deposit}) added together is %{mortgage_and_deposit_total}.</br></br>The full purchase price (%{value}) multiplied by the percentage equity stake purchased (%{equity}) is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same."
mortgage_not_used: "The cash deposit is %{deposit}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same." mortgage_not_used: "The cash deposit is %{deposit}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same."
@ -87,8 +87,8 @@ en:
mortgage_not_used_socialhomebuy: "The cash deposit (%{deposit}) and cash discount (%{cashdis}) added together is %{deposit_and_discount_total}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought (%{equity}) is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same." mortgage_not_used_socialhomebuy: "The cash deposit (%{deposit}) and cash discount (%{cashdis}) added together is %{deposit_and_discount_total}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought (%{equity}) is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same."
discount: discount:
discounted_ownership_value: "The mortgage%{mortgage}%{deposit_and_grant_sentence} added together is %{mortgage_deposit_and_grant_total}.</br></br>The full purchase price%{discount_sentence} is %{value_with_discount}.</br></br>These two amounts should be the same." discounted_ownership_value: "The mortgage%{mortgage}%{deposit_and_grant_sentence} added together is %{mortgage_deposit_and_grant_total}.</br></br>The full purchase price%{discount_sentence} is %{value_with_discount}.</br></br>These two amounts should be the same."
value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London." value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £137,400 for properties in London."
value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London." value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £103,400 for properties outside of London."
grant: grant:
discounted_ownership_value: "The mortgage%{mortgage}%{deposit_and_grant_sentence} added together is %{mortgage_deposit_and_grant_total}.</br></br>The full purchase price%{discount_sentence} is %{value_with_discount}.</br></br>These two amounts should be the same." discounted_ownership_value: "The mortgage%{mortgage}%{deposit_and_grant_sentence} added together is %{mortgage_deposit_and_grant_total}.</br></br>The full purchase price%{discount_sentence} is %{value_with_discount}.</br></br>These two amounts should be the same."
out_of_range: "Loan, grants or subsidies must be between £9,000 and £16,000." out_of_range: "Loan, grants or subsidies must be between £9,000 and £16,000."
@ -119,14 +119,14 @@ en:
staircase: staircase:
mortgage_used_value: "You must answer either ‘yes’ or ‘no’ to the question ‘was a mortgage used’ for staircasing transactions." mortgage_used_value: "You must answer either ‘yes’ or ‘no’ to the question ‘was a mortgage used’ for staircasing transactions."
la: la:
value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London." value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £137,400 for properties in London."
value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London." value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £103,400 for properties outside of London."
uprn: uprn:
value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London." value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £137,400 for properties in London."
value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London." value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £103,400 for properties outside of London."
postcode_full: postcode_full:
value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London." value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £137,400 for properties in London."
value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London." value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £103,400 for properties outside of London."
numstair: numstair:
must_be_greater_than_one: "The number of staircasing transactions must be greater than 1 when this is not the first staircasing transaction." must_be_greater_than_one: "The number of staircasing transactions must be greater than 1 when this is not the first staircasing transaction."
firststair: firststair:

2
config/locales/validations/shared.en.yml

@ -9,7 +9,9 @@ en:
above_min: "%{field} must be at least %{min}." above_min: "%{field} must be at least %{min}."
whole_number: "%{field} must be a whole number." whole_number: "%{field} must be a whole number."
nearest_ten: "%{field} must be given to the nearest ten." nearest_ten: "%{field} must be given to the nearest ten."
nearest_tenth: "%{field} must be given to the nearest tenth."
nearest_hundredth: "%{field} must be given to the nearest hundredth." nearest_hundredth: "%{field} must be given to the nearest hundredth."
nearest_step: "${field} must be given to the nearest %{step}."
normal_format: "Enter a number." normal_format: "Enter a number."
format: "%{field} must be a number." format: "%{field} must be a number."

14
config/routes.rb

@ -391,15 +391,19 @@ Rails.application.routes.draw do
end end
end end
get "create-test-lettings-log", to: "lettings_logs#create_test_log"
get "create-test-sales-log", to: "sales_logs#create_test_log"
get "create-setup-test-lettings-log", to: "lettings_logs#create_setup_test_log"
get "create-setup-test-sales-log", to: "sales_logs#create_setup_test_log"
scope via: :all do scope via: :all do
match "/404", to: "errors#not_found" match "/404", to: "errors#not_found"
match "/429", to: "errors#too_many_requests", status: 429 match "/429", to: "errors#too_many_requests", status: 429
match "/422", to: "errors#unprocessable_entity" match "/422", to: "errors#unprocessable_entity"
match "/500", to: "errors#internal_server_error" match "/500", to: "errors#internal_server_error"
end end
if FeatureToggle.create_test_logs_enabled?
get "create-test-lettings-log", to: "test_data#create_test_lettings_log"
get "create-setup-test-lettings-log", to: "test_data#create_setup_test_lettings_log"
get "create-2024-test-lettings-bulk-upload", to: "test_data#create_2024_test_lettings_bulk_upload"
get "create-test-sales-log", to: "test_data#create_test_sales_log"
get "create-setup-test-sales-log", to: "test_data#create_setup_test_sales_log"
get "create-2024-test-sales-bulk-upload", to: "test_data#create_2024_test_sales_bulk_upload"
end
end end

5
db/migrate/20250110150609_add_collection_year.rb

@ -0,0 +1,5 @@
class AddCollectionYear < ActiveRecord::Migration[7.2]
def change
add_column :log_validations, :collection_year, :string
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.2].define(version: 2024_12_06_142944) do ActiveRecord::Schema[7.2].define(version: 2025_01_10_150609) 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"
@ -450,6 +450,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_12_06_142944) do
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.boolean "checked" t.boolean "checked"
t.string "collection_year"
end end
create_table "merge_request_organisations", force: :cascade do |t| create_table "merge_request_organisations", force: :cascade do |t|

1
lib/tasks/generate_lettings_documentation.rake

@ -83,6 +83,7 @@ namespace :generate_lettings_documentation do
error_message:, error_message:,
case: validation_description, case: validation_description,
section: form.get_question(field, nil)&.subsection&.id, section: form.get_question(field, nil)&.subsection&.id,
collection_year: "#{form.start_date.year}/#{form.start_date.year + 1}",
validation_type: validation_name, validation_type: validation_name,
hard_soft: "hard") hard_soft: "hard")
end end

6
lib/tasks/set_log_validation_collection_year.rake

@ -0,0 +1,6 @@
desc "Sets value for collection_year log validations depending on the from value"
task set_log_validation_collection_year: :environment do
LogValidation.all.each do |log_validation|
log_validation.update(collection_year: "#{log_validation.from.year}/#{log_validation.from.year + 1}")
end
end

2
spec/factories/sales_log.rb

@ -33,6 +33,7 @@ FactoryBot.define do
noint { 2 } noint { 2 }
privacynotice { 1 } privacynotice { 1 }
purchid { rand(999_999_999).to_s } purchid { rand(999_999_999).to_s }
staircase { 1 }
end end
trait :discounted_ownership_setup_complete do trait :discounted_ownership_setup_complete do
saledate_today saledate_today
@ -66,6 +67,7 @@ FactoryBot.define do
postcode_full { "A1 1AA" } postcode_full { "A1 1AA" }
noint { 2 } noint { 2 }
uprn_known { 0 } uprn_known { 0 }
staircase { 1 }
end end
trait :completed do trait :completed do
purchid { rand(999_999_999).to_s } purchid { rand(999_999_999).to_s }

8
spec/features/bulk_upload_lettings_logs_spec.rb

@ -55,13 +55,13 @@ RSpec.describe "Bulk upload lettings log" do
expect(page).to have_content("Upload your file") expect(page).to have_content("Upload your file")
click_button("Upload") click_button("Upload")
allow_any_instance_of(Forms::BulkUploadLettings::UploadYourFile).to receive(:`).and_return("not a csv") allow_any_instance_of(Forms::BulkUploadForm::UploadYourFile).to receive(:`).and_return("not a csv")
expect(page).to have_content("Select which file to upload") expect(page).to have_content("Select which file to upload")
attach_file "file", file_fixture("2023_24_lettings_bulk_upload.xlsx") attach_file "file", file_fixture("2023_24_lettings_bulk_upload.xlsx")
click_button("Upload") click_button("Upload")
allow_any_instance_of(Forms::BulkUploadLettings::UploadYourFile).to receive(:`).and_return("text/csv") allow_any_instance_of(Forms::BulkUploadForm::UploadYourFile).to receive(:`).and_return("text/csv")
expect(page).to have_content("Your file must be in CSV format") expect(page).to have_content("Your file must be in CSV format")
attach_file "file", file_fixture("blank_bulk_upload_sales.csv") attach_file "file", file_fixture("blank_bulk_upload_sales.csv")
@ -76,7 +76,7 @@ RSpec.describe "Bulk upload lettings log" do
end end
it "shows file to large error" do it "shows file to large error" do
stub_const("Forms::BulkUploadLettings::UploadYourFile::MAX_FILE_SIZE", 1.bytes) stub_const("Forms::BulkUploadForm::UploadYourFile::MAX_FILE_SIZE", 1.bytes)
visit("/lettings-logs") visit("/lettings-logs")
click_link("Upload lettings logs in bulk") click_link("Upload lettings logs in bulk")
@ -86,7 +86,7 @@ RSpec.describe "Bulk upload lettings log" do
click_button("Continue") click_button("Continue")
click_button("Continue") click_button("Continue")
allow_any_instance_of(Forms::BulkUploadLettings::UploadYourFile).to receive(:`).and_return("text/csv") allow_any_instance_of(Forms::BulkUploadForm::UploadYourFile).to receive(:`).and_return("text/csv")
attach_file "file", file_fixture("2023_24_lettings_bulk_upload.xlsx") attach_file "file", file_fixture("2023_24_lettings_bulk_upload.xlsx")
click_button("Upload") click_button("Upload")

22
spec/features/bulk_upload_sales_logs_spec.rb

@ -22,7 +22,7 @@ RSpec.describe "Bulk upload sales log" do
# rubocop:disable RSpec/AnyInstance # rubocop:disable RSpec/AnyInstance
context "when during crossover period" do context "when during crossover period" do
before do before do
Timecop.freeze(2023, 5, 1) Timecop.freeze(2024, 5, 1)
end end
after do after do
@ -38,27 +38,27 @@ RSpec.describe "Bulk upload sales log" do
click_button("Continue") click_button("Continue")
expect(page).to have_content("You must select a collection period to upload for") expect(page).to have_content("You must select a collection period to upload for")
choose("2023 to 2024") choose("2024 to 2025")
click_button("Continue") click_button("Continue")
click_link("Back") click_link("Back")
expect(page.find_field("form-year-2023-field")).to be_checked expect(page.find_field("form-year-2024-field")).to be_checked
click_button("Continue") click_button("Continue")
expect(page).to have_content("Upload sales logs in bulk (2023 to 2024)") expect(page).to have_content("Upload sales logs in bulk (2024 to 2025)")
click_button("Continue") click_button("Continue")
expect(page).to have_content("Upload your file") expect(page).to have_content("Upload your file")
click_button("Upload") click_button("Upload")
allow_any_instance_of(Forms::BulkUploadSales::UploadYourFile).to receive(:`).and_return("not a csv") allow_any_instance_of(Forms::BulkUploadForm::UploadYourFile).to receive(:`).and_return("not a csv")
expect(page).to have_content("Select which file to upload") expect(page).to have_content("Select which file to upload")
attach_file "file", file_fixture("2023_24_lettings_bulk_upload.xlsx") attach_file "file", file_fixture("2023_24_lettings_bulk_upload.xlsx")
click_button("Upload") click_button("Upload")
allow_any_instance_of(Forms::BulkUploadSales::UploadYourFile).to receive(:`).and_return("text/csv") allow_any_instance_of(Forms::BulkUploadForm::UploadYourFile).to receive(:`).and_return("text/csv")
expect(page).to have_content("Your file must be in CSV format") expect(page).to have_content("Your file must be in CSV format")
attach_file "file", file_fixture("blank_bulk_upload_sales.csv") attach_file "file", file_fixture("blank_bulk_upload_sales.csv")
@ -73,18 +73,18 @@ RSpec.describe "Bulk upload sales log" do
end end
it "shows file to large error" do it "shows file to large error" do
stub_const("Forms::BulkUploadSales::UploadYourFile::MAX_FILE_SIZE", 1.bytes) stub_const("Forms::BulkUploadForm::UploadYourFile::MAX_FILE_SIZE", 1.bytes)
visit("/sales-logs") visit("/sales-logs")
click_link("Upload sales logs in bulk") click_link("Upload sales logs in bulk")
expect(page).to have_content("Which year") expect(page).to have_content("Which year")
click_button("Continue") click_button("Continue")
click_button("Continue") click_button("Continue")
choose("2023 to 2024") choose("2024 to 2025")
click_button("Continue") click_button("Continue")
click_button("Continue") click_button("Continue")
allow_any_instance_of(Forms::BulkUploadSales::UploadYourFile).to receive(:`).and_return("text/csv") allow_any_instance_of(Forms::BulkUploadForm::UploadYourFile).to receive(:`).and_return("text/csv")
attach_file "file", file_fixture("2023_24_lettings_bulk_upload.xlsx") attach_file "file", file_fixture("2023_24_lettings_bulk_upload.xlsx")
click_button("Upload") click_button("Upload")
@ -96,7 +96,7 @@ RSpec.describe "Bulk upload sales log" do
context "when not in crossover period" do context "when not in crossover period" do
before do before do
Timecop.freeze(2024, 2, 1) Timecop.freeze(2025, 2, 1)
end end
after do after do
@ -108,7 +108,7 @@ RSpec.describe "Bulk upload sales log" do
expect(page).to have_link("Upload sales logs in bulk") expect(page).to have_link("Upload sales logs in bulk")
click_link("Upload sales logs in bulk") click_link("Upload sales logs in bulk")
expect(page).to have_content("Upload sales logs in bulk (2023 to 2024)") expect(page).to have_content("Upload sales logs in bulk (2024 to 2025)")
click_button("Continue") click_button("Continue")
expect(page).to have_content("Upload your file") expect(page).to have_content("Upload your file")

10
spec/features/organisation_spec.rb

@ -132,6 +132,7 @@ RSpec.describe "User Features" do
let!(:other_general_needs_logs) { FactoryBot.create_list(:lettings_log, 4, assigned_to: user, needstype: 1) } let!(:other_general_needs_logs) { FactoryBot.create_list(:lettings_log, 4, assigned_to: user, needstype: 1) }
let!(:other_supported_housing_logs) { FactoryBot.create_list(:lettings_log, 4, assigned_to: user, needstype: 2) } let!(:other_supported_housing_logs) { FactoryBot.create_list(:lettings_log, 4, assigned_to: user, needstype: 2) }
let(:number_of_lettings_logs) { LettingsLog.count } let(:number_of_lettings_logs) { LettingsLog.count }
let(:previous_year) { FormHandler.instance.previous_lettings_form.start_date.year }
before do before do
allow(FormHandler.instance).to receive(:in_crossover_period?).and_return(true) allow(FormHandler.instance).to receive(:in_crossover_period?).and_return(true)
@ -204,9 +205,9 @@ RSpec.describe "User Features" do
end end
it "can filter lettings logs by year" do it "can filter lettings logs by year" do
check("years-2022-field") check("years-#{previous_year}-field")
click_button("Apply filters") click_button("Apply filters")
expect(page).to have_current_path("/organisations/#{org_id}/lettings-logs?%5Byears%5D[]=&years[]=2022&%5Bstatus%5D[]=&%5Bneedstypes%5D[]=&assigned_to=all&user_text_search=&user=&owning_organisation_select=all&owning_organisation_text_search=&owning_organisation=&managing_organisation_select=all&managing_organisation_text_search=&managing_organisation=") expect(page).to have_current_path("/organisations/#{org_id}/lettings-logs?%5Byears%5D[]=&years[]=#{previous_year}&%5Bstatus%5D[]=&%5Bneedstypes%5D[]=&assigned_to=all&user_text_search=&user=&owning_organisation_select=all&owning_organisation_text_search=&owning_organisation=&managing_organisation_select=all&managing_organisation_text_search=&managing_organisation=")
expect(page).not_to have_link first_log.id.to_s, href: "/lettings-logs/#{first_log.id}" expect(page).not_to have_link first_log.id.to_s, href: "/lettings-logs/#{first_log.id}"
end end
@ -226,6 +227,7 @@ RSpec.describe "User Features" do
context "when viewing sales logs for specific organisation" do context "when viewing sales logs for specific organisation" do
let(:first_log) { organisation.sales_logs.first } let(:first_log) { organisation.sales_logs.first }
let(:number_of_sales_logs) { SalesLog.count } let(:number_of_sales_logs) { SalesLog.count }
let(:previous_year) { FormHandler.instance.previous_sales_form.start_date.year }
before do before do
allow(FormHandler.instance).to receive(:in_crossover_period?).and_return(true) allow(FormHandler.instance).to receive(:in_crossover_period?).and_return(true)
@ -254,9 +256,9 @@ RSpec.describe "User Features" do
organisation.sales_logs.map(&:id).each do |sales_log_id| organisation.sales_logs.map(&:id).each do |sales_log_id|
expect(page).to have_link sales_log_id.to_s, href: "/sales-logs/#{sales_log_id}" expect(page).to have_link sales_log_id.to_s, href: "/sales-logs/#{sales_log_id}"
end end
check("years-2022-field") check("years-#{previous_year}-field")
click_button("Apply filters") click_button("Apply filters")
expect(page).to have_current_path("/organisations/#{org_id}/sales-logs?%5Byears%5D[]=&years[]=2022&%5Bstatus%5D[]=&assigned_to=all&user_text_search=&user=&owning_organisation_select=all&owning_organisation_text_search=&owning_organisation=&managing_organisation_select=all&managing_organisation_text_search=&managing_organisation=") expect(page).to have_current_path("/organisations/#{org_id}/sales-logs?%5Byears%5D[]=&years[]=#{previous_year}&%5Bstatus%5D[]=&assigned_to=all&user_text_search=&user=&owning_organisation_select=all&owning_organisation_text_search=&owning_organisation=&managing_organisation_select=all&managing_organisation_text_search=&managing_organisation=")
expect(page).not_to have_link first_log.id.to_s, href: "/sales-logs/#{first_log.id}" expect(page).not_to have_link first_log.id.to_s, href: "/sales-logs/#{first_log.id}"
end end
end end

3
spec/features/sales_log_spec.rb

@ -142,6 +142,7 @@ RSpec.describe "Sales Log Features" do
context "when downloading logs" do context "when downloading logs" do
let(:user) { create(:user, :support) } let(:user) { create(:user, :support) }
let(:other_user) { create(:user, organisation: user.organisation) } let(:other_user) { create(:user, organisation: user.organisation) }
let(:current_year) { FormHandler.instance.current_sales_form.start_date.year }
context "when I am signed in" do context "when I am signed in" do
before do before do
@ -191,7 +192,7 @@ RSpec.describe "Sales Log Features" do
context "when one year filter is selected" do context "when one year filter is selected" do
before do before do
check("2024 to 2025") check("#{current_year} to #{current_year + 1}")
click_button("Apply filters") click_button("Apply filters")
end end

1
spec/fixtures/exports/organisation.xml vendored

@ -22,5 +22,6 @@
<dpo_email>{dpo_email}</dpo_email> <dpo_email>{dpo_email}</dpo_email>
<profit_status/> <profit_status/>
<group/> <group/>
<status>active</status>
</form> </form>
</forms> </forms>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save