Browse Source

Merge branch 'main' into CLDC-3505-fix-tests-that-will-break-on-rollover-part-3

pull/2493/head
Rachael Booth 7 months ago
parent
commit
696574d470
  1. 2
      Gemfile
  2. 22
      Gemfile.lock
  3. 4
      app/components/lettings_log_summary_component.html.erb
  4. 4
      app/components/sales_log_summary_component.html.erb
  5. 2
      app/controllers/bulk_upload_lettings_logs_controller.rb
  6. 2
      app/controllers/bulk_upload_sales_logs_controller.rb
  7. 31
      app/controllers/organisations_controller.rb
  8. 12
      app/helpers/organisations_helper.rb
  9. 6
      app/helpers/schemes_helper.rb
  10. 3
      app/helpers/tab_nav_helper.rb
  11. 16
      app/models/form/lettings/questions/managing_organisation.rb
  12. 11
      app/models/form/lettings/questions/stock_owner.rb
  13. 11
      app/models/form/sales/questions/managing_organisation.rb
  14. 23
      app/models/form/sales/questions/owning_organisation_id.rb
  15. 10
      app/models/forms/bulk_upload_lettings/guidance.rb
  16. 10
      app/models/forms/bulk_upload_sales/guidance.rb
  17. 12
      app/models/organisation.rb
  18. 21
      app/policies/organisation_policy.rb
  19. 6
      app/services/bulk_upload/lettings/year2023/row_parser.rb
  20. 2
      app/services/bulk_upload/lettings/year2024/row_parser.rb
  21. 2
      app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2023.html.erb
  22. 2
      app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2024.html.erb
  23. 2
      app/views/bulk_upload_sales_logs/forms/prepare_your_file_2023.html.erb
  24. 2
      app/views/bulk_upload_sales_logs/forms/prepare_your_file_2024.html.erb
  25. 1
      app/views/layouts/_collection_resources.html.erb
  26. 24
      app/views/organisations/delete_confirmation.html.erb
  27. 4
      app/views/organisations/show.html.erb
  28. 2
      app/views/start/guidance.html.erb
  29. 2
      app/views/users/show.html.erb
  30. 1
      config/locales/en.yml
  31. 2
      config/routes.rb
  32. 5
      db/migrate/20240610142812_add_discarded_at_to_organisations.rb
  33. 3
      db/schema.rb
  34. 49
      docs/testing.md
  35. 170
      lib/tasks/handle_unpended_logs.rake
  36. 19
      lib/tasks/recalculate_status_when_la_missing.rake
  37. 2
      spec/components/create_log_actions_component_spec.rb
  38. 16
      spec/components/data_protection_confirmation_banner_component_spec.rb
  39. 4
      spec/factories/organisation.rb
  40. 16
      spec/factories/user.rb
  41. 2
      spec/features/user_spec.rb
  42. 8
      spec/helpers/tab_nav_helper_spec.rb
  43. 1
      spec/mailers/resend_invitation_mailer_spec.rb
  44. 2
      spec/models/form/lettings/pages/address_matcher_spec.rb
  45. 2
      spec/models/form/lettings/pages/uprn_selection_spec.rb
  46. 2
      spec/models/form/lettings/pages/uprn_spec.rb
  47. 9
      spec/models/form/lettings/questions/managing_organisation_spec.rb
  48. 2
      spec/models/form/lettings/questions/property_reference_spec.rb
  49. 24
      spec/models/form/lettings/questions/stock_owner_spec.rb
  50. 10
      spec/models/form/lettings/questions/uprn_confirmation_spec.rb
  51. 2
      spec/models/form/sales/pages/address_matcher_spec.rb
  52. 6
      spec/models/form/sales/pages/uprn_confirmation_spec.rb
  53. 2
      spec/models/form/sales/pages/uprn_selection_spec.rb
  54. 2
      spec/models/form/sales/pages/uprn_spec.rb
  55. 2
      spec/models/form/sales/questions/address_line1_for_address_matcher_spec.rb
  56. 2
      spec/models/form/sales/questions/buyer1_mortgage_spec.rb
  57. 2
      spec/models/form/sales/questions/buyer2_mortgage_spec.rb
  58. 2
      spec/models/form/sales/questions/buyers_organisations_spec.rb
  59. 2
      spec/models/form/sales/questions/deposit_amount_spec.rb
  60. 4
      spec/models/form/sales/questions/managing_organisation_spec.rb
  61. 9
      spec/models/form/sales/questions/owning_organisation_id_spec.rb
  62. 2
      spec/models/form/sales/questions/postcode_for_address_matcher_spec.rb
  63. 8
      spec/models/form/sales/questions/uprn_confirmation_spec.rb
  64. 2
      spec/models/form/sales/questions/uprn_selection_spec.rb
  65. 2
      spec/models/form/sales/questions/uprn_spec.rb
  66. 4
      spec/models/form/sales/subsections/discounted_ownership_scheme_spec.rb
  67. 4
      spec/models/form/sales/subsections/household_characteristics_spec.rb
  68. 4
      spec/models/form/sales/subsections/outright_sale_spec.rb
  69. 4
      spec/models/form/sales/subsections/shared_ownership_scheme_spec.rb
  70. 43
      spec/models/forms/bulk_upload_lettings/guidance_spec.rb
  71. 43
      spec/models/forms/bulk_upload_sales/guidance_spec.rb
  72. 4
      spec/models/scheme_spec.rb
  73. 1
      spec/models/user_spec.rb
  74. 2
      spec/models/validations/date_validations_spec.rb
  75. 2
      spec/models/validations/local_authority_validations_spec.rb
  76. 2
      spec/models/validations/sales/soft_validations_spec.rb
  77. 4
      spec/models/validations/setup_validations_spec.rb
  78. 6
      spec/models/validations/tenancy_validations_spec.rb
  79. 108
      spec/policies/organisation_policy_spec.rb
  80. 2
      spec/policies/user_policy_spec.rb
  81. 2
      spec/requests/bulk_upload_lettings_logs_controller_spec.rb
  82. 2
      spec/requests/bulk_upload_sales_logs_controller_spec.rb
  83. 251
      spec/requests/organisations_controller_spec.rb
  84. 11
      spec/requests/users_controller_spec.rb
  85. 10
      spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb
  86. 9
      spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb
  87. 2
      spec/views/logs/_create_for_org_actions.html.erb_spec.rb
  88. 2
      spec/views/organisations/data_sharing_agreement.html.erb_spec.rb
  89. 12
      spec/views/organisations/show.html.erb_spec.rb

2
Gemfile

@ -63,7 +63,7 @@ gem "possessive"
gem "auto_strip_attributes"
# Use sidekiq for background processing
gem "method_source", "~> 1.1"
gem "rails_admin", "~> 3.0"
gem "rails_admin", "~> 3.1"
gem "ruby-openai"
gem "sidekiq"
gem "sidekiq-cron"

22
Gemfile.lock

@ -104,7 +104,7 @@ GEM
bindex (0.8.1)
bootsnap (1.18.3)
msgpack (~> 1.2)
builder (3.2.4)
builder (3.3.0)
bundler-audit (0.9.1)
bundler (>= 1.2.0, < 3)
thor (~> 1.0)
@ -128,7 +128,7 @@ GEM
launchy
childprocess (5.0.0)
coderay (1.1.3)
concurrent-ruby (1.3.1)
concurrent-ruby (1.3.3)
connection_pool (2.4.1)
crack (1.0.0)
bigdecimal
@ -163,7 +163,7 @@ GEM
rainbow
rubocop
smart_properties
erubi (1.12.0)
erubi (1.13.0)
et-orbi (1.2.7)
tzinfo
event_stream_parser (1.0.0)
@ -241,13 +241,13 @@ GEM
matrix (0.4.2)
method_source (1.1.0)
mini_mime (1.1.5)
minitest (5.23.1)
minitest (5.24.1)
msgpack (1.7.2)
multipart-post (2.4.1)
nested_form (0.3.2)
net-http (0.4.1)
uri
net-imap (0.4.12)
net-imap (0.4.14)
date
net-protocol
net-pop (0.1.2)
@ -257,11 +257,11 @@ GEM
net-smtp (0.5.0)
net-protocol
nio4r (2.7.3)
nokogiri (1.16.5-arm64-darwin)
nokogiri (1.16.6-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.5-x86_64-darwin)
nokogiri (1.16.6-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.16.5-x86_64-linux)
nokogiri (1.16.6-x86_64-linux)
racc (~> 1.4)
notifications-ruby-client (6.0.0)
jwt (>= 1.5, < 3)
@ -333,7 +333,7 @@ GEM
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
nokogiri (~> 1.14)
rails_admin (3.1.2)
rails_admin (3.1.3)
activemodel-serializers-xml (>= 1.0)
kaminari (>= 0.14, < 2.0)
nested_form (~> 0.3)
@ -484,7 +484,7 @@ GEM
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.15)
zeitwerk (2.6.16)
PLATFORMS
arm64-darwin-21
@ -535,7 +535,7 @@ DEPENDENCIES
rack-attack
rack-mini-profiler (~> 2.0)
rails (~> 7.0.8.3)
rails_admin (~> 3.0)
rails_admin (~> 3.1)
redis (~> 4.8)
roo
rspec-rails

4
app/components/lettings_log_summary_component.html.erb

@ -33,13 +33,13 @@
<% if log.owning_organisation %>
<div class="app-metadata__item">
<dt class="app-metadata__term">Owned by</dt>
<dd class="app-metadata__definition"><%= log.owning_organisation&.name %></dd>
<dd class="app-metadata__definition"><%= log.owning_organisation&.label %></dd>
</div>
<% end %>
<% if log.managing_organisation %>
<div class="app-metadata__item">
<dt class="app-metadata__term">Managed by</dt>
<dd class="app-metadata__definition"><%= log.managing_organisation&.name %></dd>
<dd class="app-metadata__definition"><%= log.managing_organisation&.label %></dd>
</div>
<% end %>
</dl>

4
app/components/sales_log_summary_component.html.erb

@ -26,13 +26,13 @@
<% if log.owning_organisation %>
<div class="app-metadata__item">
<dt class="app-metadata__term">Owned by</dt>
<dd class="app-metadata__definition"><%= log.owning_organisation&.name %></dd>
<dd class="app-metadata__definition"><%= log.owning_organisation&.label %></dd>
</div>
<% end %>
<% if log.managing_organisation %>
<div class="app-metadata__item">
<dt class="app-metadata__term">Reported by</dt>
<dd class="app-metadata__definition"><%= log.managing_organisation&.name %></dd>
<dd class="app-metadata__definition"><%= log.managing_organisation&.label %></dd>
</div>
<% end %>
</dl>

2
app/controllers/bulk_upload_lettings_logs_controller.rb

@ -47,7 +47,7 @@ private
when "prepare-your-file"
Forms::BulkUploadLettings::PrepareYourFile.new(form_params)
when "guidance"
Forms::BulkUploadLettings::Guidance.new(form_params)
Forms::BulkUploadLettings::Guidance.new(form_params.merge(referrer: params[:referrer]))
when "needstype"
Forms::BulkUploadLettings::Needstype.new(form_params)
when "upload-your-file"

2
app/controllers/bulk_upload_sales_logs_controller.rb

@ -47,7 +47,7 @@ private
when "prepare-your-file"
Forms::BulkUploadSales::PrepareYourFile.new(form_params)
when "guidance"
Forms::BulkUploadSales::Guidance.new(form_params)
Forms::BulkUploadSales::Guidance.new(form_params.merge(referrer: params[:referrer]))
when "upload-your-file"
Forms::BulkUploadSales::UploadYourFile.new(form_params.merge(current_user:))
when "checking-file"

31
app/controllers/organisations_controller.rb

