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" gem "auto_strip_attributes"
# Use sidekiq for background processing # Use sidekiq for background processing
gem "method_source", "~> 1.1" gem "method_source", "~> 1.1"
gem "rails_admin", "~> 3.0" gem "rails_admin", "~> 3.1"
gem "ruby-openai" gem "ruby-openai"
gem "sidekiq" gem "sidekiq"
gem "sidekiq-cron" gem "sidekiq-cron"

22
Gemfile.lock

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

4
app/components/lettings_log_summary_component.html.erb

@ -33,13 +33,13 @@
<% if log.owning_organisation %> <% if log.owning_organisation %>
<div class="app-metadata__item"> <div class="app-metadata__item">
<dt class="app-metadata__term">Owned by</dt> <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> </div>
<% end %> <% end %>
<% if log.managing_organisation %> <% if log.managing_organisation %>
<div class="app-metadata__item"> <div class="app-metadata__item">
<dt class="app-metadata__term">Managed by</dt> <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> </div>
<% end %> <% end %>
</dl> </dl>

4
app/components/sales_log_summary_component.html.erb

@ -26,13 +26,13 @@
<% if log.owning_organisation %> <% if log.owning_organisation %>
<div class="app-metadata__item"> <div class="app-metadata__item">
<dt class="app-metadata__term">Owned by</dt> <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> </div>
<% end %> <% end %>
<% if log.managing_organisation %> <% if log.managing_organisation %>
<div class="app-metadata__item"> <div class="app-metadata__item">
<dt class="app-metadata__term">Reported by</dt> <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> </div>
<% end %> <% end %>
</dl> </dl>

2
app/controllers/bulk_upload_lettings_logs_controller.rb

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

2
app/controllers/bulk_upload_sales_logs_controller.rb

@ -47,7 +47,7 @@ private
when "prepare-your-file" when "prepare-your-file"
Forms::BulkUploadSales::PrepareYourFile.new(form_params) Forms::BulkUploadSales::PrepareYourFile.new(form_params)
when "guidance" when "guidance"
Forms::BulkUploadSales::Guidance.new(form_params) Forms::BulkUploadSales::Guidance.new(form_params.merge(referrer: params[:referrer]))
when "upload-your-file" when "upload-your-file"
Forms::BulkUploadSales::UploadYourFile.new(form_params.merge(current_user:)) Forms::BulkUploadSales::UploadYourFile.new(form_params.merge(current_user:))
when "checking-file" 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? redirect_to organisation_path(current_user.organisation) unless current_user.support?
all_organisations = Organisation.order(:name) 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 @searched = search_term.presence
@total_count = all_organisations.size @total_count = all_organisations.visible.size
end end
def schemes def schemes
@ -117,7 +117,6 @@ class OrganisationsController < ApplicationController
end end
def update 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 (current_user.data_coordinator? && org_params[:active].nil?) || current_user.support?
if @organisation.update(org_params) if @organisation.update(org_params)
case org_params[:active] case org_params[:active]
@ -136,11 +135,14 @@ class OrganisationsController < ApplicationController
else else
flash[:notice] = I18n.t("organisation.updated") flash[:notice] = I18n.t("organisation.updated")
end end
used_rent_periods = @organisation.lettings_logs.pluck(:period).uniq.compact.map(&:to_s) if rent_period_params[:rent_periods].present?
rent_periods_to_delete = rent_period_params[:all_rent_periods] - selected_rent_periods - used_rent_periods selected_rent_periods = rent_period_params[:rent_periods].compact_blank
OrganisationRentPeriod.transaction do used_rent_periods = @organisation.lettings_logs.pluck(:period).uniq.compact.map(&:to_s)
selected_rent_periods.each { |period| OrganisationRentPeriod.create(organisation: @organisation, rent_period: period) } rent_periods_to_delete = rent_period_params[:all_rent_periods] - selected_rent_periods - used_rent_periods
OrganisationRentPeriod.where(organisation: @organisation, rent_period: rent_periods_to_delete).destroy_all 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 end
redirect_to details_organisation_path(@organisation) redirect_to details_organisation_path(@organisation)
else else
@ -152,6 +154,17 @@ class OrganisationsController < ApplicationController
end end
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 def lettings_logs
organisation_logs = LettingsLog.visible.filter_by_organisation(@organisation).filter_by_years_or_nil(FormHandler.instance.years_of_available_lettings_forms) 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) unpaginated_filtered_logs = filter_manager.filtered_logs(organisation_logs, search_term, session_filters)
@ -306,7 +319,7 @@ private
end end
def authenticate_scope! 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? head :unauthorized and return unless current_user.support?
elsif current_user.organisation != @organisation && !current_user.support? elsif current_user.organisation != @organisation && !current_user.support?
render_not_found 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: "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: "Rent periods", value: organisation.rent_period_labels, editable: true, format: :bullet },
{ name: "Data Sharing Agreement" }, { 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 end
@ -39,9 +39,19 @@ module OrganisationsHelper
end end
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) def rent_periods_with_checked_attr(checked_periods: nil)
RentPeriod.rent_period_mappings.each_with_object({}) do |(period_code, period_value), result| 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)) result[period_code] = period_value.merge(checked: checked_periods.nil? || checked_periods.include?(period_code))
end end
end end
def delete_organisation_link(organisation)
govuk_button_link_to "Delete this organisation", delete_confirmation_organisation_path(organisation), warning: true
end
end end

