Browse Source

Merge branch 'main' into CLDC-3618-Support-user-view-bulk-uploads-page

pull/2666/head
Manny Dinssa 2 years ago committed by GitHub
parent
commit
a0961d6717
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      app/controllers/form_controller.rb
  2. 40
      app/controllers/organisations_controller.rb
  3. 95
      app/controllers/start_controller.rb
  4. 23
      app/helpers/collection_resources_helper.rb
  5. 8
      app/helpers/schemes_helper.rb
  6. 10
      app/models/forms/bulk_upload_lettings/prepare_your_file.rb
  7. 10
      app/models/forms/bulk_upload_sales/prepare_your_file.rb
  8. 5
      app/models/log.rb
  9. 5
      app/models/validations/financial_validations.rb
  10. 8
      app/policies/organisation_policy.rb
  11. 6
      app/services/bulk_upload/sales/validator.rb
  12. 21
      app/services/collection_resources_service.rb
  13. 4
      app/services/feature_toggle.rb
  14. 9
      app/services/storage/local_disk_service.rb
  15. 4
      app/services/storage/s3_service.rb
  16. 138
      app/views/organisations/duplicate_schemes.html.erb
  17. 10
      app/views/organisations/schemes.html.erb
  18. 4
      config/locales/en.yml
  19. 2
      config/routes.rb
  20. 5
      db/migrate/20240920144611_add_schemes_deduplicated_at.rb
  21. 1
      db/schema.rb
  22. 19
      lib/tasks/recalculate_status_when_la_missing.rake
  23. BIN
      public/files/2023_24_lettings_paper_form.pdf
  24. BIN
      public/files/2023_24_sales_paper_form.pdf
  25. BIN
      public/files/2024_25_lettings_paper_form.pdf
  26. BIN
      public/files/2024_25_sales_paper_form.pdf
  27. BIN
      public/files/bulk-upload-lettings-legacy-template-2023-24.xlsx
  28. BIN
      public/files/bulk-upload-lettings-specification-2023-24.xlsx
  29. BIN
      public/files/bulk-upload-lettings-specification-2024-25.xlsx
  30. BIN
      public/files/bulk-upload-lettings-template-2023-24.xlsx
  31. BIN
      public/files/bulk-upload-lettings-template-2024-25.xlsx
  32. BIN
      public/files/bulk-upload-sales-legacy-template-2023-24.xlsx
  33. BIN
      public/files/bulk-upload-sales-specification-2023-24.xlsx
  34. BIN
      public/files/bulk-upload-sales-specification-2024-25.xlsx
  35. BIN
      public/files/bulk-upload-sales-template-2023-24.xlsx
  36. BIN
      public/files/bulk-upload-sales-template-2024-25.xlsx
  37. 9
      spec/features/accessibility_spec.rb
  38. 7
      spec/features/lettings_log_spec.rb
  39. 6
      spec/features/notifications_spec.rb
  40. 3
      spec/features/organisation_spec.rb
  41. 6
      spec/features/start_page_spec.rb
  42. 7
      spec/features/test_spec.rb
  43. 3
      spec/features/user_spec.rb
  44. 14
      spec/helpers/collection_resources_helper_spec.rb
  45. 117
      spec/helpers/schemes_helper_spec.rb
  46. 16
      spec/models/validations/financial_validations_spec.rb
  47. 3
      spec/requests/auth/passwords_controller_spec.rb
  48. 27
      spec/requests/check_errors_controller_spec.rb
  49. 3
      spec/requests/maintenance_controller_spec.rb
  50. 150
      spec/requests/organisations_controller_spec.rb
  51. 3
      spec/requests/rails_admin_controller_spec.rb
  52. 3
      spec/requests/start_controller_spec.rb
  53. 3
      spec/requests/users_controller_spec.rb
  54. 2
      spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb
  55. 2
      spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb

1
app/controllers/form_controller.rb

@ -409,6 +409,7 @@ private
next if question.subsection.id == "setup" next if question.subsection.id == "setup"
question.page.questions.map(&:id).each { |id| @log[id] = nil } question.page.questions.map(&:id).each { |id| @log[id] = nil }
@log.previous_la_known = nil if question.id == "ppostcode_full"
end end
@log.save! @log.save!
@questions = params[@log.model_name.param_key].keys.reject { |id| %w[clear_question_ids page].include?(id) }.map { |id| @log.form.get_question(id, @log) } @questions = params[@log.model_name.param_key].keys.reject { |id| %w[clear_question_ids page].include?(id) }.map { |id| @log.form.get_question(id, @log) }

40
app/controllers/organisations_controller.rb

@ -44,6 +44,12 @@ class OrganisationsController < ApplicationController
redirect_to schemes_csv_confirmation_organisation_path redirect_to schemes_csv_confirmation_organisation_path
end end
def duplicate_schemes
authorize @organisation
get_duplicate_schemes_and_locations
end
def show def show
redirect_to details_organisation_path(@organisation) redirect_to details_organisation_path(@organisation)
end end
@ -295,6 +301,22 @@ class OrganisationsController < ApplicationController
render json: org_data.to_json render json: org_data.to_json
end end
def confirm_duplicate_schemes
authorize @organisation
if scheme_duplicates_checked_params[:scheme_duplicates_checked] == "true"
@organisation.schemes_deduplicated_at = Time.zone.now
if @organisation.save
flash[:notice] = I18n.t("organisation.duplicate_schemes_confirmed")
redirect_to schemes_organisation_path(@organisation)
end
else
@organisation.errors.add(:scheme_duplicates_checked, I18n.t("validations.organisation.scheme_duplicates_not_resolved"))
get_duplicate_schemes_and_locations
render :duplicate_schemes, status: :unprocessable_entity
end
end
private private
def filter_type def filter_type
@ -325,6 +347,10 @@ private
params.require(:organisation).permit(rent_periods: [], all_rent_periods: []) params.require(:organisation).permit(rent_periods: [], all_rent_periods: [])
end end
def scheme_duplicates_checked_params
params.require(:organisation).permit(:scheme_duplicates_checked)
end
def codes_only_export? def codes_only_export?
params.require(:codes_only) == "true" params.require(:codes_only) == "true"
end end
@ -344,4 +370,18 @@ private
def find_resource def find_resource
@organisation = Organisation.find(params[:id]) @organisation = Organisation.find(params[:id])
end end
def get_duplicate_schemes_and_locations
duplicate_scheme_sets = @organisation.owned_schemes.duplicate_sets
@duplicate_schemes = duplicate_scheme_sets.map { |set| set.map { |id| @organisation.owned_schemes.find(id) } }
@duplicate_locations = []
@organisation.owned_schemes.each do |scheme|
duplicate_location_sets = scheme.locations.duplicate_sets
next unless duplicate_location_sets.any?
duplicate_location_sets.each do |duplicate_set|
@duplicate_locations << { scheme: scheme, locations: duplicate_set.map { |id| scheme.locations.find(id) } }
end
end
end
end end

95
app/controllers/start_controller.rb