@ -15,9 +15,9 @@ class OrganisationsController < ApplicationController
redirect_to organisation_path(current_user.organisation) unless current_user.support?
all_organisations = Organisation.order(:name)
@pagy, @organisations = pagy(filtered_collection(all_organisations, search_term))
@pagy, @organisations = pagy(filtered_collection(all_organisations.visible, search_term))
@searched = search_term.presence
@total_count = all_organisations.size
@total_count = all_organisations.visible.size
end
def schemes
@ -117,7 +117,6 @@ class OrganisationsController < ApplicationController
end
def update
selected_rent_periods = rent_period_params[:rent_periods].compact_blank
if (current_user.data_coordinator? && org_params[:active].nil?) || current_user.support?
if @organisation.update(org_params)
case org_params[:active]
@ -136,11 +135,14 @@ class OrganisationsController < ApplicationController
else
flash[:notice] = I18n.t("organisation.updated")
end
used_rent_periods = @organisation.lettings_logs.pluck(:period).uniq.compact.map(&:to_s)
rent_periods_to_delete = rent_period_params[:all_rent_periods] - selected_rent_periods - used_rent_periods
OrganisationRentPeriod.transaction do
selected_rent_periods.each { |period| OrganisationRentPeriod.create(organisation: @organisation, rent_period: period) }
OrganisationRentPeriod.where(organisation: @organisation, rent_period: rent_periods_to_delete).destroy_all
if rent_period_params[:rent_periods].present?
selected_rent_periods = rent_period_params[:rent_periods].compact_blank
used_rent_periods = @organisation.lettings_logs.pluck(:period).uniq.compact.map(&:to_s)
rent_periods_to_delete = rent_period_params[:all_rent_periods] - selected_rent_periods - used_rent_periods
OrganisationRentPeriod.transaction do
selected_rent_periods.each { |period| OrganisationRentPeriod.create(organisation: @organisation, rent_period: period) }
OrganisationRentPeriod.where(organisation: @organisation, rent_period: rent_periods_to_delete).destroy_all
end
end
redirect_to details_organisation_path(@organisation)
else
@ -152,6 +154,17 @@ class OrganisationsController < ApplicationController
end
end
def delete
authorize @organisation
@organisation.discard!
redirect_to organisations_path, notice: I18n.t("notification.organisation_deleted", name: @organisation.name)
end
def delete_confirmation
authorize @organisation
end
def lettings_logs
organisation_logs = LettingsLog.visible.filter_by_organisation(@organisation).filter_by_years_or_nil(FormHandler.instance.years_of_available_lettings_forms)
unpaginated_filtered_logs = filter_manager.filtered_logs(organisation_logs, search_term, session_filters)
@ -306,7 +319,7 @@ private
end
def authenticate_scope!
if %w[create new lettings_logs sales_logs download_lettings_csv email_lettings_csv email_sales_csv download_sales_csv].include? action_name
if %w[create new lettings_logs sales_logs download_lettings_csv email_lettings_csv email_sales_csv download_sales_csv delete_confirmation delete].include? action_name
head :unauthorized and return unless current_user.support?
elsif current_user.organisation != @organisation && !current_user.support?
render_not_found

12
app/helpers/organisations_helper.rb

@ -19,7 +19,7 @@ module OrganisationsHelper
{ name: "Owns housing stock", value: organisation.holds_own_stock ? "Yes" : "No", editable: false },
{ name: "Rent periods", value: organisation.rent_period_labels, editable: true, format: :bullet },
{ name: "Data Sharing Agreement" },
{ name: "Status", value: status_tag(organisation.status), editable: false },
{ name: "Status", value: status_tag(organisation.status) + delete_organisation_text(organisation), editable: false },
]
end
@ -39,9 +39,19 @@ module OrganisationsHelper
end
end
def delete_organisation_text(organisation)
if organisation.active == false && current_user.support? && !OrganisationPolicy.new(current_user, organisation).delete?
"<div class=\"app-!-colour-muted\">This organisation was active in an open or editable collection year, and cannot be deleted.</div>".html_safe
end
end
def rent_periods_with_checked_attr(checked_periods: nil)
RentPeriod.rent_period_mappings.each_with_object({}) do |(period_code, period_value), result|
result[period_code] = period_value.merge(checked: checked_periods.nil? || checked_periods.include?(period_code))
end
end
def delete_organisation_link(organisation)
govuk_button_link_to "Delete this organisation", delete_confirmation_organisation_path(organisation), warning: true
end
end

6
app/helpers/schemes_helper.rb

@ -20,10 +20,10 @@ module SchemesHelper
end
def owning_organisation_options(current_user)
all_orgs = Organisation.all.map { |org| OpenStruct.new(id: org.id, name: org.name) }
all_orgs = Organisation.visible.map { |org| OpenStruct.new(id: org.id, name: org.name) }
user_org = [OpenStruct.new(id: current_user.organisation_id, name: current_user.organisation.name)]
stock_owners = current_user.organisation.stock_owners.map { |org| OpenStruct.new(id: org.id, name: org.name) }
merged_organisations = current_user.organisation.absorbed_organisations.merged_during_open_collection_period.map { |org| OpenStruct.new(id: org.id, name: org.name) }
stock_owners = current_user.organisation.stock_owners.visible.map { |org| OpenStruct.new(id: org.id, name: org.name) }
merged_organisations = current_user.organisation.absorbed_organisations.visible.merged_during_open_collection_period.map { |org| OpenStruct.new(id: org.id, name: org.name) }
current_user.support? ? all_orgs : user_org + stock_owners + merged_organisations
end

3
app/helpers/tab_nav_helper.rb

@ -19,7 +19,8 @@ module TabNavHelper
end
def org_cell(user)
org_name = current_user.support? ? govuk_link_to(user.organisation.name, lettings_logs_organisation_path(user.organisation)) : user.organisation.name
role = "<span class=\"app-!-colour-muted\">#{user.role.to_s.humanize}</span>"
[user.organisation.name, role].join("\n")
[org_name, role].join("\n")
end
end

16
app/models/form/lettings/questions/managing_organisation.rb

@ -17,7 +17,8 @@ class Form::Lettings::Questions::ManagingOrganisation < ::Form::Question
return opts unless log
if log.managing_organisation.present?
opts = opts.merge({ log.managing_organisation.id => log.managing_organisation.name })
org_value = log.managing_organisation.label
opts = opts.merge({ log.managing_organisation.id => org_value })
end
if user.support?
@ -31,14 +32,14 @@ class Form::Lettings::Questions::ManagingOrganisation < ::Form::Question
end
orgs = if user.support?
log.owning_organisation.managing_agents.filter_by_active
log.owning_organisation.managing_agents.visible.filter_by_active
elsif user.organisation.absorbed_organisations.include?(log.owning_organisation)
user.organisation.managing_agents.filter_by_active + log.owning_organisation.managing_agents.filter_by_active
user.organisation.managing_agents.visible.filter_by_active + log.owning_organisation.managing_agents.visible.filter_by_active # here
else
user.organisation.managing_agents.filter_by_active
user.organisation.managing_agents.visible.filter_by_active
end
user.organisation.absorbed_organisations.each do |absorbed_org|
user.organisation.absorbed_organisations.visible.each do |absorbed_org|
opts[absorbed_org.id] = "#{absorbed_org.name} (inactive as of #{absorbed_org.merge_date.to_fs(:govuk_date)})"
end
@ -72,7 +73,10 @@ class Form::Lettings::Questions::ManagingOrganisation < ::Form::Question
end
def answer_label(log, _current_user = nil)
Organisation.find_by(id: log.managing_organisation_id)&.name
organisation = Organisation.find_by(id: log.managing_organisation_id)
return unless organisation
organisation.label
end
private

11
app/models/form/lettings/questions/stock_owner.rb

@ -17,7 +17,8 @@ class Form::Lettings::Questions::StockOwner < ::Form::Question
return answer_opts unless log
if log.owning_organisation_id.present?
answer_opts[log.owning_organisation.id] = log.owning_organisation.name
org_value = log.owning_organisation.label
answer_opts[log.owning_organisation.id] = org_value
end
recently_absorbed_organisations = user.organisation.absorbed_organisations.merged_during_open_collection_period
@ -30,7 +31,7 @@ class Form::Lettings::Questions::StockOwner < ::Form::Question
end
if user.support?
Organisation.filter_by_active.where(holds_own_stock: true).find_each do |org|
Organisation.visible.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,10 +41,10 @@ class Form::Lettings::Questions::StockOwner < ::Form::Question
end
end
else
user.organisation.stock_owners.filter_by_active.each do |stock_owner|
user.organisation.stock_owners.visible.filter_by_active.each do |stock_owner|
answer_opts[stock_owner.id] = stock_owner.name
end
recently_absorbed_organisations.each do |absorbed_org|
recently_absorbed_organisations.visible.each do |absorbed_org|
answer_opts[absorbed_org.id] = merged_organisation_label(absorbed_org.name, absorbed_org.merge_date) if absorbed_org.holds_own_stock?
end
end
@ -64,7 +65,7 @@ class Form::Lettings::Questions::StockOwner < ::Form::Question
def hidden_in_check_answers?(_log, user = nil)
return false if user.support?
stock_owners = user.organisation.stock_owners + user.organisation.absorbed_organisations.where(holds_own_stock: true)
stock_owners = user.organisation.stock_owners.visible + user.organisation.absorbed_organisations.visible.where(holds_own_stock: true)
if user.organisation.holds_own_stock?
stock_owners.count.zero?

11
app/models/form/sales/questions/managing_organisation.rb

@ -17,7 +17,8 @@ class Form::Sales::Questions::ManagingOrganisation < ::Form::Question
return opts unless log
if log.managing_organisation.present?
opts = opts.merge({ log.managing_organisation.id => log.managing_organisation.name })
org_value = log.managing_organisation.label
opts = opts.merge({ log.managing_organisation.id => org_value })
end
if user.support?
@ -31,14 +32,14 @@ class Form::Sales::Questions::ManagingOrganisation < ::Form::Question
end
orgs = if user.support?
log.owning_organisation.managing_agents.filter_by_active
log.owning_organisation.managing_agents.visible.filter_by_active
elsif user.organisation.absorbed_organisations.include?(log.owning_organisation)
user.organisation.managing_agents.filter_by_active + log.owning_organisation.managing_agents.filter_by_active
user.organisation.managing_agents.visible.filter_by_active + log.owning_organisation.managing_agents.visible.filter_by_active
else
user.organisation.managing_agents.filter_by_active
user.organisation.managing_agents.visible.filter_by_active
end.pluck(:id, :name).to_h
user.organisation.absorbed_organisations.each do |absorbed_org|
user.organisation.absorbed_organisations.visible.each do |absorbed_org|
opts[absorbed_org.id] = "#{absorbed_org.name} (inactive as of #{absorbed_org.merge_date.to_fs(:govuk_date)})"
end

23
app/models/form/sales/questions/owning_organisation_id.rb

@ -16,25 +16,22 @@ class Form::Sales::Questions::OwningOrganisationId < ::Form::Question
return answer_opts unless user
return answer_opts unless log
unless user.support?
if log.owning_organisation_id.present?
answer_opts[log.owning_organisation.id] = log.owning_organisation.name
end
if log.owning_organisation_id.present?
org_value = log.owning_organisation.label
answer_opts[log.owning_organisation.id] = org_value
end
unless user.support?
if user.organisation.holds_own_stock?
answer_opts[user.organisation.id] = "#{user.organisation.name} (Your organisation)"
end
user.organisation.stock_owners.filter_by_active.where(holds_own_stock: true).find_each do |org|
user.organisation.stock_owners.visible.filter_by_active.where(holds_own_stock: true).find_each do |org|
answer_opts[org.id] = org.name
end
end
if log.owning_organisation_id.present?
answer_opts[log.owning_organisation.id] = log.owning_organisation.name
end
recently_absorbed_organisations = user.organisation.absorbed_organisations.merged_during_open_collection_period
recently_absorbed_organisations = user.organisation.absorbed_organisations.visible.merged_during_open_collection_period
if !user.support? && user.organisation.holds_own_stock?
answer_opts[user.organisation.id] = if recently_absorbed_organisations.exists? && user.organisation.available_from.present?
"#{user.organisation.name} (Your organisation, active as of #{user.organisation.available_from.to_fs(:govuk_date)})"
@ -44,7 +41,7 @@ class Form::Sales::Questions::OwningOrganisationId < ::Form::Question
end
if user.support?
Organisation.filter_by_active.where(holds_own_stock: true).find_each do |org|
Organisation.visible.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?
@ -54,7 +51,7 @@ class Form::Sales::Questions::OwningOrganisationId < ::Form::Question
end
end
else
recently_absorbed_organisations.each do |absorbed_org|
recently_absorbed_organisations.visible.each do |absorbed_org|
answer_opts[absorbed_org.id] = merged_organisation_label(absorbed_org.name, absorbed_org.merge_date) if absorbed_org.holds_own_stock?
end
end
@ -75,7 +72,7 @@ class Form::Sales::Questions::OwningOrganisationId < ::Form::Question
def hidden_in_check_answers?(_log, user = nil)
return false if user.support?
stock_owners = user.organisation.stock_owners + user.organisation.absorbed_organisations.where(holds_own_stock: true)
stock_owners = user.organisation.stock_owners.visible + user.organisation.absorbed_organisations.visible.where(holds_own_stock: true)
if user.organisation.holds_own_stock?
stock_owners.count.zero?

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

@ -6,13 +6,21 @@ module Forms
include Rails.application.routes.url_helpers
attribute :year, :integer
attribute :referrer
def view_path
"bulk_upload_shared/guidance"
end
def back_path
bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year: })
case referrer
when "prepare-your-file"
bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year: })
when "home"
root_path
else
guidance_path
end
end
def lettings_legacy_template_path

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

@ -6,13 +6,21 @@ module Forms
include Rails.application.routes.url_helpers
attribute :year, :integer
attribute :referrer
def view_path
"bulk_upload_shared/guidance"
end
def back_path
bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: })
case referrer
when "prepare-your-file"
bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: })
when "home"
root_path
else
guidance_path
end
end
def lettings_legacy_template_path