6
app/helpers/schemes_helper.rb

@ -20,10 +20,10 @@ module SchemesHelper
end end
def owning_organisation_options(current_user) 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)] 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) } 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.merged_during_open_collection_period.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 current_user.support? ? all_orgs : user_org + stock_owners + merged_organisations
end end

3
app/helpers/tab_nav_helper.rb

@ -19,7 +19,8 @@ module TabNavHelper
end end
def org_cell(user) 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>" 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
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 return opts unless log
if log.managing_organisation.present? 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 end
if user.support? if user.support?
@ -31,14 +32,14 @@ class Form::Lettings::Questions::ManagingOrganisation < ::Form::Question
end end
orgs = if user.support? 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) 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 else
user.organisation.managing_agents.filter_by_active user.organisation.managing_agents.visible.filter_by_active
end 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)})" opts[absorbed_org.id] = "#{absorbed_org.name} (inactive as of #{absorbed_org.merge_date.to_fs(:govuk_date)})"
end end
@ -72,7 +73,10 @@ class Form::Lettings::Questions::ManagingOrganisation < ::Form::Question
end end
def answer_label(log, _current_user = nil) 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 end
private 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 return answer_opts unless log
if log.owning_organisation_id.present? 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 end
recently_absorbed_organisations = user.organisation.absorbed_organisations.merged_during_open_collection_period recently_absorbed_organisations = user.organisation.absorbed_organisations.merged_during_open_collection_period
@ -30,7 +31,7 @@ class Form::Lettings::Questions::StockOwner < ::Form::Question
end end
if user.support? 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? 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 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? 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
end end
else 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 answer_opts[stock_owner.id] = stock_owner.name
end 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? answer_opts[absorbed_org.id] = merged_organisation_label(absorbed_org.name, absorbed_org.merge_date) if absorbed_org.holds_own_stock?
end end
end end
@ -64,7 +65,7 @@ class Form::Lettings::Questions::StockOwner < ::Form::Question
def hidden_in_check_answers?(_log, user = nil) def hidden_in_check_answers?(_log, user = nil)
return false if user.support? 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? if user.organisation.holds_own_stock?
stock_owners.count.zero? 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 return opts unless log
if log.managing_organisation.present? 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 end
if user.support? if user.support?
@ -31,14 +32,14 @@ class Form::Sales::Questions::ManagingOrganisation < ::Form::Question
end end
orgs = if user.support? 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) 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 else
user.organisation.managing_agents.filter_by_active user.organisation.managing_agents.visible.filter_by_active
end.pluck(:id, :name).to_h 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)})" opts[absorbed_org.id] = "#{absorbed_org.name} (inactive as of #{absorbed_org.merge_date.to_fs(:govuk_date)})"
end 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 user
return answer_opts unless log return answer_opts unless log
unless user.support? if log.owning_organisation_id.present?
if log.owning_organisation_id.present? org_value = log.owning_organisation.label
answer_opts[log.owning_organisation.id] = log.owning_organisation.name answer_opts[log.owning_organisation.id] = org_value
end end
unless user.support?
if user.organisation.holds_own_stock? if user.organisation.holds_own_stock?
answer_opts[user.organisation.id] = "#{user.organisation.name} (Your organisation)" answer_opts[user.organisation.id] = "#{user.organisation.name} (Your organisation)"
end 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 answer_opts[org.id] = org.name
end end
end end
if log.owning_organisation_id.present? recently_absorbed_organisations = user.organisation.absorbed_organisations.visible.merged_during_open_collection_period
answer_opts[log.owning_organisation.id] = log.owning_organisation.name
end
recently_absorbed_organisations = user.organisation.absorbed_organisations.merged_during_open_collection_period
if !user.support? && user.organisation.holds_own_stock? if !user.support? && user.organisation.holds_own_stock?
answer_opts[user.organisation.id] = if recently_absorbed_organisations.exists? && user.organisation.available_from.present? 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)})" "#{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 end
if user.support? 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? 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 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? 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
end end
else 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? answer_opts[absorbed_org.id] = merged_organisation_label(absorbed_org.name, absorbed_org.merge_date) if absorbed_org.holds_own_stock?
end end
end end
@ -75,7 +72,7 @@ class Form::Sales::Questions::OwningOrganisationId < ::Form::Question
def hidden_in_check_answers?(_log, user = nil) def hidden_in_check_answers?(_log, user = nil)
return false if user.support? 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? if user.organisation.holds_own_stock?
stock_owners.count.zero? 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 include Rails.application.routes.url_helpers
attribute :year, :integer attribute :year, :integer
attribute :referrer
def view_path def view_path
"bulk_upload_shared/guidance" "bulk_upload_shared/guidance"
end end
def back_path 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 end
def lettings_legacy_template_path 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 include Rails.application.routes.url_helpers
attribute :year, :integer attribute :year, :integer
attribute :referrer
def view_path def view_path
"bulk_upload_shared/guidance" "bulk_upload_shared/guidance"
end end
def back_path 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 end
def lettings_legacy_template_path 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 belongs_to :absorbing_organisation, class_name: "Organisation", optional: true
has_many :absorbed_organisations, class_name: "Organisation", foreign_key: "absorbing_organisation_id" has_many :absorbed_organisations, class_name: "Organisation", foreign_key: "absorbing_organisation_id"
scope :visible, -> { where(discarded_at: nil) }
def affiliated_stock_owners def affiliated_stock_owners
ids = [] ids = []
@ -143,6 +144,7 @@ class Organisation < ApplicationRecord
end end
def status_at(date) def status_at(date)
return :deleted if discarded_at.present?
return :merged if merge_date.present? && merge_date < date return :merged if merge_date.present? && merge_date < date
return :deactivated unless active return :deactivated unless active
@ -178,4 +180,14 @@ class Organisation < ApplicationRecord
false false
end 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 end