@ -1,4 +1,6 @@
class StartController < ApplicationController class StartController < ApplicationController
include CollectionResourcesHelper
def index def index
if current_user if current_user
@homepage_presenter = HomepagePresenter.new(current_user) @homepage_presenter = HomepagePresenter.new(current_user)
@ -7,114 +9,67 @@ class StartController < ApplicationController
end end
def download_24_25_sales_form def download_24_25_sales_form
send_file( download_resource("2024_25_sales_paper_form.pdf", "2024-25 Sales paper form.pdf")
Rails.root.join("public/files/2024_25_sales_paper_form.pdf"),
filename: "2024-25 Sales paper form.pdf",
type: "application/pdf",
)
end end
def download_23_24_sales_form def download_23_24_sales_form
send_file( download_resource("2023_24_sales_paper_form.pdf", "2023-24 Sales paper form.pdf")
Rails.root.join("public/files/2023_24_sales_paper_form.pdf"),
filename: "2023-24 Sales paper form.pdf",
type: "application/pdf",
)
end end
def download_24_25_lettings_form def download_24_25_lettings_form
send_file( download_resource("2024_25_lettings_paper_form.pdf", "2024-25 Lettings paper form.pdf")
Rails.root.join("public/files/2024_25_lettings_paper_form.pdf"),
filename: "2024-25 Lettings paper form.pdf",
type: "application/pdf",
)
end end
def download_23_24_lettings_form def download_23_24_lettings_form
send_file( download_resource("2023_24_lettings_paper_form.pdf", "2023-24 Lettings paper form.pdf")
Rails.root.join("public/files/2023_24_lettings_paper_form.pdf"),
filename: "2023-24 Lettings paper form.pdf",
type: "application/pdf",
)
end end
def download_24_25_lettings_bulk_upload_template def download_24_25_lettings_bulk_upload_template
send_file( download_resource("bulk-upload-lettings-template-2024-25.xlsx", "2024-25-lettings-bulk-upload-template.xlsx")
Rails.root.join("public/files/bulk-upload-lettings-template-2024-25.xlsx"),
filename: "2024-25-lettings-bulk-upload-template.xlsx",
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
end end
def download_24_25_lettings_bulk_upload_specification def download_24_25_lettings_bulk_upload_specification
send_file( download_resource("bulk-upload-lettings-specification-2024-25.xlsx", "2024-25-lettings-bulk-upload-specification.xlsx")
Rails.root.join("public/files/bulk-upload-lettings-specification-2024-25.xlsx"),
filename: "2024-25-lettings-bulk-upload-specification.xlsx",
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
end end
def download_24_25_sales_bulk_upload_template def download_24_25_sales_bulk_upload_template
send_file( download_resource("bulk-upload-sales-template-2024-25.xlsx", "2024-25-sales-bulk-upload-template.xlsx")
Rails.root.join("public/files/bulk-upload-sales-template-2024-25.xlsx"),
filename: "2024-25-sales-bulk-upload-template.xlsx",
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
end end
def download_24_25_sales_bulk_upload_specification def download_24_25_sales_bulk_upload_specification
send_file( download_resource("bulk-upload-sales-specification-2024-25.xlsx", "2024-25-sales-bulk-upload-specification.xlsx")
Rails.root.join("public/files/bulk-upload-sales-specification-2024-25.xlsx"),
filename: "2024-25-sales-bulk-upload-specification.xlsx",
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
end end
def download_23_24_lettings_bulk_upload_template def download_23_24_lettings_bulk_upload_template
send_file( download_resource("bulk-upload-lettings-template-2023-24.xlsx", "2023-24-lettings-bulk-upload-template.xlsx")
Rails.root.join("public/files/bulk-upload-lettings-template-2023-24.xlsx"),
filename: "2023-24-lettings-bulk-upload-template.xlsx",
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
end end
def download_23_24_lettings_bulk_upload_legacy_template def download_23_24_lettings_bulk_upload_legacy_template
send_file( download_resource("bulk-upload-lettings-legacy-template-2023-24.xlsx", "2023-24-lettings-bulk-upload-legacy-template.xlsx")
Rails.root.join("public/files/bulk-upload-lettings-legacy-template-2023-24.xlsx"),
filename: "2023-24-lettings-bulk-upload-legacy-template.xlsx",
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
end end
def download_23_24_lettings_bulk_upload_specification def download_23_24_lettings_bulk_upload_specification
send_file( download_resource("bulk-upload-lettings-specification-2023-24.xlsx", "2023-24-lettings-bulk-upload-specification.xlsx")
Rails.root.join("public/files/bulk-upload-lettings-specification-2023-24.xlsx"),
filename: "2023-24-lettings-bulk-upload-specification.xlsx",
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
end end
def download_23_24_sales_bulk_upload_template def download_23_24_sales_bulk_upload_template
send_file( download_resource("bulk-upload-sales-template-2023-24.xlsx", "2023-24-sales-bulk-upload-template.xlsx")
Rails.root.join("public/files/bulk-upload-sales-template-2023-24.xlsx"),
filename: "2023-24-sales-bulk-upload-template.xlsx",
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
end end
def download_23_24_sales_bulk_upload_legacy_template def download_23_24_sales_bulk_upload_legacy_template
send_file( download_resource("bulk-upload-sales-legacy-template-2023-24.xlsx", "2023-24-sales-bulk-upload-legacy-template.xlsx")
Rails.root.join("public/files/bulk-upload-sales-legacy-template-2023-24.xlsx"),
filename: "2023-24-sales-bulk-upload-legacy-template.xlsx",
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
end end
def download_23_24_sales_bulk_upload_specification def download_23_24_sales_bulk_upload_specification
send_file( download_resource("bulk-upload-sales-specification-2023-24.xlsx", "2023-24-sales-bulk-upload-specification.xlsx")
Rails.root.join("public/files/bulk-upload-sales-specification-2023-24.xlsx"), end
filename: "2023-24-sales-bulk-upload-specification.xlsx",
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", private
)
def download_resource(filename, download_filename)
file = CollectionResourcesService.new.get_file(filename)
return render_not_found unless file
send_data(file, disposition: "attachment", filename: download_filename)
end end
end end

23
app/helpers/collection_resources_helper.rb

@ -1,15 +1,22 @@
module CollectionResourcesHelper module CollectionResourcesHelper
HUMAN_READABLE_CONTENT_TYPE = { "application/pdf": "PDF",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "Microsoft Excel",
"application/vnd.ms-excel": "Microsoft Excel (Old Format)",
"application/msword": "Microsoft Word",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "Microsoft Word (DOCX)",
"image/jpeg": "JPEG Image",
"image/png": "PNG Image",
"text/plain": "Text Document",
"text/html": "HTML Document" }.freeze
def file_type_size_and_pages(file, number_of_pages: nil) def file_type_size_and_pages(file, number_of_pages: nil)
extension_mapping = { file_pages = number_of_pages ? pluralize(number_of_pages, "page") : nil
"xlsx" => "Microsoft Excel", metadata = CollectionResourcesService.new.get_file_metadata(file)
"pdf" => "PDF",
}
extension = File.extname(file)[1..]
file_type = extension_mapping.fetch(extension, extension) return [file_pages].compact.join(", ") unless metadata
file_size = number_to_human_size(File.size("public/files/#{file}"), precision: 0, significant: false) file_size = number_to_human_size(metadata["content_length"].to_i)
file_pages = number_of_pages ? pluralize(number_of_pages, "page") : nil file_type = HUMAN_READABLE_CONTENT_TYPE[metadata["content_type"].to_sym] || "Unknown File Type"
[file_type, file_size, file_pages].compact.join(", ") [file_type, file_size, file_pages].compact.join(", ")
end end
end end

8
app/helpers/schemes_helper.rb

@ -85,6 +85,14 @@ module SchemesHelper
end end
end end
def display_duplicate_schemes_banner?(organisation, current_user)
return unless organisation.absorbed_organisations.merged_during_open_collection_period.any?
return unless current_user.data_coordinator? || current_user.support?
return if organisation.schemes_deduplicated_at.present? && organisation.schemes_deduplicated_at > organisation.absorbed_organisations.map(&:merge_date).max
organisation.owned_schemes.duplicate_sets.any? || organisation.owned_schemes.any? { |scheme| scheme.locations.duplicate_sets.any? }
end
private private
ActivePeriod = Struct.new(:from, :to) ActivePeriod = Struct.new(:from, :to)

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

@ -35,25 +35,25 @@ module Forms
def legacy_template_path def legacy_template_path
case year case year
when 2023 when 2023
"/files/bulk-upload-lettings-legacy-template-2023-24.xlsx" download_23_24_lettings_bulk_upload_legacy_template_path
end end
end end
def template_path def template_path
case year case year
when 2023 when 2023
"/files/bulk-upload-lettings-template-2023-24.xlsx" download_23_24_lettings_bulk_upload_template_path
when 2024 when 2024
"/files/bulk-upload-lettings-template-2024-25.xlsx" download_24_25_lettings_bulk_upload_template_path
end end
end end
def specification_path def specification_path
case year case year
when 2023 when 2023
"/files/bulk-upload-lettings-specification-2023-24.xlsx" download_23_24_lettings_bulk_upload_specification_path
when 2024 when 2024
"/files/bulk-upload-lettings-specification-2024-25.xlsx" download_24_25_lettings_bulk_upload_specification_path
end end
end end

10
app/models/forms/bulk_upload_sales/prepare_your_file.rb

@ -34,25 +34,25 @@ module Forms
def legacy_template_path def legacy_template_path
case year case year
when 2023 when 2023
"/files/bulk-upload-sales-legacy-template-2023-24.xlsx" download_23_24_sales_bulk_upload_legacy_template_path
end end
end end
def template_path def template_path
case year case year
when 2023 when 2023
"/files/bulk-upload-sales-template-2023-24.xlsx" download_23_24_sales_bulk_upload_template_path
when 2024 when 2024
"/files/bulk-upload-sales-template-2024-25.xlsx" download_24_25_sales_bulk_upload_template_path
end end
end end
def specification_path def specification_path
case year case year
when 2023 when 2023
"/files/bulk-upload-sales-specification-2023-24.xlsx" download_23_24_sales_bulk_upload_specification_path
when 2024 when 2024
"/files/bulk-upload-sales-specification-2024-25.xlsx" download_24_25_sales_bulk_upload_specification_path
end end
end end

5
app/models/log.rb

@ -130,7 +130,10 @@ class Log < ApplicationRecord
if [address_line1_input, postcode_full_input].all?(&:present?) if [address_line1_input, postcode_full_input].all?(&:present?)
service = AddressClient.new(address_string) service = AddressClient.new(address_string)
service.call service.call
return nil if service.result.blank? || service.error.present? if service.result.blank? || service.error.present?
@address_options = []
return @answer_options
end
address_opts = [] address_opts = []
service.result.first(10).each do |result| service.result.first(10).each do |result|

5
app/models/validations/financial_validations.rb

@ -121,6 +121,11 @@ module Validations::FinancialValidations
def validate_rent_amount(record) def validate_rent_amount(record)
if record.wtshortfall if record.wtshortfall
if record.is_supported_housing? && record.wchchrg && (record.wtshortfall > record.wchchrg)
record.errors.add :tshortfall, message: I18n.t("validations.financial.tshortfall.more_than_carehome_charge")
record.errors.add :chcharge, I18n.t("validations.financial.carehome.less_than_shortfall")
end
if record.wtcharge && (record.wtshortfall > record.wtcharge) if record.wtcharge && (record.wtshortfall > record.wtcharge)
record.errors.add :tshortfall, :more_than_rent, message: I18n.t("validations.financial.tshortfall.more_than_total_charge") record.errors.add :tshortfall, :more_than_rent, message: I18n.t("validations.financial.tshortfall.more_than_total_charge")
record.errors.add :tcharge, I18n.t("validations.financial.tcharge.less_than_shortfall") record.errors.add :tcharge, I18n.t("validations.financial.tcharge.less_than_shortfall")

8
app/policies/organisation_policy.rb

@ -34,4 +34,12 @@ class OrganisationPolicy
editable_sales_logs = organisation.sales_logs.visible.after_date(editable_from_date) editable_sales_logs = organisation.sales_logs.visible.after_date(editable_from_date)
organisation.sales_logs.visible.where(saledate: nil).any? || editable_sales_logs.any? organisation.sales_logs.visible.where(saledate: nil).any? || editable_sales_logs.any?
end end
def duplicate_schemes?
user.support? || (user.data_coordinator? && user.organisation == organisation)
end
def confirm_duplicate_schemes?
duplicate_schemes?
end
end end

6
app/services/bulk_upload/sales/validator.rb

@ -43,12 +43,12 @@ class BulkUpload::Sales::Validator
return false if any_setup_errors? return false if any_setup_errors?
if row_parsers.any?(&:block_log_creation?) if row_parsers.any?(&:block_log_creation?)
Sentry.capture_exception("Bulk upload log creation blocked: #{bulk_upload.id}.") Sentry.capture_message("Bulk upload log creation blocked: #{bulk_upload.id}.")
return false return false
end end
if any_logs_already_exist? && FeatureToggle.bulk_upload_duplicate_log_check_enabled? if any_logs_already_exist? && FeatureToggle.bulk_upload_duplicate_log_check_enabled?
Sentry.capture_exception("Bulk upload log creation blocked due to duplicate logs: #{bulk_upload.id}.") Sentry.capture_message("Bulk upload log creation blocked due to duplicate logs: #{bulk_upload.id}.")
return false return false
end end
@ -57,7 +57,7 @@ class BulkUpload::Sales::Validator
end end
if any_logs_invalid? if any_logs_invalid?
Sentry.capture_exception("Bulk upload log creation blocked due to invalid logs after blanking non setup fields: #{bulk_upload.id}.") Sentry.capture_message("Bulk upload log creation blocked due to invalid logs after blanking non setup fields: #{bulk_upload.id}.")
return false return false
end end

21
app/services/collection_resources_service.rb

@ -0,0 +1,21 @@
class CollectionResourcesService
def initialize
@storage_service = if FeatureToggle.local_storage?
Storage::LocalDiskService.new
else
Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["COLLECTION_RESOURCES_BUCKET"])
end
end
def get_file(file)
@storage_service.get_file_io(file)
rescue StandardError
nil
end
def get_file_metadata(file)
@storage_service.get_file_metadata(file)
rescue StandardError
nil
end
end

4
app/services/feature_toggle.rb

@ -34,4 +34,8 @@ class FeatureToggle
def self.delete_user_enabled? def self.delete_user_enabled?
true true
end end
def self.local_storage?
Rails.env.development?
end
end end

9
app/services/storage/local_disk_service.rb

@ -22,5 +22,14 @@ module Storage
f.write data f.write data
end end
end end
def get_file_metadata(filename)
path = Rails.root.join("tmp/storage", filename)
{
"content_length" => File.size(path),
"content_type" => MiniMime.lookup_by_filename(path.to_s)&.content_type || "application/octet-stream",
}
end
end end
end end

4
app/services/storage/s3_service.rb

@ -39,6 +39,10 @@ module Storage
) )
end end
def get_file_metadata(file_name)
@client.head_object(bucket: @configuration.bucket_name, key: file_name)
end
private private
def create_configuration def create_configuration

138
app/views/organisations/duplicate_schemes.html.erb

@ -0,0 +1,138 @@
<% content_for :before_content do %>
<%= govuk_back_link href: schemes_organisation_path(@organisation) %>
<% end %>
<%= form_with model: @organisation, url: schemes_duplicates_organisation_path(@organisation), method: "post" do |f| %>
<%= f.govuk_error_summary %>
<% if @duplicate_schemes.any? %>
<% if @duplicate_locations.any? %>
<% title = "Review these sets of schemes and locations" %>
<% else %>
<% title = "Review these sets of schemes" %>
<% end %>
<% else %>
<% title = "Review these sets of locations" %>
<% end %>
<% content_for :title, title %>
<% if current_user.support? %>
<%= render SubNavigationComponent.new(
items: secondary_items(request.path, @organisation.id),
) %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<h1 class="govuk-heading-xl"><%= title %></h1>
<p class="govuk-body">Since your organisation recently merged, we’ve reviewed your schemes for possible duplicates.</p>
<p class="govuk-body">These sets of schemes and locations might be duplicates because they have the same answers for certain fields.</p>
<h2 class="govuk-heading-m">What you need to do</h2>
<ul class="govuk-list govuk-list--bullet">
<li>Review each set of schemes or locations and decide if they are duplicates.</li>
<li>If they are, choose one to keep and deactivate the others on the date your organisation merged.</li>
<li>When you have resolved all duplicates, confirm below.</li>
</ul>
<p class="govuk-body">If you need help with this, <%= govuk_link_to "contact the helpdesk (opens in a new tab)", GlobalConstants::HELPDESK_URL, target: "#" %>.</p>
<% if @duplicate_schemes.any? %>
<h2 class="govuk-heading-m"><%= @duplicate_schemes.count == 1 ? "This set" : "These #{@duplicate_schemes.count} sets" %> of schemes might have duplicates</h2>
<%= govuk_details(summary_text: "Why are these schemes identified as duplicates?") do %>
<p class="govuk-body">
These schemes have the same answers for the following fields:
</p>
<ul class="govuk-list govuk-list--bullet">
<li>Type of scheme</li>
<li>Registered under Care Standards Act 2000</li>
<li>Housing stock owned by</li>
<li>Support services provided by</li>
<li>Primary client group</li>
<li>Has another client group</li>
<li>Secondary client group</li>
<li>Level of support given</li>
<li>Intended length of stay</li>
</ul>
<% end %>
<%= govuk_table do |table| %>
<%= table.with_head do |head| %>
<% head.with_row do |row| %>
<% row.with_cell(header: true, text: "Schemes") %>
<% end %>
<%= table.with_body do |body| %>
<% @duplicate_schemes.each do |duplicate_set| %>
<% body.with_row do |row| %>
<% row.with_cell do %>
<ol class="govuk-list govuk-list--number">
<% duplicate_set.each do |scheme| %>
<li>
<%= govuk_link_to scheme.service_name, scheme %>
</li>
<% end %>
</ol>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% if @duplicate_locations.any? %>
<h2 class="govuk-heading-m"><%= @duplicate_locations.count == 1 ? "This set" : "These #{@duplicate_locations.count} sets" %> of locations might have duplicates</h2>
<%= govuk_details(summary_text: "Why are these locations identified as duplicates?") do %>
<p class="govuk-body">
These locations belong to the same scheme and have the same answers for the following fields:
</p>
<ul class="govuk-list govuk-list--bullet">
<li>Postcode</li>
<li>Mobility standards</li>
</ul>
<% end %>
<%= govuk_table do |table| %>
<%= table.with_head do |head| %>
<% head.with_row do |row| %>
<% row.with_cell(header: true, text: "Locations") %>
<% row.with_cell(header: true, text: "Scheme") %>
<% end %>
<%= table.with_body do |body| %>
<% @duplicate_locations.each do |duplicate_set| %>
<% body.with_row do |row| %>
<% row.with_cell do %>
<ol class="govuk-list govuk-list--number">
<% duplicate_set[:locations].each do |location| %>
<li>
<%= govuk_link_to location.name, scheme_location_path(location) %>
</li>
<% end %>
</ol>
<% end %>
<% row.with_cell do %>
<%= govuk_link_to duplicate_set[:scheme].service_name, duplicate_set[:scheme] %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<%= f.govuk_check_boxes_fieldset :scheme_duplicates_checked,
legend: { text: "Have you resolved all duplicates?" } do %>
<%= f.govuk_check_box :scheme_duplicates_checked,
true,
false,
multiple: false,
checked: false,
label: { text: "Yes, none of the schemes and locations above are duplicates" } %>
<% end %>
<%= f.govuk_submit "Confirm" %>
</div>
</div>
<% end %>

10
app/views/organisations/schemes.html.erb

@ -11,6 +11,16 @@
) %> ) %>
<h2 class="govuk-visually-hidden">Supported housing schemes</h2> <h2 class="govuk-visually-hidden">Supported housing schemes</h2>
<% end %> <% end %>
<% if display_duplicate_schemes_banner?(@organisation, current_user) %>
<%= govuk_notification_banner(title_text: "Important") do %>
<p class="govuk-notification-banner__heading govuk-!-width-full" style="max-width: fit-content">
Some schemes and locations might be duplicates.
<p>
<%= govuk_link_to "Review possible duplicates", href: schemes_duplicates_organisation_path(@organisation) %>
<% end %>
<% end %>
<div class="app-filter-layout" data-controller="filter-layout"> <div class="app-filter-layout" data-controller="filter-layout">
<% if SchemePolicy.new(current_user, nil).create? %> <% if SchemePolicy.new(current_user, nil).create? %>
<%= govuk_button_link_to "Create a new supported housing scheme", new_scheme_path, html: { method: :post } %> <%= govuk_button_link_to "Create a new supported housing scheme", new_scheme_path, html: { method: :post } %>

4
config/locales/en.yml

@ -37,6 +37,7 @@ en:
updated: "Organisation details updated." updated: "Organisation details updated."
reactivated: "%{organisation} has been reactivated." reactivated: "%{organisation} has been reactivated."
deactivated: "%{organisation} has been deactivated." deactivated: "%{organisation} has been deactivated."
duplicate_schemes_confirmed: "You’ve confirmed the remaining schemes and locations are not duplicates."
user: user:
create_password: "Create a password to finish setting up your account." create_password: "Create a password to finish setting up your account."
reset_password: "Reset your password." reset_password: "Reset your password."
@ -229,6 +230,7 @@ en:
blank: "You must choose a managing agent." blank: "You must choose a managing agent."
already_added: "You have already added this managing agent." already_added: "You have already added this managing agent."
merged: "That organisation has already been merged. Select a different organisation." merged: "That organisation has already been merged. Select a different organisation."
scheme_duplicates_not_resolved: "You must resolve all duplicates or indicate that there are no duplicates"
not_answered: "You must answer %{question}" not_answered: "You must answer %{question}"
invalid_option: "Enter a valid value for %{question}" invalid_option: "Enter a valid value for %{question}"
invalid_number: "Enter a number for %{question}" invalid_number: "Enter a number for %{question}"
@ -382,6 +384,7 @@ en:
tshortfall: tshortfall:
outstanding_amount_not_expected: "You cannot answer the outstanding amount question if you don’t have outstanding rent or charges." outstanding_amount_not_expected: "You cannot answer the outstanding amount question if you don’t have outstanding rent or charges."
more_than_total_charge: "Enter a value less than the total charge." more_than_total_charge: "Enter a value less than the total charge."
more_than_carehome_charge: "Enter a value less than the care home charge."
must_be_positive: "Enter a value over £0.01 as you told us there is an outstanding amount." must_be_positive: "Enter a value over £0.01 as you told us there is an outstanding amount."
hbrentshortfall: hbrentshortfall:
outstanding_amount_not_expected: "Answer must be ‘yes’ as you have answered the outstanding amount question." outstanding_amount_not_expected: "Answer must be ‘yes’ as you have answered the outstanding amount question."
@ -454,6 +457,7 @@ en:
carehome: carehome:
out_of_range: "Household rent and other charges must be between %{min_chcharge} and %{max_chcharge} if paying %{period}." out_of_range: "Household rent and other charges must be between %{min_chcharge} and %{max_chcharge} if paying %{period}."
not_provided: "Enter how much rent and other charges the household pays %{period}." not_provided: "Enter how much rent and other charges the household pays %{period}."
less_than_shortfall: "The care home charge must be more than the outstanding amount."
cash_discount_invalid: "Cash discount must be £0 - £999,999." cash_discount_invalid: "Cash discount must be £0 - £999,999."
staircasing: staircasing:
percentage_bought_must_be_greater_than_percentage_owned: "Total percentage %{buyer_now_owns} must be more than percentage bought in this transaction." percentage_bought_must_be_greater_than_percentage_owned: "Total percentage %{buyer_now_owns} must be more than percentage bought in this transaction."

2
config/routes.rb

@ -178,6 +178,8 @@ Rails.application.routes.draw do
get "schemes/csv-download", to: "organisations#download_schemes_csv" get "schemes/csv-download", to: "organisations#download_schemes_csv"
post "schemes/email-csv", to: "organisations#email_schemes_csv" post "schemes/email-csv", to: "organisations#email_schemes_csv"
get "schemes/csv-confirmation", to: "schemes#csv_confirmation" get "schemes/csv-confirmation", to: "schemes#csv_confirmation"
get "schemes/duplicates", to: "organisations#duplicate_schemes"
post "schemes/duplicates", to: "organisations#confirm_duplicate_schemes"
get "stock-owners", to: "organisation_relationships#stock_owners" get "stock-owners", to: "organisation_relationships#stock_owners"
get "stock-owners/add", to: "organisation_relationships#add_stock_owner" get "stock-owners/add", to: "organisation_relationships#add_stock_owner"
get "stock-owners/remove", to: "organisation_relationships#remove_stock_owner" get "stock-owners/remove", to: "organisation_relationships#remove_stock_owner"

5
db/migrate/20240920144611_add_schemes_deduplicated_at.rb

@ -0,0 +1,5 @@
class AddSchemesDeduplicatedAt < ActiveRecord::Migration[7.0]
def change
add_column :organisations, :schemes_deduplicated_at, :datetime
end
end

1
db/schema.rb

@ -516,6 +516,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_10_02_163937) do
t.bigint "absorbing_organisation_id" t.bigint "absorbing_organisation_id"
t.datetime "available_from" t.datetime "available_from"
t.datetime "discarded_at" t.datetime "discarded_at"
t.datetime "schemes_deduplicated_at"
t.index ["absorbing_organisation_id"], name: "index_organisations_on_absorbing_organisation_id" t.index ["absorbing_organisation_id"], name: "index_organisations_on_absorbing_organisation_id"
t.index ["old_visible_id"], name: "index_organisations_on_old_visible_id", unique: true t.index ["old_visible_id"], name: "index_organisations_on_old_visible_id", unique: true
end end

19
lib/tasks/recalculate_status_when_la_missing.rake

@ -17,3 +17,22 @@ task recalculate_status_missing_la: :environment do
end end
end end
end end
desc "Recalculates status for 2024 completed logs with missing LA"
task recalculate_status_missing_la_2024: :environment do
LettingsLog.filter_by_year(2024).where(needstype: 1, la: nil, status: "completed").find_each do |log|
log.status = log.calculate_status
unless log.save
Rails.logger.info "Could not save changes to lettings log #{log.id}"
end
end
SalesLog.filter_by_year(2024).where(la: nil, status: "completed").find_each do |log|
log.status = log.calculate_status
unless log.save
Rails.logger.info "Could not save changes to sales log #{log.id}"
end
end
end

BIN
public/files/2023_24_lettings_paper_form.pdf

Binary file not shown.

BIN
public/files/2023_24_sales_paper_form.pdf

Binary file not shown.

BIN
public/files/2024_25_lettings_paper_form.pdf

Binary file not shown.

BIN
public/files/2024_25_sales_paper_form.pdf

Binary file not shown.

BIN
public/files/bulk-upload-lettings-legacy-template-2023-24.xlsx

Binary file not shown.

BIN
public/files/bulk-upload-lettings-specification-2023-24.xlsx

Binary file not shown.

BIN
public/files/bulk-upload-lettings-specification-2024-25.xlsx

Binary file not shown.

BIN
public/files/bulk-upload-lettings-template-2023-24.xlsx

Binary file not shown.

BIN
public/files/bulk-upload-lettings-template-2024-25.xlsx

Binary file not shown.

BIN
public/files/bulk-upload-sales-legacy-template-2023-24.xlsx

Binary file not shown.

BIN
public/files/bulk-upload-sales-specification-2023-24.xlsx

Binary file not shown.

BIN
public/files/bulk-upload-sales-specification-2024-25.xlsx

Binary file not shown.

BIN
public/files/bulk-upload-sales-template-2023-24.xlsx

Binary file not shown.

BIN
public/files/bulk-upload-sales-template-2024-25.xlsx

Binary file not shown.

9
spec/features/accessibility_spec.rb

@ -3,7 +3,7 @@ require "rails_helper"
RSpec.describe "Accessibility", js: true do RSpec.describe "Accessibility", js: true do
let(:user) { create(:user, :support) } let(:user) { create(:user, :support) }
let!(:other_user) { create(:user, name: "new user", organisation: user.organisation, email: "new_user@example.com", confirmation_token: "abc") } let!(:other_user) { create(:user, name: "new user", organisation: user.organisation, email: "new_user@example.com", confirmation_token: "abc") }
let(:mock_storage_service) { instance_double("S3Service") } let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
def find_routes(type, resource, subresource) def find_routes(type, resource, subresource)
routes = Rails.application.routes.routes.select do |route| routes = Rails.application.routes.routes.select do |route|
@ -21,7 +21,8 @@ RSpec.describe "Accessibility", js: true do
end end
before do before do
allow(Storage::S3Service).to receive(:new).and_return(mock_storage_service) allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:configuration).and_return(OpenStruct.new(bucket_name: "core-test-collection-resources"))
allow(user).to receive(:need_two_factor_authentication?).and_return(false) allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in(user) sign_in(user)
end end
@ -109,7 +110,7 @@ RSpec.describe "Accessibility", js: true do
log.save(validate: false) log.save(validate: false)
end end
allow(FormHandler.instance).to receive(:in_crossover_period?).and_return(true) allow(FormHandler.instance).to receive(:in_crossover_period?).and_return(true)
allow(mock_storage_service).to receive(:get_presigned_url).with(bulk_upload.identifier, 60, response_content_disposition: "attachment; filename=#{bulk_upload.filename}").and_return("http://example.com/lettings-logs/bulk-uploads/#{bulk_upload.id}/download") allow(storage_service).to receive(:get_presigned_url).with(bulk_upload.identifier, 60, response_content_disposition: "attachment; filename=#{bulk_upload.filename}").and_return("http://example.com/lettings-logs/bulk-uploads/#{bulk_upload.id}/download")
end end
it "is has accessible pages" do it "is has accessible pages" do
@ -149,7 +150,7 @@ RSpec.describe "Accessibility", js: true do
end end
before do before do
allow(mock_storage_service).to receive(:get_presigned_url).with(bulk_upload.identifier, 60, response_content_disposition: "attachment; filename=#{bulk_upload.filename}").and_return("http://example.com/sales-logs/bulk-uploads/#{bulk_upload.id}/download") allow(storage_service).to receive(:get_presigned_url).with(bulk_upload.identifier, 60, response_content_disposition: "attachment; filename=#{bulk_upload.filename}").and_return("http://example.com/sales-logs/bulk-uploads/#{bulk_upload.id}/download")
end end
it "is has accessible pages" do it "is has accessible pages" do

7
spec/features/lettings_log_spec.rb

@ -1,6 +1,13 @@
require "rails_helper" require "rails_helper"
RSpec.describe "Lettings Log Features" do RSpec.describe "Lettings Log Features" do
let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:configuration).and_return(OpenStruct.new(bucket_name: "core-test-collection-resources"))
end
context "when searching for specific logs" do context "when searching for specific logs" do
context "when I am signed in and there are logs in the database" do context "when I am signed in and there are logs in the database" do
let(:user) { create(:user, last_sign_in_at: Time.zone.now) } let(:user) { create(:user, last_sign_in_at: Time.zone.now) }

6
spec/features/notifications_spec.rb

@ -3,6 +3,12 @@ require_relative "form/helpers"
RSpec.describe "Notifications Features" do RSpec.describe "Notifications Features" do
include Helpers include Helpers
let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:configuration).and_return(OpenStruct.new(bucket_name: "core-test-collection-resources"))
end
context "when there are notifications" do context "when there are notifications" do
let!(:user) { FactoryBot.create(:user) } let!(:user) { FactoryBot.create(:user) }