12
app/models/organisation.rb

@ -17,6 +17,7 @@ class Organisation < ApplicationRecord
belongs_to :absorbing_organisation, class_name: "Organisation", optional: true
has_many :absorbed_organisations, class_name: "Organisation", foreign_key: "absorbing_organisation_id"
scope :visible, -> { where(discarded_at: nil) }
def affiliated_stock_owners
ids = []
@ -143,6 +144,7 @@ class Organisation < ApplicationRecord
end
def status_at(date)
return :deleted if discarded_at.present?
return :merged if merge_date.present? && merge_date < date
return :deactivated unless active
@ -178,4 +180,14 @@ class Organisation < ApplicationRecord
false
end
def discard!
owned_schemes.each(&:discard!)
users.each(&:discard!)
update!(discarded_at: Time.zone.now)
end
def label
status == :deleted ? "#{name} (deleted)" : name
end
end

21
app/policies/organisation_policy.rb

@ -13,4 +13,25 @@ class OrganisationPolicy
def reactivate?
user.support? && organisation.status == :deactivated
end
def delete_confirmation?
delete?
end
def delete?
return false unless user.support?
return false unless organisation.status == :deactivated || organisation.status == :merged
!has_any_logs_in_editable_collection_period
end
def has_any_logs_in_editable_collection_period
editable_from_date = FormHandler.instance.earliest_open_for_editing_collection_start_date
editable_lettings_logs = organisation.lettings_logs.visible.after_date(editable_from_date)
return true if organisation.lettings_logs.visible.where(startdate: nil).any? || editable_lettings_logs.any?
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
end

6
app/services/bulk_upload/lettings/year2023/row_parser.rb

@ -1317,11 +1317,11 @@ private
end
def log_uses_new_scheme_id?
field_16&.start_with?("S")
field_16&.strip&.start_with?("S")
end
def log_uses_old_scheme_id?
field_16.present? && !field_16.start_with?("S")
field_16.present? && !field_16.strip.start_with?("S")
end
def scheme_field
@ -1330,7 +1330,7 @@ private
end
def scheme_id
return field_16 if log_uses_new_scheme_id?
return field_16.strip if log_uses_new_scheme_id?
return field_15 if log_uses_old_scheme_id?
end

2
app/services/bulk_upload/lettings/year2024/row_parser.rb

@ -1358,7 +1358,7 @@ private
def scheme
return if field_5.nil? || owning_organisation.nil? || managing_organisation.nil?
@scheme ||= Scheme.where(id: (owning_organisation.owned_schemes + managing_organisation.owned_schemes).map(&:id)).find_by_id_on_multiple_fields(field_5, field_6)
@scheme ||= Scheme.where(id: (owning_organisation.owned_schemes + managing_organisation.owned_schemes).map(&:id)).find_by_id_on_multiple_fields(field_5.strip, field_6)
end
def location

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

@ -9,7 +9,7 @@
<span class="govuk-caption-l">Upload lettings logs in bulk (<%= @form.year_combo %>)</span>
<h1 class="govuk-heading-l">Prepare your file</h1>
<p class="govuk-body govuk-!-margin-bottom-2"><%= govuk_link_to "Read the full guidance", bulk_upload_lettings_log_path(id: "guidance", form: { year: @form.year }) %> before you start if you have not used bulk upload before.</p>
<p class="govuk-body govuk-!-margin-bottom-2"><%= govuk_link_to "Read the full guidance", bulk_upload_lettings_log_path(id: "guidance", form: { year: @form.year }, referrer: "prepare-your-file") %> before you start if you have not used bulk upload before.</p>
<h2 class="govuk-heading-s">Download template</h2>

2
app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2024.html.erb

@ -9,7 +9,7 @@
<span class="govuk-caption-l">Upload lettings logs in bulk (<%= @form.year_combo %>)</span>
<h1 class="govuk-heading-l">Prepare your file</h1>
<p class="govuk-body govuk-!-margin-bottom-2"><%= govuk_link_to "Read the full guidance", bulk_upload_lettings_log_path(id: "guidance", form: { year: @form.year }) %> before you start if you have not used bulk upload before.</p>
<p class="govuk-body govuk-!-margin-bottom-2"><%= govuk_link_to "Read the full guidance", bulk_upload_lettings_log_path(id: "guidance", form: { year: @form.year }, referrer: "prepare-your-file") %> before you start if you have not used bulk upload before.</p>
<h2 class="govuk-heading-s">Download template</h2>

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

@ -9,7 +9,7 @@
<span class="govuk-caption-l">Upload sales logs in bulk (<%= @form.year_combo %>)</span>
<h1 class="govuk-heading-l">Prepare your file</h1>
<p class="govuk-body govuk-!-margin-bottom-2"><%= govuk_link_to "Read the full guidance", bulk_upload_sales_log_path(id: "guidance", form: { year: @form.year }) %> before you start if you have not used bulk upload before.</p>
<p class="govuk-body govuk-!-margin-bottom-2"><%= govuk_link_to "Read the full guidance", bulk_upload_sales_log_path(id: "guidance", form: { year: @form.year }, referrer: "prepare-your-file") %> before you start if you have not used bulk upload before.</p>
<h2 class="govuk-heading-s">Download template</h2>

2
app/views/bulk_upload_sales_logs/forms/prepare_your_file_2024.html.erb

@ -9,7 +9,7 @@
<span class="govuk-caption-l">Upload sales logs in bulk (<%= @form.year_combo %>)</span>
<h1 class="govuk-heading-l">Prepare your file</h1>
<p class="govuk-body govuk-!-margin-bottom-2"><%= govuk_link_to "Read the full guidance", bulk_upload_sales_log_path(id: "guidance", form: { year: @form.year }) %> before you start if you have not used bulk upload before.</p>
<p class="govuk-body govuk-!-margin-bottom-2"><%= govuk_link_to "Read the full guidance", bulk_upload_sales_log_path(id: "guidance", form: { year: @form.year }, referrer: "prepare-your-file") %> before you start if you have not used bulk upload before.</p>
<h2 class="govuk-heading-s">Download template</h2>

1
app/views/layouts/_collection_resources.html.erb

@ -1,6 +1,7 @@
<% if current_user %>
<h1 class="govuk-heading-l">Collection resources</h1>
<p class="govuk-body"><strong><%= govuk_link_to "Guidance for submitting social housing lettings and sales data (CORE)", guidance_path %></strong></p>
<p class="govuk-body"><strong><%= govuk_link_to "How to upload logs in bulk", bulk_upload_lettings_log_path(id: "guidance", form: { year: current_collection_start_year }, referrer: "home") %></strong></p>
<% else %>
<h2 class="govuk-heading-m">Collection resources</h2>
<% end %>

24
app/views/organisations/delete_confirmation.html.erb

@ -0,0 +1,24 @@
<% content_for :before_content do %>
<% content_for :title, "Are you sure you want to delete this organisation?" %>
<%= govuk_back_link(href: :back) %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<span class="govuk-caption-xl">Delete <%= @organisation.name %></span>
<h1 class="govuk-heading-xl">
<%= content_for(:title) %>
</h1>
<%= govuk_warning_text(text: "You will not be able to undo this action.") %>
<div class="govuk-button-group">
<%= govuk_button_to(
"Delete this organisation",
delete_organisation_path(@organisation),
method: :delete,
) %>
<%= govuk_button_link_to "Cancel", organisation_path(@organisation), html: { method: :get }, secondary: true %>
</div>
</div>
</div>

4
app/views/organisations/show.html.erb

@ -52,3 +52,7 @@
<%= govuk_button_link_to "Reactivate this organisation", reactivate_organisation_path(@organisation) %>
</span>
<% end %>
<% if OrganisationPolicy.new(current_user, @organisation).delete? %>
<%= delete_organisation_link(@organisation) %>
<% end %>

2
app/views/start/guidance.html.erb

@ -9,7 +9,7 @@
<%= accordion.with_section(heading_text: "How to create logs", expanded: true) do %>
<p class="govuk-body">There are 2 ways to create logs on CORE.</p>
<p class="govuk-body">You can create logs one at a time by answering questions using the online form. Click the “Create a new log” button on the logs page to create logs this way.</p>
<p class="govuk-body">You can also create many logs at once by uploading a CSV file. This might be faster than creating logs individually if your organisation has its own database and a way to export the data. Click the “Upload logs in bulk” button on the logs page to create logs this way. For more information, <%= govuk_link_to "read the full guidance on bulk upload", bulk_upload_lettings_log_path(id: "guidance", form: { year: current_collection_start_year }) %>.</p>
<p class="govuk-body">You can also create many logs at once by uploading a CSV file. This might be faster than creating logs individually if your organisation has its own database and a way to export the data. Click the “Upload logs in bulk” button on the logs page to create logs this way. For more information, <%= govuk_link_to "read the full guidance on bulk upload", bulk_upload_lettings_log_path(id: "guidance", form: { year: current_collection_start_year }, referrer: "guidance") %>.</p>
<p class="govuk-body">Once you have created and completed a log, there is nothing more you need to do to submit the data.</p>
<% end %>

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

@ -67,7 +67,7 @@
<%= summary_list.with_row do |row|
row.with_key { "Organisation" }
row.with_value { @user.organisation.name }
row.with_value { current_user.support? ? govuk_link_to(@user.organisation.name, lettings_logs_organisation_path(@user.organisation)) : @user.organisation.name }
row.with_action
end %>

1
config/locales/en.yml

@ -202,6 +202,7 @@ en:
location_deleted: "%{postcode} has been deleted."
scheme_deleted: "%{service_name} has been deleted."
user_deleted: "%{name} has been deleted."
organisation_deleted: "%{name} has been deleted."
validations:
organisation:

2
config/routes.rb

@ -190,6 +190,8 @@ Rails.application.routes.draw do
get "sales-logs/filters/#{filter}", to: "sales_logs_filters#organisation_#{filter.underscore}"
get "sales-logs/filters/update-#{filter}", to: "sales_logs_filters#update_organisation_#{filter.underscore}"
end
get "delete-confirmation", to: "organisations#delete_confirmation"
delete "delete", to: "organisations#delete"
end
end

5
db/migrate/20240610142812_add_discarded_at_to_organisations.rb

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

3
db/schema.rb

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2024_05_29_133005) do
ActiveRecord::Schema[7.0].define(version: 2024_06_10_142812) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -492,6 +492,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_05_29_133005) do
t.datetime "merge_date"
t.bigint "absorbing_organisation_id"
t.datetime "available_from"
t.datetime "discarded_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

49
docs/testing.md

@ -31,3 +31,52 @@ bundle exec rake parallel:setup
```sh
RAILS_ENV=test bundle exec rake parallel:spec
```
## Factories for Lettings Log, Sales Log, Organisation, and User
Each of these factories has nested relationships and callbacks that ensure associated objects are created and linked properly. For instance, creating a `lettings_log` involves creating or associating with a `user`, which in turn is linked to an `organisation`, potentially leading to creating `organisation_rent_periods` and a `data_protection_confirmation`.
This documentation outlines the objects that are created and/or persisted to the database when using FactoryBot to create or build models for LettingsLog, SalesLog, Organisation, and User. There are other factories, but they are simpler, less frequently used and don't have as much resource hierarchy.
### Lettings Log
Objects Created/Persisted:
- **User**: The `assigned_to` user is created.
- **Organisation**: The `assigned_to` user’s organisation created by `User` factory.
- **DataProtectionConfirmation**: If `organisation` does not have DSA signed, `DataProtectionConfirmation` gets created with `assigned_to` user as a `data_protection_officer`
- **OrganisationRentPeriod**: If `log.period` is present and the `managing_organisation` does not have an `OrganisationRentPeriod` for that period, a new `OrganisationRentPeriod` is created and associated with `managing_organisation`.
Example Usage:
```
let(:lettings_log) { create(:lettings_log) }
```
### Sales Log
Objects Created/Persisted:
- **User**: The `assigned_to` user is created.
- **Organisation**: The `assigned_to` user’s organisation created by `User` factory.
- **DataProtectionConfirmation**: If `organisation` does not have DSA signed, `DataProtectionConfirmation` gets created with `assigned_to` user as a `data_protection_officer`
Example Usage:
```
let(:sales_log) { create(:sales_log) }
```
### Organisation
Objects Created/Persisted:
- **OrganisationRentPeriod**: For each rent period in transient attribute `rent_periods`, an `OrganisationRentPeriod` is created.
- **DataProtectionConfirmation**: If `with_dsa` is `true` (default), a `DataProtectionConfirmation` is created with a `data_protection_officer`
- **User**: Data protection officer that signs the data protection confirmation
Example Usage:
```
let(:organisation) { create(:organisation, rent_periods: [1, 2])}
```
### User
Objects Created/Persisted:
- **Organisation**: User’s organisation.
- **DataProtectionConfirmation**: If `organisation` does not have DSA signed, `DataProtectionConfirmation` gets created with this user as a `data_protection_officer`
Example Usage:
```
let(:user) { create(:user) }
```