21
app/policies/organisation_policy.rb

@ -13,4 +13,25 @@ class OrganisationPolicy
def reactivate? def reactivate?
user.support? && organisation.status == :deactivated user.support? && organisation.status == :deactivated
end 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 end

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

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

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

@ -1358,7 +1358,7 @@ private
def scheme def scheme
return if field_5.nil? || owning_organisation.nil? || managing_organisation.nil? 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 end
def location 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> <span class="govuk-caption-l">Upload lettings logs in bulk (<%= @form.year_combo %>)</span>
<h1 class="govuk-heading-l">Prepare your file</h1> <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> <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> <span class="govuk-caption-l">Upload lettings logs in bulk (<%= @form.year_combo %>)</span>
<h1 class="govuk-heading-l">Prepare your file</h1> <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> <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> <span class="govuk-caption-l">Upload sales logs in bulk (<%= @form.year_combo %>)</span>
<h1 class="govuk-heading-l">Prepare your file</h1> <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> <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> <span class="govuk-caption-l">Upload sales logs in bulk (<%= @form.year_combo %>)</span>
<h1 class="govuk-heading-l">Prepare your file</h1> <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> <h2 class="govuk-heading-s">Download template</h2>

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

@ -1,6 +1,7 @@
<% if current_user %> <% if current_user %>
<h1 class="govuk-heading-l">Collection resources</h1> <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 "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 %> <% else %>
<h2 class="govuk-heading-m">Collection resources</h2> <h2 class="govuk-heading-m">Collection resources</h2>
<% end %> <% 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) %> <%= govuk_button_link_to "Reactivate this organisation", reactivate_organisation_path(@organisation) %>
</span> </span>
<% end %> <% 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 %> <%= 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">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 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> <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 %> <% end %>

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

@ -67,7 +67,7 @@
<%= summary_list.with_row do |row| <%= summary_list.with_row do |row|
row.with_key { "Organisation" } 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 row.with_action
end %> end %>

1
config/locales/en.yml

@ -202,6 +202,7 @@ en:
location_deleted: "%{postcode} has been deleted." location_deleted: "%{postcode} has been deleted."
scheme_deleted: "%{service_name} has been deleted." scheme_deleted: "%{service_name} has been deleted."
user_deleted: "%{name} has been deleted." user_deleted: "%{name} has been deleted."
organisation_deleted: "%{name} has been deleted."
validations: validations:
organisation: 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/#{filter}", to: "sales_logs_filters#organisation_#{filter.underscore}"
get "sales-logs/filters/update-#{filter}", to: "sales_logs_filters#update_organisation_#{filter.underscore}" get "sales-logs/filters/update-#{filter}", to: "sales_logs_filters#update_organisation_#{filter.underscore}"
end end
get "delete-confirmation", to: "organisations#delete_confirmation"
delete "delete", to: "organisations#delete"
end end
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. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -492,6 +492,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_05_29_133005) do
t.datetime "merge_date" t.datetime "merge_date"
t.bigint "absorbing_organisation_id" t.bigint "absorbing_organisation_id"
t.datetime "available_from" t.datetime "available_from"
t.datetime "discarded_at"
t.index ["absorbing_organisation_id"], name: "index_organisations_on_absorbing_organisation_id" t.index ["absorbing_organisation_id"], name: "index_organisations_on_absorbing_organisation_id"
t.index ["old_visible_id"], name: "index_organisations_on_old_visible_id", unique: true t.index ["old_visible_id"], name: "index_organisations_on_old_visible_id", unique: true
end end

49
docs/testing.md

@ -31,3 +31,52 @@ bundle exec rake parallel:setup
```sh ```sh
RAILS_ENV=test bundle exec rake parallel:spec 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 "when not support user" do
context "without data sharing agreement" 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 it "does not render actions" do
expect(component).not_to be_display_actions 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(:component) { described_class.new(user:, organisation:) }
let(:render) { render_inline(component) } let(:render) { render_inline(component) }
let(:user) { create(:user) } let(:user) { create(:user, with_dsa: false) }
let(:organisation) { user.organisation } let(:organisation) { user.organisation }
context "when user is support and organisation is blank" do 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 } let(:organisation) { nil }
it "does not display banner" do 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 org does not have a signed data sharing agreement" do
context "when user is not a DPO" do context "when user is not a DPO" do
let(:organisation) { create(:organisation, :without_dpc) } let(:organisation) { create(:organisation, :without_dpc) }
let(:user) { create(:user, organisation:) } let(:user) { create(:user, organisation:, with_dsa: false) }
let!(:dpo) { create(:user, :data_protection_officer, organisation:) } let!(:dpo) { create(:user, :data_protection_officer, organisation:, with_dsa: false) }
it "displays the banner and shows DPOs" do it "displays the banner and shows DPOs" do
expect(component.display_banner?).to eq(true) expect(component.display_banner?).to eq(true)
@ -50,7 +50,7 @@ RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
context "when user is a DPO" do context "when user is a DPO" do
let(:organisation) { create(:organisation, :without_dpc) } 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 it "displays the banner and asks to sign" do
expect(component.display_banner?).to eq(true) 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 org does not have a signed data sharing agreement" do
context "when user is not a DPO" do context "when user is not a DPO" do
let(:organisation) { create(:organisation, :without_dpc) } let(:organisation) { create(:organisation, :without_dpc) }
let(:user) { create(:user, organisation:) } let(:user) { create(:user, organisation:, with_dsa: false) }
let!(:dpo) { create(:user, :data_protection_officer, organisation:) } let!(:dpo) { create(:user, :data_protection_officer, organisation:, with_dsa: false) }
it "displays the banner and shows DPOs" do it "displays the banner and shows DPOs" do
expect(component.display_banner?).to eq(true) expect(component.display_banner?).to eq(true)
@ -168,7 +168,7 @@ RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
context "when user is a DPO" do context "when user is a DPO" do
let(:organisation) { create(:organisation, :without_dpc) } 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 it "displays the banner and asks to sign" do
expect(component.display_banner?).to eq(true) expect(component.display_banner?).to eq(true)