3
spec/features/organisation_spec.rb

@ -10,8 +10,11 @@ RSpec.describe "User Features" do
let(:notify_client) { instance_double(Notifications::Client) } let(:notify_client) { instance_double(Notifications::Client) }
let(:confirmation_token) { "MCDH5y6Km-U7CFPgAMVS" } let(:confirmation_token) { "MCDH5y6Km-U7CFPgAMVS" }
let(:devise_notify_mailer) { DeviseNotifyMailer.new } let(:devise_notify_mailer) { DeviseNotifyMailer.new }
let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do before do
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:configuration).and_return(OpenStruct.new(bucket_name: "core-test-collection-resources"))
allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer) allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer)
allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client) allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client)
allow(Devise).to receive(:friendly_token).and_return(confirmation_token) allow(Devise).to receive(:friendly_token).and_return(confirmation_token)

6
spec/features/start_page_spec.rb

@ -4,6 +4,12 @@ require_relative "form/helpers"
RSpec.describe "Start Page Features" do RSpec.describe "Start Page Features" do
include Helpers include Helpers
let(:user) { FactoryBot.create(:user) } let(:user) { FactoryBot.create(:user) }
let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:configuration).and_return(OpenStruct.new(bucket_name: "core-test-collection-resources"))
end
context "when the user is signed in" do context "when the user is signed in" do
before do before do