170
lib/tasks/handle_unpended_logs.rake

@ -0,0 +1,170 @@
desc "Deduplicates logs where we have inadvertently turned some pending logs to in progress / completed"
task :handle_unpended_logs, %i[perform_updates] => :environment do |_task, args|
dry_run = args[:perform_updates].blank? || args[:perform_updates] != "true"
pg = ActiveRecord::Base.connection
query = "SELECT \"versions\".* FROM \"versions\" WHERE \"versions\".\"item_type\" = 'LettingsLog' AND whodunnit is null AND ((object_changes like '%status:\n- 3\n- 1%') OR (object_changes like '%status:\n- 3\n- 2%'))"
results = pg.execute(query)
duplicate_log_attributes = %w[owning_organisation_id tenancycode startdate age1_known age1 sex1 ecstat1 tcharge household_charge chcharge]
seen = [].to_set
output = CSV.generate(headers: true) do |csv|
csv << ["Log ID", "Collection Year", "Current Status", "Owning Org", "Owner Id", "Owner Email", "Outcome", "Reason"]
results.each do |result|
next if result["object_changes"].count("\n") <= 7 && result["object_changes"].include?("status:\n- 3\n- 2")
id = YAML.safe_load(result["object"], permitted_classes: [Time, BigDecimal])["id"]
next if seen.include?(id)
log = LettingsLog.find(id)
if log.updated_at != result["created_at"]
has_been_manually_updated = false
v = log.versions.last
while !has_been_manually_updated && v.created_at != result["created_at"]
if !v.whodunnit.nil?
has_been_manually_updated = true
else
v = v.previous
end
end
if has_been_manually_updated
seen.add(id)
csv << [id, log.collection_start_year, log.status, log.owning_organisation_name, log.assigned_to_id, log.assigned_to.email, "Leave", "Log updated in UI"]
next
end
end
# This is the normal query for duplicates but without the check that the logs are visible (i.e. not deleted/pending)
duplicates = LettingsLog.where.not(id: log.id)
.where.not(startdate: nil)
.where.not(sex1: nil)
.where.not(ecstat1: nil)
.where.not(needstype: nil)
.age1_answered
.tcharge_answered
.chcharge_answered
.location_for_log_answered(log)
.postcode_for_log_answered(log)
.where(log.slice(*duplicate_log_attributes))
duplicate_count = duplicates.length
if duplicate_count.zero?
seen.add(id)
csv << [id, log.collection_start_year, log.status, log.owning_organisation_name, log.assigned_to_id, log.assigned_to.email, "Leave", "Log has no duplicates"]
next
end
visible_duplicates = duplicates.where(status: %w[in_progress completed])
deleted_duplicates = duplicates.where(status: %w[deleted])
if visible_duplicates.length.zero? && deleted_duplicates.any? { |dup| dup.discarded_at > result["created_at"] }
seen.add(id)
csv << [id, log.collection_start_year, log.status, log.owning_organisation_name, log.assigned_to_id, log.assigned_to.email, "Leave", "Log has no visible duplicates and at least one duplicate has been deleted since being affected"]
next
end
if visible_duplicates.length.zero?
seen.add(id)
csv << [id, log.collection_start_year, log.status, log.owning_organisation_name, log.assigned_to_id, log.assigned_to.email, "Leave", "Log has no visible duplicates"]
next
end
unaffected_duplicates = []
affected_updated_duplicates = []
affected_non_updated_duplicates = [log]
visible_duplicates.each do |dup|
vquery = "SELECT \"versions\".* FROM \"versions\" WHERE \"versions\".\"item_type\" = 'LettingsLog' AND \"versions\".\"item_id\" = #{dup.id} AND whodunnit is null AND ((object_changes like '%status:\n- 3\n- 1%') OR (object_changes like '%status:\n- 3\n- 2%'))"
res = pg.execute(vquery)
if res.count.zero?
unaffected_duplicates.push(dup)
elsif res[0]["object_changes"].count("\n") <= 7 && res[0]["object_changes"].include?("status:\n- 3\n- 2")
unaffected_duplicates.push(dup)
else
has_been_manually_updated = false
v = dup.versions.last
while !has_been_manually_updated && v.created_at != res[0]["created_at"]
if !v.whodunnit.nil?
has_been_manually_updated = true
else
v = v.previous
end
end
if has_been_manually_updated
affected_updated_duplicates.push(dup)
else
affected_non_updated_duplicates.push(dup)
end
end
end
unless unaffected_duplicates.empty?
unaffected_logs_reference = "log#{unaffected_duplicates.length > 1 ? 's' : ''} #{unaffected_duplicates.map(&:id).join(', ')}"
affected_updated_duplicates.each do |d|
unless seen.include?(d.id)
seen.add(d.id)
csv << [d.id, d.collection_start_year, d.status, d.owning_organisation_name, d.assigned_to_id, d.assigned_to.email, "Leave", "Log updated in UI"]
end
end
affected_non_updated_duplicates.each do |d|
seen.add(d.id)
csv << [d.id, d.collection_start_year, d.status, d.owning_organisation_name, d.assigned_to_id, d.assigned_to.email, "Delete", "Log is a duplicate of unaffected log(s)", unaffected_logs_reference]
# rubocop:disable Style/Next
unless dry_run
d.discarded_at = Time.zone.now
d.status = "deleted"
d.save!(validate: false)
end
end
next
end
unless affected_updated_duplicates.empty?
updated_logs_reference = "log#{affected_updated_duplicates.length > 1 ? 's' : ''} #{affected_updated_duplicates.map(&:id).join(', ')}"
affected_updated_duplicates.each do |d|
unless seen.include?(d.id)
seen.add(d.id)
csv << [d.id, d.collection_start_year, d.status, d.owning_organisation_name, d.assigned_to_id, d.assigned_to.email, "Leave", "Log updated in UI"]
end
end
affected_non_updated_duplicates.each do |d|
seen.add(d.id)
csv << [d.id, d.collection_start_year, d.status, d.owning_organisation_name, d.assigned_to_id, d.assigned_to.email, "Delete", "Log is a duplicate of log(s) which have been updated since being affected", updated_logs_reference]
unless dry_run
d.discarded_at = Time.zone.now
d.status = "deleted"
d.save!(validate: false)
end
end
next
end
latest_created = affected_non_updated_duplicates.max_by(&:created_at)
seen.add(latest_created.id)
csv << [latest_created.id, latest_created.collection_start_year, latest_created.status, latest_created.owning_organisation_name, latest_created.assigned_to_id, latest_created.assigned_to.email, "Leave", "Log is the most recently created of a duplicate group"]
affected_non_updated_duplicates.each do |d|
next unless d.id != latest_created.id
seen.add(d.id)
csv << [d.id, d.collection_start_year, d.status, d.owning_organisation_name, d.assigned_to_id, d.assigned_to.email, "Delete", "Log is a duplicate of more recently created affected log", latest_created.id]
unless dry_run
d.discarded_at = Time.zone.now
d.status = "deleted"
d.save!(validate: false)
end
# rubocop:enable Style/Next
end
end
end
s3_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
output_file = "HandleUnpendedLogs_#{dry_run ? 'dry_run' : ''}_#{Time.zone.now}.csv"
s3_service.write_file(output_file, output)
end

19
lib/tasks/recalculate_status_when_la_missing.rake

@ -0,0 +1,19 @@
desc "Recalculates status for 2023 completed logs with missing LA"
task recalculate_status_missing_la: :environment do
# See reinfer_local_authority - this covers cases where postcode_full was not set that should have been returned to in progress
LettingsLog.filter_by_year(2023).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(2023).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

2
spec/components/create_log_actions_component_spec.rb

@ -66,7 +66,7 @@ RSpec.describe CreateLogActionsComponent, type: :component do
context "when not support user" do
context "without data sharing agreement" do
let(:user) { create(:user, organisation: create(:organisation, :without_dpc)) }
let(:user) { create(:user, organisation: create(:organisation, :without_dpc), with_dsa: false) }
it "does not render actions" do
expect(component).not_to be_display_actions

16
spec/components/data_protection_confirmation_banner_component_spec.rb

@ -5,11 +5,11 @@ RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
let(:component) { described_class.new(user:, organisation:) }
let(:render) { render_inline(component) }
let(:user) { create(:user) }
let(:user) { create(:user, with_dsa: false) }
let(:organisation) { user.organisation }
context "when user is support and organisation is blank" do
let(:user) { create(:user, :support) }
let(:user) { create(:user, :support, with_dsa: false) }
let(:organisation) { nil }
it "does not display banner" do
@ -37,8 +37,8 @@ RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
context "when org does not have a signed data sharing agreement" do
context "when user is not a DPO" do
let(:organisation) { create(:organisation, :without_dpc) }
let(:user) { create(:user, organisation:) }
let!(:dpo) { create(:user, :data_protection_officer, organisation:) }
let(:user) { create(:user, organisation:, with_dsa: false) }
let!(:dpo) { create(:user, :data_protection_officer, organisation:, with_dsa: false) }
it "displays the banner and shows DPOs" do
expect(component.display_banner?).to eq(true)
@ -50,7 +50,7 @@ RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
context "when user is a DPO" do
let(:organisation) { create(:organisation, :without_dpc) }
let(:user) { create(:user, :data_protection_officer, organisation:) }
let(:user) { create(:user, :data_protection_officer, organisation:, with_dsa: false) }
it "displays the banner and asks to sign" do
expect(component.display_banner?).to eq(true)
@ -141,8 +141,8 @@ RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
context "when org does not have a signed data sharing agreement" do
context "when user is not a DPO" do
let(:organisation) { create(:organisation, :without_dpc) }
let(:user) { create(:user, organisation:) }
let!(:dpo) { create(:user, :data_protection_officer, organisation:) }
let(:user) { create(:user, organisation:, with_dsa: false) }
let!(:dpo) { create(:user, :data_protection_officer, organisation:, with_dsa: false) }
it "displays the banner and shows DPOs" do
expect(component.display_banner?).to eq(true)
@ -168,7 +168,7 @@ RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
context "when user is a DPO" do
let(:organisation) { create(:organisation, :without_dpc) }
let(:user) { create(:user, :data_protection_officer, organisation:) }
let(:user) { create(:user, :data_protection_officer, organisation:, with_dsa: false) }
it "displays the banner and asks to sign" do
expect(component.display_banner?).to eq(true)

4
spec/factories/organisation.rb

@ -25,11 +25,11 @@ FactoryBot.define do
end
after(:create) do |org, evaluator|
if evaluator.with_dsa
if evaluator.with_dsa && !org.data_protection_confirmed?
create(
:data_protection_confirmation,
organisation: org,
data_protection_officer: org.users.any? ? org.users.first : create(:user, :data_protection_officer, organisation: org),
data_protection_officer: org.users.any? ? org.users.first : create(:user, :data_protection_officer, organisation: org, with_dsa: false),
)
end
end

16
spec/factories/user.rb

@ -3,7 +3,7 @@ FactoryBot.define do
sequence(:email) { "test#{SecureRandom.hex}@example.com" }
name { "Danny Rojas" }
password { "pAssword1" }
organisation
organisation { association :organisation, with_dsa: is_dpo ? false : true }
role { "data_provider" }
phone { "1234512345123" }
trait :data_provider do
@ -27,10 +27,18 @@ FactoryBot.define do
old_user_id { SecureRandom.uuid }
end
after(:create) do |user, evaluator|
FactoryBot.create(:legacy_user, old_user_id: evaluator.old_user_id, user:)
transient do
with_dsa { true }
end
user.reload
after(:create) do |user, evaluator|
if evaluator.with_dsa && !user.organisation.data_protection_confirmed?
create(
:data_protection_confirmation,
organisation: user.organisation,
data_protection_officer: user,
)
end
end
end
end

2
spec/features/user_spec.rb

@ -594,7 +594,7 @@ RSpec.describe "User Features" do
end
before do
other_user.update!(initial_confirmation_sent: true, last_sign_in_at: nil)
other_user.update!(initial_confirmation_sent: true, last_sign_in_at: nil, old_user_id: "old-user-id")
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in(user)
visit(user_path(other_user))

8
spec/helpers/tab_nav_helper_spec.rb

@ -2,21 +2,21 @@ require "rails_helper"
RSpec.describe TabNavHelper do
let(:organisation) { FactoryBot.create(:organisation) }
let(:user) { FactoryBot.build(:user, organisation:) }
let(:current_user) { FactoryBot.build(:user, organisation:) }
let(:scheme) { FactoryBot.create(:scheme, service_name: "Some name") }
let(:location) { FactoryBot.create(:location, scheme:) }
describe "#user_cell" do
it "returns user link and email separated by a newline character" do
expected_html = "<a class=\"govuk-link\" href=\"/users\">#{user.name}</a>\n<span class=\"govuk-visually-hidden\">User </span><span class=\"govuk-!-font-weight-regular app-!-colour-muted\">#{user.email}</span>"
expect(user_cell(user)).to match(expected_html)
expected_html = "<a class=\"govuk-link\" href=\"/users\">#{current_user.name}</a>\n<span class=\"govuk-visually-hidden\">User </span><span class=\"govuk-!-font-weight-regular app-!-colour-muted\">#{current_user.email}</span>"
expect(user_cell(current_user)).to match(expected_html)
end
end
describe "#org_cell" do
it "returns the users org name and role separated by a newline character" do
expected_html = "DLUHC\n<span class=\"app-!-colour-muted\">Data provider</span>"
expect(org_cell(user)).to match(expected_html)
expect(org_cell(current_user)).to match(expected_html)
end
end

