diff --git a/app/controllers/organisations_controller.rb b/app/controllers/organisations_controller.rb
index 044bab74d..61cd43674 100644
--- a/app/controllers/organisations_controller.rb
+++ b/app/controllers/organisations_controller.rb
@@ -44,6 +44,12 @@ class OrganisationsController < ApplicationController
redirect_to schemes_csv_confirmation_organisation_path
end
+ def duplicate_schemes
+ authorize @organisation
+
+ get_duplicate_schemes_and_locations
+ end
+
def show
redirect_to details_organisation_path(@organisation)
end
@@ -295,6 +301,22 @@ class OrganisationsController < ApplicationController
render json: org_data.to_json
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
def filter_type
@@ -325,6 +347,10 @@ private
params.require(:organisation).permit(rent_periods: [], all_rent_periods: [])
end
+ def scheme_duplicates_checked_params
+ params.require(:organisation).permit(:scheme_duplicates_checked)
+ end
+
def codes_only_export?
params.require(:codes_only) == "true"
end
@@ -344,4 +370,18 @@ private
def find_resource
@organisation = Organisation.find(params[:id])
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
diff --git a/app/controllers/start_controller.rb b/app/controllers/start_controller.rb
index f3f793a17..dd4232b7b 100644
--- a/app/controllers/start_controller.rb
+++ b/app/controllers/start_controller.rb
@@ -1,4 +1,6 @@
class StartController < ApplicationController
+ include CollectionResourcesHelper
+
def index
if current_user
@homepage_presenter = HomepagePresenter.new(current_user)
@@ -7,114 +9,67 @@ class StartController < ApplicationController
end
def download_24_25_sales_form
- send_file(
- Rails.root.join("public/files/2024_25_sales_paper_form.pdf"),
- filename: "2024-25 Sales paper form.pdf",
- type: "application/pdf",
- )
+ download_resource("2024_25_sales_paper_form.pdf", "2024-25 Sales paper form.pdf")
end
def download_23_24_sales_form
- send_file(
- Rails.root.join("public/files/2023_24_sales_paper_form.pdf"),
- filename: "2023-24 Sales paper form.pdf",
- type: "application/pdf",
- )
+ download_resource("2023_24_sales_paper_form.pdf", "2023-24 Sales paper form.pdf")
end
def download_24_25_lettings_form
- send_file(
- Rails.root.join("public/files/2024_25_lettings_paper_form.pdf"),
- filename: "2024-25 Lettings paper form.pdf",
- type: "application/pdf",
- )
+ download_resource("2024_25_lettings_paper_form.pdf", "2024-25 Lettings paper form.pdf")
end
def download_23_24_lettings_form
- send_file(
- Rails.root.join("public/files/2023_24_lettings_paper_form.pdf"),
- filename: "2023-24 Lettings paper form.pdf",
- type: "application/pdf",
- )
+ download_resource("2023_24_lettings_paper_form.pdf", "2023-24 Lettings paper form.pdf")
end
def download_24_25_lettings_bulk_upload_template
- send_file(
- 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",
- )
+ download_resource("bulk-upload-lettings-template-2024-25.xlsx", "2024-25-lettings-bulk-upload-template.xlsx")
end
def download_24_25_lettings_bulk_upload_specification
- send_file(
- 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",
- )
+ download_resource("bulk-upload-lettings-specification-2024-25.xlsx", "2024-25-lettings-bulk-upload-specification.xlsx")
end
def download_24_25_sales_bulk_upload_template
- send_file(
- 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",
- )
+ download_resource("bulk-upload-sales-template-2024-25.xlsx", "2024-25-sales-bulk-upload-template.xlsx")
end
def download_24_25_sales_bulk_upload_specification
- send_file(
- 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",
- )
+ download_resource("bulk-upload-sales-specification-2024-25.xlsx", "2024-25-sales-bulk-upload-specification.xlsx")
end
def download_23_24_lettings_bulk_upload_template
- send_file(
- 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",
- )
+ download_resource("bulk-upload-lettings-template-2023-24.xlsx", "2023-24-lettings-bulk-upload-template.xlsx")
end
def download_23_24_lettings_bulk_upload_legacy_template
- send_file(
- 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",
- )
+ download_resource("bulk-upload-lettings-legacy-template-2023-24.xlsx", "2023-24-lettings-bulk-upload-legacy-template.xlsx")
end
def download_23_24_lettings_bulk_upload_specification
- send_file(
- 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",
- )
+ download_resource("bulk-upload-lettings-specification-2023-24.xlsx", "2023-24-lettings-bulk-upload-specification.xlsx")
end
def download_23_24_sales_bulk_upload_template
- send_file(
- 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",
- )
+ download_resource("bulk-upload-sales-template-2023-24.xlsx", "2023-24-sales-bulk-upload-template.xlsx")
end
def download_23_24_sales_bulk_upload_legacy_template
- send_file(
- 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",
- )
+ download_resource("bulk-upload-sales-legacy-template-2023-24.xlsx", "2023-24-sales-bulk-upload-legacy-template.xlsx")
end
def download_23_24_sales_bulk_upload_specification
- send_file(
- Rails.root.join("public/files/bulk-upload-sales-specification-2023-24.xlsx"),
- filename: "2023-24-sales-bulk-upload-specification.xlsx",
- type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
- )
+ download_resource("bulk-upload-sales-specification-2023-24.xlsx", "2023-24-sales-bulk-upload-specification.xlsx")
+ end
+
+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
diff --git a/app/helpers/collection_resources_helper.rb b/app/helpers/collection_resources_helper.rb
index a85670bda..5ab539cde 100644
--- a/app/helpers/collection_resources_helper.rb
+++ b/app/helpers/collection_resources_helper.rb
@@ -1,15 +1,22 @@
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)
- extension_mapping = {
- "xlsx" => "Microsoft Excel",
- "pdf" => "PDF",
- }
- extension = File.extname(file)[1..]
+ file_pages = number_of_pages ? pluralize(number_of_pages, "page") : nil
+ metadata = CollectionResourcesService.new.get_file_metadata(file)
- 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_pages = number_of_pages ? pluralize(number_of_pages, "page") : nil
+ file_size = number_to_human_size(metadata["content_length"].to_i)
+ file_type = HUMAN_READABLE_CONTENT_TYPE[metadata["content_type"].to_sym] || "Unknown File Type"
[file_type, file_size, file_pages].compact.join(", ")
end
end
diff --git a/app/helpers/schemes_helper.rb b/app/helpers/schemes_helper.rb
index 0e318d283..7efb9fffd 100644
--- a/app/helpers/schemes_helper.rb
+++ b/app/helpers/schemes_helper.rb
@@ -85,6 +85,14 @@ module SchemesHelper
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
ActivePeriod = Struct.new(:from, :to)
diff --git a/app/models/forms/bulk_upload_lettings/prepare_your_file.rb b/app/models/forms/bulk_upload_lettings/prepare_your_file.rb
index 159436ce1..984451dbb 100644
--- a/app/models/forms/bulk_upload_lettings/prepare_your_file.rb
+++ b/app/models/forms/bulk_upload_lettings/prepare_your_file.rb
@@ -35,25 +35,25 @@ module Forms
def legacy_template_path
case year
when 2023
- "/files/bulk-upload-lettings-legacy-template-2023-24.xlsx"
+ download_23_24_lettings_bulk_upload_legacy_template_path
end
end
def template_path
case year
when 2023
- "/files/bulk-upload-lettings-template-2023-24.xlsx"
+ download_23_24_lettings_bulk_upload_template_path
when 2024
- "/files/bulk-upload-lettings-template-2024-25.xlsx"
+ download_24_25_lettings_bulk_upload_template_path
end
end
def specification_path
case year
when 2023
- "/files/bulk-upload-lettings-specification-2023-24.xlsx"
+ download_23_24_lettings_bulk_upload_specification_path
when 2024
- "/files/bulk-upload-lettings-specification-2024-25.xlsx"
+ download_24_25_lettings_bulk_upload_specification_path
end
end
diff --git a/app/models/forms/bulk_upload_sales/prepare_your_file.rb b/app/models/forms/bulk_upload_sales/prepare_your_file.rb
index 4bf0797a8..d6d5276c2 100644
--- a/app/models/forms/bulk_upload_sales/prepare_your_file.rb
+++ b/app/models/forms/bulk_upload_sales/prepare_your_file.rb
@@ -34,25 +34,25 @@ module Forms
def legacy_template_path
case year
when 2023
- "/files/bulk-upload-sales-legacy-template-2023-24.xlsx"
+ download_23_24_sales_bulk_upload_legacy_template_path
end
end
def template_path
case year
when 2023
- "/files/bulk-upload-sales-template-2023-24.xlsx"
+ download_23_24_sales_bulk_upload_template_path
when 2024
- "/files/bulk-upload-sales-template-2024-25.xlsx"
+ download_24_25_sales_bulk_upload_template_path
end
end
def specification_path
case year
when 2023
- "/files/bulk-upload-sales-specification-2023-24.xlsx"
+ download_23_24_sales_bulk_upload_specification_path
when 2024
- "/files/bulk-upload-sales-specification-2024-25.xlsx"
+ download_24_25_sales_bulk_upload_specification_path
end
end
diff --git a/app/policies/organisation_policy.rb b/app/policies/organisation_policy.rb
index 9c27d6e91..9c5fc4449 100644
--- a/app/policies/organisation_policy.rb
+++ b/app/policies/organisation_policy.rb
@@ -34,4 +34,12 @@ class OrganisationPolicy
editable_sales_logs = organisation.sales_logs.visible.after_date(editable_from_date)
organisation.sales_logs.visible.where(saledate: nil).any? || editable_sales_logs.any?
end
+
+ def duplicate_schemes?
+ user.support? || (user.data_coordinator? && user.organisation == organisation)
+ end
+
+ def confirm_duplicate_schemes?
+ duplicate_schemes?
+ end
end
diff --git a/app/services/bulk_upload/sales/validator.rb b/app/services/bulk_upload/sales/validator.rb
index 777424349..a473a6461 100644
--- a/app/services/bulk_upload/sales/validator.rb
+++ b/app/services/bulk_upload/sales/validator.rb
@@ -43,12 +43,12 @@ class BulkUpload::Sales::Validator
return false if any_setup_errors?
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
end
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
end
@@ -57,7 +57,7 @@ class BulkUpload::Sales::Validator
end
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
end
diff --git a/app/services/collection_resources_service.rb b/app/services/collection_resources_service.rb
new file mode 100644
index 000000000..50e1fc59a
--- /dev/null
+++ b/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
diff --git a/app/services/feature_toggle.rb b/app/services/feature_toggle.rb
index 91989ff86..f63eceaef 100644
--- a/app/services/feature_toggle.rb
+++ b/app/services/feature_toggle.rb
@@ -34,4 +34,8 @@ class FeatureToggle
def self.delete_user_enabled?
true
end
+
+ def self.local_storage?
+ Rails.env.development?
+ end
end
diff --git a/app/services/storage/local_disk_service.rb b/app/services/storage/local_disk_service.rb
index f0cc358d1..cd99d3d48 100644
--- a/app/services/storage/local_disk_service.rb
+++ b/app/services/storage/local_disk_service.rb
@@ -22,5 +22,14 @@ module Storage
f.write data
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
diff --git a/app/services/storage/s3_service.rb b/app/services/storage/s3_service.rb
index 3592eaa67..0cd4a1f20 100644
--- a/app/services/storage/s3_service.rb
+++ b/app/services/storage/s3_service.rb
@@ -39,6 +39,10 @@ module Storage
)
end
+ def get_file_metadata(file_name)
+ @client.head_object(bucket: @configuration.bucket_name, key: file_name)
+ end
+
private
def create_configuration
diff --git a/app/views/organisations/duplicate_schemes.html.erb b/app/views/organisations/duplicate_schemes.html.erb
new file mode 100644
index 000000000..427cf427c
--- /dev/null
+++ b/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 %>
+
+
+
+
<%= title %>
+
+
Since your organisation recently merged, we’ve reviewed your schemes for possible duplicates.
+
These sets of schemes and locations might be duplicates because they have the same answers for certain fields.
+
What you need to do
+
+ - Review each set of schemes or locations and decide if they are duplicates.
+ - If they are, choose one to keep and deactivate the others on the date your organisation merged.
+ - When you have resolved all duplicates, confirm below.
+
+
If you need help with this, <%= govuk_link_to "contact the helpdesk (opens in a new tab)", GlobalConstants::HELPDESK_URL, target: "#" %>.
+
+ <% if @duplicate_schemes.any? %>
+
<%= @duplicate_schemes.count == 1 ? "This set" : "These #{@duplicate_schemes.count} sets" %> of schemes might have duplicates
+
+ <%= govuk_details(summary_text: "Why are these schemes identified as duplicates?") do %>
+
+ These schemes have the same answers for the following fields:
+
+
+ - Type of scheme
+ - Registered under Care Standards Act 2000
+ - Housing stock owned by
+ - Support services provided by
+ - Primary client group
+ - Has another client group
+ - Secondary client group
+ - Level of support given
+ - Intended length of stay
+
+ <% 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 %>
+
+ <% duplicate_set.each do |scheme| %>
+ -
+ <%= govuk_link_to scheme.service_name, scheme %>
+
+ <% end %>
+
+ <% end %>
+ <% end %>
+ <% end %>
+ <% end %>
+ <% end %>
+ <% end %>
+ <% end %>
+
+ <% if @duplicate_locations.any? %>
+
<%= @duplicate_locations.count == 1 ? "This set" : "These #{@duplicate_locations.count} sets" %> of locations might have duplicates
+ <%= govuk_details(summary_text: "Why are these locations identified as duplicates?") do %>
+
+ These locations belong to the same scheme and have the same answers for the following fields:
+
+
+ - Postcode
+ - Mobility standards
+
+ <% 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 %>
+
+ <% duplicate_set[:locations].each do |location| %>
+ -
+ <%= govuk_link_to location.name, scheme_location_path(location) %>
+
+ <% end %>
+
+ <% 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" %>
+
+
+<% end %>
diff --git a/app/views/organisations/schemes.html.erb b/app/views/organisations/schemes.html.erb
index 58b16243a..b9706d4db 100644
--- a/app/views/organisations/schemes.html.erb
+++ b/app/views/organisations/schemes.html.erb
@@ -11,6 +11,16 @@
) %>
Supported housing schemes
<% end %>
+
+<% if display_duplicate_schemes_banner?(@organisation, current_user) %>
+ <%= govuk_notification_banner(title_text: "Important") do %>
+
+ Some schemes and locations might be duplicates.
+
+ <%= govuk_link_to "Review possible duplicates", href: schemes_duplicates_organisation_path(@organisation) %>
+ <% end %>
+<% end %>
+
<% if SchemePolicy.new(current_user, nil).create? %>
<%= govuk_button_link_to "Create a new supported housing scheme", new_scheme_path, html: { method: :post } %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 50a821ce0..e3ef39517 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -37,6 +37,7 @@ en:
updated: "Organisation details updated."
reactivated: "%{organisation} has been reactivated."
deactivated: "%{organisation} has been deactivated."
+ duplicate_schemes_confirmed: "You’ve confirmed the remaining schemes and locations are not duplicates."
user:
create_password: "Create a password to finish setting up your account."
reset_password: "Reset your password."
@@ -229,6 +230,7 @@ en:
blank: "You must choose a managing agent."
already_added: "You have already added this managing agent."
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}"
invalid_option: "Enter a valid value for %{question}"
invalid_number: "Enter a number for %{question}"
diff --git a/config/routes.rb b/config/routes.rb
index 77b862e5a..ed6f47bbd 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -178,6 +178,8 @@ Rails.application.routes.draw do
get "schemes/csv-download", to: "organisations#download_schemes_csv"
post "schemes/email-csv", to: "organisations#email_schemes_csv"
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/add", to: "organisation_relationships#add_stock_owner"
get "stock-owners/remove", to: "organisation_relationships#remove_stock_owner"
diff --git a/db/migrate/20240920144611_add_schemes_deduplicated_at.rb b/db/migrate/20240920144611_add_schemes_deduplicated_at.rb
new file mode 100644
index 000000000..02c1a6e05
--- /dev/null
+++ b/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
diff --git a/db/schema.rb b/db/schema.rb
index ff1f913df..174ae0199 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -514,6 +514,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_23_145326) do
t.bigint "absorbing_organisation_id"
t.datetime "available_from"
t.datetime "discarded_at"
+ t.datetime "schemes_deduplicated_at"
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
end
diff --git a/lib/tasks/recalculate_status_when_la_missing.rake b/lib/tasks/recalculate_status_when_la_missing.rake
index 16af29cc7..1e304c6fa 100644
--- a/lib/tasks/recalculate_status_when_la_missing.rake
+++ b/lib/tasks/recalculate_status_when_la_missing.rake
@@ -17,3 +17,22 @@ task recalculate_status_missing_la: :environment do
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
diff --git a/public/files/2023_24_lettings_paper_form.pdf b/public/files/2023_24_lettings_paper_form.pdf
deleted file mode 100644
index 9db94cc83..000000000
Binary files a/public/files/2023_24_lettings_paper_form.pdf and /dev/null differ
diff --git a/public/files/2023_24_sales_paper_form.pdf b/public/files/2023_24_sales_paper_form.pdf
deleted file mode 100644
index 1ad25cda0..000000000
Binary files a/public/files/2023_24_sales_paper_form.pdf and /dev/null differ
diff --git a/public/files/2024_25_lettings_paper_form.pdf b/public/files/2024_25_lettings_paper_form.pdf
deleted file mode 100644
index 910b28a19..000000000
Binary files a/public/files/2024_25_lettings_paper_form.pdf and /dev/null differ
diff --git a/public/files/2024_25_sales_paper_form.pdf b/public/files/2024_25_sales_paper_form.pdf
deleted file mode 100644
index 203757d9a..000000000
Binary files a/public/files/2024_25_sales_paper_form.pdf and /dev/null differ
diff --git a/public/files/bulk-upload-lettings-legacy-template-2023-24.xlsx b/public/files/bulk-upload-lettings-legacy-template-2023-24.xlsx
deleted file mode 100644
index 78050bd22..000000000
Binary files a/public/files/bulk-upload-lettings-legacy-template-2023-24.xlsx and /dev/null differ
diff --git a/public/files/bulk-upload-lettings-specification-2023-24.xlsx b/public/files/bulk-upload-lettings-specification-2023-24.xlsx
deleted file mode 100644
index e7e56b6e3..000000000
Binary files a/public/files/bulk-upload-lettings-specification-2023-24.xlsx and /dev/null differ
diff --git a/public/files/bulk-upload-lettings-specification-2024-25.xlsx b/public/files/bulk-upload-lettings-specification-2024-25.xlsx
deleted file mode 100644
index 577239804..000000000
Binary files a/public/files/bulk-upload-lettings-specification-2024-25.xlsx and /dev/null differ
diff --git a/public/files/bulk-upload-lettings-template-2023-24.xlsx b/public/files/bulk-upload-lettings-template-2023-24.xlsx
deleted file mode 100644
index a0c46b73a..000000000
Binary files a/public/files/bulk-upload-lettings-template-2023-24.xlsx and /dev/null differ
diff --git a/public/files/bulk-upload-lettings-template-2024-25.xlsx b/public/files/bulk-upload-lettings-template-2024-25.xlsx
deleted file mode 100644
index d0e0bfecd..000000000
Binary files a/public/files/bulk-upload-lettings-template-2024-25.xlsx and /dev/null differ
diff --git a/public/files/bulk-upload-sales-legacy-template-2023-24.xlsx b/public/files/bulk-upload-sales-legacy-template-2023-24.xlsx
deleted file mode 100644
index d52e722e5..000000000
Binary files a/public/files/bulk-upload-sales-legacy-template-2023-24.xlsx and /dev/null differ
diff --git a/public/files/bulk-upload-sales-specification-2023-24.xlsx b/public/files/bulk-upload-sales-specification-2023-24.xlsx
deleted file mode 100644
index 9d1d4be62..000000000
Binary files a/public/files/bulk-upload-sales-specification-2023-24.xlsx and /dev/null differ
diff --git a/public/files/bulk-upload-sales-specification-2024-25.xlsx b/public/files/bulk-upload-sales-specification-2024-25.xlsx
deleted file mode 100644
index 5eab435c3..000000000
Binary files a/public/files/bulk-upload-sales-specification-2024-25.xlsx and /dev/null differ
diff --git a/public/files/bulk-upload-sales-template-2023-24.xlsx b/public/files/bulk-upload-sales-template-2023-24.xlsx
deleted file mode 100644
index 773054750..000000000
Binary files a/public/files/bulk-upload-sales-template-2023-24.xlsx and /dev/null differ
diff --git a/public/files/bulk-upload-sales-template-2024-25.xlsx b/public/files/bulk-upload-sales-template-2024-25.xlsx
deleted file mode 100644
index f63878aa5..000000000
Binary files a/public/files/bulk-upload-sales-template-2024-25.xlsx and /dev/null differ
diff --git a/spec/features/accessibility_spec.rb b/spec/features/accessibility_spec.rb
index 97f632d92..2a95b684e 100644
--- a/spec/features/accessibility_spec.rb
+++ b/spec/features/accessibility_spec.rb
@@ -3,6 +3,7 @@ require "rails_helper"
RSpec.describe "Accessibility", js: true do
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(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
def find_routes(type, resource, subresource)
routes = Rails.application.routes.routes.select do |route|
@@ -20,6 +21,8 @@ RSpec.describe "Accessibility", js: true do
end
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(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in(user)
end
diff --git a/spec/features/lettings_log_spec.rb b/spec/features/lettings_log_spec.rb
index ac9a1e4a8..ea250529d 100644
--- a/spec/features/lettings_log_spec.rb
+++ b/spec/features/lettings_log_spec.rb
@@ -1,6 +1,13 @@
require "rails_helper"
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 I am signed in and there are logs in the database" do
let(:user) { create(:user, last_sign_in_at: Time.zone.now) }
diff --git a/spec/features/notifications_spec.rb b/spec/features/notifications_spec.rb
index e2bd4b151..a542cc97f 100644
--- a/spec/features/notifications_spec.rb
+++ b/spec/features/notifications_spec.rb
@@ -3,6 +3,12 @@ require_relative "form/helpers"
RSpec.describe "Notifications Features" do
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
let!(:user) { FactoryBot.create(:user) }
diff --git a/spec/features/organisation_spec.rb b/spec/features/organisation_spec.rb
index 3d65cda87..98ae86475 100644
--- a/spec/features/organisation_spec.rb
+++ b/spec/features/organisation_spec.rb
@@ -10,8 +10,11 @@ RSpec.describe "User Features" do
let(:notify_client) { instance_double(Notifications::Client) }
let(:confirmation_token) { "MCDH5y6Km-U7CFPgAMVS" }
let(:devise_notify_mailer) { DeviseNotifyMailer.new }
+ 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"))
allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer)
allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client)
allow(Devise).to receive(:friendly_token).and_return(confirmation_token)
diff --git a/spec/features/start_page_spec.rb b/spec/features/start_page_spec.rb
index ab09fd446..555db9238 100644
--- a/spec/features/start_page_spec.rb
+++ b/spec/features/start_page_spec.rb
@@ -4,6 +4,12 @@ require_relative "form/helpers"
RSpec.describe "Start Page Features" do
include Helpers
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
before do
diff --git a/spec/features/test_spec.rb b/spec/features/test_spec.rb
index 6dc977a9b..a58867182 100644
--- a/spec/features/test_spec.rb
+++ b/spec/features/test_spec.rb
@@ -1,5 +1,12 @@
require "rails_helper"
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
visit(root_path)
expect(page).to have_content("Submit social housing lettings and sales data (CORE)")
diff --git a/spec/features/user_spec.rb b/spec/features/user_spec.rb
index 119dbfa52..bc562824c 100644
--- a/spec/features/user_spec.rb
+++ b/spec/features/user_spec.rb
@@ -6,12 +6,15 @@ RSpec.describe "User Features" do
let(:notify_client) { instance_double(Notifications::Client) }
let(:reset_password_token) { "MCDH5y6Km-U7CFPgAMVS" }
let(:devise_notify_mailer) { DeviseNotifyMailer.new }
+ let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do
allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer)
allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client)
allow(notify_client).to receive(:send_email).and_return(true)
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
context "when the user navigates to lettings logs" do
diff --git a/spec/helpers/collection_resources_helper_spec.rb b/spec/helpers/collection_resources_helper_spec.rb
index c028177ce..4d39a0c2d 100644
--- a/spec/helpers/collection_resources_helper_spec.rb
+++ b/spec/helpers/collection_resources_helper_spec.rb
@@ -3,15 +3,29 @@ require "rails_helper"
RSpec.describe CollectionResourcesHelper do
let(:current_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
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
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
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
expect(file_type_size_and_pages("bulk-upload-lettings-template-2023-24.xlsx")).to eq("Microsoft Excel, 19 KB")
end
diff --git a/spec/helpers/schemes_helper_spec.rb b/spec/helpers/schemes_helper_spec.rb
index 0df032c3f..8ffde636a 100644
--- a/spec/helpers/schemes_helper_spec.rb
+++ b/spec/helpers/schemes_helper_spec.rb
@@ -118,4 +118,121 @@ RSpec.describe SchemesHelper do
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
diff --git a/spec/requests/auth/passwords_controller_spec.rb b/spec/requests/auth/passwords_controller_spec.rb
index 333985d9e..7f9bdfa30 100644
--- a/spec/requests/auth/passwords_controller_spec.rb
+++ b/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(:notify_client) { instance_double(Notifications::Client) }
let(:devise_notify_mailer) { DeviseNotifyMailer.new }
+ let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do
allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer)
allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client)
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
context "when a regular user" do
diff --git a/spec/requests/maintenance_controller_spec.rb b/spec/requests/maintenance_controller_spec.rb
index 39e587302..28287dbc0 100644
--- a/spec/requests/maintenance_controller_spec.rb
+++ b/spec/requests/maintenance_controller_spec.rb
@@ -3,8 +3,11 @@ require "rails_helper"
RSpec.describe MaintenanceController, type: :request do
let(:page) { Capybara::Node::Simple.new(response.body) }
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"))
sign_in user
end
diff --git a/spec/requests/organisations_controller_spec.rb b/spec/requests/organisations_controller_spec.rb
index 91dc07094..223cc9e00 100644
--- a/spec/requests/organisations_controller_spec.rb
+++ b/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")
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
context "when data coordinator user" do
@@ -348,6 +366,77 @@ RSpec.describe OrganisationsController, type: :request do
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
context "with an organisation that the user belongs to" do
let(:set_time) {}
@@ -2263,4 +2352,65 @@ RSpec.describe OrganisationsController, type: :request do
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
diff --git a/spec/requests/rails_admin_controller_spec.rb b/spec/requests/rails_admin_controller_spec.rb
index 4d0bbb7c2..79f56d18f 100644
--- a/spec/requests/rails_admin_controller_spec.rb
+++ b/spec/requests/rails_admin_controller_spec.rb
@@ -4,9 +4,12 @@ RSpec.describe "RailsAdmin", type: :request do
let(:user) { create(:user) }
let(:support_user) { create(:user, :support) }
let(:page) { Capybara::Node::Simple.new(response.body) }
+ let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do
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
describe "GET /admin" do
diff --git a/spec/requests/start_controller_spec.rb b/spec/requests/start_controller_spec.rb
index 699bdfa9c..40c45a9f9 100644
--- a/spec/requests/start_controller_spec.rb
+++ b/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(:notify_client) { instance_double(Notifications::Client) }
let(:devise_notify_mailer) { DeviseNotifyMailer.new }
+ let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do
allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer)
allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client)
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
describe "GET" do
diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb
index 1ab5aa9d4..05dc06d5b 100644
--- a/spec/requests/users_controller_spec.rb
+++ b/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(:notify_client) { instance_double(Notifications::Client) }
let(:devise_notify_mailer) { DeviseNotifyMailer.new }
+ let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do
allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer)
allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client)
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
context "when user is not signed in" do