7
spec/features/test_spec.rb

@ -1,5 +1,12 @@
require "rails_helper" require "rails_helper"
RSpec.describe "Test Features" do RSpec.describe "Test Features" do
let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:configuration).and_return(OpenStruct.new(bucket_name: "core-test-collection-resources"))
end
it "Displays the name of the app" do it "Displays the name of the app" do
visit(root_path) visit(root_path)
expect(page).to have_content("Submit social housing lettings and sales data (CORE)") expect(page).to have_content("Submit social housing lettings and sales data (CORE)")

3
spec/features/user_spec.rb

@ -6,12 +6,15 @@ RSpec.describe "User Features" do
let(:notify_client) { instance_double(Notifications::Client) } let(:notify_client) { instance_double(Notifications::Client) }
let(:reset_password_token) { "MCDH5y6Km-U7CFPgAMVS" } let(:reset_password_token) { "MCDH5y6Km-U7CFPgAMVS" }
let(:devise_notify_mailer) { DeviseNotifyMailer.new } let(:devise_notify_mailer) { DeviseNotifyMailer.new }
let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do before do
allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer) allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer)
allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client) allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client)
allow(notify_client).to receive(:send_email).and_return(true) allow(notify_client).to receive(:send_email).and_return(true)
allow(Devise.token_generator).to receive(:generate).and_return(reset_password_token) allow(Devise.token_generator).to receive(:generate).and_return(reset_password_token)
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:configuration).and_return(OpenStruct.new(bucket_name: "core-test-collection-resources"))
end end
context "when the user navigates to lettings logs" do context "when the user navigates to lettings logs" do