1
spec/mailers/resend_invitation_mailer_spec.rb

@ -41,6 +41,7 @@ RSpec.describe ResendInvitationMailer do
end
it "sends an initial invitation" do
FactoryBot.create(:legacy_user, old_user_id: new_active_migrated_user.old_user_id, user: new_active_migrated_user)
expect(notify_client).to receive(:send_email).with(email_address: "new_active_migrated_user@example.com", template_id: User::BETA_ONBOARDING_TEMPLATE_ID, personalisation:).once
described_class.new.resend_invitation_email(new_active_migrated_user)
end

2
spec/models/form/lettings/pages/address_matcher_spec.rb

@ -6,7 +6,7 @@ RSpec.describe Form::Lettings::Pages::AddressMatcher, type: :model do
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
let(:log) { create(:lettings_log) }
let(:log) { build(:lettings_log) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)

2
spec/models/form/lettings/pages/uprn_selection_spec.rb

@ -6,7 +6,7 @@ RSpec.describe Form::Lettings::Pages::UprnSelection, type: :model do
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
let(:log) { create(:lettings_log) }
let(:log) { build(:lettings_log) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)

2
spec/models/form/lettings/pages/uprn_spec.rb

@ -45,7 +45,7 @@ RSpec.describe Form::Lettings::Pages::Uprn, type: :model do
end
context "when log is present" do
let(:log) { create(:lettings_log) }
let(:log) { build(:lettings_log) }
context "with 2023/24 form" do
it "points to address page" do

9
spec/models/form/lettings/questions/managing_organisation_spec.rb

@ -58,6 +58,7 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do
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(:deleted_managing_org) { create(:organisation, name: "Deleted managing org", discarded_at: Time.zone.yesterday) }
let(:log) { create(:lettings_log, managing_organisation: managing_org1) }
let!(:org_rel1) do
@ -79,6 +80,7 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model 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)
create(:organisation_relationship, parent_organisation: user.organisation, child_organisation: deleted_managing_org)
expect(question.displayed_answer_options(log, user)).to eq(options)
end
@ -104,7 +106,10 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do
end
before do
create(:organisation, name: "Inactive managing org", active: false)
inactive_managing_org = create(:organisation, name: "Inactive managing org", active: false)
create(:organisation_relationship, parent_organisation: log_owning_org, child_organisation: inactive_managing_org)
deleted_managing_org = create(:organisation, name: "Deleted managing org", discarded_at: Time.zone.yesterday)
create(:organisation_relationship, parent_organisation: log_owning_org, child_organisation: deleted_managing_org)
end
context "when org owns stock" do
@ -192,10 +197,12 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do
context "when organisation has merged" do
let(:absorbing_org) { create(:organisation, name: "Absorbing org", holds_own_stock: true) }
let!(:merged_org) { create(:organisation, name: "Merged org", holds_own_stock: false) }
let!(:merged_deleted_org) { create(:organisation, name: "Merged org", holds_own_stock: false, discarded_at: Time.zone.yesterday) }
let(:user) { create(:user, :data_coordinator, organisation: absorbing_org) }
let(:log) do
merged_org.update!(merge_date: Time.zone.local(2023, 8, 2), absorbing_organisation_id: absorbing_org.id)
merged_deleted_org.update!(merge_date: Time.zone.local(2023, 8, 2), absorbing_organisation_id: absorbing_org.id)
create(:lettings_log, owning_organisation: absorbing_org, managing_organisation: nil)
end

2
spec/models/form/lettings/questions/property_reference_spec.rb

@ -6,7 +6,7 @@ RSpec.describe Form::Lettings::Questions::PropertyReference, type: :model do
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
let(:lettings_log) { FactoryBot.create(:lettings_log) }
let(:lettings_log) { FactoryBot.build(:lettings_log) }
it "has correct page" do
expect(question.page).to eq(page)

24
spec/models/form/lettings/questions/stock_owner_spec.rb