4
spec/factories/organisation.rb

@ -25,11 +25,11 @@ FactoryBot.define do
end end
after(:create) do |org, evaluator| after(:create) do |org, evaluator|
if evaluator.with_dsa if evaluator.with_dsa && !org.data_protection_confirmed?
create( create(
:data_protection_confirmation, :data_protection_confirmation,
organisation: org, 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
end end

16
spec/factories/user.rb

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

2
spec/features/user_spec.rb

@ -594,7 +594,7 @@ RSpec.describe "User Features" do
end end
before do 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) allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in(user) sign_in(user)
visit(user_path(other_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 RSpec.describe TabNavHelper do
let(:organisation) { FactoryBot.create(:organisation) } 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(:scheme) { FactoryBot.create(:scheme, service_name: "Some name") }
let(:location) { FactoryBot.create(:location, scheme:) } let(:location) { FactoryBot.create(:location, scheme:) }
describe "#user_cell" do describe "#user_cell" do
it "returns user link and email separated by a newline character" 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>" 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(user)).to match(expected_html) expect(user_cell(current_user)).to match(expected_html)
end end
end end
describe "#org_cell" do describe "#org_cell" do
it "returns the users org name and role separated by a newline character" 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>" 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
end end

1
spec/mailers/resend_invitation_mailer_spec.rb

@ -41,6 +41,7 @@ RSpec.describe ResendInvitationMailer do
end end
it "sends an initial invitation" do 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 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) described_class.new.resend_invitation_email(new_active_migrated_user)
end 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_id) { nil }
let(:page_definition) { nil } let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) } let(:subsection) { instance_double(Form::Subsection) }
let(:log) { create(:lettings_log) } let(:log) { build(:lettings_log) }
it "has correct subsection" do it "has correct subsection" do
expect(page.subsection).to eq(subsection) 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_id) { nil }
let(:page_definition) { nil } let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) } let(:subsection) { instance_double(Form::Subsection) }
let(:log) { create(:lettings_log) } let(:log) { build(:lettings_log) }
it "has correct subsection" do it "has correct subsection" do
expect(page.subsection).to eq(subsection) 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 end
context "when log is present" do context "when log is present" do
let(:log) { create(:lettings_log) } let(:log) { build(:lettings_log) }
context "with 2023/24 form" do context "with 2023/24 form" do
it "points to address page" 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_org2) { create(:organisation, name: "Managing org 2") }
let(:managing_org3) { create(:organisation, name: "Managing org 3") } let(:managing_org3) { create(:organisation, name: "Managing org 3") }
let(:inactive_managing_org) { create(:organisation, name: "Inactive managing org", active: false) } 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(:log) { create(:lettings_log, managing_organisation: managing_org1) }
let!(:org_rel1) do 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 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: 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) expect(question.displayed_answer_options(log, user)).to eq(options)
end end
@ -104,7 +106,10 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do
end end
before do 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 end
context "when org owns stock" do 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 context "when organisation has merged" do
let(:absorbing_org) { create(:organisation, name: "Absorbing org", holds_own_stock: true) } 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_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(:user) { create(:user, :data_coordinator, organisation: absorbing_org) }
let(:log) do let(:log) do
merged_org.update!(merge_date: Time.zone.local(2023, 8, 2), absorbing_organisation_id: absorbing_org.id) 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) create(:lettings_log, owning_organisation: absorbing_org, managing_organisation: nil)
end 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_id) { nil }
let(:question_definition) { 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(: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 it "has correct page" do
expect(question.page).to eq(page) 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_1) { create(:organisation, name: "Owning org 1") }
let(:owning_org_2) { create(:organisation, name: "Owning org 2") } let(:owning_org_2) { create(:organisation, name: "Owning org 2") }
let(:inactive_owning_org) { create(:organisation, name: "Inactive owning org", active: false) } 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 let!(:org_rel) do
create(:organisation_relationship, child_organisation: user.organisation, parent_organisation: owning_org_2) create(:organisation_relationship, child_organisation: user.organisation, parent_organisation: owning_org_2)
end 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 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: inactive_owning_org)
create(:organisation_relationship, child_organisation: user.organisation, parent_organisation: deleted_owning_org)
user.organisation.update!(holds_own_stock: true) user.organisation.update!(holds_own_stock: true)
expect(question.displayed_answer_options(log, user)).to eq(options) expect(question.displayed_answer_options(log, user)).to eq(options)
end 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 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_organisation) { create(:organisation, name: "Merged org") }
let(:merged_deleted_organisation) { create(:organisation, name: "Merged deleted org", discarded_at: Time.zone.yesterday) }
let(:options) do let(:options) do
{ {
"" => "Select an option", "" => "Select an option",
@ -139,6 +142,7 @@ RSpec.describe Form::Lettings::Questions::StockOwner, type: :model do
before do before do
merged_organisation.update!(merge_date: Time.zone.local(2023, 2, 2), absorbing_organisation: user.organisation) 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)) allow(Time).to receive(:now).and_return(Time.zone.local(2023, 11, 10))
end 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 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_organisation) { create(:organisation, name: "Merged org") }
let(:merged_deleted_organisation) { create(:organisation, name: "Merged deleted org", discarded_at: Time.zone.yesterday) }
let(:options) do let(:options) do
{ {
"" => "Select an option", "" => "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)) allow(Time).to receive(:now).and_return(Time.zone.local(2023, 4, 2))
org_rel.update!(child_organisation: merged_organisation) org_rel.update!(child_organisation: merged_organisation)
merged_organisation.update!(merge_date: Time.zone.local(2021, 6, 2), absorbing_organisation: user.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)) user.organisation.update!(available_from: Time.zone.local(2021, 2, 2))
end end
@ -200,8 +206,9 @@ RSpec.describe Form::Lettings::Questions::StockOwner, type: :model do
it "shows active orgs where organisation holds own stock" do it "shows active orgs where organisation holds own stock" do
non_stock_organisation = create(:organisation, name: "Non-stockholding org", holds_own_stock: false) non_stock_organisation = create(:organisation, name: "Non-stockholding org", holds_own_stock: false)
inactive_organisation = create(:organisation, name: "Inactive org", active: 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[organisation.id] = organisation.name
hsh hsh
end 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)).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(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(inactive_organisation.id)
expect(question.displayed_answer_options(log, user)).not_to include(deleted_organisation.id)
end end
context "and org has recently absorbed other orgs and does not have available from date" do 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_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(:org) { create(:organisation, name: "User org") }
let(:options) do let(:options) do
{ {
@ -226,6 +235,7 @@ RSpec.describe Form::Lettings::Questions::StockOwner, type: :model do
before do before do
merged_organisation.update!(merge_date: Time.zone.local(2023, 2, 2), absorbing_organisation: org) 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)) allow(Time).to receive(:now).and_return(Time.zone.local(2023, 11, 10))
end 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 expect(question.hidden_in_check_answers?(nil, user)).to be false
end end
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 end
context "when org does not hold own stock", :aggregate_failures do 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 describe "notification_banner" do
context "when address is not present" do context "when address is not present" do
it "returns nil" do it "returns nil" do
log = create(:lettings_log) log = build(:lettings_log)
expect(question.notification_banner(log)).to be_nil expect(question.notification_banner(log)).to be_nil
end end
@ -50,7 +50,7 @@ RSpec.describe Form::Lettings::Questions::UprnConfirmation, type: :model do
context "when address is present" do context "when address is present" do
it "returns formatted value" 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( 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 describe "has the correct hidden_in_check_answers" do
context "when uprn_known != 1 && uprn_confirmed == nil" 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 it "returns true" do
expect(question.hidden_in_check_answers?(log)).to eq(true) expect(question.hidden_in_check_answers?(log)).to eq(true)
@ -72,7 +72,7 @@ RSpec.describe Form::Lettings::Questions::UprnConfirmation, type: :model do
end end
context "when uprn_known == 1 && uprn_confirmed == nil" do 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 it "returns false" do
expect(question.hidden_in_check_answers?(log)).to eq(false) expect(question.hidden_in_check_answers?(log)).to eq(false)
@ -80,7 +80,7 @@ RSpec.describe Form::Lettings::Questions::UprnConfirmation, type: :model do
end end
context "when uprn_known != 1 && uprn_confirmed == 1" do context "when uprn_known != 1 && uprn_confirmed == 1" do
let(:log) { create(:lettings_log) } let(:log) { build(:lettings_log) }
it "returns true" do it "returns true" do
log.uprn_known = 1 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_id) { nil }
let(:page_definition) { nil } let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) } let(:subsection) { instance_double(Form::Subsection) }
let(:log) { create(:sales_log) } let(:log) { build(:sales_log) }
it "has correct subsection" do it "has correct subsection" do
expect(page.subsection).to eq(subsection) 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 describe "has correct routed_to?" do
context "when uprn present && uprn_known == 1 " do context "when uprn present && uprn_known == 1 " do
let(:log) { create(:sales_log) } let(:log) { build(:sales_log) }
it "returns true" do it "returns true" do
log.uprn_known = 1 log.uprn_known = 1
@ -43,7 +43,7 @@ RSpec.describe Form::Sales::Pages::UprnConfirmation, type: :model do
end end
context "when uprn = nil" do 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 it "returns false" do
expect(page.routed_to?(log)).to eq(false) expect(page.routed_to?(log)).to eq(false)
@ -51,7 +51,7 @@ RSpec.describe Form::Sales::Pages::UprnConfirmation, type: :model do
end end
context "when uprn_known == 0" do 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 it "returns false" do
expect(page.routed_to?(log)).to eq(false) 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_id) { nil }
let(:page_definition) { nil } let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) } let(:subsection) { instance_double(Form::Subsection) }
let(:log) { create(:sales_log) } let(:log) { build(:sales_log) }
it "has correct subsection" do it "has correct subsection" do
expect(page.subsection).to eq(subsection) 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 end
context "when log is present" do context "when log is present" do
let(:log) { create(:sales_log) } let(:log) { build(:sales_log) }
context "with 2023/24 form" do context "with 2023/24 form" do
it "points to address page" 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_id) { nil }
let(:question_definition) { nil } let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) } 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 it "has correct page" do
expect(question.page).to eq(page) 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_id) { nil }
let(:question_definition) { 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(: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 it "has correct page" do
expect(question.page).to eq(page) 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_id) { nil }
let(:question_definition) { 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(: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 it "has correct page" do
expect(question.page).to eq(page) 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 end
it "has the correct displayed answer_options" do 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" }, "pregyrha" => { "value" => "Their private registered provider (PRP) - housing association" },
"pregother" => { "value" => "Other 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)))) } 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 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 it "is not marked as derived" do
expect(question.derived?(log)).to be false 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_org2) { create(:organisation, name: "Managing org 2") }
let(:managing_org3) { create(:organisation, name: "Managing org 3") } let(:managing_org3) { create(:organisation, name: "Managing org 3") }
let(:inactive_org) { create(:organisation, name: "Inactive org", active: false) } 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 let(:log) do
create(:lettings_log, owning_organisation: log_owning_org, managing_organisation: managing_org1, 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 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: inactive_org)
create(:organisation_relationship, parent_organisation: log_owning_org, child_organisation: deleted_org)
log_owning_org.update!(holds_own_stock: true) log_owning_org.update!(holds_own_stock: true)
expect(question.displayed_answer_options(log, user)).to eq(options) expect(question.displayed_answer_options(log, user)).to eq(options)
end end
@ -135,10 +137,12 @@ RSpec.describe Form::Sales::Questions::ManagingOrganisation, type: :model do
context "when organisation has merged" do context "when organisation has merged" do
let(:absorbing_org) { create(:organisation, name: "Absorbing org", holds_own_stock: true) } 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_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(:user) { create(:user, :support, organisation: absorbing_org) }
let(:log) do let(:log) do
merged_org.update!(merge_date: Time.zone.local(2023, 8, 2), absorbing_organisation_id: absorbing_org.id) 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) create(:lettings_log, owning_organisation: absorbing_org, managing_organisation: nil)
end 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 it "shows merged organisation as an option" do
expect(question.displayed_answer_options(log, user)).to eq(options) expect(question.displayed_answer_options(log, user)).to eq(options)
end 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 end
context "when user's org has recently absorbed other orgs and it has available from date" do 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 it "shows active orgs where organisation holds own stock" do
non_stock_organisation = create(:organisation, holds_own_stock: false) non_stock_organisation = create(:organisation, holds_own_stock: false)
inactive_org = create(:organisation, active: 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[organisation.id] = organisation.name
hsh hsh
end 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(non_stock_organisation.id)
expect(question.displayed_answer_options(log, user)).not_to include(inactive_org.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)).to include(organisation_1.id)
expect(question.displayed_answer_options(log, user)).not_to include(deleted_organisation.id)
end end
context "when an org has recently absorbed other orgs" do 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_id) { nil }
let(:question_definition) { nil } let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) } 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 it "has correct page" do
expect(question.page).to eq(page) 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 describe "notification_banner" do
context "when address is not present" do context "when address is not present" do
it "returns nil" do it "returns nil" do
log = create(:sales_log) log = build(:sales_log)
expect(question.notification_banner(log)).to be_nil expect(question.notification_banner(log)).to be_nil
end end
@ -64,7 +64,7 @@ RSpec.describe Form::Sales::Questions::UprnConfirmation, type: :model do
describe "has the correct hidden_in_check_answers" do describe "has the correct hidden_in_check_answers" do
context "when uprn_known != 1 && uprn_confirmed == nil" 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 it "returns true" do
expect(question.hidden_in_check_answers?(log)).to eq(true) expect(question.hidden_in_check_answers?(log)).to eq(true)
@ -72,7 +72,7 @@ RSpec.describe Form::Sales::Questions::UprnConfirmation, type: :model do
end end
context "when uprn_known == 1 && uprn_confirmed == nil" do context "when uprn_known == 1 && uprn_confirmed == nil" do
let(:log) { create(:sales_log) } let(:log) { build(:sales_log) }
it "returns false" do it "returns false" do
log.uprn_known = 1 log.uprn_known = 1
@ -83,7 +83,7 @@ RSpec.describe Form::Sales::Questions::UprnConfirmation, type: :model do
end end
context "when uprn_known != 1 && uprn_confirmed == 1" do 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 it "returns true" do
expect(question.hidden_in_check_answers?(log)).to eq(true) 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_id) { nil }
let(:question_definition) { nil } let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, skip_href: "skip_href") } 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) } let(:address_client_instance) { AddressClient.new(log.address_string) }
before do 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 describe "get_extra_check_answer_value" do
context "when address is not present" do context "when address is not present" do
let(:log) { create(:sales_log) } let(:log) { build(:sales_log) }
it "returns nil" do it "returns nil" do
expect(question.get_extra_check_answer_value(log)).to be_nil 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 end
context "when it is a discounted ownership scheme" do 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 it "is displayed in tasklist" do
expect(discounted_ownership_scheme.displayed_in_tasklist?(log)).to eq(true) expect(discounted_ownership_scheme.displayed_in_tasklist?(log)).to eq(true)
@ -75,7 +75,7 @@ RSpec.describe Form::Sales::Subsections::DiscountedOwnershipScheme, type: :model
end end
context "when it is not a discounted ownership scheme" do 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 it "is displayed in tasklist" do
expect(discounted_ownership_scheme.displayed_in_tasklist?(log)).to eq(false) 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 end
context "when the sale is to a company buyer" do 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 it "is not displayed in tasklist" do
expect(household_characteristics.displayed_in_tasklist?(log)).to eq(false) expect(household_characteristics.displayed_in_tasklist?(log)).to eq(false)
@ -386,7 +386,7 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
end end
context "when the sale is not to a company buyer" do 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 it "is displayed in tasklist" do
expect(household_characteristics.displayed_in_tasklist?(log)).to eq(true) 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 end
context "when it is a outright sale" do 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 it "is displayed in tasklist" do
expect(outright_sale.displayed_in_tasklist?(log)).to eq(true) expect(outright_sale.displayed_in_tasklist?(log)).to eq(true)
@ -130,7 +130,7 @@ RSpec.describe Form::Sales::Subsections::OutrightSale, type: :model do
end end
context "when it is not a outright sale" do 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 it "is displayed in tasklist" do
expect(outright_sale.displayed_in_tasklist?(log)).to eq(false) 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 end
context "when it is a shared ownership scheme" do 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 it "is displayed in tasklist" do
expect(shared_ownership_scheme.displayed_in_tasklist?(log)).to eq(true) 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 end
context "when it is not a shared ownership scheme" do 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 it "is displayed in tasklist" do
expect(shared_ownership_scheme.displayed_in_tasklist?(log)).to eq(false) 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 end
context "when scheme has discarded_at value" do 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 it "returns deleted" do
expect(scheme.status).to eq(:deleted) expect(scheme.status).to eq(:deleted)
@ -368,7 +368,7 @@ RSpec.describe Scheme, type: :model do
describe "owning organisation" do describe "owning organisation" do
let(:stock_owning_org) { FactoryBot.create(:organisation, holds_own_stock: true) } let(:stock_owning_org) { FactoryBot.create(:organisation, holds_own_stock: true) }
let(:non_stock_owning_org) { FactoryBot.create(:organisation, holds_own_stock: false) } 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 context "when the owning organisation is set as a non-stock-owning organisation" do
it "throws the correct validation error" 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 end
it "can have one or more legacy users" do 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) expect(user.legacy_users.size).to eq(1)
end end