14
spec/helpers/collection_resources_helper_spec.rb

@ -3,15 +3,29 @@ require "rails_helper"
RSpec.describe CollectionResourcesHelper do RSpec.describe CollectionResourcesHelper do
let(:current_user) { create(:user, :data_coordinator) } let(:current_user) { create(:user, :data_coordinator) }
let(:user) { create(:user, :data_coordinator) } let(:user) { create(:user, :data_coordinator) }
let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:configuration).and_return(OpenStruct.new(bucket_name: "core-test-collection-resources"))
end
describe "when displaying file metadata" do describe "when displaying file metadata" do
context "with pages" do context "with pages" do
before do
allow(storage_service).to receive(:get_file_metadata).with("2023_24_lettings_paper_form.pdf").and_return("content_length" => 292_864, "content_type" => "application/pdf")
end
it "returns correct metadata" do it "returns correct metadata" do
expect(file_type_size_and_pages("2023_24_lettings_paper_form.pdf", number_of_pages: 8)).to eq("PDF, 286 KB, 8 pages") expect(file_type_size_and_pages("2023_24_lettings_paper_form.pdf", number_of_pages: 8)).to eq("PDF, 286 KB, 8 pages")
end end
end end
context "without pages" do context "without pages" do
before do
allow(storage_service).to receive(:get_file_metadata).with("bulk-upload-lettings-template-2023-24.xlsx").and_return("content_length" => 19_456, "content_type" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
end
it "returns correct metadata" do it "returns correct metadata" do
expect(file_type_size_and_pages("bulk-upload-lettings-template-2023-24.xlsx")).to eq("Microsoft Excel, 19 KB") expect(file_type_size_and_pages("bulk-upload-lettings-template-2023-24.xlsx")).to eq("Microsoft Excel, 19 KB")
end end

117
spec/helpers/schemes_helper_spec.rb

@ -118,4 +118,121 @@ RSpec.describe SchemesHelper do
end end
end end
end end
describe "display_duplicate_schemes_banner?" do
let(:organisation) { create(:organisation) }
let(:current_user) { create(:user, :support) }
context "when organisation has not absorbed other organisations" do
context "and it has duplicate schemes" do
before do
create_list(:scheme, 2, :duplicate, owning_organisation: organisation)
end
it "does not display the banner" do
expect(display_duplicate_schemes_banner?(organisation, current_user)).to be_falsey
end
end
end
context "when organisation has absorbed other organisations in open collection year" do
before do
build(:organisation, merge_date: Time.zone.yesterday, absorbing_organisation_id: organisation.id).save(validate: false)
end
context "and it has duplicate schemes" do
before do
create_list(:scheme, 2, :duplicate, owning_organisation: organisation)
end
it "displays the banner" do
expect(display_duplicate_schemes_banner?(organisation, current_user)).to be_truthy
end
context "and organisation has confirmed duplicate schemes after the most recent merge" do
before do
organisation.update!(schemes_deduplicated_at: Time.zone.today)
end
it "does not display the banner" do
expect(display_duplicate_schemes_banner?(organisation, current_user)).to be_falsey
end
end
context "and organisation has confirmed duplicate schemes before the most recent merge" do
before do
organisation.update!(schemes_deduplicated_at: Time.zone.today - 2.days)
end
it "displays the banner" do
expect(display_duplicate_schemes_banner?(organisation, current_user)).to be_truthy
end
end
end
context "and it has duplicate locations" do
let(:scheme) { create(:scheme, owning_organisation: organisation) }
before do
create_list(:location, 2, postcode: "AB1 2CD", mobility_type: "A", scheme:)
end
it "displays the banner" do
expect(display_duplicate_schemes_banner?(organisation, current_user)).to be_truthy
end
end
context "and it has no duplicate schemes or locations" do
it "does not display the banner" do
expect(display_duplicate_schemes_banner?(organisation, current_user)).to be_falsey
end
end
context "and it is viewed by data provider" do
let(:current_user) { create(:user, :data_provider) }
before do
create_list(:scheme, 2, :duplicate, owning_organisation: organisation)
end
it "does not display the banner" do
expect(display_duplicate_schemes_banner?(organisation, current_user)).to be_falsey
end
end
end
context "when organisation has absorbed other organisations in closed collection year" do
before do
build(:organisation, merge_date: Time.zone.today - 2.years, absorbing_organisation_id: organisation.id).save(validate: false)
end
context "and it has duplicate schemes" do
before do
create_list(:scheme, 2, :duplicate, owning_organisation: organisation)
end
it "does not display the banner" do
expect(display_duplicate_schemes_banner?(organisation, current_user)).to be_falsey
end
end
context "and it has duplicate locations" do
let(:scheme) { create(:scheme, owning_organisation: organisation) }
before do
create(:location, postcode: "AB1 2CD", mobility_type: "A", scheme:)
end
it "does not display the banner" do
expect(display_duplicate_schemes_banner?(organisation, current_user)).to be_falsey
end
end
context "and it has no duplicate schemes or locations" do
it "does not display the banner" do
expect(display_duplicate_schemes_banner?(organisation, current_user)).to be_falsey
end
end
end
end
end end

16
spec/models/validations/financial_validations_spec.rb

@ -123,6 +123,22 @@ RSpec.describe Validations::FinancialValidations do
.to include(match I18n.t("validations.financial.tshortfall.more_than_total_charge")) .to include(match I18n.t("validations.financial.tshortfall.more_than_total_charge"))
end end
it "validates that carehome charge is no less than the shortfall" do
record.hb = 6
record.hbrentshortfall = 1
record.tshortfall_known = 0
record.tshortfall = 299.50
record.chcharge = 198
record.needstype = 2
record.period = 2
record.set_derived_fields!
financial_validator.validate_rent_amount(record)
expect(record.errors["chcharge"])
.to include(match I18n.t("validations.financial.carehome.less_than_shortfall"))
expect(record.errors["tshortfall"])
.to include(match I18n.t("validations.financial.tshortfall.more_than_carehome_charge"))
end
it "expects that rent can be less than the shortfall if total charge is higher" do it "expects that rent can be less than the shortfall if total charge is higher" do
record.hb = 6 record.hb = 6
record.hbrentshortfall = 1 record.hbrentshortfall = 1

3
spec/requests/auth/passwords_controller_spec.rb

@ -5,11 +5,14 @@ RSpec.describe Auth::PasswordsController, type: :request do
let(:page) { Capybara::Node::Simple.new(response.body) } let(:page) { Capybara::Node::Simple.new(response.body) }
let(:notify_client) { instance_double(Notifications::Client) } let(:notify_client) { instance_double(Notifications::Client) }
let(:devise_notify_mailer) { DeviseNotifyMailer.new } let(:devise_notify_mailer) { DeviseNotifyMailer.new }
let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do before do
allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer) allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer)
allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client) allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client)
allow(notify_client).to receive(:send_email).and_return(true) allow(notify_client).to receive(:send_email).and_return(true)
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:configuration).and_return(OpenStruct.new(bucket_name: "core-test-collection-resources"))
end end
context "when a regular user" do context "when a regular user" do