@ -47,6 +47,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(:deleted_owning_org) { create(:organisation, name: "Deleted owning org", discarded_at: Time.zone.yesterday) }
let!(:org_rel) do
create(:organisation_relationship, child_organisation: user.organisation, parent_organisation: owning_org_2)
end
@ -64,6 +65,7 @@ RSpec.describe Form::Lettings::Questions::StockOwner, type: :model 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)
create(:organisation_relationship, child_organisation: user.organisation, parent_organisation: deleted_owning_org)
user.organisation.update!(holds_own_stock: true)
expect(question.displayed_answer_options(log, user)).to eq(options)
end
@ -127,6 +129,7 @@ RSpec.describe Form::Lettings::Questions::StockOwner, type: :model do
context "when user's org has recently absorbed other orgs and does not have available from date" do
let(:merged_organisation) { create(:organisation, name: "Merged org") }
let(:merged_deleted_organisation) { create(:organisation, name: "Merged deleted org", discarded_at: Time.zone.yesterday) }
let(:options) do
{
"" => "Select an option",
@ -139,6 +142,7 @@ RSpec.describe Form::Lettings::Questions::StockOwner, type: :model do
before do
merged_organisation.update!(merge_date: Time.zone.local(2023, 2, 2), absorbing_organisation: user.organisation)
merged_deleted_organisation.update!(merge_date: Time.zone.local(2023, 2, 2), absorbing_organisation: user.organisation)
allow(Time).to receive(:now).and_return(Time.zone.local(2023, 11, 10))
end
@ -172,6 +176,7 @@ RSpec.describe Form::Lettings::Questions::StockOwner, type: :model do
context "when user's org has absorbed other orgs with parent organisations during closed collection periods" do
let(:merged_organisation) { create(:organisation, name: "Merged org") }
let(:merged_deleted_organisation) { create(:organisation, name: "Merged deleted org", discarded_at: Time.zone.yesterday) }
let(:options) do
{
"" => "Select an option",
@ -184,6 +189,7 @@ RSpec.describe Form::Lettings::Questions::StockOwner, type: :model do
allow(Time).to receive(:now).and_return(Time.zone.local(2023, 4, 2))
org_rel.update!(child_organisation: merged_organisation)
merged_organisation.update!(merge_date: Time.zone.local(2021, 6, 2), absorbing_organisation: user.organisation)
merged_deleted_organisation.update!(merge_date: Time.zone.local(2021, 6, 2), absorbing_organisation: user.organisation)
user.organisation.update!(available_from: Time.zone.local(2021, 2, 2))
end
@ -200,8 +206,9 @@ RSpec.describe Form::Lettings::Questions::StockOwner, type: :model do
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)
deleted_organisation = create(:organisation, name: "Deleted org", discarded_at: Time.zone.yesterday)
expected_opts = Organisation.filter_by_active.where(holds_own_stock: true).each_with_object(options) do |organisation, hsh|
expected_opts = Organisation.visible.filter_by_active.where(holds_own_stock: true).each_with_object(options) do |organisation, hsh|
hsh[organisation.id] = organisation.name
hsh
end
@ -209,10 +216,12 @@ RSpec.describe Form::Lettings::Questions::StockOwner, type: :model 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)
expect(question.displayed_answer_options(log, user)).not_to include(deleted_organisation.id)
end
context "and org has recently absorbed other orgs and does not have available from date" do
let(:merged_organisation) { create(:organisation, name: "Merged org") }
let(:merged_deleted_organisation) { create(:organisation, name: "Merged deleted org", discarded_at: Time.zone.yesterday) }
let(:org) { create(:organisation, name: "User org") }
let(:options) do
{
@ -226,6 +235,7 @@ RSpec.describe Form::Lettings::Questions::StockOwner, type: :model do
before do
merged_organisation.update!(merge_date: Time.zone.local(2023, 2, 2), absorbing_organisation: org)
merged_deleted_organisation.update!(merge_date: Time.zone.local(2023, 2, 2), absorbing_organisation: org)
allow(Time).to receive(:now).and_return(Time.zone.local(2023, 11, 10))
end
@ -273,6 +283,18 @@ RSpec.describe Form::Lettings::Questions::StockOwner, type: :model do
expect(question.hidden_in_check_answers?(nil, user)).to be false
end
end
context "when stock owner is deleted" do
before do
organisation_relationship = create(:organisation_relationship, child_organisation: user.organisation)
organisation_relationship.parent_organisation.update!(discarded_at: Time.zone.yesterday)
end
it "is hidden in check answers" do
expect(user.organisation.stock_owners.count).to eq(1)
expect(question.hidden_in_check_answers?(nil, user)).to be true
end
end
end
context "when org does not hold own stock", :aggregate_failures do

10
spec/models/form/lettings/questions/uprn_confirmation_spec.rb

@ -42,7 +42,7 @@ RSpec.describe Form::Lettings::Questions::UprnConfirmation, type: :model do
describe "notification_banner" do
context "when address is not present" do
it "returns nil" do
log = create(:lettings_log)
log = build(:lettings_log)
expect(question.notification_banner(log)).to be_nil
end
@ -50,7 +50,7 @@ RSpec.describe Form::Lettings::Questions::UprnConfirmation, type: :model do
context "when address is present" do
it "returns formatted value" do
log = create(:lettings_log, :setup_completed, address_line1: "1, Test Street", town_or_city: "Test Town", postcode_full: "AA1 1AA", uprn: "1", uprn_known: 1)
log = build(:lettings_log, :setup_completed, address_line1: "1, Test Street", town_or_city: "Test Town", postcode_full: "AA1 1AA", uprn: "1", uprn_known: 1)
expect(question.notification_banner(log)).to eq(
{
@ -64,7 +64,7 @@ RSpec.describe Form::Lettings::Questions::UprnConfirmation, type: :model do
describe "has the correct hidden_in_check_answers" do
context "when uprn_known != 1 && uprn_confirmed == nil" do
let(:log) { create(:lettings_log, uprn_known: 0, uprn_confirmed: nil) }
let(:log) { build(:lettings_log, uprn_known: 0, uprn_confirmed: nil) }
it "returns true" do
expect(question.hidden_in_check_answers?(log)).to eq(true)
@ -72,7 +72,7 @@ RSpec.describe Form::Lettings::Questions::UprnConfirmation, type: :model do
end
context "when uprn_known == 1 && uprn_confirmed == nil" do
let(:log) { create(:lettings_log, :completed, uprn_known: 1, uprn: 1, uprn_confirmed: nil) }
let(:log) { build(:lettings_log, :completed, uprn_known: 1, uprn: 1, uprn_confirmed: nil) }
it "returns false" do
expect(question.hidden_in_check_answers?(log)).to eq(false)
@ -80,7 +80,7 @@ RSpec.describe Form::Lettings::Questions::UprnConfirmation, type: :model do
end
context "when uprn_known != 1 && uprn_confirmed == 1" do
let(:log) { create(:lettings_log) }
let(:log) { build(:lettings_log) }
it "returns true" do
log.uprn_known = 1

2
spec/models/form/sales/pages/address_matcher_spec.rb

@ -6,7 +6,7 @@ RSpec.describe Form::Sales::Pages::AddressMatcher, type: :model do
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
let(:log) { create(:sales_log) }
let(:log) { build(:sales_log) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)

6
spec/models/form/sales/pages/uprn_confirmation_spec.rb

@ -33,7 +33,7 @@ RSpec.describe Form::Sales::Pages::UprnConfirmation, type: :model do
describe "has correct routed_to?" do
context "when uprn present && uprn_known == 1 " do
let(:log) { create(:sales_log) }
let(:log) { build(:sales_log) }
it "returns true" do
log.uprn_known = 1
@ -43,7 +43,7 @@ RSpec.describe Form::Sales::Pages::UprnConfirmation, type: :model do
end
context "when uprn = nil" do
let(:log) { create(:sales_log, uprn_known: 1, uprn: nil) }
let(:log) { build(:sales_log, uprn_known: 1, uprn: nil) }
it "returns false" do
expect(page.routed_to?(log)).to eq(false)
@ -51,7 +51,7 @@ RSpec.describe Form::Sales::Pages::UprnConfirmation, type: :model do
end
context "when uprn_known == 0" do
let(:log) { create(:sales_log, uprn_known: 0, uprn: "123456789") }
let(:log) { build(:sales_log, uprn_known: 0, uprn: "123456789") }
it "returns false" do
expect(page.routed_to?(log)).to eq(false)

2
spec/models/form/sales/pages/uprn_selection_spec.rb

@ -6,7 +6,7 @@ RSpec.describe Form::Sales::Pages::UprnSelection, type: :model do
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
let(:log) { create(:sales_log) }
let(:log) { build(:sales_log) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)

2
spec/models/form/sales/pages/uprn_spec.rb

@ -45,7 +45,7 @@ RSpec.describe Form::Sales::Pages::Uprn, type: :model do
end
context "when log is present" do
let(:log) { create(:sales_log) }
let(:log) { build(:sales_log) }
context "with 2023/24 form" do
it "points to address page" do

2
spec/models/form/sales/questions/address_line1_for_address_matcher_spec.rb

@ -6,7 +6,7 @@ RSpec.describe Form::Sales::Questions::AddressLine1ForAddressMatcher, type: :mod
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:log) { create(:sales_log, :in_progress, address_line1_input: "Address line 1", postcode_full_input: "AA1 1AA") }
let(:log) { build(:sales_log, :in_progress, address_line1_input: "Address line 1", postcode_full_input: "AA1 1AA") }
it "has correct page" do
expect(question.page).to eq(page)

2
spec/models/form/sales/questions/buyer1_mortgage_spec.rb

@ -6,7 +6,7 @@ RSpec.describe Form::Sales::Questions::Buyer1Mortgage, type: :model do
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
let(:log) { create(:sales_log) }
let(:log) { build(:sales_log) }
it "has correct page" do
expect(question.page).to eq(page)

2
spec/models/form/sales/questions/buyer2_mortgage_spec.rb

@ -6,7 +6,7 @@ RSpec.describe Form::Sales::Questions::Buyer2Mortgage, type: :model do
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
let(:log) { create(:sales_log) }
let(:log) { build(:sales_log) }
it "has correct page" do
expect(question.page).to eq(page)

2
spec/models/form/sales/questions/buyers_organisations_spec.rb

@ -48,7 +48,7 @@ RSpec.describe Form::Sales::Questions::BuyersOrganisations, type: :model do
end
it "has the correct displayed answer_options" do
expect(question.displayed_answer_options(FactoryBot.create(:sales_log))).to eq(
expect(question.displayed_answer_options(FactoryBot.build(:sales_log))).to eq(
{
"pregyrha" => { "value" => "Their private registered provider (PRP) - housing association" },
"pregother" => { "value" => "Other private registered provider (PRP) - housing association" },

2
spec/models/form/sales/questions/deposit_amount_spec.rb

@ -9,7 +9,7 @@ RSpec.describe Form::Sales::Questions::DepositAmount, type: :model do
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
context "when the ownership type is shared" do
let(:log) { create(:sales_log, :completed, ownershipsch: 1, mortgageused: 2) }
let(:log) { build(:sales_log, :completed, ownershipsch: 1, mortgageused: 2) }
it "is not marked as derived" do
expect(question.derived?(log)).to be false

4
spec/models/form/sales/questions/managing_organisation_spec.rb

@ -58,6 +58,7 @@ RSpec.describe Form::Sales::Questions::ManagingOrganisation, type: :model do
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(:deleted_org) { create(:organisation, name: "Deleted org", discarded_at: Time.zone.yesterday) }
let(:log) do
create(:lettings_log, owning_organisation: log_owning_org, managing_organisation: managing_org1,
@ -83,6 +84,7 @@ RSpec.describe Form::Sales::Questions::ManagingOrganisation, type: :model 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)
create(:organisation_relationship, parent_organisation: log_owning_org, child_organisation: deleted_org)
log_owning_org.update!(holds_own_stock: true)
expect(question.displayed_answer_options(log, user)).to eq(options)
end
@ -135,10 +137,12 @@ RSpec.describe Form::Sales::Questions::ManagingOrganisation, type: :model do
context "when organisation has merged" do
let(:absorbing_org) { create(:organisation, name: "Absorbing org", holds_own_stock: true) }
let!(:merged_org) { create(:organisation, name: "Merged org", holds_own_stock: false) }
let!(:merged_deleted_org) { create(:organisation, name: "Merged deleted org", holds_own_stock: false, discarded_at: Time.zone.yesterday) }
let(:user) { create(:user, :support, organisation: absorbing_org) }
let(:log) do
merged_org.update!(merge_date: Time.zone.local(2023, 8, 2), absorbing_organisation_id: absorbing_org.id)
merged_deleted_org.update!(merge_date: Time.zone.local(2023, 8, 2), absorbing_organisation_id: absorbing_org.id)
create(:lettings_log, owning_organisation: absorbing_org, managing_organisation: nil)
end

9
spec/models/form/sales/questions/owning_organisation_id_spec.rb

@ -123,6 +123,11 @@ RSpec.describe Form::Sales::Questions::OwningOrganisationId, type: :model do
it "shows merged organisation as an option" do
expect(question.displayed_answer_options(log, user)).to eq(options)
end
it "does not show absorbed organisation if it has been deleted" do
merged_organisation.update!(discarded_at: Time.zone.yesterday)
expect(question.displayed_answer_options(log, user)).to eq(options.except(merged_organisation.id))
end
end
context "when user's org has recently absorbed other orgs and it has available from date" do
@ -200,8 +205,9 @@ RSpec.describe Form::Sales::Questions::OwningOrganisationId, type: :model do
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)
deleted_organisation = create(:organisation, discarded_at: Time.zone.yesterday)
expected_opts = Organisation.filter_by_active.where(holds_own_stock: true).each_with_object(options) do |organisation, hsh|
expected_opts = Organisation.visible.filter_by_active.where(holds_own_stock: true).each_with_object(options) do |organisation, hsh|
hsh[organisation.id] = organisation.name
hsh
end
@ -210,6 +216,7 @@ RSpec.describe Form::Sales::Questions::OwningOrganisationId, type: :model do
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)
expect(question.displayed_answer_options(log, user)).not_to include(deleted_organisation.id)
end
context "when an org has recently absorbed other orgs" do

2
spec/models/form/sales/questions/postcode_for_address_matcher_spec.rb

@ -6,7 +6,7 @@ RSpec.describe Form::Sales::Questions::PostcodeForAddressMatcher, type: :model d
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:log) { create(:sales_log, :in_progress, address_line1_input: "Address line 1", postcode_full_input: "AA1 1AA") }
let(:log) { build(:sales_log, :in_progress, address_line1_input: "Address line 1", postcode_full_input: "AA1 1AA") }
it "has correct page" do
expect(question.page).to eq(page)

8
spec/models/form/sales/questions/uprn_confirmation_spec.rb

@ -42,7 +42,7 @@ RSpec.describe Form::Sales::Questions::UprnConfirmation, type: :model do
describe "notification_banner" do
context "when address is not present" do
it "returns nil" do
log = create(:sales_log)
log = build(:sales_log)
expect(question.notification_banner(log)).to be_nil
end
@ -64,7 +64,7 @@ RSpec.describe Form::Sales::Questions::UprnConfirmation, type: :model do
describe "has the correct hidden_in_check_answers" do
context "when uprn_known != 1 && uprn_confirmed == nil" do
let(:log) { create(:sales_log, uprn_known: 0, uprn_confirmed: nil) }
let(:log) { build(:sales_log, uprn_known: 0, uprn_confirmed: nil) }
it "returns true" do
expect(question.hidden_in_check_answers?(log)).to eq(true)
@ -72,7 +72,7 @@ RSpec.describe Form::Sales::Questions::UprnConfirmation, type: :model do
end
context "when uprn_known == 1 && uprn_confirmed == nil" do
let(:log) { create(:sales_log) }
let(:log) { build(:sales_log) }
it "returns false" do
log.uprn_known = 1
@ -83,7 +83,7 @@ RSpec.describe Form::Sales::Questions::UprnConfirmation, type: :model do
end
context "when uprn_known != 1 && uprn_confirmed == 1" do
let(:log) { create(:sales_log, uprn_known: 1, uprn: "12345", uprn_confirmed: 1) }
let(:log) { build(:sales_log, uprn_known: 1, uprn: "12345", uprn_confirmed: 1) }
it "returns true" do
expect(question.hidden_in_check_answers?(log)).to eq(true)

2
spec/models/form/sales/questions/uprn_selection_spec.rb

@ -6,7 +6,7 @@ RSpec.describe Form::Sales::Questions::UprnSelection, type: :model do
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, skip_href: "skip_href") }
let(:log) { create(:sales_log, :in_progress, address_line1_input: "Address line 1", postcode_full_input: "AA1 1AA") }
let(:log) { build(:sales_log, :in_progress, address_line1_input: "Address line 1", postcode_full_input: "AA1 1AA") }
let(:address_client_instance) { AddressClient.new(log.address_string) }
before do

2
spec/models/form/sales/questions/uprn_spec.rb

@ -45,7 +45,7 @@ RSpec.describe Form::Sales::Questions::Uprn, type: :model do
describe "get_extra_check_answer_value" do
context "when address is not present" do
let(:log) { create(:sales_log) }
let(:log) { build(:sales_log) }
it "returns nil" do
expect(question.get_extra_check_answer_value(log)).to be_nil

4
spec/models/form/sales/subsections/discounted_ownership_scheme_spec.rb

@ -67,7 +67,7 @@ RSpec.describe Form::Sales::Subsections::DiscountedOwnershipScheme, type: :model
end
context "when it is a discounted ownership scheme" do
let(:log) { FactoryBot.create(:sales_log, ownershipsch: 2) }
let(:log) { FactoryBot.build(:sales_log, ownershipsch: 2) }
it "is displayed in tasklist" do
expect(discounted_ownership_scheme.displayed_in_tasklist?(log)).to eq(true)
@ -75,7 +75,7 @@ RSpec.describe Form::Sales::Subsections::DiscountedOwnershipScheme, type: :model
end
context "when it is not a discounted ownership scheme" do
let(:log) { FactoryBot.create(:sales_log, ownershipsch: 1) }
let(:log) { FactoryBot.build(:sales_log, ownershipsch: 1) }
it "is displayed in tasklist" do
expect(discounted_ownership_scheme.displayed_in_tasklist?(log)).to eq(false)

4
spec/models/form/sales/subsections/household_characteristics_spec.rb

@ -378,7 +378,7 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
end
context "when the sale is to a company buyer" do
let(:log) { FactoryBot.create(:sales_log, ownershipsch: 3, companybuy: 1) }
let(:log) { FactoryBot.build(:sales_log, ownershipsch: 3, companybuy: 1) }
it "is not displayed in tasklist" do
expect(household_characteristics.displayed_in_tasklist?(log)).to eq(false)
@ -386,7 +386,7 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
end
context "when the sale is not to a company buyer" do
let(:log) { FactoryBot.create(:sales_log, ownershipsch: 3, companybuy: 2) }
let(:log) { FactoryBot.build(:sales_log, ownershipsch: 3, companybuy: 2) }
it "is displayed in tasklist" do
expect(household_characteristics.displayed_in_tasklist?(log)).to eq(true)

4
spec/models/form/sales/subsections/outright_sale_spec.rb

@ -122,7 +122,7 @@ RSpec.describe Form::Sales::Subsections::OutrightSale, type: :model do
end
context "when it is a outright sale" do
let(:log) { FactoryBot.create(:sales_log, ownershipsch: 3) }
let(:log) { FactoryBot.build(:sales_log, ownershipsch: 3) }
it "is displayed in tasklist" do
expect(outright_sale.displayed_in_tasklist?(log)).to eq(true)
@ -130,7 +130,7 @@ RSpec.describe Form::Sales::Subsections::OutrightSale, type: :model do
end
context "when it is not a outright sale" do
let(:log) { FactoryBot.create(:sales_log, ownershipsch: 2) }
let(:log) { FactoryBot.build(:sales_log, ownershipsch: 2) }
it "is displayed in tasklist" do
expect(outright_sale.displayed_in_tasklist?(log)).to eq(false)

4
spec/models/form/sales/subsections/shared_ownership_scheme_spec.rb

@ -77,7 +77,7 @@ RSpec.describe Form::Sales::Subsections::SharedOwnershipScheme, type: :model do
end
context "when it is a shared ownership scheme" do
let(:log) { FactoryBot.create(:sales_log, ownershipsch: 1) }
let(:log) { FactoryBot.build(:sales_log, ownershipsch: 1) }
it "is displayed in tasklist" do
expect(shared_ownership_scheme.displayed_in_tasklist?(log)).to eq(true)
@ -85,7 +85,7 @@ RSpec.describe Form::Sales::Subsections::SharedOwnershipScheme, type: :model do
end
context "when it is not a shared ownership scheme" do
let(:log) { FactoryBot.create(:sales_log, ownershipsch: 2) }
let(:log) { FactoryBot.build(:sales_log, ownershipsch: 2) }
it "is displayed in tasklist" do
expect(shared_ownership_scheme.displayed_in_tasklist?(log)).to eq(false)

43
spec/models/forms/bulk_upload_lettings/guidance_spec.rb