2
spec/models/validations/date_validations_spec.rb

@ -4,7 +4,7 @@ RSpec.describe Validations::DateValidations do
subject(:date_validator) { validator_class.new } subject(:date_validator) { validator_class.new }
let(:validator_class) { Class.new { include Validations::DateValidations } } 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) { create(:scheme, end_date: Time.zone.today - 5.days) }
let(:scheme_no_end_date) { create(:scheme, end_date: nil) } 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 } subject(:local_auth_validator) { validator_class.new }
let(:validator_class) { Class.new { include Validations::LocalAuthorityValidations } } let(:validator_class) { Class.new { include Validations::LocalAuthorityValidations } }
let(:log) { create(:lettings_log) } let(:log) { build(:lettings_log) }
describe "#validate_previous_accommodation_postcode" do describe "#validate_previous_accommodation_postcode" do
it "does not add an error if the log ppostcode_full is missing" 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" require "rails_helper"
RSpec.describe Validations::Sales::SoftValidations do RSpec.describe Validations::Sales::SoftValidations do
let(:record) { create(:sales_log) } let(:record) { build(:sales_log) }
describe "income1 min validations" do describe "income1 min validations" do
context "when validating soft min" 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 } subject(:setup_validator) { setup_validator_class.new }
let(:setup_validator_class) { Class.new { include Validations::SetupValidations } } let(:setup_validator_class) { Class.new { include Validations::SetupValidations } }
let(:record) { create(:lettings_log) } let(:record) { build(:lettings_log) }
describe "tenancy start date" do describe "tenancy start date" do
context "when in 22/23 collection" do context "when in 22/23 collection" do
@ -853,7 +853,7 @@ RSpec.describe Validations::SetupValidations do
end end
context "when updating" do 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_with_dpc) { create(:organisation) }
let(:org_without_dpc) { create(:organisation, :without_dpc) } 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 } } let(:validator_class) { Class.new { include Validations::TenancyValidations } }
describe "tenancy length validations" do 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| 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 context "and tenancy type is #{tenancy_type_case[:name]}" do
@ -276,7 +276,7 @@ RSpec.describe Validations::TenancyValidations do
end end
describe "tenancy type validations" do 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(:field) { "validations.other_field_missing" }
let(:main_field_label) { "tenancy type" } let(:main_field_label) { "tenancy type" }
let(:other_field) { "tenancyother" } let(:other_field) { "tenancyother" }
@ -320,7 +320,7 @@ RSpec.describe Validations::TenancyValidations do
describe "joint tenancy validation" do describe "joint tenancy validation" do
context "when the data inputter has said that there is only one member in the household" 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(:expected_error) { I18n.t("validations.tenancy.not_joint") }
let(:hhmemb_expected_error) { I18n.t("validations.tenancy.joint_more_than_one_member") } 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) expect(policy).not_to permit(support, organisation)
end end
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 end