27
spec/requests/check_errors_controller_spec.rb

@ -300,6 +300,33 @@ RSpec.describe CheckErrorsController, type: :request do
end end
end end
context "and clearing ppostcode_full when previous_la_known is yes" do
let(:params) do
{
id: lettings_log.id,
lettings_log: {
layear: "1",
clear_question_ids: "ppostcode_full",
page: "time_lived_in_local_authority",
},
check_errors: "",
}
end
before do
lettings_log.update!(previous_la_known: 1, ppcodenk: 0, ppostcode_full: "AA11AA")
sign_in user
post "/lettings-logs/#{lettings_log.id}/time-lived-in-local-authority", params:
end
it "clears related previous location fields" do
expect(lettings_log.reload.prevloc).to eq(nil)
expect(lettings_log.reload.previous_la_known).to eq(nil)
expect(lettings_log.reload.ppostcode_full).to eq(nil)
expect(lettings_log.reload.ppcodenk).to eq(nil)
end
end
context "and clearing specific sales question" do context "and clearing specific sales question" do
let(:params) do let(:params) do
{ {

3
spec/requests/maintenance_controller_spec.rb

@ -3,8 +3,11 @@ require "rails_helper"
RSpec.describe MaintenanceController, type: :request do RSpec.describe MaintenanceController, type: :request do
let(:page) { Capybara::Node::Simple.new(response.body) } let(:page) { Capybara::Node::Simple.new(response.body) }
let(:user) { FactoryBot.create(:user) } let(:user) { FactoryBot.create(:user) }
let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do before do
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:configuration).and_return(OpenStruct.new(bucket_name: "core-test-collection-resources"))
sign_in user sign_in user
end end

150
spec/requests/organisations_controller_spec.rb

@ -207,6 +207,24 @@ RSpec.describe OrganisationsController, type: :request do
expect(page).to have_title("#{user.organisation.name} (1 scheme matching ‘#{search_param}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") expect(page).to have_title("#{user.organisation.name} (1 scheme matching ‘#{search_param}’) - Submit social housing lettings and sales data (CORE) - GOV.UK")
end end
end end
context "when organisation has absorbed other organisations" do
before do
create(:organisation, merge_date: Time.zone.today, absorbing_organisation: organisation)
end
context "and it has duplicate schemes or locations" do
before do
create_list(:scheme, 2, :duplicate, owning_organisation: organisation)
get "/organisations/#{organisation.id}/schemes", headers:, params: {}
end
it "displays a banner with correct content" do
expect(page).to have_content("Some schemes and locations might be duplicates.")
expect(page).to have_link("Review possible duplicates", href: "/organisations/#{organisation.id}/schemes/duplicates")
end
end
end
end end
context "when data coordinator user" do context "when data coordinator user" do
@ -348,6 +366,77 @@ RSpec.describe OrganisationsController, type: :request do
end end
end end
describe "#duplicate_schemes" do
context "with support user" do
let(:user) { create(:user, :support) }
before do
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in user
get "/organisations/#{organisation.id}/schemes/duplicates", headers:
end
context "with duplicate schemes and locations" do
let(:schemes) { create_list(:scheme, 5, :duplicate, owning_organisation: organisation) }
before do
create_list(:location, 2, scheme: schemes.first, postcode: "M1 1AA", mobility_type: "M")
create_list(:location, 2, scheme: schemes.first, postcode: "M1 1AA", mobility_type: "A")
get "/organisations/#{organisation.id}/schemes/duplicates", headers:
end
it "displays the duplicate schemes" do
expect(page).to have_content("This set of schemes might have duplicates")
end
it "displays the duplicate locations" do
expect(page).to have_content("These 2 sets of locations might have duplicates")
end
it "has page heading" do
expect(page).to have_content("Review these sets of schemes and locations")
end
end
context "without duplicate schemes and locations" do
it "does not display the schemes" do
expect(page).not_to have_content("schemes might have duplicates")
end
it "does not display the locations" do
expect(page).not_to have_content("locations might have duplicates")
end
end
end
context "with data coordinator user" do
let(:user) { create(:user, :data_coordinator) }
before do
sign_in user
create_list(:scheme, 5, :duplicate, owning_organisation: organisation)
get "/organisations/#{organisation.id}/schemes/duplicates", headers:
end
it "has page heading" do
expect(page).to have_content("Review these sets of schemes")
end
end
context "with data provider user" do
let(:user) { create(:user, :data_provider) }
before do
sign_in user
get "/organisations/#{organisation.id}/schemes/duplicates", headers:
end
it "be unauthorised" do
expect(response).to have_http_status(:unauthorized)
end
end
end
describe "#show" do describe "#show" do
context "with an organisation that the user belongs to" do context "with an organisation that the user belongs to" do
let(:set_time) {} let(:set_time) {}
@ -2263,4 +2352,65 @@ RSpec.describe OrganisationsController, type: :request do
end end
end end
end end
describe "POST #confirm_duplicate_schemes" do
let(:organisation) { create(:organisation) }
context "when not signed in" do
it "redirects to sign in" do
post "/organisations/#{organisation.id}/schemes/duplicates", headers: headers
expect(response).to redirect_to("/account/sign-in")
end
end
context "when signed in" do
before do
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in user
end
context "when user is data provider" do
let(:user) { create(:user, role: "data_provider", organisation:) }
it "returns not found" do
post "/organisations/#{organisation.id}/schemes/duplicates", headers: headers
expect(response).to have_http_status(:unauthorized)
end
end
context "when user is coordinator" do
let(:user) { create(:user, role: "data_coordinator", organisation:) }
context "and the duplicate schemes have been confirmed" do
let(:params) { { "organisation": { scheme_duplicates_checked: "true" } } }
it "redirects to schemes page" do
post "/organisations/#{organisation.id}/schemes/duplicates", headers: headers, params: params
expect(response).to redirect_to("/organisations/#{organisation.id}/schemes")
expect(flash[:notice]).to eq("You’ve confirmed the remaining schemes and locations are not duplicates.")
end
it "updates schemes_deduplicated_at" do
expect(organisation.reload.schemes_deduplicated_at).to be_nil
post "/organisations/#{organisation.id}/schemes/duplicates", headers: headers, params: params
expect(organisation.reload.schemes_deduplicated_at).not_to be_nil
end
end
context "and the duplicate schemes have not been confirmed" do
let(:params) { { "organisation": { scheme_duplicates_checked: "" } } }
it "displays an error" do
post "/organisations/#{organisation.id}/schemes/duplicates", headers: headers, params: params
expect(response).to have_http_status(:unprocessable_entity)
expect(page).to have_content("You must resolve all duplicates or indicate that there are no duplicates")
end
end
end
end
end
end end

3
spec/requests/rails_admin_controller_spec.rb

@ -4,9 +4,12 @@ RSpec.describe "RailsAdmin", type: :request do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:support_user) { create(:user, :support) } let(:support_user) { create(:user, :support) }
let(:page) { Capybara::Node::Simple.new(response.body) } let(:page) { Capybara::Node::Simple.new(response.body) }
let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do before do
allow(support_user).to receive(:need_two_factor_authentication?).and_return(false) allow(support_user).to receive(:need_two_factor_authentication?).and_return(false)
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:configuration).and_return(OpenStruct.new(bucket_name: "core-test-collection-resources"))
end end
describe "GET /admin" do describe "GET /admin" do