@ -0,0 +1,43 @@
require "rails_helper"
RSpec.describe Forms::BulkUploadLettings::Guidance do
include Rails.application.routes.url_helpers
subject(:bu_guidance) { described_class.new(year:, referrer:) }
let(:year) { 2024 }
describe "#back_path" do
context "when referrer is prepare-your-file" do
let(:referrer) { "prepare-your-file" }
it "returns the prepare your file path" do
expect(bu_guidance.back_path).to eq bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year: })
end
end
context "when referrer is home" do
let(:referrer) { "home" }
it "returns the root path" do
expect(bu_guidance.back_path).to eq root_path
end
end
context "when referrer is guidance" do
let(:referrer) { "guidance" }
it "returns the main guidance page path" do
expect(bu_guidance.back_path).to eq guidance_path
end
end
context "when referrer is absent" do
let(:referrer) { nil }
it "returns the main guidance page path" do
expect(bu_guidance.back_path).to eq guidance_path
end
end
end
end

43
spec/models/forms/bulk_upload_sales/guidance_spec.rb

@ -0,0 +1,43 @@
require "rails_helper"
RSpec.describe Forms::BulkUploadSales::Guidance do
include Rails.application.routes.url_helpers
subject(:bu_guidance) { described_class.new(year:, referrer:) }
let(:year) { 2024 }
describe "#back_path" do
context "when referrer is prepare-your-file" do
let(:referrer) { "prepare-your-file" }
it "returns the prepare your file path" do
expect(bu_guidance.back_path).to eq bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: })
end
end
context "when referrer is home" do
let(:referrer) { "home" }
it "returns the root path" do
expect(bu_guidance.back_path).to eq root_path
end
end
context "when referrer is guidance" do
let(:referrer) { "guidance" }
it "returns the main guidance page path" do
expect(bu_guidance.back_path).to eq guidance_path
end
end
context "when referrer is absent" do
let(:referrer) { nil }
it "returns the main guidance page path" do
expect(bu_guidance.back_path).to eq guidance_path
end
end
end
end

4
spec/models/scheme_spec.rb

@ -305,7 +305,7 @@ RSpec.describe Scheme, type: :model do
end
context "when scheme has discarded_at value" do
let(:scheme) { FactoryBot.create(:scheme, discarded_at: Time.zone.now) }
let(:scheme) { FactoryBot.build(:scheme, discarded_at: Time.zone.now) }
it "returns deleted" do
expect(scheme.status).to eq(:deleted)
@ -368,7 +368,7 @@ RSpec.describe Scheme, type: :model do
describe "owning organisation" do
let(:stock_owning_org) { FactoryBot.create(:organisation, holds_own_stock: true) }
let(:non_stock_owning_org) { FactoryBot.create(:organisation, holds_own_stock: false) }
let(:scheme) { FactoryBot.create(:scheme, owning_organisation_id: stock_owning_org.id) }
let(:scheme) { FactoryBot.build(:scheme, owning_organisation_id: stock_owning_org.id) }
context "when the owning organisation is set as a non-stock-owning organisation" do
it "throws the correct validation error" do

1
spec/models/user_spec.rb

@ -114,6 +114,7 @@ RSpec.describe User, type: :model do
end
it "can have one or more legacy users" do
FactoryBot.create(:legacy_user, old_user_id: user.old_user_id, user:)
expect(user.legacy_users.size).to eq(1)
end

2
spec/models/validations/date_validations_spec.rb

@ -4,7 +4,7 @@ RSpec.describe Validations::DateValidations do
subject(:date_validator) { validator_class.new }
let(:validator_class) { Class.new { include Validations::DateValidations } }
let(:record) { create(:lettings_log) }
let(:record) { build(:lettings_log) }
let(:scheme) { create(:scheme, end_date: Time.zone.today - 5.days) }
let(:scheme_no_end_date) { create(:scheme, end_date: nil) }

2
spec/models/validations/local_authority_validations_spec.rb

@ -4,7 +4,7 @@ RSpec.describe Validations::LocalAuthorityValidations do
subject(:local_auth_validator) { validator_class.new }
let(:validator_class) { Class.new { include Validations::LocalAuthorityValidations } }
let(:log) { create(:lettings_log) }
let(:log) { build(:lettings_log) }
describe "#validate_previous_accommodation_postcode" do
it "does not add an error if the log ppostcode_full is missing" do

2
spec/models/validations/sales/soft_validations_spec.rb

@ -1,7 +1,7 @@
require "rails_helper"
RSpec.describe Validations::Sales::SoftValidations do
let(:record) { create(:sales_log) }
let(:record) { build(:sales_log) }
describe "income1 min validations" do
context "when validating soft min" do

4
spec/models/validations/setup_validations_spec.rb

@ -4,7 +4,7 @@ RSpec.describe Validations::SetupValidations do
subject(:setup_validator) { setup_validator_class.new }
let(:setup_validator_class) { Class.new { include Validations::SetupValidations } }
let(:record) { create(:lettings_log) }
let(:record) { build(:lettings_log) }
describe "tenancy start date" do
context "when in 22/23 collection" do
@ -853,7 +853,7 @@ RSpec.describe Validations::SetupValidations do
end
context "when updating" do
let(:log) { create(:lettings_log, :in_progress) }
let(:log) { build(:lettings_log, :in_progress) }
let(:org_with_dpc) { create(:organisation) }
let(:org_without_dpc) { create(:organisation, :without_dpc) }

6
spec/models/validations/tenancy_validations_spec.rb

@ -6,7 +6,7 @@ RSpec.describe Validations::TenancyValidations do
let(:validator_class) { Class.new { include Validations::TenancyValidations } }
describe "tenancy length validations" do
let(:record) { FactoryBot.create(:lettings_log, :setup_completed) }
let(:record) { FactoryBot.build(:lettings_log, :setup_completed) }
shared_examples "adds expected errors based on the tenancy length" do |tenancy_type_case, error_fields, min_tenancy_length|
context "and tenancy type is #{tenancy_type_case[:name]}" do
@ -276,7 +276,7 @@ RSpec.describe Validations::TenancyValidations do
end
describe "tenancy type validations" do
let(:record) { FactoryBot.create(:lettings_log, :setup_completed) }
let(:record) { FactoryBot.build(:lettings_log, :setup_completed) }
let(:field) { "validations.other_field_missing" }
let(:main_field_label) { "tenancy type" }
let(:other_field) { "tenancyother" }
@ -320,7 +320,7 @@ RSpec.describe Validations::TenancyValidations do
describe "joint tenancy validation" do
context "when the data inputter has said that there is only one member in the household" do
let(:record) { FactoryBot.create(:lettings_log, :setup_completed, hhmemb: 1) }
let(:record) { FactoryBot.build(:lettings_log, :setup_completed, hhmemb: 1) }
let(:expected_error) { I18n.t("validations.tenancy.not_joint") }
let(:hhmemb_expected_error) { I18n.t("validations.tenancy.joint_more_than_one_member") }

108
spec/policies/organisation_policy_spec.rb

@ -63,4 +63,112 @@ RSpec.describe OrganisationPolicy do
expect(policy).not_to permit(support, organisation)
end
end
permissions :delete? do
let(:organisation) { FactoryBot.create(:organisation) }
context "with active organisation" do
it "does not allow deleting a organisation as a provider" do
expect(policy).not_to permit(data_provider, organisation)
end
it "does not allow allows deleting a organisation as a coordinator" do
expect(policy).not_to permit(data_coordinator, organisation)
end
it "does not allow deleting a organisation as a support user" do
expect(policy).not_to permit(support, organisation)
end
end
context "with deactivated organisation" do
before do
organisation.update!(active: false)
end
it "does not allow deleting a organisation as a provider" do
expect(policy).not_to permit(data_provider, organisation)
end
it "does not allow allows deleting a organisation as a coordinator" do
expect(policy).not_to permit(data_coordinator, organisation)
end
it "allows deleting a organisation as a support user" do
expect(policy).to permit(support, organisation)
end
context "and associated lettings logs in editable collection period" do
before do
create(:lettings_log, owning_organisation: organisation)
end
it "does not allow deleting a organisation as a provider" do
expect(policy).not_to permit(data_provider, organisation)
end
it "does not allow allows deleting a organisation as a coordinator" do
expect(policy).not_to permit(data_coordinator, organisation)
end
it "does not allow deleting a organisation as a support user" do
expect(policy).not_to permit(support, organisation)
end
end
context "and deleted associated lettings logs in editable collection period" do
before do
create(:lettings_log, owning_organisation: organisation, discarded_at: Time.zone.yesterday)
end
it "does not allow deleting a organisation as a provider" do
expect(policy).not_to permit(data_provider, organisation)
end
it "does not allow allows deleting a organisation as a coordinator" do
expect(policy).not_to permit(data_coordinator, organisation)
end
it "allows deleting a organisation as a support user" do
expect(policy).to permit(support, organisation)
end
end
context "and associated sales logs in editable collection period" do
before do
create(:sales_log, owning_organisation: organisation)
end
it "does not allow deleting a organisation as a provider" do
expect(policy).not_to permit(data_provider, organisation)
end
it "does not allow allows deleting a organisation as a coordinator" do
expect(policy).not_to permit(data_coordinator, organisation)
end
it "does not allow deleting a organisation as a support user" do
expect(policy).not_to permit(support, organisation)
end
end
context "and deleted associated sales logs in editable collection period" do
before do
create(:sales_log, owning_organisation: organisation, discarded_at: Time.zone.yesterday)
end
it "does not allow deleting a organisation as a provider" do
expect(policy).not_to permit(data_provider, organisation)
end
it "does not allow allows deleting a organisation as a coordinator" do
expect(policy).not_to permit(data_coordinator, organisation)
end
it "allows deleting a organisation as a support user" do
expect(policy).to permit(support, organisation)
end
end
end
end
end

2
spec/policies/user_policy_spec.rb

@ -201,7 +201,7 @@ RSpec.describe UserPolicy do
end
context "and user is the DPO that hasn't signed the agreement" do
let(:user) { create(:user, active: false, is_dpo: true) }
let(:user) { create(:user, active: false, is_dpo: true, with_dsa: false) }
it "does not allow deleting a user as a provider" do
expect(policy).not_to permit(data_provider, user)

2
spec/requests/bulk_upload_lettings_logs_controller_spec.rb

@ -13,7 +13,7 @@ RSpec.describe BulkUploadLettingsLogsController, type: :request do
describe "GET /lettings-logs/bulk-upload-logs/start" do
context "when data protection confirmation not signed" do
let(:organisation) { create(:organisation, :without_dpc) }
let(:user) { create(:user, organisation:) }
let(:user) { create(:user, organisation:, with_dsa: false) }
it "redirects to lettings index page" do
get "/lettings-logs/bulk-upload-logs/start", params: {}

2
spec/requests/bulk_upload_sales_logs_controller_spec.rb

@ -13,7 +13,7 @@ RSpec.describe BulkUploadSalesLogsController, type: :request do
describe "GET /sales-logs/bulk-upload-logs/start" do
context "when data protection confirmation not signed" do
let(:organisation) { create(:organisation, :without_dpc) }
let(:user) { create(:user, organisation:) }
let(:user) { create(:user, organisation:, with_dsa: false) }
it "redirects to sales index page" do
get "/sales-logs/bulk-upload-logs/start", params: {}

251
spec/requests/organisations_controller_spec.rb

