From d2518d7c383eb564be806172add7edd04839f168 Mon Sep 17 00:00:00 2001 From: Robert Sullivan Date: Thu, 11 Apr 2024 13:43:54 +0100 Subject: [PATCH] CLDC-2481: Add ability to disable organisations (#2293) * CLDC-2481: Add ability to disable organisations. * CLDC-2481: Add ability to disable organisations * Tidy up tests * CLDC-2481: Review markups * CLDC-2481: Fix location and scheme filters * Add banners for stock owners and managing agents * Update view test --------- Co-authored-by: Kat --- .../organisation_relationships_controller.rb | 10 +- app/controllers/organisations_controller.rb | 33 ++++- app/controllers/users_controller.rb | 3 +- .../toggle_active_organisation_helper.rb | 13 ++ .../questions/managing_organisation.rb | 6 +- .../form/lettings/questions/stock_owner.rb | 4 +- .../sales/questions/managing_organisation.rb | 6 +- .../sales/questions/owning_organisation_id.rb | 4 +- app/models/location.rb | 22 +++- app/models/organisation.rb | 3 + app/models/scheme.rb | 25 +++- app/models/user.rb | 17 +++ app/policies/organisation_policy.rb | 16 +++ app/views/locations/show.html.erb | 2 +- .../managing_agents.html.erb | 18 ++- .../stock_owners.html.erb | 18 ++- app/views/organisations/show.html.erb | 9 ++ .../organisations/toggle_active.html.erb | 29 +++++ app/views/schemes/show.html.erb | 2 +- app/views/users/new.html.erb | 2 +- app/views/users/show.html.erb | 4 +- config/locales/en.yml | 5 + config/routes.rb | 2 + ...d_reactivate_with_organisation_to_users.rb | 5 + ...ault_value_to_organisation_active_field.rb | 12 ++ db/schema.rb | 3 +- .../questions/managing_organisation_spec.rb | 15 ++- .../lettings/questions/stock_owner_spec.rb | 21 +-- .../questions/managing_organisation_spec.rb | 7 +- .../questions/owning_organisation_id_spec.rb | 20 +-- spec/models/location_spec.rb | 13 +- spec/models/organisation_spec.rb | 41 +++++- spec/models/scheme_spec.rb | 10 +- spec/policies/organisation_policy_spec.rb | 66 ++++++++++ ...anisation_relationships_controller_spec.rb | 60 +++++++++ .../requests/organisations_controller_spec.rb | 120 ++++++++++++++++++ spec/views/locations/show.html.erb_spec.rb | 113 +++++++++++------ .../views/organisations/show.html.erb_spec.rb | 14 ++ spec/views/schemes/show.html.erb_spec.rb | 34 ++++- 39 files changed, 699 insertions(+), 108 deletions(-) create mode 100644 app/helpers/toggle_active_organisation_helper.rb create mode 100644 app/policies/organisation_policy.rb create mode 100644 app/views/organisations/toggle_active.html.erb create mode 100644 db/migrate/20240304112411_add_reactivate_with_organisation_to_users.rb create mode 100644 db/migrate/20240305112507_add_default_value_to_organisation_active_field.rb create mode 100644 spec/policies/organisation_policy_spec.rb diff --git a/app/controllers/organisation_relationships_controller.rb b/app/controllers/organisation_relationships_controller.rb index 0ac66bd31..0e3d230ef 100644 --- a/app/controllers/organisation_relationships_controller.rb +++ b/app/controllers/organisation_relationships_controller.rb @@ -14,7 +14,7 @@ class OrganisationRelationshipsController < ApplicationController ] def stock_owners - stock_owners = organisation.stock_owners + stock_owners = organisation.stock_owners.filter_by_active unpaginated_filtered_stock_owners = filtered_collection(stock_owners, search_term) @pagy, @stock_owners = pagy(unpaginated_filtered_stock_owners) @@ -23,7 +23,7 @@ class OrganisationRelationshipsController < ApplicationController end def managing_agents - managing_agents = organisation.managing_agents + managing_agents = organisation.managing_agents.filter_by_active unpaginated_filtered_managing_agents = filtered_collection(managing_agents, search_term) @pagy, @managing_agents = pagy(unpaginated_filtered_managing_agents) @@ -48,7 +48,7 @@ class OrganisationRelationshipsController < ApplicationController flash[:notice] = "#{@organisation_relationship.parent_organisation.name} is now one of #{current_user.data_coordinator? ? 'your' : "this organisation's"} stock owners" redirect_to stock_owners_organisation_path else - @organisations = Organisation.where.not(id: organisation.id).pluck(:id, :name) + @organisations = Organisation.filter_by_active.where.not(id: organisation.id).pluck(:id, :name) render "organisation_relationships/add_stock_owner", status: :unprocessable_entity end end @@ -60,7 +60,7 @@ class OrganisationRelationshipsController < ApplicationController flash[:notice] = "#{@organisation_relationship.child_organisation.name} is now one of #{current_user.data_coordinator? ? 'your' : "this organisation's"} managing agents" redirect_to managing_agents_organisation_path else - @organisations = Organisation.where.not(id: organisation.id).pluck(:id, :name) + @organisations = Organisation.filter_by_active.where.not(id: organisation.id).pluck(:id, :name) render "organisation_relationships/add_managing_agent", status: :unprocessable_entity end end @@ -110,7 +110,7 @@ private end def organisations - @organisations ||= Organisation.where.not(id: organisation.id).pluck(:id, :name) + @organisations ||= Organisation.filter_by_active.where.not(id: organisation.id).pluck(:id, :name) end def parent_organisation diff --git a/app/controllers/organisations_controller.rb b/app/controllers/organisations_controller.rb index 4982c4287..e7f5c4bef 100644 --- a/app/controllers/organisations_controller.rb +++ b/app/controllers/organisations_controller.rb @@ -94,10 +94,37 @@ class OrganisationsController < ApplicationController end end + def deactivate + authorize @organisation + + render "toggle_active", locals: { action: "deactivate" } + end + + def reactivate + authorize @organisation + + render "toggle_active", locals: { action: "reactivate" } + end + def update - if current_user.data_coordinator? || current_user.support? + if (current_user.data_coordinator? && org_params[:active].nil?) || current_user.support? if @organisation.update(org_params) - flash[:notice] = I18n.t("organisation.updated") + case org_params[:active] + when "false" + @organisation.users.filter_by_active.each do |user| + user.deactivate!(reactivate_with_organisation: true) + end + flash[:notice] = I18n.t("organisation.deactivated", organisation: @organisation.name) + when "true" + users_to_reactivate = @organisation.users.where(reactivate_with_organisation: true) + users_to_reactivate.each do |user| + user.reactivate! + user.send_confirmation_instructions + end + flash[:notice] = I18n.t("organisation.reactivated", organisation: @organisation.name) + else + flash[:notice] = I18n.t("organisation.updated") + end redirect_to details_organisation_path(@organisation) end else @@ -239,7 +266,7 @@ private end def org_params - params.require(:organisation).permit(:name, :address_line1, :address_line2, :postcode, :phone, :holds_own_stock, :provider_type, :housing_registration_no) + params.require(:organisation).permit(:name, :address_line1, :address_line2, :postcode, :phone, :holds_own_stock, :provider_type, :housing_registration_no, :active) end def codes_only_export? diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 6a76cb047..3fe3b1813 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -62,9 +62,10 @@ class UsersController < ApplicationController else user_name = @user.name&.possessive || @user.email.possessive if user_params[:active] == "false" - @user.update!(confirmed_at: nil, sign_in_count: 0, initial_confirmation_sent: false) + @user.deactivate! flash[:notice] = I18n.t("devise.activation.deactivated", user_name:) elsif user_params[:active] == "true" + @user.reactivate! @user.send_confirmation_instructions flash[:notice] = I18n.t("devise.activation.reactivated", user_name:) elsif user_params.key?("email") diff --git a/app/helpers/toggle_active_organisation_helper.rb b/app/helpers/toggle_active_organisation_helper.rb new file mode 100644 index 000000000..10b9b0983 --- /dev/null +++ b/app/helpers/toggle_active_organisation_helper.rb @@ -0,0 +1,13 @@ +module ToggleActiveOrganisationHelper + def toggle_organisation_form_path(action, organisation) + if action == "deactivate" + organisation_new_deactivation_path(organisation) + else + organisation_reactivate_path(organisation) + end + end + + def date_type_question(action) + action == "deactivate" ? :deactivation_date_type : :reactivation_date_type + end +end diff --git a/app/models/form/lettings/questions/managing_organisation.rb b/app/models/form/lettings/questions/managing_organisation.rb index 51eccdaac..ea85ff207 100644 --- a/app/models/form/lettings/questions/managing_organisation.rb +++ b/app/models/form/lettings/questions/managing_organisation.rb @@ -31,11 +31,11 @@ class Form::Lettings::Questions::ManagingOrganisation < ::Form::Question end orgs = if user.support? - log.owning_organisation.managing_agents + log.owning_organisation.managing_agents.filter_by_active elsif user.organisation.absorbed_organisations.include?(log.owning_organisation) - user.organisation.managing_agents + log.owning_organisation.managing_agents + user.organisation.managing_agents.filter_by_active + log.owning_organisation.managing_agents.filter_by_active else - user.organisation.managing_agents + user.organisation.managing_agents.filter_by_active end user.organisation.absorbed_organisations.each do |absorbed_org| diff --git a/app/models/form/lettings/questions/stock_owner.rb b/app/models/form/lettings/questions/stock_owner.rb index 682c64247..6ce0e5496 100644 --- a/app/models/form/lettings/questions/stock_owner.rb +++ b/app/models/form/lettings/questions/stock_owner.rb @@ -30,7 +30,7 @@ class Form::Lettings::Questions::StockOwner < ::Form::Question end if user.support? - Organisation.where(holds_own_stock: true).find_each do |org| + Organisation.filter_by_active.where(holds_own_stock: true).find_each do |org| if org.merge_date.present? answer_opts[org.id] = "#{org.name} (inactive as of #{org.merge_date.to_fs(:govuk_date)})" if org.merge_date >= FormHandler.instance.start_date_of_earliest_open_for_editing_collection_period elsif org.absorbed_organisations.merged_during_open_collection_period.exists? && org.available_from.present? @@ -40,7 +40,7 @@ class Form::Lettings::Questions::StockOwner < ::Form::Question end end else - user.organisation.stock_owners.each do |stock_owner| + user.organisation.stock_owners.filter_by_active.each do |stock_owner| answer_opts[stock_owner.id] = stock_owner.name end recently_absorbed_organisations.each do |absorbed_org| diff --git a/app/models/form/sales/questions/managing_organisation.rb b/app/models/form/sales/questions/managing_organisation.rb index f98dd0100..54d7ab6e0 100644 --- a/app/models/form/sales/questions/managing_organisation.rb +++ b/app/models/form/sales/questions/managing_organisation.rb @@ -31,11 +31,11 @@ class Form::Sales::Questions::ManagingOrganisation < ::Form::Question end orgs = if user.support? - log.owning_organisation.managing_agents + log.owning_organisation.managing_agents.filter_by_active elsif user.organisation.absorbed_organisations.include?(log.owning_organisation) - user.organisation.managing_agents + log.owning_organisation.managing_agents + user.organisation.managing_agents.filter_by_active + log.owning_organisation.managing_agents.filter_by_active else - user.organisation.managing_agents + user.organisation.managing_agents.filter_by_active end.pluck(:id, :name).to_h user.organisation.absorbed_organisations.each do |absorbed_org| diff --git a/app/models/form/sales/questions/owning_organisation_id.rb b/app/models/form/sales/questions/owning_organisation_id.rb index 062fbcaaf..dc3913882 100644 --- a/app/models/form/sales/questions/owning_organisation_id.rb +++ b/app/models/form/sales/questions/owning_organisation_id.rb @@ -25,7 +25,7 @@ class Form::Sales::Questions::OwningOrganisationId < ::Form::Question answer_opts[user.organisation.id] = "#{user.organisation.name} (Your organisation)" end - user.organisation.stock_owners.where(holds_own_stock: true).find_each do |org| + user.organisation.stock_owners.filter_by_active.where(holds_own_stock: true).find_each do |org| answer_opts[org.id] = org.name end end @@ -44,7 +44,7 @@ class Form::Sales::Questions::OwningOrganisationId < ::Form::Question end if user.support? - Organisation.where(holds_own_stock: true).find_each do |org| + Organisation.filter_by_active.where(holds_own_stock: true).find_each do |org| if org.merge_date.present? answer_opts[org.id] = "#{org.name} (inactive as of #{org.merge_date.to_fs(:govuk_date)})" if org.merge_date >= FormHandler.instance.start_date_of_earliest_open_for_editing_collection_period elsif org.absorbed_organisations.merged_during_open_collection_period.exists? && org.available_from.present? diff --git a/app/models/location.rb b/app/models/location.rb index 43285bfbb..dfcbef41b 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -40,6 +40,7 @@ class Location < ApplicationRecord if scopes.any? filtered_records = filtered_records .left_outer_joins(:location_deactivation_periods) + .joins(scheme: [:owning_organisation]) .order("location_deactivation_periods.created_at DESC") .merge(scopes.reduce(&:or)) end @@ -53,27 +54,39 @@ class Location < ApplicationRecord } scope :deactivated, lambda { + deactivated_by_organisation + .or(deactivated_directly) + } + + scope :deactivated_by_organisation, lambda { + merge(Organisation.filter_by_inactive) + } + + scope :deactivated_directly, lambda { merge(LocationDeactivationPeriod.deactivations_without_reactivation) - .where("location_deactivation_periods.deactivation_date <= ?", Time.zone.now) + .where("location_deactivation_periods.deactivation_date <= ?", Time.zone.now) } scope :deactivating_soon, lambda { merge(LocationDeactivationPeriod.deactivations_without_reactivation) .where("location_deactivation_periods.deactivation_date > ?", Time.zone.now) + .where.not(id: joins(scheme: [:owning_organisation]).deactivated_by_organisation.pluck(:id)) } scope :reactivating_soon, lambda { where.not("location_deactivation_periods.reactivation_date IS NULL") .where("location_deactivation_periods.reactivation_date > ?", Time.zone.now) + .where.not(id: joins(scheme: [:owning_organisation]).deactivated_by_organisation.pluck(:id)) } scope :activating_soon, lambda { - where("startdate > ?", Time.zone.now) + where("locations.startdate > ?", Time.zone.now) } scope :active_status, lambda { where.not(id: joins(:location_deactivation_periods).reactivating_soon.pluck(:id)) - .where.not(id: joins(:location_deactivation_periods).deactivated.pluck(:id)) + .where.not(id: joins(scheme: [:owning_organisation]).deactivated_by_organisation.pluck(:id)) + .where.not(id: joins(:location_deactivation_periods).deactivated_directly.pluck(:id)) .where.not(id: incomplete.pluck(:id)) .where.not(id: joins(:location_deactivation_periods).deactivating_soon.pluck(:id)) .where.not(id: activating_soon.pluck(:id)) @@ -142,7 +155,8 @@ class Location < ApplicationRecord def status_at(date) return :deleted if discarded_at.present? return :incomplete unless confirmed - return :deactivated if open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date + return :deactivated if scheme.owning_organisation.status_at(date) == :deactivated || + open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date return :deactivating_soon if open_deactivation&.deactivation_date.present? && date < open_deactivation.deactivation_date return :reactivating_soon if last_deactivation_before(date)&.reactivation_date.present? && date < last_deactivation_before(date).reactivation_date return :activating_soon if startdate.present? && date < startdate diff --git a/app/models/organisation.rb b/app/models/organisation.rb index 0a852d2fd..b5c6f36aa 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -37,6 +37,8 @@ class Organisation < ApplicationRecord scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") } scope :search_by, ->(param) { search_by_name(param) } + scope :filter_by_active, -> { where(active: true) } + scope :filter_by_inactive, -> { where(active: false) } scope :merged_during_open_collection_period, -> { where("merge_date >= ?", FormHandler.instance.start_date_of_earliest_open_for_editing_collection_period) } has_paper_trail @@ -138,6 +140,7 @@ class Organisation < ApplicationRecord def status_at(date) return :merged if merge_date.present? && merge_date < date + return :deactivated unless active :active end diff --git a/app/models/scheme.rb b/app/models/scheme.rb index 2518293a0..5b95ed98a 100644 --- a/app/models/scheme.rb +++ b/app/models/scheme.rb @@ -35,6 +35,7 @@ class Scheme < ApplicationRecord if scopes.any? filtered_records = filtered_records .left_outer_joins(:scheme_deactivation_periods) + .joins(:owning_organisation) .merge(scopes.reduce(&:or)) end @@ -45,35 +46,48 @@ class Scheme < ApplicationRecord where.not(confirmed: true) .or(where(confirmed: nil)) .or(where.not(id: Location.select(:scheme_id).where(confirmed: true).distinct)) + .where.not(id: joins(:owning_organisation).deactivated_by_organisation.pluck(:id)) + .where.not(id: joins(:scheme_deactivation_periods).deactivated_directly.pluck(:id)) .where.not(id: joins(:scheme_deactivation_periods).reactivating_soon.pluck(:id)) - .where.not(id: joins(:scheme_deactivation_periods).deactivated.pluck(:id)) .where.not(id: joins(:scheme_deactivation_periods).deactivating_soon.pluck(:id)) } scope :deactivated, lambda { + deactivated_by_organisation + .or(deactivated_directly) + } + + scope :deactivated_by_organisation, lambda { + merge(Organisation.filter_by_inactive) + } + + scope :deactivated_directly, lambda { merge(SchemeDeactivationPeriod.deactivations_without_reactivation) - .where("scheme_deactivation_periods.deactivation_date <= ?", Time.zone.now) + .where("scheme_deactivation_periods.deactivation_date <= ?", Time.zone.now) } scope :deactivating_soon, lambda { merge(SchemeDeactivationPeriod.deactivations_without_reactivation) .where("scheme_deactivation_periods.deactivation_date > ? AND scheme_deactivation_periods.deactivation_date < ? ", Time.zone.now, 6.months.from_now) + .where.not(id: joins(:owning_organisation).deactivated_by_organisation.pluck(:id)) } scope :reactivating_soon, lambda { where.not("scheme_deactivation_periods.reactivation_date IS NULL") .where("scheme_deactivation_periods.reactivation_date > ?", Time.zone.now) + .where.not(id: joins(:owning_organisation).deactivated_by_organisation.pluck(:id)) } scope :activating_soon, lambda { - where("startdate > ?", Time.zone.now) + where("schemes.startdate > ?", 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)) + .where.not(id: joins(:owning_organisation).deactivated_by_organisation.pluck(:id)) + .where.not(id: joins(:owning_organisation).joins(:scheme_deactivation_periods).deactivated_directly.pluck(:id)) .where.not(id: activating_soon.pluck(:id)) } @@ -268,7 +282,8 @@ class Scheme < ApplicationRecord def status_at(date) return :deleted if discarded_at.present? return :incomplete unless confirmed && locations.confirmed.any? - return :deactivated if open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date + return :deactivated if owning_organisation.status_at(date) == :deactivated || + (open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date) return :deactivating_soon if open_deactivation&.deactivation_date.present? && date < open_deactivation.deactivation_date return :reactivating_soon if last_deactivation_before(date)&.reactivation_date.present? && date < last_deactivation_before(date).reactivation_date return :activating_soon if startdate.present? && date < startdate diff --git a/app/models/user.rb b/app/models/user.rb index 6d13e8cd1..27187529a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -134,6 +134,23 @@ class User < ApplicationRecord update!(is_dpo: true) end + def deactivate!(reactivate_with_organisation: false) + update!( + active: false, + confirmed_at: nil, + sign_in_count: 0, + initial_confirmation_sent: false, + reactivate_with_organisation:, + ) + end + + def reactivate! + update!( + active: true, + reactivate_with_organisation: false, + ) + end + MFA_TEMPLATE_ID = "6bdf5ee1-8e01-4be1-b1f9-747061d8a24c".freeze RESET_PASSWORD_TEMPLATE_ID = "2c410c19-80a7-481c-a531-2bcb3264f8e6".freeze CONFIRMABLE_TEMPLATE_ID = "3fc2e3a7-0835-4b84-ab7a-ce51629eb614".freeze diff --git a/app/policies/organisation_policy.rb b/app/policies/organisation_policy.rb new file mode 100644 index 000000000..3e0c9a946 --- /dev/null +++ b/app/policies/organisation_policy.rb @@ -0,0 +1,16 @@ +class OrganisationPolicy + attr_reader :user, :organisation + + def initialize(user, organisation) + @user = user + @organisation = organisation + end + + def deactivate? + user.support? && organisation.status == :active + end + + def reactivate? + user.support? && organisation.status == :deactivated + end +end diff --git a/app/views/locations/show.html.erb b/app/views/locations/show.html.erb index 86de3a362..8ac8f6b23 100644 --- a/app/views/locations/show.html.erb +++ b/app/views/locations/show.html.erb @@ -47,7 +47,7 @@ -<% if LocationPolicy.new(current_user, @location).deactivate? %> +<% if @location.scheme.owning_organisation.active? && LocationPolicy.new(current_user, @location).deactivate? %> <%= toggle_location_link(@location) %> <% end %> diff --git a/app/views/organisation_relationships/managing_agents.html.erb b/app/views/organisation_relationships/managing_agents.html.erb index a01a354d2..726533e53 100644 --- a/app/views/organisation_relationships/managing_agents.html.erb +++ b/app/views/organisation_relationships/managing_agents.html.erb @@ -5,19 +5,35 @@ <%= render SubNavigationComponent.new( items: secondary_items(request.path, @organisation.id), ) %> + <% if !@organisation.active? %> + <%= govuk_notification_banner(title_text: "Important") do %> +

+ This organisation is deactivated. +

+ You cannot add any new managing agents. + <% end %> + <% end %>

Managing Agents

A managing agent can submit logs for this organisation.

<% if @total_count == 0 %>

This organisation does not currently have any managing agents.

<% end %> <% else %> + <% if !@organisation.active? %> + <%= govuk_notification_banner(title_text: "Important") do %> +

+ This organisation is deactivated. +

+ You cannot add any new managing agents. + <% end %> + <% end %> <%= render partial: "organisations/headings", locals: { main: "Your managing agents", sub: current_user.organisation.name } %>

A managing agent can submit logs for this organisation.

<% if @total_count == 0 %>

This organisation does not currently have any managing agents.

<% end %> <% end %> -<% if current_user.support? || current_user.data_coordinator? %> +<% if (current_user.support? || current_user.data_coordinator?) && @organisation.active? %> <%= govuk_button_link_to "Add a managing agent", managing_agents_add_organisation_path, html: { method: :get } %> <% end %> <% if @total_count != 0 %> diff --git a/app/views/organisation_relationships/stock_owners.html.erb b/app/views/organisation_relationships/stock_owners.html.erb index 4be22f7b9..41b7af06d 100644 --- a/app/views/organisation_relationships/stock_owners.html.erb +++ b/app/views/organisation_relationships/stock_owners.html.erb @@ -2,19 +2,35 @@ <% if current_user.support? %> <%= render partial: "organisations/headings", locals: { main: @organisation.name, sub: nil } %> <%= render SubNavigationComponent.new(items: secondary_items(request.path, @organisation.id)) %> + <% if !@organisation.active? %> + <%= govuk_notification_banner(title_text: "Important") do %> +

+ This organisation is deactivated. +

+ You cannot add any new stock owners. + <% end %> + <% end %>

Stock Owners

This organisation can submit logs for its stock owners.

<% if @total_count == 0 %>

This organisation does not currently have any stock owners.

<% end %> <% else %> + <% if !@organisation.active? %> + <%= govuk_notification_banner(title_text: "Important") do %> +

+ This organisation is deactivated. +

+ You cannot add any new stock owners. + <% end %> + <% end %> <%= render partial: "organisations/headings", locals: { main: "Your stock owners", sub: current_user.organisation.name } %>

Your organisation can submit logs for its stock owners.

<% if @total_count == 0 %>

You do not currently have any stock owners.

<% end %> <% end %> -<% if current_user.support? || current_user.data_coordinator? %> +<% if (current_user.support? || current_user.data_coordinator?) && @organisation.active? %> <%= govuk_button_link_to "Add a stock owner", stock_owners_add_organisation_path, html: { method: :get } %> <% end %> <% if @total_count != 0 %> diff --git a/app/views/organisations/show.html.erb b/app/views/organisations/show.html.erb index e2e07e28c..2dced52bd 100644 --- a/app/views/organisations/show.html.erb +++ b/app/views/organisations/show.html.erb @@ -40,3 +40,12 @@ <%= render partial: "organisations/merged_organisation_details" %> + +<% if OrganisationPolicy.new(current_user, @organisation).deactivate? %> + <%= govuk_button_link_to "Deactivate this organisation", deactivate_organisation_path(@organisation), warning: true %> +<% end %> +<% if OrganisationPolicy.new(current_user, @organisation).reactivate? %> + + <%= govuk_button_link_to "Reactivate this organisation", reactivate_organisation_path(@organisation) %> + +<% end %> diff --git a/app/views/organisations/toggle_active.html.erb b/app/views/organisations/toggle_active.html.erb new file mode 100644 index 000000000..42f1e8403 --- /dev/null +++ b/app/views/organisations/toggle_active.html.erb @@ -0,0 +1,29 @@ +<% title = "#{action.humanize} #{@organisation.name}" %> +<% content_for :title, title %> + +<% content_for :before_content do %> + <%= govuk_back_link( + href: organisation_path(@organisation), + ) %> +<% end %> + +<%= form_for(@organisation, as: :organisation, html: { method: :patch }) do |f| %> +
+
+

+ <%= @organisation.name %> + Are you sure you want to <%= action %> this organisation? +

+ <%= govuk_warning_text text: I18n.t("warnings.organisation.#{action}") %> + + <% active_value = action != "deactivate" %> + <%= f.hidden_field :active, value: active_value %> + + <%= f.govuk_submit "#{action.capitalize} this organisation" %> + +

+ <%= govuk_link_to("Cancel", organisation_path(@organisation)) %> +

+
+
+<% end %> diff --git a/app/views/schemes/show.html.erb b/app/views/schemes/show.html.erb index 31ca1cba9..1aefadef5 100644 --- a/app/views/schemes/show.html.erb +++ b/app/views/schemes/show.html.erb @@ -49,7 +49,7 @@ -<% if SchemePolicy.new(current_user, @scheme).deactivate? %> +<% if @scheme.owning_organisation.active? && SchemePolicy.new(current_user, @scheme).deactivate? %> <%= toggle_scheme_link(@scheme) %> <% end %> diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb index 0465f67bb..925f1ada7 100644 --- a/app/views/users/new.html.erb +++ b/app/views/users/new.html.erb @@ -31,7 +31,7 @@ <% if current_user.support? %> <% null_option = [OpenStruct.new(id: "", name: "Select an option")] %> - <% organisations = Organisation.all.map { |org| OpenStruct.new(id: org.id, name: org.name) } %> + <% organisations = Organisation.filter_by_active.map { |org| OpenStruct.new(id: org.id, name: org.name) } %> <% answer_options = null_option + organisations %> <% if @organisation_id %> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index a93532e7b..c62fccf2c 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -127,8 +127,8 @@ end %> <% end %> -
- <% if current_user.can_toggle_active?(@user) %> + <% if @user.organisation.active? && current_user.can_toggle_active?(@user) %> +
<% if @user.active? %> <%= govuk_button_link_to "Deactivate user", deactivate_user_path(@user), warning: true %> <% if current_user.support? && @user.last_sign_in_at.nil? %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 079967095..68bea0c8f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -34,6 +34,8 @@ en: feedback_form: "https://forms.office.com/Pages/ResponsePage.aspx?id=EGg0v32c3kOociSi7zmVqC4YDsCJ3llAvEZelBFBLUBURFVUTzFDTUJPQlM4M0laTE5DTlNFSjJBQi4u" organisation: updated: "Organisation details updated" + reactivated: "%{organisation} has been reactivated." + deactivated: "%{organisation} has been deactivated." user: create_password: "Create a password to finish setting up your account" reset_password: "Reset your password" @@ -860,6 +862,9 @@ Make sure these answers are correct." offered: "Times previously offered since becoming available" warnings: + organisation: + deactivate: "All schemes and users at this organisation will be deactivated. All the organisation's relationships will be removed. It will no longer be possible to create logs for this organisation." + reactivate: "All schemes, users, and relationships that were active when this organisation was deactivated will be reactivated." location: deactivate: existing_logs: "It will not be possible to add logs with this location if their tenancy start date is on or after the date you enter. Any existing logs may be affected." diff --git a/config/routes.rb b/config/routes.rb index 79dcf0a04..b6b362569 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -179,6 +179,8 @@ Rails.application.routes.draw do post "managing-agents", to: "organisation_relationships#create_managing_agent" delete "managing-agents", to: "organisation_relationships#delete_managing_agent" get "merge-request", to: "organisations#merge_request" + get "deactivate", to: "organisations#deactivate" + get "reactivate", to: "organisations#reactivate" end end diff --git a/db/migrate/20240304112411_add_reactivate_with_organisation_to_users.rb b/db/migrate/20240304112411_add_reactivate_with_organisation_to_users.rb new file mode 100644 index 000000000..e66b3108a --- /dev/null +++ b/db/migrate/20240304112411_add_reactivate_with_organisation_to_users.rb @@ -0,0 +1,5 @@ +class AddReactivateWithOrganisationToUsers < ActiveRecord::Migration[7.0] + def change + add_column :users, :reactivate_with_organisation, :boolean + end +end diff --git a/db/migrate/20240305112507_add_default_value_to_organisation_active_field.rb b/db/migrate/20240305112507_add_default_value_to_organisation_active_field.rb new file mode 100644 index 000000000..8984b80b5 --- /dev/null +++ b/db/migrate/20240305112507_add_default_value_to_organisation_active_field.rb @@ -0,0 +1,12 @@ +class AddDefaultValueToOrganisationActiveField < ActiveRecord::Migration[7.0] + def up + change_column :organisations, :active, :boolean, default: true + + execute "UPDATE organisations + SET active = true;" + end + + def down + change_column :organisations, :active, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index cca8b7f5c..bb13372cd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -453,7 +453,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_03_19_122706) do t.string "managing_agents_label" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.boolean "active" + t.boolean "active", default: true t.integer "old_association_type" t.string "software_supplier_id" t.string "housing_management_system" @@ -764,6 +764,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_03_19_122706) do t.string "unconfirmed_email" t.boolean "initial_confirmation_sent" t.datetime "discarded_at" + t.boolean "reactivate_with_organisation" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true t.index ["encrypted_otp_secret_key"], name: "index_users_on_encrypted_otp_secret_key", unique: true diff --git a/spec/models/form/lettings/questions/managing_organisation_spec.rb b/spec/models/form/lettings/questions/managing_organisation_spec.rb index 86d58d4d3..45c089698 100644 --- a/spec/models/form/lettings/questions/managing_organisation_spec.rb +++ b/spec/models/form/lettings/questions/managing_organisation_spec.rb @@ -57,6 +57,7 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do let(:managing_org1) { create(:organisation, name: "Managing org 1") } let(:managing_org2) { create(:organisation, name: "Managing org 2") } let(:managing_org3) { create(:organisation, name: "Managing org 3") } + let(:inactive_managing_org) { create(:organisation, name: "Inactive managing org", active: false) } let(:log) { create(:lettings_log, managing_organisation: managing_org1) } let!(:org_rel1) do @@ -76,7 +77,9 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do } end - it "shows current managing agent at top, followed by user's org (with hint), followed by the managing agents of the user's org" do + it "shows current managing agent at top, followed by user's org (with hint), followed by the active managing agents of the user's org" do + create(:organisation_relationship, parent_organisation: user.organisation, child_organisation: inactive_managing_org) + expect(question.displayed_answer_options(log, user)).to eq(options) end end @@ -100,6 +103,10 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do create(:organisation_relationship, parent_organisation: log_owning_org, child_organisation: managing_org3) end + before do + create(:organisation, name: "Inactive managing org", active: false) + end + context "when org owns stock" do let(:options) do { @@ -111,7 +118,7 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do } end - it "shows current managing agent at top, followed by the current owning organisation (with hint), followed by the managing agents of the current owning organisation" do + it "shows current managing agent at top, followed by the current owning organisation (with hint), followed by the active managing agents of the current owning organisation" do log_owning_org.update!(holds_own_stock: true) expect(question.displayed_answer_options(log, user)).to eq(options) end @@ -133,7 +140,7 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do org_rel2.child_organisation.update!(merge_date: Time.zone.local(2023, 8, 2), absorbing_organisation_id: log_owning_org.id) end - it "shows current managing agent at top, followed by the current owning organisation (with hint), followed by the managing agents of the current owning organisation" do + it "shows current managing agent at top, followed by the current owning organisation (with hint), followed by the active managing agents of the current owning organisation" do log_owning_org.update!(holds_own_stock: true) expect(question.displayed_answer_options(log, user)).to eq(options) end @@ -149,7 +156,7 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do } end - it "shows current managing agent at top, followed by the managing agents of the current owning organisation" do + it "shows current managing agent at top, followed by the active managing agents of the current owning organisation" do log_owning_org.update!(holds_own_stock: false) expect(question.displayed_answer_options(log, user)).to eq(options) end diff --git a/spec/models/form/lettings/questions/stock_owner_spec.rb b/spec/models/form/lettings/questions/stock_owner_spec.rb index af899e4ab..cb600b986 100644 --- a/spec/models/form/lettings/questions/stock_owner_spec.rb +++ b/spec/models/form/lettings/questions/stock_owner_spec.rb @@ -46,6 +46,7 @@ RSpec.describe Form::Lettings::Questions::StockOwner, type: :model do let(:owning_org_1) { create(:organisation, name: "Owning org 1") } let(:owning_org_2) { create(:organisation, name: "Owning org 2") } + let(:inactive_owning_org) { create(:organisation, name: "Inactive owning org", active: false) } let!(:org_rel) do create(:organisation_relationship, child_organisation: user.organisation, parent_organisation: owning_org_2) end @@ -61,7 +62,8 @@ RSpec.describe Form::Lettings::Questions::StockOwner, type: :model do } end - it "shows current stock owner at top, followed by user's org (with hint), followed by the stock owners of the user's org" do + it "shows current stock owner at top, followed by user's org (with hint), followed by the active stock owners of the user's org" do + create(:organisation_relationship, child_organisation: user.organisation, parent_organisation: inactive_owning_org) user.organisation.update!(holds_own_stock: true) expect(question.displayed_answer_options(log, user)).to eq(options) end @@ -93,7 +95,8 @@ RSpec.describe Form::Lettings::Questions::StockOwner, type: :model do } end - it "shows current stock owner at top, followed by the stock owners of the user's org" do + it "shows current stock owner at top, followed by the active stock active owners of the user's org" do + create(:organisation_relationship, child_organisation: user.organisation, parent_organisation: inactive_owning_org) user.organisation.update!(holds_own_stock: false) expect(question.displayed_answer_options(log, user)).to eq(options) end @@ -203,21 +206,21 @@ RSpec.describe Form::Lettings::Questions::StockOwner, type: :model do end context "when user is support" do - let(:user) { create(:user, :support) } + let!(:user) { create(:user, :support) } + let!(:log) { create(:lettings_log) } - let(:log) { create(:lettings_log) } + it "shows active orgs where organisation holds own stock" do + non_stock_organisation = create(:organisation, name: "Non-stockholding org", holds_own_stock: false) + inactive_organisation = create(:organisation, name: "Inactive org", active: false) - let(:non_stock_organisation) { create(:organisation, holds_own_stock: false) } - let(:expected_opts) do - Organisation.where(holds_own_stock: true).each_with_object(options) do |organisation, hsh| + expected_opts = Organisation.filter_by_active.where(holds_own_stock: true).each_with_object(options) do |organisation, hsh| hsh[organisation.id] = organisation.name hsh end - end - it "shows orgs where organisation holds own stock" do expect(question.displayed_answer_options(log, user)).to eq(expected_opts) expect(question.displayed_answer_options(log, user)).not_to include(non_stock_organisation.id) + expect(question.displayed_answer_options(log, user)).not_to include(inactive_organisation.id) end context "and org has recently absorbed other orgs and does not have available from date" do diff --git a/spec/models/form/sales/questions/managing_organisation_spec.rb b/spec/models/form/sales/questions/managing_organisation_spec.rb index 716b1b917..30574e049 100644 --- a/spec/models/form/sales/questions/managing_organisation_spec.rb +++ b/spec/models/form/sales/questions/managing_organisation_spec.rb @@ -57,6 +57,7 @@ RSpec.describe Form::Sales::Questions::ManagingOrganisation, type: :model do let(:managing_org1) { create(:organisation, name: "Managing org 1") } let(:managing_org2) { create(:organisation, name: "Managing org 2") } let(:managing_org3) { create(:organisation, name: "Managing org 3") } + let(:inactive_org) { create(:organisation, name: "Inactive org", active: false) } let(:log) do create(:lettings_log, owning_organisation: log_owning_org, managing_organisation: managing_org1, @@ -80,7 +81,8 @@ RSpec.describe Form::Sales::Questions::ManagingOrganisation, type: :model do } end - it "shows current managing agent at top, followed by the current owning organisation (with hint), followed by the managing agents of the current owning organisation" do + it "shows current managing agent at top, followed by the current owning organisation (with hint), followed by the active managing agents of the current owning organisation" do + create(:organisation_relationship, parent_organisation: log_owning_org, child_organisation: inactive_org) log_owning_org.update!(holds_own_stock: true) expect(question.displayed_answer_options(log, user)).to eq(options) end @@ -96,7 +98,8 @@ RSpec.describe Form::Sales::Questions::ManagingOrganisation, type: :model do } end - it "shows current managing agent at top, followed by the managing agents of the current owning organisation" do + it "shows current managing agent at top, followed by the active managing agents of the current owning organisation" do + create(:organisation_relationship, parent_organisation: log_owning_org, child_organisation: inactive_org) log_owning_org.update!(holds_own_stock: false) expect(question.displayed_answer_options(log, user)).to eq(options) end diff --git a/spec/models/form/sales/questions/owning_organisation_id_spec.rb b/spec/models/form/sales/questions/owning_organisation_id_spec.rb index 7f14824db..54a08299b 100644 --- a/spec/models/form/sales/questions/owning_organisation_id_spec.rb +++ b/spec/models/form/sales/questions/owning_organisation_id_spec.rb @@ -57,6 +57,7 @@ RSpec.describe Form::Sales::Questions::OwningOrganisationId, type: :model do let(:owning_org_1) { create(:organisation, name: "Owning org 1") } let(:owning_org_2) { create(:organisation, name: "Owning org 2") } + let(:inactive_owning_org) { create(:organisation, name: "Inactive owning org", active: false) } let(:non_stock_owner) { create(:organisation, name: "Non stock owner", holds_own_stock: false) } let(:log) { create(:lettings_log, owning_organisation: owning_org_1) } @@ -75,7 +76,8 @@ RSpec.describe Form::Sales::Questions::OwningOrganisationId, type: :model do } end - it "shows user organisation, current owning organisation and the stock owners that hold their stock" do + it "shows user organisation, current owning organisation and the activestock owners that hold their stock" do + create(:organisation_relationship, child_organisation: user.organisation, parent_organisation: inactive_owning_org) user.organisation.update!(holds_own_stock: true) expect(question.displayed_answer_options(log, user)).to eq(options) end @@ -95,7 +97,8 @@ RSpec.describe Form::Sales::Questions::OwningOrganisationId, type: :model do create(:organisation_relationship, child_organisation: user.organisation, parent_organisation: non_stock_owner) end - it "shows current owning organisation and the stock owners that hold their stock" do + it "shows current owning organisation and the active stock owners that hold their stock" do + create(:organisation_relationship, child_organisation: user.organisation, parent_organisation: inactive_owning_org) user.organisation.update!(holds_own_stock: false) expect(question.displayed_answer_options(log, user)).to eq(options) end @@ -204,20 +207,21 @@ RSpec.describe Form::Sales::Questions::OwningOrganisationId, type: :model do context "when user is support" do let(:user) { create(:user, :support, organisation: organisation_1) } - let(:log) { create(:lettings_log, created_by: user) } - let(:non_stock_organisation) { create(:organisation, holds_own_stock: false) } - let(:expected_opts) do - Organisation.where(holds_own_stock: true).each_with_object(options) do |organisation, hsh| + it "shows active orgs where organisation holds own stock" do + non_stock_organisation = create(:organisation, holds_own_stock: false) + inactive_org = create(:organisation, active: false) + + expected_opts = Organisation.filter_by_active.where(holds_own_stock: true).each_with_object(options) do |organisation, hsh| hsh[organisation.id] = organisation.name hsh end - end - it "shows orgs where organisation holds own stock" do expect(question.displayed_answer_options(log, user)).to eq(expected_opts) expect(question.displayed_answer_options(log, user)).not_to include(non_stock_organisation.id) + expect(question.displayed_answer_options(log, user)).not_to include(inactive_org.id) + expect(question.displayed_answer_options(log, user)).to include(organisation_1.id) end context "when an org has recently absorbed other orgs" do diff --git a/spec/models/location_spec.rb b/spec/models/location_spec.rb index 58a101496..3bdc55b44 100644 --- a/spec/models/location_spec.rb +++ b/spec/models/location_spec.rb @@ -870,6 +870,11 @@ RSpec.describe Location, type: :model do expect(location.status).to eq(:deactivating_soon) end + it "returns deactivated if the owning organisation is deactivated" do + location.scheme.owning_organisation.active = false + expect(location.status).to eq(:deactivated) + end + it "returns deactivated if deactivation_date is in the past" do FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 6), location:) location.save! @@ -969,6 +974,9 @@ RSpec.describe Location, type: :model do end describe "filter by status" do + let!(:deactivated_organisation) { FactoryBot.create(:organisation, active: false) } + let!(:deactivated_by_organisation_scheme) { FactoryBot.create(:scheme, owning_organisation: deactivated_organisation) } + let!(:deactivated_by_organisation_location) { FactoryBot.create(:location, scheme: deactivated_by_organisation_scheme) } let!(:incomplete_location) { FactoryBot.create(:location, :incomplete, startdate: Time.zone.local(2022, 4, 1)) } let!(:incomplete_location_with_nil_confirmed) { FactoryBot.create(:location, :incomplete, startdate: Time.zone.local(2022, 4, 1), confirmed: nil) } let!(:active_location) { FactoryBot.create(:location, startdate: Time.zone.local(2022, 4, 1)) } @@ -1015,8 +1023,9 @@ RSpec.describe Location, type: :model do context "when filtering by deactivated status" do it "returns only deactivated locations" 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_location) + expect(described_class.filter_by_status(%w[deactivated]).count).to eq(2) + expect(described_class.filter_by_status(%w[deactivated])).to include(deactivated_location) + expect(described_class.filter_by_status(%w[deactivated])).to include(deactivated_by_organisation_location) end end diff --git a/spec/models/organisation_spec.rb b/spec/models/organisation_spec.rb index ad9034d35..98b082c56 100644 --- a/spec/models/organisation_spec.rb +++ b/spec/models/organisation_spec.rb @@ -212,8 +212,8 @@ RSpec.describe Organisation, type: :model do describe "scopes" do before do - create(:organisation, name: "Joe Bloggs") - create(:organisation, name: "Tom Smith") + create(:organisation, name: "Joe Bloggs", active: false) + create(:organisation, name: "Tom Smith", active: true) end context "when searching by name" do @@ -229,5 +229,42 @@ RSpec.describe Organisation, type: :model do expect(described_class.search_by("joe").count).to eq(1) end end + + context "when searching by active" do + it "returns only active records" do + results = described_class.filter_by_active + expect(results.count).to eq(1) + expect(results[0].name).to eq("Tom Smith") + end + end + end + + describe "status" do + let!(:organisation) { create(:organisation) } + + it "returns inactive when organisation inactive" do + organisation.active = false + + expect(organisation.status).to be(:deactivated) + end + + it "returns active when organisation active" do + organisation.active = true + + expect(organisation.status).to be(:active) + end + + it "returns merged when organisation merged in the past" do + organisation.merge_date = 1.month.ago + + expect(organisation.status).to be(:merged) + end + + it "does not return merged when organisation merges in the future" do + organisation.active = true + organisation.merge_date = Time.zone.now + 1.month + + expect(organisation.status).to be(:active) + end end end diff --git a/spec/models/scheme_spec.rb b/spec/models/scheme_spec.rb index 9b663595c..c84be444f 100644 --- a/spec/models/scheme_spec.rb +++ b/spec/models/scheme_spec.rb @@ -112,11 +112,13 @@ RSpec.describe Scheme, type: :model do end context "when filtering by status" do + let!(:deactivated_organisation) { FactoryBot.create(:organisation, active: false) } let!(:incomplete_scheme) { FactoryBot.create(:scheme, :incomplete, service_name: "name") } let!(:incomplete_scheme_2) { FactoryBot.create(:scheme, :incomplete, service_name: "name") } let!(:incomplete_scheme_with_nil_confirmed) { FactoryBot.create(:scheme, :incomplete, service_name: "name", confirmed: nil) } let(:active_scheme) { FactoryBot.create(:scheme) } let(:active_scheme_2) { FactoryBot.create(:scheme) } + let!(:deactivated_by_organisation_scheme) { FactoryBot.create(:scheme, owning_organisation: deactivated_organisation) } let(:deactivating_soon_scheme) { FactoryBot.create(:scheme) } let(:deactivating_soon_scheme_2) { FactoryBot.create(:scheme) } let(:deactivated_scheme) { FactoryBot.create(:scheme) } @@ -181,9 +183,10 @@ RSpec.describe Scheme, type: :model do context "when filtering by deactivated status" do it "returns only deactivated schemes" do - expect(described_class.filter_by_status(%w[deactivated]).count).to eq(2) + expect(described_class.filter_by_status(%w[deactivated]).count).to eq(3) expect(described_class.filter_by_status(%w[deactivated])).to include(deactivated_scheme) expect(described_class.filter_by_status(%w[deactivated])).to include(deactivated_scheme_2) + expect(described_class.filter_by_status(%w[deactivated])).to include(deactivated_by_organisation_scheme) end end @@ -231,6 +234,11 @@ RSpec.describe Scheme, type: :model do expect(scheme.status).to eq(:deactivating_soon) end + it "returns deactivated if the owning organisation is deactivated" do + scheme.owning_organisation.active = false + expect(scheme.status).to eq(:deactivated) + end + it "returns deactivated if deactivation_date is in the past" do FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 6), scheme:) scheme.reload diff --git a/spec/policies/organisation_policy_spec.rb b/spec/policies/organisation_policy_spec.rb new file mode 100644 index 000000000..4fb4c6761 --- /dev/null +++ b/spec/policies/organisation_policy_spec.rb @@ -0,0 +1,66 @@ +require "rails_helper" + +RSpec.describe OrganisationPolicy do + subject(:policy) { described_class } + + let(:organisation) { FactoryBot.create(:organisation) } + let(:data_provider) { FactoryBot.create(:user, :data_provider) } + let(:data_coordinator) { FactoryBot.create(:user, :data_coordinator) } + let(:support) { FactoryBot.create(:user, :support) } + + permissions :deactivate? do + it "does not permit data providers to deactivate an organisation" do + organisation.active = true + expect(policy).not_to permit(data_provider, organisation) + end + + it "does not permit data coordinators to deactivate an organisation" do + organisation.active = true + expect(policy).not_to permit(data_coordinator, organisation) + end + + it "permits support users to deactivate an active organisation" do + organisation.active = true + expect(policy).to permit(support, organisation) + end + + it "does not permit support users to deactivate an inactive organisation" do + organisation.active = false + expect(policy).not_to permit(support, organisation) + end + + it "does not permit support users to deactivate a merged organisation" do + organisation.active = true + organisation.merge_date = Time.zone.local(2023, 8, 2) + expect(policy).not_to permit(support, organisation) + end + end + + permissions :reactivate? do + it "does not permit data providers to reactivate an organisation" do + organisation.active = false + expect(policy).not_to permit(data_provider, organisation) + end + + it "does not permit data coordinators to reactivate an organisation" do + organisation.active = false + expect(policy).not_to permit(data_coordinator, organisation) + end + + it "permits support users to reactivate an inactive organisation" do + organisation.active = false + expect(policy).to permit(support, organisation) + end + + it "does not permit support users to reactivate an active organisation" do + organisation.active = true + expect(policy).not_to permit(support, organisation) + end + + it "does not permit support users to reactivate a merged organisation" do + organisation.active = false + organisation.merge_date = Time.zone.local(2023, 8, 2) + expect(policy).not_to permit(support, organisation) + end + end +end diff --git a/spec/requests/organisation_relationships_controller_spec.rb b/spec/requests/organisation_relationships_controller_spec.rb index 4c22a2aa2..feb687e0c 100644 --- a/spec/requests/organisation_relationships_controller_spec.rb +++ b/spec/requests/organisation_relationships_controller_spec.rb @@ -18,11 +18,13 @@ RSpec.describe OrganisationRelationshipsController, type: :request do context "with an organisation that the user belongs to" do let!(:stock_owner) { FactoryBot.create(:organisation) } let!(:other_org_stock_owner) { FactoryBot.create(:organisation, name: "Foobar LTD") } + let!(:inactive_stock_owner) { FactoryBot.create(:organisation, name: "Inactive LTD", active: false) } let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD 2") } before do FactoryBot.create(:organisation_relationship, child_organisation: organisation, parent_organisation: stock_owner) FactoryBot.create(:organisation_relationship, child_organisation: other_organisation, parent_organisation: other_org_stock_owner) + FactoryBot.create(:organisation_relationship, child_organisation: organisation, parent_organisation: inactive_stock_owner) get "/organisations/#{organisation.id}/stock-owners", headers:, params: {} end @@ -46,11 +48,18 @@ RSpec.describe OrganisationRelationshipsController, type: :request do expect(page).not_to have_content(other_org_stock_owner.name) end + it "does not show inactive stock owners" do + expect(page).not_to have_content(inactive_stock_owner.name) + end + it "shows the pagination count" do expect(page).to have_content("1 total stock owners") end context "when adding a stock owner" do + let!(:active_organisation) { FactoryBot.create(:organisation, name: "Active Org", active: true) } + let!(:inactive_organisation) { FactoryBot.create(:organisation, name: "Inactive LTD", active: false) } + before do get "/organisations/#{organisation.id}/stock-owners/add", headers:, params: {} end @@ -66,6 +75,27 @@ RSpec.describe OrganisationRelationshipsController, type: :request do it "shows a cancel button" do expect(page).to have_link("Cancel", href: "/organisations/#{organisation.id}/stock-owners") end + + it "includes only active organisations as options" do + expect(response.body).to include(active_organisation.name) + expect(response.body).not_to include(inactive_organisation.name) + end + end + + context "and current organisation is deactivated" do + before do + organisation.update!(active: false) + get "/organisations/#{organisation.id}/stock-owners", headers:, params: {} + end + + it "does not show the add stock owner button" do + expect(page).not_to have_link("Add a stock owner") + end + + it "shows a banner" do + expect(page).to have_content("This organisation is deactivated.") + expect(page).to have_content("You cannot add any new stock owners.") + end end end @@ -84,11 +114,13 @@ RSpec.describe OrganisationRelationshipsController, type: :request do context "with an organisation that the user belongs to" do let!(:managing_agent) { FactoryBot.create(:organisation) } let!(:other_org_managing_agent) { FactoryBot.create(:organisation, name: "Foobar LTD") } + let!(:inactive_managing_agent) { FactoryBot.create(:organisation, name: "Inactive LTD", active: false) } let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD") } before do FactoryBot.create(:organisation_relationship, parent_organisation: organisation, child_organisation: managing_agent) FactoryBot.create(:organisation_relationship, parent_organisation: other_organisation, child_organisation: other_org_managing_agent) + FactoryBot.create(:organisation_relationship, parent_organisation: organisation, child_organisation: inactive_managing_agent) get "/organisations/#{organisation.id}/managing-agents", headers:, params: {} end @@ -112,12 +144,35 @@ RSpec.describe OrganisationRelationshipsController, type: :request do expect(page).not_to have_content(other_org_managing_agent.name) end + it "does not show inactive managing-agents" do + expect(page).not_to have_content(inactive_managing_agent.name) + end + it "shows the pagination count" do expect(page).to have_content("1 total managing agents") end + + context "and current organisation is deactivated" do + before do + organisation.update!(active: false) + get "/organisations/#{organisation.id}/managing-agents", headers:, params: {} + end + + it "does not show the add managing agent button" do + expect(page).not_to have_link("Add a managing agent") + end + + it "shows a banner" do + expect(page).to have_content("This organisation is deactivated.") + expect(page).to have_content("You cannot add any new managing agents.") + end + end end context "when adding a managing agent" do + let!(:active_organisation) { FactoryBot.create(:organisation, name: "Active Org", active: true) } + let!(:inactive_organisation) { FactoryBot.create(:organisation, name: "Inactive LTD", active: false) } + before do get "/organisations/#{organisation.id}/managing-agents/add", headers:, params: {} end @@ -125,6 +180,11 @@ RSpec.describe OrganisationRelationshipsController, type: :request do it "has the correct header" do expect(response.body).to include("What is the name of your managing agent?") end + + it "includes only active organisations as options" do + expect(response.body).to include(active_organisation.name) + expect(response.body).not_to include(inactive_organisation.name) + end end context "with an organisation that are not in scope for the user, i.e. that they do not belong to" do diff --git a/spec/requests/organisations_controller_spec.rb b/spec/requests/organisations_controller_spec.rb index 53e830592..fa5163003 100644 --- a/spec/requests/organisations_controller_spec.rb +++ b/spec/requests/organisations_controller_spec.rb @@ -579,6 +579,32 @@ RSpec.describe OrganisationsController, type: :request do expect(whodunnit_actor).to be_a(User) expect(whodunnit_actor.id).to eq(user.id) end + + context "with active parameter true" do + let(:params) do + { + id: organisation.id, + organisation: { active: "true" }, + } + end + + it "redirects" do + expect(response).to have_http_status(:unauthorized) + end + end + + context "with active parameter false" do + let(:params) do + { + id: organisation.id, + organisation: { active: "false" }, + } + end + + it "redirects" do + expect(response).to have_http_status(:unauthorized) + end + end end context "with an organisation that the user does not belong to" do @@ -1407,6 +1433,100 @@ RSpec.describe OrganisationsController, type: :request do end end + describe "#update" do + context "with active parameter false" do + let(:params) { { id: organisation.id, organisation: { active: "false" } } } + + user_to_update = nil + + before do + user_to_update = create(:user, :data_coordinator, organisation:) + patch "/organisations/#{organisation.id}", headers:, params: + end + + it "deactivates associated users" do + user_to_update.reload + expect(user_to_update.active).to eq(false) + expect(user_to_update.reactivate_with_organisation).to eq(true) + end + end + + context "with active parameter true" do + user_to_reactivate = nil + user_not_to_reactivate = nil + + let(:params) do + { + id: organisation.id, + organisation: { active: "true" }, + } + end + let(:notify_client) { instance_double(Notifications::Client) } + let(:devise_notify_mailer) { DeviseNotifyMailer.new } + + let(:expected_personalisation) do + { + name: user_to_reactivate.name, + email: user_to_reactivate.email, + organisation: organisation.name, + link: include("/account/confirmation?confirmation_token="), + } + end + + 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) + + user_to_reactivate = create(:user, :data_coordinator, organisation:, active: false, reactivate_with_organisation: true) + user_not_to_reactivate = create(:user, :data_coordinator, organisation:, active: false, reactivate_with_organisation: false) + patch "/organisations/#{organisation.id}", headers:, params: + end + + it "reactivates users deactivated with organisation" do + user_to_reactivate.reload + user_not_to_reactivate.reload + expect(user_to_reactivate.active).to eq(true) + expect(user_to_reactivate.reactivate_with_organisation).to eq(false) + expect(user_not_to_reactivate.active).to eq(false) + end + + it "sends invitation emails" do + expect(notify_client).to have_received(:send_email).with(email_address: user_to_reactivate.email, template_id: User::BETA_ONBOARDING_TEMPLATE_ID, personalisation: expected_personalisation).once + end + end + end + + describe "#deactivate" do + before do + get "/organisations/#{organisation.id}/deactivate", headers:, params: {} + end + + it "shows deactivation page with deactivate and cancel buttons for the organisation" do + expect(path).to include("/organisations/#{organisation.id}/deactivate") + expect(page).to have_content(organisation.name) + expect(page).to have_content("Are you sure you want to deactivate this organisation?") + expect(page).to have_button("Deactivate this organisation") + expect(page).to have_link("Cancel", href: "/organisations/#{organisation.id}") + end + end + + describe "#reactivate" do + let(:inactive_organisation) { create(:organisation, name: "Inactive org", active: false) } + + before do + get "/organisations/#{inactive_organisation.id}/reactivate", headers:, params: {} + end + + it "shows reactivation page with reactivate and cancel buttons for the organisation" do + expect(path).to include("/organisations/#{inactive_organisation.id}/reactivate") + expect(page).to have_content(inactive_organisation.name) + expect(page).to have_content("Are you sure you want to reactivate this organisation?") + expect(page).to have_button("Reactivate this organisation") + expect(page).to have_link("Cancel", href: "/organisations/#{inactive_organisation.id}") + end + end + describe "#create" do let(:name) { " Unique new org name" } let(:address_line1) { "12 Random Street" } diff --git a/spec/views/locations/show.html.erb_spec.rb b/spec/views/locations/show.html.erb_spec.rb index c2510c79b..9caa079e0 100644 --- a/spec/views/locations/show.html.erb_spec.rb +++ b/spec/views/locations/show.html.erb_spec.rb @@ -1,50 +1,51 @@ require "rails_helper" RSpec.describe "locations/show.html.erb" do - context "when a data provider" do - let(:user) { create(:user) } + let(:scheme) do + instance_double( + Scheme, + owning_organisation: user.organisation, + id: 1, + service_name: "some name", + id_to_display: "S1", + sensitive: false, + scheme_type: "some type", + registered_under_care_act: false, + arrangement_type: "some other type", + primary_client_group: false, + has_other_client_group: false, + secondary_client_group: false, + support_type: "some support type", + intended_stay: "some intended stay", + available_from: 1.week.ago, + scheme_deactivation_periods: [], + status: :active, + ) + end - let(:scheme) do - instance_double( - Scheme, - owning_organisation: user.organisation, - id: 1, - service_name: "some name", - id_to_display: "S1", - sensitive: false, - scheme_type: "some type", - registered_under_care_act: false, - arrangement_type: "some other type", - primary_client_group: false, - has_other_client_group: false, - secondary_client_group: false, - support_type: "some support type", - intended_stay: "some intended stay", - available_from: 1.week.ago, - scheme_deactivation_periods: [], - status: :active, - ) - end + let(:location) do + instance_double( + Location, + id: 5, + name: "some location", + postcode: "EC1N 2TD", + linked_local_authorities: [], + units: "", + type_of_unit: "", + mobility_type: "", + available_from: 1.week.ago, + location_deactivation_periods: [], + status: :active, + active?: true, + scheme:, + deactivates_in_a_long_time?: false, + is_la_inferred: nil, + deactivated?: false, + ) + end - let(:location) do - instance_double( - Location, - id: 5, - name: "some location", - postcode: "EC1N 2TD", - linked_local_authorities: [], - units: "", - type_of_unit: "", - mobility_type: "", - available_from: 1.week.ago, - location_deactivation_periods: [], - status: :active, - active?: true, - scheme:, - deactivates_in_a_long_time?: false, - is_la_inferred: nil, - ) - end + context "when a data provider" do + let(:user) { create(:user) } it "does not see add a location button" do assign(:scheme, scheme) @@ -70,4 +71,32 @@ RSpec.describe "locations/show.html.erb" do expect(rendered).not_to have_content("Change") end end + + context "when a support user" do + let(:user) { create(:user, role: "support") } + + it "sees deactivate scheme location button" do + assign(:scheme, scheme) + assign(:location, location) + + allow(view).to receive(:current_user).and_return(user) + + render + + expect(rendered).to have_content("Deactivate this location") + end + + it "does not see deactivate scheme location button when organisation is deactivated" do + user.organisation.active = false + + assign(:scheme, scheme) + assign(:location, location) + + allow(view).to receive(:current_user).and_return(user) + + render + + expect(rendered).not_to have_content("Deactivate this location") + end + end end diff --git a/spec/views/organisations/show.html.erb_spec.rb b/spec/views/organisations/show.html.erb_spec.rb index 119462349..de4996c36 100644 --- a/spec/views/organisations/show.html.erb_spec.rb +++ b/spec/views/organisations/show.html.erb_spec.rb @@ -113,6 +113,20 @@ RSpec.describe "organisations/show.html.erb" do expect(fragment).to have_link(text: "View agreement", href: "/organisations/#{organisation_with_dsa.id}/data-sharing-agreement") end end + + it "shows deactivate button when organisation is active" do + user.organisation.active = true + render + expect(fragment).to have_content("Deactivate this organisation") + expect(fragment).not_to have_content("Reactivate this organisation") + end + + it "shows reactivate button when organisation is inactive" do + user.organisation.active = false + render + expect(fragment).not_to have_content("Deactivate this organisation") + expect(fragment).to have_content("Reactivate this organisation") + end end context "when not dpo" do diff --git a/spec/views/schemes/show.html.erb_spec.rb b/spec/views/schemes/show.html.erb_spec.rb index 4a0447c11..797a95d60 100644 --- a/spec/views/schemes/show.html.erb_spec.rb +++ b/spec/views/schemes/show.html.erb_spec.rb @@ -1,10 +1,15 @@ require "rails_helper" RSpec.describe "schemes/show.html.erb" do + let(:organisation) { create(:organisation, holds_own_stock: true) } + let(:scheme) { create(:scheme, owning_organisation: organisation, confirmed: true) } + + before do + create(:location, scheme:, confirmed: true) + end + context "when data provider" do - let(:organisation) { create(:organisation, holds_own_stock: true) } let(:user) { build(:user, organisation:) } - let(:scheme) { create(:scheme, owning_organisation: user.organisation) } it "does not render button to deactivate schemes" do assign(:scheme, scheme) @@ -26,4 +31,29 @@ RSpec.describe "schemes/show.html.erb" do expect(rendered).not_to have_content("Change") end end + + context "when support" do + let(:user) { build(:user, organisation:, role: "support") } + + it "renders button to deactivate scheme" do + assign(:scheme, scheme) + + allow(view).to receive(:current_user).and_return(user) + + render + + expect(rendered).to have_content("Deactivate this scheme") + end + + it "does not render button to deactivate scheme if organisation is deactivated" do + organisation.active = false + assign(:scheme, scheme) + + allow(view).to receive(:current_user).and_return(user) + + render + + expect(rendered).not_to have_content("Deactivate this scheme") + end + end end