3
spec/requests/start_controller_spec.rb

@ -5,11 +5,14 @@ RSpec.describe StartController, type: :request do
let(:page) { Capybara::Node::Simple.new(response.body) } let(:page) { Capybara::Node::Simple.new(response.body) }
let(:notify_client) { instance_double(Notifications::Client) } let(:notify_client) { instance_double(Notifications::Client) }
let(:devise_notify_mailer) { DeviseNotifyMailer.new } let(:devise_notify_mailer) { DeviseNotifyMailer.new }
let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do before do
allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer) allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer)
allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client) allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client)
allow(notify_client).to receive(:send_email).and_return(true) allow(notify_client).to receive(:send_email).and_return(true)
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:configuration).and_return(OpenStruct.new(bucket_name: "core-test-collection-resources"))
end end
describe "GET" do describe "GET" do

3
spec/requests/users_controller_spec.rb

@ -10,11 +10,14 @@ RSpec.describe UsersController, type: :request do
let(:params) { { id: user.id, user: { name: new_name } } } let(:params) { { id: user.id, user: { name: new_name } } }
let(:notify_client) { instance_double(Notifications::Client) } let(:notify_client) { instance_double(Notifications::Client) }
let(:devise_notify_mailer) { DeviseNotifyMailer.new } let(:devise_notify_mailer) { DeviseNotifyMailer.new }
let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do before do
allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer) allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer)
allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client) allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client)
allow(notify_client).to receive(:send_email).and_return(true) allow(notify_client).to receive(:send_email).and_return(true)
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:configuration).and_return(OpenStruct.new(bucket_name: "core-test-collection-resources"))
end end
context "when user is not signed in" do context "when user is not signed in" do

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

@ -215,7 +215,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
field_131: "101.11", field_131: "101.11",
field_132: "1500.19", field_132: "1500.19",
field_133: "1", field_133: "1",
field_134: "234.56", field_134: "34.56",
field_27: "15", field_27: "15",
field_28: "0", field_28: "0",

2
spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb

@ -235,7 +235,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
field_127: "13.14", field_127: "13.14",
field_128: "101.11", field_128: "101.11",
field_129: "1", field_129: "1",
field_130: "234.56", field_130: "34.56",
field_24: "15", field_24: "15",
field_30: now.day.to_s, field_30: now.day.to_s,

Loading…
Cancel
Save