diff --git a/app/controllers/organisations_controller.rb b/app/controllers/organisations_controller.rb index 8ca6d5bb8..7cf191189 100644 --- a/app/controllers/organisations_controller.rb +++ b/app/controllers/organisations_controller.rb @@ -5,9 +5,9 @@ class OrganisationsController < ApplicationController before_action :authenticate_user! before_action :find_resource, except: %i[index new create] before_action :authenticate_scope!, except: [:index] - before_action :session_filters, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_lettings_csv download_lettings_csv email_sales_csv download_sales_csv] + before_action :session_filters, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_lettings_csv download_lettings_csv email_sales_csv download_sales_csv schemes] before_action :session_filters, only: %i[users] - before_action -> { filter_manager.serialize_filters_to_session }, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_lettings_csv download_lettings_csv email_sales_csv download_sales_csv] + before_action -> { filter_manager.serialize_filters_to_session }, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_lettings_csv download_lettings_csv email_sales_csv download_sales_csv schemes] before_action -> { filter_manager.serialize_filters_to_session }, only: %i[users] def index @@ -22,9 +22,10 @@ class OrganisationsController < ApplicationController def schemes all_schemes = Scheme.where(owning_organisation: [@organisation] + @organisation.parent_organisations).order_by_completion.order_by_service_name - @pagy, @schemes = pagy(filtered_collection(all_schemes, search_term)) + @pagy, @schemes = pagy(filter_manager.filtered_schemes(all_schemes, search_term, session_filters)) @searched = search_term.presence @total_count = all_schemes.size + @filter_type = "schemes" end def show @@ -209,6 +210,8 @@ private "sales_logs" elsif params[:action].include?("users") "users" + elsif params[:action].include?("schemes") + "schemes" end end diff --git a/app/controllers/schemes_controller.rb b/app/controllers/schemes_controller.rb index 6dc24ddcd..a433832b5 100644 --- a/app/controllers/schemes_controller.rb +++ b/app/controllers/schemes_controller.rb @@ -6,6 +6,8 @@ class SchemesController < ApplicationController before_action :find_resource, except: %i[index create new] before_action :redirect_if_scheme_confirmed, only: %i[primary_client_group confirm_secondary_client_group secondary_client_group support details] before_action :authorize_user + before_action :session_filters, if: :current_user, only: %i[index] + before_action -> { filter_manager.serialize_filters_to_session }, if: :current_user, only: %i[index] rescue_from ActiveRecord::RecordNotFound, with: :render_not_found @@ -13,9 +15,10 @@ class SchemesController < ApplicationController redirect_to schemes_organisation_path(current_user.organisation) unless current_user.support? all_schemes = Scheme.order_by_completion.order_by_service_name - @pagy, @schemes = pagy(filtered_collection(all_schemes, search_term)) + @pagy, @schemes = pagy(filter_manager.filtered_schemes(all_schemes, search_term, session_filters)) @searched = search_term.presence @total_count = all_schemes.size + @filter_type = "schemes" end def show @@ -336,4 +339,12 @@ private logs.update!(location: nil, scheme: nil, unresolved: true) logs end + + def filter_manager + FilterManager.new(current_user:, session:, params:, filter_type: "schemes") + end + + def session_filters + filter_manager.session_filters + end end diff --git a/app/models/scheme.rb b/app/models/scheme.rb index fdb5cf394..385263cfb 100644 --- a/app/models/scheme.rb +++ b/app/models/scheme.rb @@ -19,6 +19,52 @@ class Scheme < ApplicationRecord scope :order_by_completion, -> { order("confirmed ASC NULLS FIRST") } scope :order_by_service_name, -> { order(service_name: :asc) } + scope :filter_by_status, lambda { |statuses, _user = nil| + filtered_records = all + scopes = [] + + statuses.each do |status| + status = status == "active" ? "active_status" : status + if respond_to?(status, true) + scopes << send(status) + end + end + + if scopes.any? + filtered_records = filtered_records + .left_outer_joins(:scheme_deactivation_periods) + .order("scheme_deactivation_periods.created_at DESC") + .merge(scopes.reduce(&:or)) + end + + filtered_records + } + + scope :incomplete, lambda { + where.not(confirmed: true) + } + + scope :deactivated, lambda { + merge(SchemeDeactivationPeriod.deactivations_without_reactivation) + .where("scheme_deactivation_periods.deactivation_date <= ?", Time.zone.now) + } + + scope :deactivating_soon, lambda { + merge(SchemeDeactivationPeriod.deactivations_without_reactivation) + .where("scheme_deactivation_periods.deactivation_date > ?", Time.zone.now) + } + + scope :reactivating_soon, lambda { + where.not("scheme_deactivation_periods.reactivation_date IS NULL") + .where("scheme_deactivation_periods.reactivation_date > ?", Time.zone.now) + } + + scope :active_status, lambda { + where.not(id: joins(:scheme_deactivation_periods).reactivating_soon.pluck(:id)) + .where.not(id: joins(:scheme_deactivation_periods).deactivated.pluck(:id)) + .where.not(id: incomplete.pluck(:id)) + .where.not(id: joins(:scheme_deactivation_periods).deactivating_soon.pluck(:id)) + } validate :validate_confirmed validate :validate_owning_organisation diff --git a/app/services/filter_manager.rb b/app/services/filter_manager.rb index 9de3fcdbb..f9ae7fe26 100644 --- a/app/services/filter_manager.rb +++ b/app/services/filter_manager.rb @@ -50,6 +50,17 @@ class FilterManager users end + def self.filter_schemes(schemes, search_term, filters, user) + schemes = filter_by_search(schemes, search_term) + + filters.each do |category, values| + next if Array(values).reject(&:empty?).blank? + + schemes = schemes.public_send("filter_by_#{category}", values, user) + end + schemes.order(created_at: :desc) + end + def serialize_filters_to_session(specific_org: false) session[session_name_for(filter_type)] = session_filters(specific_org:).to_json end @@ -73,7 +84,7 @@ class FilterManager new_filters["user"] = current_user.id.to_s if params["assigned_to"] == "you" end - if @filter_type.include?("users") && params["status"].present? + if (@filter_type.include?("schemes") || @filter_type.include?("users")) && params["status"].present? new_filters["status"] = params["status"] end @@ -90,6 +101,10 @@ class FilterManager FilterManager.filter_users(users, search_term, filters, current_user) end + def filtered_schemes(schemes, search_term, filters) + FilterManager.filter_schemes(schemes, search_term, filters, current_user) + end + def bulk_upload id = (logs_filters["bulk_upload_id"] || []).reject(&:blank?)[0] @bulk_upload ||= current_user.bulk_uploads.find_by(id:) diff --git a/spec/factories/scheme.rb b/spec/factories/scheme.rb index 155dea11a..5441df32e 100644 --- a/spec/factories/scheme.rb +++ b/spec/factories/scheme.rb @@ -25,5 +25,9 @@ FactoryBot.define do trait :with_old_visible_id do old_visible_id { rand(9_999_999) } end + trait :incomplete do + confirmed { false } + support_type { nil } + end end end diff --git a/spec/models/scheme_spec.rb b/spec/models/scheme_spec.rb index e7b50e5c8..2fb3ee0d0 100644 --- a/spec/models/scheme_spec.rb +++ b/spec/models/scheme_spec.rb @@ -89,6 +89,74 @@ RSpec.describe Scheme, type: :model do expect(described_class.search_by(location.name.downcase).first.locations.first.name).to eq(location.name) end end + + context "when filtering by status" do + let!(:incomplete_scheme) { FactoryBot.create(:scheme, :incomplete) } + let!(:active_scheme) { FactoryBot.create(:scheme) } + let(:deactivating_soon_scheme) { FactoryBot.create(:scheme) } + let(:deactivated_scheme) { FactoryBot.create(:scheme) } + let(:reactivating_soon_scheme) { FactoryBot.create(:scheme) } + + before do + scheme.destroy! + scheme_1.destroy! + scheme_2.destroy! + Timecop.freeze(2022, 6, 7) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 8, 8), scheme: deactivating_soon_scheme) + deactivating_soon_scheme.save! + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 6), scheme: deactivated_scheme) + deactivated_scheme.save! + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 7), reactivation_date: Time.zone.local(2022, 6, 8), scheme: reactivating_soon_scheme) + reactivating_soon_scheme.save! + end + + after do + Timecop.unfreeze + end + + context "when filtering by incomplete status" do + it "returns only incomplete schemes" do + expect(described_class.filter_by_status(%w[incomplete]).count).to eq(1) + expect(described_class.filter_by_status(%w[incomplete]).first).to eq(incomplete_scheme) + end + end + + context "when filtering by active status" do + it "returns only active schemes" do + expect(described_class.filter_by_status(%w[active]).count).to eq(1) + expect(described_class.filter_by_status(%w[active]).first).to eq(active_scheme) + end + end + + context "when filtering by deactivating_soon status" do + it "returns only deactivating_soon schemes" do + expect(described_class.filter_by_status(%w[deactivating_soon]).count).to eq(1) + expect(described_class.filter_by_status(%w[deactivating_soon]).first).to eq(deactivating_soon_scheme) + end + end + + context "when filtering by deactivated status" do + it "returns only deactivated schemes" do + expect(described_class.filter_by_status(%w[deactivated]).count).to eq(1) + expect(described_class.filter_by_status(%w[deactivated]).first).to eq(deactivated_scheme) + end + end + + context "when filtering by reactivating_soon status" do + it "returns only reactivating_soon schemes" do + expect(described_class.filter_by_status(%w[reactivating_soon]).count).to eq(1) + expect(described_class.filter_by_status(%w[reactivating_soon]).first).to eq(reactivating_soon_scheme) + end + end + + context "when filtering by multiple statuses" do + it "returns relevant schemes" do + expect(described_class.filter_by_status(%w[deactivating_soon reactivating_soon]).count).to eq(2) + expect(described_class.filter_by_status(%w[deactivating_soon reactivating_soon])).to include(reactivating_soon_scheme) + expect(described_class.filter_by_status(%w[deactivating_soon reactivating_soon])).to include(deactivating_soon_scheme) + end + end + end end end diff --git a/spec/requests/schemes_controller_spec.rb b/spec/requests/schemes_controller_spec.rb index 3fe859010..eddf68427 100644 --- a/spec/requests/schemes_controller_spec.rb +++ b/spec/requests/schemes_controller_spec.rb @@ -75,6 +75,64 @@ RSpec.describe SchemesController, type: :request do end end end + + context "when filtering" do + context "with status filter" do + let!(:incomplete_scheme) { create(:scheme, :incomplete, owning_organisation: user.organisation) } + let!(:active_scheme) { create(:scheme, owning_organisation: user.organisation) } + let!(:deactivated_scheme) { create(:scheme, owning_organisation: user.organisation) } + + before do + create(:scheme_deactivation_period, scheme: deactivated_scheme, deactivation_date: Time.zone.local(2022, 4, 1)) + end + + it "shows schemes for multiple selected statuses" do + get "/schemes?status[]=incomplete&status[]=active", headers:, params: {} + follow_redirect! + expect(page).to have_link(incomplete_scheme.service_name) + expect(page).to have_link(active_scheme.service_name) + expect(page).not_to have_link(deactivated_scheme.service_name) + end + + it "shows filtered incomplete schemes" do + get "/schemes?status[]=incomplete", headers:, params: {} + follow_redirect! + expect(page).to have_link(incomplete_scheme.service_name) + expect(page).not_to have_link(active_scheme.service_name) + expect(page).not_to have_link(deactivated_scheme.service_name) + end + + it "shows filtered active schemes" do + get "/schemes?status[]=active", headers:, params: {} + follow_redirect! + expect(page).to have_link(active_scheme.service_name) + expect(page).not_to have_link(incomplete_scheme.service_name) + expect(page).not_to have_link(deactivated_scheme.service_name) + end + + it "shows filtered deactivated schemes" do + get "/schemes?status[]=deactivated", headers:, params: {} + follow_redirect! + expect(page).to have_link(deactivated_scheme.service_name) + expect(page).not_to have_link(active_scheme.service_name) + expect(page).not_to have_link(incomplete_scheme.service_name) + end + + it "does not reset the filters" do + get "/schemes?status[]=incomplete", headers:, params: {} + follow_redirect! + expect(page).to have_link(incomplete_scheme.service_name) + expect(page).not_to have_link(active_scheme.service_name) + expect(page).not_to have_link(deactivated_scheme.service_name) + + get "/schemes", headers:, params: {} + follow_redirect! + expect(page).to have_link(incomplete_scheme.service_name) + expect(page).not_to have_link(active_scheme.service_name) + expect(page).not_to have_link(deactivated_scheme.service_name) + end + end + end end context "when signed in as a support user" do @@ -221,6 +279,58 @@ RSpec.describe SchemesController, type: :request do expect(page).to have_title("Supported housing schemes (1 scheme matching ‘#{search_param}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") end end + + context "when filtering" do + context "with status filter" do + let!(:incomplete_scheme) { create(:scheme, :incomplete) } + let!(:active_scheme) { create(:scheme) } + let!(:deactivated_scheme) { create(:scheme) } + + before do + create(:scheme_deactivation_period, scheme: deactivated_scheme, deactivation_date: Time.zone.local(2022, 4, 1)) + end + + it "shows schemes for multiple selected statuses" do + get "/schemes?status[]=incomplete&status[]=active", headers:, params: {} + expect(page).to have_link(incomplete_scheme.service_name) + expect(page).to have_link(active_scheme.service_name) + expect(page).not_to have_link(deactivated_scheme.service_name) + end + + it "shows filtered incomplete schemes" do + get "/schemes?status[]=incomplete", headers:, params: {} + expect(page).to have_link(incomplete_scheme.service_name) + expect(page).not_to have_link(active_scheme.service_name) + expect(page).not_to have_link(deactivated_scheme.service_name) + end + + it "shows filtered active schemes" do + get "/schemes?status[]=active", headers:, params: {} + expect(page).to have_link(active_scheme.service_name) + expect(page).not_to have_link(incomplete_scheme.service_name) + expect(page).not_to have_link(deactivated_scheme.service_name) + end + + it "shows filtered deactivated schemes" do + get "/schemes?status[]=deactivated", headers:, params: {} + expect(page).to have_link(deactivated_scheme.service_name) + expect(page).not_to have_link(active_scheme.service_name) + expect(page).not_to have_link(incomplete_scheme.service_name) + end + + it "does not reset the filters" do + get "/schemes?status[]=incomplete", headers:, params: {} + expect(page).to have_link(incomplete_scheme.service_name) + expect(page).not_to have_link(active_scheme.service_name) + expect(page).not_to have_link(deactivated_scheme.service_name) + + get "/schemes", headers:, params: {} + expect(page).to have_link(incomplete_scheme.service_name) + expect(page).not_to have_link(active_scheme.service_name) + expect(page).not_to have_link(deactivated_scheme.service_name) + end + end + end end end