@ -47,6 +47,34 @@ RSpec.describe OrganisationsController, type: :request do
expect(response).to redirect_to("/account/sign-in")
end
end
describe "#delete-confirmation" do
let(:organisation) { create(:organisation) }
before do
get "/organisations/#{organisation.id}/delete-confirmation"
end
context "when not signed in" do
it "redirects to the sign in page" do
expect(response).to redirect_to("/account/sign-in")
end
end
end
describe "#delete" do
let(:organisation) { create(:organisation) }
before do
delete "/organisations/#{organisation.id}/delete"
end
context "when not signed in" do
it "redirects to the sign in page" do
expect(response).to redirect_to("/account/sign-in")
end
end
end
end
context "when user is signed in" do
@ -747,6 +775,38 @@ RSpec.describe OrganisationsController, type: :request do
end
end
end
describe "#delete-confirmation" do
let(:organisation) { user.organisation }
before do
get "/organisations/#{organisation.id}/delete-confirmation"
end
context "with a data provider user" do
let(:user) { create(:user) }
it "returns 401 unauthorized" do
expect(response).to have_http_status(:unauthorized)
end
end
end
describe "#delete" do
let(:organisation) { user.organisation }
before do
delete "/organisations/#{organisation.id}/delete"
end
context "with a data provider user" do
let(:user) { create(:user) }
it "returns 401 unauthorized" do
expect(response).to have_http_status(:unauthorized)
end
end
end
end
context "with a data provider user" do
@ -876,6 +936,38 @@ RSpec.describe OrganisationsController, type: :request do
expect(response).to have_http_status(:unauthorized)
end
end
describe "#delete-confirmation" do
let(:organisation) { user.organisation }
before do
get "/organisations/#{organisation.id}/delete-confirmation"
end
context "with a data provider user" do
let(:user) { create(:user) }
it "returns 401 unauthorized" do
expect(response).to have_http_status(:unauthorized)
end
end
end
describe "#delete" do
let(:organisation) { user.organisation }
before do
delete "/organisations/#{organisation.id}/delete"
end
context "with a data provider user" do
let(:user) { create(:user) }
it "returns 401 unauthorized" do
expect(response).to have_http_status(:unauthorized)
end
end
end
end
context "with a support user" do
@ -1431,6 +1523,89 @@ RSpec.describe OrganisationsController, type: :request do
end
end
describe "#show" do
let(:organisation) { create(:organisation) }
before do
get "/organisations/#{organisation.id}", headers:, params: {}
end
context "with an active organisation" do
it "does not render delete this organisation" do
follow_redirect!
expect(response).to have_http_status(:ok)
expect(page).not_to have_link("Delete this organisation", href: "/organisations/#{organisation.id}/delete-confirmation")
end
it "does not render informative text about deleting the organisation" do
follow_redirect!
expect(response).to have_http_status(:ok)
expect(page).not_to have_content("This organisation was active in an open or editable collection year, and cannot be deleted.")
end
end
context "with an inactive organisation" do
let(:organisation) { create(:organisation, active: false) }
it "renders delete this organisation" do
follow_redirect!
expect(response).to have_http_status(:ok)
expect(page).to have_link("Delete this organisation", href: "/organisations/#{organisation.id}/delete-confirmation")
end
context "and associated lettings logs in editable collection period" do
before do
create(:lettings_log, owning_organisation: organisation)
get "/organisations/#{organisation.id}"
end
it "does not render delete this organisation" do
follow_redirect!
expect(response).to have_http_status(:ok)
expect(page).not_to have_link("Delete this organisation", href: "/organisations/#{organisation.id}/delete-confirmation")
end
it "adds informative text about deleting the organisation" do
follow_redirect!
expect(response).to have_http_status(:ok)
expect(page).to have_content("This organisation was active in an open or editable collection year, and cannot be deleted.")
end
end
context "and associated sales logs in editable collection period" do
before do
create(:sales_log, owning_organisation: organisation)
get "/organisations/#{organisation.id}"
end
it "does not render delete this organisation" do
follow_redirect!
expect(response).to have_http_status(:ok)
expect(page).not_to have_link("Delete this organisation", href: "/organisations/#{organisation.id}/delete-confirmation")
end
it "adds informative text about deleting the organisation" do
follow_redirect!
expect(response).to have_http_status(:ok)
expect(page).to have_content("This organisation was active in an open or editable collection year, and cannot be deleted.")
end
end
end
context "with merged organisation" do
before do
organisation.update!(merge_date: Time.zone.yesterday)
get "/organisations/#{organisation.id}", headers:, params: {}
end
it "renders delete this organisation" do
follow_redirect!
expect(response).to have_http_status(:ok)
expect(page).to have_link("Delete this organisation", href: "/organisations/#{organisation.id}/delete-confirmation")
end
end
end
describe "#update" do
let(:params) { { id: organisation.id, organisation: { active:, rent_periods: [], all_rent_periods: [] } } }
@ -1473,6 +1648,7 @@ RSpec.describe OrganisationsController, type: :request do
allow(notify_client).to receive(:send_email).and_return(true)
user_to_reactivate = create(:user, :data_coordinator, organisation:, active: false, reactivate_with_organisation: true)
FactoryBot.create(:legacy_user, old_user_id: user_to_reactivate.old_user_id, user: user_to_reactivate)
user_not_to_reactivate = create(:user, :data_coordinator, organisation:, active: false, reactivate_with_organisation: false)
patch "/organisations/#{organisation.id}", headers:, params:
end
@ -1581,6 +1757,79 @@ RSpec.describe OrganisationsController, type: :request do
end
end
describe "#delete-confirmation" do
let(:organisation) { create(:organisation, active: false) }
before do
get "/organisations/#{organisation.id}/delete-confirmation"
end
it "shows the correct title" do
expect(page.find("h1").text).to include "Are you sure you want to delete this organisation?"
end
it "shows a warning to the user" do
expect(page).to have_selector(".govuk-warning-text", text: "You will not be able to undo this action")
end
it "shows a button to delete the selected organisation" do
expect(page).to have_selector("form.button_to button", text: "Delete this organisation")
end
it "the delete organisation button submits the correct data to the correct path" do
form_containing_button = page.find("form.button_to")
expect(form_containing_button[:action]).to eq delete_organisation_path(organisation)
expect(form_containing_button).to have_field "_method", type: :hidden, with: "delete"
end
it "shows a cancel link with the correct style" do
expect(page).to have_selector("a.govuk-button--secondary", text: "Cancel")
end
it "shows cancel link that links back to the organisation page" do
expect(page).to have_link(text: "Cancel", href: organisation_path(organisation))
end
end
describe "#delete" do
let(:organisation) { create(:organisation, active: false) }
let!(:org_user) { create(:user, active: false, organisation:) }
let!(:scheme) { create(:scheme, owning_organisation: organisation) }
let!(:location) { create(:location, scheme:) }
before do
scheme.scheme_deactivation_periods << SchemeDeactivationPeriod.new(deactivation_date: Time.zone.yesterday)
location.location_deactivation_periods << LocationDeactivationPeriod.new(deactivation_date: Time.zone.yesterday)
delete "/organisations/#{organisation.id}/delete"
end
it "deletes the organisation and related resources" do
organisation.reload
expect(organisation.status).to eq(:deleted)
expect(organisation.discarded_at).not_to be nil
expect(location.reload.status).to eq(:deleted)
expect(location.discarded_at).not_to be nil
expect(scheme.reload.status).to eq(:deleted)
expect(scheme.discarded_at).not_to be nil
expect(org_user.reload.status).to eq(:deleted)
expect(org_user.discarded_at).not_to be nil
end
it "redirects to the organisations list and displays a notice that the organisation has been deleted" do
expect(response).to redirect_to organisations_path
follow_redirect!
expect(page).to have_selector(".govuk-notification-banner--success")
expect(page).to have_selector(".govuk-notification-banner--success", text: "#{organisation.name} has been deleted.")
end
it "does not display the deleted organisation" do
expect(response).to redirect_to organisations_path
follow_redirect!
expect(page).not_to have_content("Organisation to delete")
end
end
context "when they view the lettings logs tab" do
let(:tenancycode) { "42" }
@ -1896,7 +2145,7 @@ RSpec.describe OrganisationsController, type: :request do
Timecop.unfreeze
end
let(:user) { create(:user, is_dpo: true, organisation:) }
let(:user) { create(:user, is_dpo: true, organisation:, with_dsa: false) }
it "returns redirects to details page" do
post "/organisations/#{organisation.id}/data-sharing-agreement", headers: headers

11
spec/requests/users_controller_spec.rb

@ -1188,7 +1188,7 @@ RSpec.describe UsersController, type: :request do
describe "#index" do
let!(:other_user) { create(:user, organisation: user.organisation, name: "User 2", email: "other@example.com") }
let!(:inactive_user) { create(:user, organisation: user.organisation, active: false, name: "User 3", email: "inactive@example.com", last_sign_in_at: Time.zone.local(2022, 10, 10)) }
let!(:other_org_user) { create(:user, name: "User 4", email: "otherorg@otherexample.com", organisation: create(:organisation, :without_dpc)) }
let!(:other_org_user) { create(:user, name: "User 4", email: "otherorg@otherexample.com", organisation: create(:organisation, :without_dpc, name: "Other name")) }
before do
get "/users", headers:, params: {}
@ -1201,6 +1201,11 @@ RSpec.describe UsersController, type: :request do
expect(page).to have_content(other_org_user.name)
end
it "links to user organisations" do
expect(page).to have_link(user.organisation.name, href: "/organisations/#{user.organisation.id}/lettings-logs", count: 3)
expect(page).to have_link(other_org_user.organisation.name, href: "/organisations/#{other_org_user.organisation.id}/lettings-logs", count: 1)
end
it "shows last logged in date for all users" do
expect(page).to have_content("10 October 2022")
end
@ -1432,6 +1437,10 @@ RSpec.describe UsersController, type: :request do
expect(page).to have_link("Change", text: "if a key contact")
end
it "links to user organisation" do
expect(page).to have_link(other_user.organisation.name, href: "/organisations/#{other_user.organisation.id}/lettings-logs")
end
it "does not show option to resend confirmation email" do
expect(page).not_to have_button("Resend invite link")
end

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

@ -1004,6 +1004,16 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
end
end
context "when scheme ID has leading spaces" do
let(:attributes) { { bulk_upload:, field_1: owning_org.old_visible_id, field_2: owning_org.old_visible_id, field_4: "2", field_5: "2", field_16: " S#{scheme.id}", field_17: location.id } }
it "does not return an error" do
expect(parser.errors[:field_15]).to be_blank
expect(parser.errors[:field_16]).to be_blank
expect(parser.errors[:field_17]).to be_blank
end
end
context "when location exists but not related" do
let(:other_scheme) { create(:scheme, :with_old_visible_id) }
let(:other_location) { create(:location, :with_old_visible_id, scheme: other_scheme) }

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

@ -936,6 +936,15 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
end
end
context "when scheme ID has leading spaces" do
let(:attributes) { { bulk_upload:, field_1: owning_org.old_visible_id, field_2: owning_org.old_visible_id, field_4: "2", field_11: "1", field_5: " S#{scheme.id}", field_6: location.id } }
it "does not return an error" do
expect(parser.errors[:field_5]).to be_blank
expect(parser.errors[:field_6]).to be_blank
end
end
context "when location exists but not related" do
let(:other_scheme) { create(:scheme, :with_old_visible_id) }
let(:other_location) { create(:location, :with_old_visible_id, scheme: other_scheme) }

2
spec/views/logs/_create_for_org_actions.html.erb_spec.rb

@ -20,7 +20,7 @@ RSpec.describe "logs/_create_for_org_actions.html.erb" do
end
context "without data sharing agreement" do
let(:user) { create(:user, organisation: create(:organisation, :without_dpc)) }
let(:user) { create(:user, organisation: create(:organisation, :without_dpc), with_dsa: false) }
it "does not include create log buttons" do
render

2
spec/views/organisations/data_sharing_agreement.html.erb_spec.rb

@ -17,7 +17,7 @@ RSpec.describe "organisations/data_sharing_agreement.html.erb", :aggregate_failu
let(:data_protection_confirmation) { nil }
context "when dpo" do
let(:user) { create(:user, is_dpo: true, organisation: create(:organisation, :without_dpc)) }
let(:user) { create(:user, is_dpo: true, organisation: create(:organisation, :without_dpc), with_dsa: false) }
it "renders dynamic content" do
render

12
spec/views/organisations/show.html.erb_spec.rb

@ -11,7 +11,7 @@ RSpec.describe "organisations/show.html.erb" do
let(:organisation_with_dsa) { create(:organisation) }
context "when dpo" do
let(:user) { create(:user, is_dpo: true, organisation: organisation_without_dpc) }
let(:user) { create(:user, is_dpo: true, organisation: organisation_without_dpc, with_dsa: false) }
it "includes data sharing agreement row" do
render
@ -32,7 +32,7 @@ RSpec.describe "organisations/show.html.erb" do
end
context "when accepted" do
let(:user) { create(:user, organisation: organisation_with_dsa) }
let(:user) { create(:user, organisation: organisation_with_dsa, with_dsa: false) }
it "includes data sharing agreement row" do
render
@ -55,7 +55,7 @@ RSpec.describe "organisations/show.html.erb" do
end
context "when support user" do
let(:user) { create(:user, :support, organisation: organisation_without_dpc) }
let(:user) { create(:user, :support, organisation: organisation_without_dpc, with_dsa: false) }
it "includes data sharing agreement row" do
render
@ -82,7 +82,7 @@ RSpec.describe "organisations/show.html.erb" do
end
context "when accepted" do
let(:user) { create(:user, :support, organisation: organisation_with_dsa) }
let(:user) { create(:user, :support, organisation: organisation_with_dsa, with_dsa: false) }
it "includes data sharing agreement row" do
render
@ -125,7 +125,7 @@ RSpec.describe "organisations/show.html.erb" do
end
context "when not dpo" do
let(:user) { create(:user, organisation: organisation_without_dpc) }
let(:user) { create(:user, organisation: organisation_without_dpc, with_dsa: false) }
it "includes data sharing agreement row" do
render
@ -149,7 +149,7 @@ RSpec.describe "organisations/show.html.erb" do
end
context "when accepted" do
let(:user) { create(:user, organisation: organisation_with_dsa) }
let(:user) { create(:user, organisation: organisation_with_dsa, with_dsa: false) }
it "includes data sharing agreement row" do
render

Loading…
Cancel
Save