2
spec/policies/user_policy_spec.rb

@ -201,7 +201,7 @@ RSpec.describe UserPolicy do
end end
context "and user is the DPO that hasn't signed the agreement" do 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 it "does not allow deleting a user as a provider" do
expect(policy).not_to permit(data_provider, user) 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 describe "GET /lettings-logs/bulk-upload-logs/start" do
context "when data protection confirmation not signed" do context "when data protection confirmation not signed" do
let(:organisation) { create(:organisation, :without_dpc) } 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 it "redirects to lettings index page" do
get "/lettings-logs/bulk-upload-logs/start", params: {} 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 describe "GET /sales-logs/bulk-upload-logs/start" do
context "when data protection confirmation not signed" do context "when data protection confirmation not signed" do
let(:organisation) { create(:organisation, :without_dpc) } 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 it "redirects to sales index page" do
get "/sales-logs/bulk-upload-logs/start", params: {} 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") expect(response).to redirect_to("/account/sign-in")
end end
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 end
context "when user is signed in" do context "when user is signed in" do
@ -747,6 +775,38 @@ RSpec.describe OrganisationsController, type: :request do
end end
end 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 end
context "with a data provider user" do context "with a data provider user" do
@ -876,6 +936,38 @@ RSpec.describe OrganisationsController, type: :request do
expect(response).to have_http_status(:unauthorized) expect(response).to have_http_status(:unauthorized)
end 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 end
context "with a support user" do context "with a support user" do
@ -1431,6 +1523,89 @@ RSpec.describe OrganisationsController, type: :request do
end end
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 describe "#update" do
let(:params) { { id: organisation.id, organisation: { active:, rent_periods: [], all_rent_periods: [] } } } 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) allow(notify_client).to receive(:send_email).and_return(true)
user_to_reactivate = create(:user, :data_coordinator, organisation:, active: false, reactivate_with_organisation: true) user_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) user_not_to_reactivate = create(:user, :data_coordinator, organisation:, active: false, reactivate_with_organisation: false)
patch "/organisations/#{organisation.id}", headers:, params: patch "/organisations/#{organisation.id}", headers:, params:
end end
@ -1581,6 +1757,79 @@ RSpec.describe OrganisationsController, type: :request do
end end
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 context "when they view the lettings logs tab" do
let(:tenancycode) { "42" } let(:tenancycode) { "42" }
@ -1896,7 +2145,7 @@ RSpec.describe OrganisationsController, type: :request do
Timecop.unfreeze Timecop.unfreeze
end 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 it "returns redirects to details page" do
post "/organisations/#{organisation.id}/data-sharing-agreement", headers: headers 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 describe "#index" do
let!(:other_user) { create(:user, organisation: user.organisation, name: "User 2", email: "other@example.com") } 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!(: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 before do
get "/users", headers:, params: {} get "/users", headers:, params: {}
@ -1201,6 +1201,11 @@ RSpec.describe UsersController, type: :request do
expect(page).to have_content(other_org_user.name) expect(page).to have_content(other_org_user.name)
end 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 it "shows last logged in date for all users" do
expect(page).to have_content("10 October 2022") expect(page).to have_content("10 October 2022")
end end
@ -1432,6 +1437,10 @@ RSpec.describe UsersController, type: :request do
expect(page).to have_link("Change", text: "if a key contact") expect(page).to have_link("Change", text: "if a key contact")
end 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 it "does not show option to resend confirmation email" do
expect(page).not_to have_button("Resend invite link") expect(page).not_to have_button("Resend invite link")
end 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
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 context "when location exists but not related" do
let(:other_scheme) { create(:scheme, :with_old_visible_id) } let(:other_scheme) { create(:scheme, :with_old_visible_id) }
let(:other_location) { create(:location, :with_old_visible_id, scheme: other_scheme) } 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
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 context "when location exists but not related" do
let(:other_scheme) { create(:scheme, :with_old_visible_id) } let(:other_scheme) { create(:scheme, :with_old_visible_id) }
let(:other_location) { create(:location, :with_old_visible_id, scheme: other_scheme) } 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 end
context "without data sharing agreement" 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 include create log buttons" do it "does not include create log buttons" do
render 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 } let(:data_protection_confirmation) { nil }
context "when dpo" do 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 it "renders dynamic content" do
render 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) } let(:organisation_with_dsa) { create(:organisation) }
context "when dpo" do 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 it "includes data sharing agreement row" do
render render
@ -32,7 +32,7 @@ RSpec.describe "organisations/show.html.erb" do
end end
context "when accepted" do 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 it "includes data sharing agreement row" do
render render
@ -55,7 +55,7 @@ RSpec.describe "organisations/show.html.erb" do
end end
context "when support user" do 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 it "includes data sharing agreement row" do
render render
@ -82,7 +82,7 @@ RSpec.describe "organisations/show.html.erb" do
end end
context "when accepted" do 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 it "includes data sharing agreement row" do
render render
@ -125,7 +125,7 @@ RSpec.describe "organisations/show.html.erb" do
end end
context "when not dpo" do 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 it "includes data sharing agreement row" do
render render
@ -149,7 +149,7 @@ RSpec.describe "organisations/show.html.erb" do
end end
context "when accepted" do 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 it "includes data sharing agreement row" do
render render

Loading…
Cancel
Save