Browse Source

Merge branch 'main' into CreateNotifications

pull/2613/head
Rachael Booth 2 years ago
parent
commit
31e3b2473d
  1. 1
      .env.example
  2. 1
      .env.test
  3. 2
      Dockerfile
  4. 201
      app/controllers/merge_requests_controller.rb
  5. 4
      app/controllers/organisations_controller.rb
  6. 19
      app/controllers/schemes_controller.rb
  7. 4
      app/frontend/controllers/index.js
  8. 35
      app/frontend/controllers/tabs_controller.js
  9. 8
      app/helpers/deactivate_confirm_helper.rb
  10. 31
      app/helpers/locations_helper.rb
  11. 279
      app/helpers/merge_requests_helper.rb
  12. 11
      app/helpers/schemes_helper.rb
  13. 8
      app/helpers/tag_helper.rb
  14. 15
      app/jobs/process_merge_request_job.rb
  15. 70
      app/models/location.rb
  16. 178
      app/models/merge_request.rb
  17. 13
      app/models/merge_request_organisation.rb
  18. 8
      app/models/organisation.rb
  19. 38
      app/models/scheme.rb
  20. 1
      app/models/scheme_deactivation_period.rb
  21. 4
      app/models/validations/sales/sale_information_validations.rb
  22. 6
      app/models/validations/shared_validations.rb
  23. 7
      app/services/bulk_upload/lettings/year2024/csv_parser.rb
  24. 2
      app/services/bulk_upload/lettings/year2024/row_parser.rb
  25. 6
      app/services/bulk_upload/sales/year2024/csv_parser.rb
  26. 2
      app/services/bulk_upload/sales/year2024/row_parser.rb
  27. 4
      app/views/bulk_upload_shared/guidance.html.erb
  28. 3
      app/views/layouts/application.html.erb
  29. 13
      app/views/locations/index.html.erb
  30. 33
      app/views/merge_requests/_details_list.html.erb
  31. 46
      app/views/merge_requests/_merge_request_list.html.erb
  32. 21
      app/views/merge_requests/_notification_banners.html.erb
  33. 8
      app/views/merge_requests/_summary_card.html.erb
  34. 40
      app/views/merge_requests/absorbing_organisation.html.erb
  35. 41
      app/views/merge_requests/confirm_telephone_number.html.erb
  36. 23
      app/views/merge_requests/delete_confirmation.html.erb
  37. 27
      app/views/merge_requests/existing_absorbing_organisation.html.erb
  38. 27
      app/views/merge_requests/helpdesk_ticket.html.erb
  39. 27
      app/views/merge_requests/logs_outcomes.html.erb
  40. 24
      app/views/merge_requests/merge_date.html.erb
  41. 0
      app/views/merge_requests/merge_request.html.erb
  42. 27
      app/views/merge_requests/merge_start_confirmation.html.erb
  43. 50
      app/views/merge_requests/merging_organisations.html.erb
  44. 33
      app/views/merge_requests/new_organisation_address.html.erb
  45. 19
      app/views/merge_requests/new_organisation_name.html.erb
  46. 20
      app/views/merge_requests/new_organisation_telephone_number.html.erb
  47. 5
      app/views/merge_requests/new_organisation_type.html.erb
  48. 51
      app/views/merge_requests/organisations.html.erb
  49. 25
      app/views/merge_requests/relationship_outcomes.html.erb
  50. 23
      app/views/merge_requests/scheme_outcomes.html.erb
  51. 26
      app/views/merge_requests/show.html.erb
  52. 23
      app/views/merge_requests/user_outcomes.html.erb
  53. 2
      app/views/organisation_relationships/_related_organisation_select_question.html.erb
  54. 1
      app/views/organisation_relationships/add_managing_agent.html.erb
  55. 1
      app/views/organisation_relationships/add_stock_owner.html.erb
  56. 25
      app/views/organisations/index.html.erb
  57. 41
      app/views/schemes/deactivate_confirm.html.erb
  58. 13
      app/views/schemes/toggle_active.html.erb
  59. 17
      config/locales/en.yml
  60. 21
      config/routes.rb
  61. 5
      db/migrate/20240808134014_add_merge_date_to_merge_requests.rb
  62. 16
      db/migrate/20240809154241_add_additional_fields_to_merge_requests.rb
  63. 5
      db/migrate/20240813072041_remove_other_merging_org_field.rb
  64. 27
      db/migrate/20240813112119_remove_new_org_merge_request_fields.rb
  65. 5
      db/migrate/20240814083017_add_last_failed_attempt.rb
  66. 17
      db/migrate/20240819100411_update_merge_request_fields_for_status.rb
  67. 5
      db/migrate/20240822080228_add_existing_absorbing_organisation_field.rb
  68. 25
      db/schema.rb
  69. 24
      db/seeds.rb
  70. 8
      spec/factories/merge_request.rb
  71. 6
      spec/factories/merge_request_organisation.rb
  72. 2
      spec/features/schemes_spec.rb
  73. 2
      spec/fixtures/files/locations_csv_export.csv
  74. 2
      spec/fixtures/files/schemes_and_locations_csv_export.csv
  75. 272
      spec/helpers/merge_requests_helper_spec.rb
  76. 65
      spec/jobs/process_merge_request_job_spec.rb
  77. 33
      spec/models/location_spec.rb
  78. 451
      spec/models/merge_request_spec.rb
  79. 28
      spec/requests/merge_request_spec.rb
  80. 815
      spec/requests/merge_requests_controller_spec.rb
  81. 13
      spec/requests/organisations_controller_spec.rb
  82. 31
      spec/requests/schemes_controller_spec.rb
  83. 5
      spec/services/bulk_upload/lettings/year2024/csv_parser_spec.rb
  84. 12
      spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb
  85. 5
      spec/services/bulk_upload/sales/year2024/csv_parser_spec.rb
  86. 12
      spec/services/bulk_upload/sales/year2024/row_parser_spec.rb
  87. 93
      spec/views/merge_requests/show.html.erb_spec.rb
  88. 46
      spec/views/schemes/deactivate_confirm.html.erb_spec.rb

1
.env.example

@ -6,3 +6,4 @@ OTP_SECRET_ENCRYPTION_KEY="<Generate this using bundle exec rake secret>"
APP_HOST="http://localhost:3000"
OS_DATA_KEY=OS_DATA_KEY
REVIEW_APP_USER_PASSWORD=password

1
.env.test

@ -1,2 +1,3 @@
APP_HOST="http://localhost:3000"
OS_DATA_KEY=OS_DATA_KEY
REVIEW_APP_USER_PASSWORD=password

2
Dockerfile

@ -10,7 +10,7 @@ RUN apk add --update --no-cache tzdata && \
# build-base: compilation tools for bundle
# yarn: node package manager
# postgresql-dev: postgres driver and libraries
RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r7 nodejs-current=20.8.1-r0 yarn=1.22.19-r0 postgresql13-dev=13.15-r0 git=2.40.1-r0 bash=5.2.15-r5
RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r7 nodejs-current=20.8.1-r0 yarn=1.22.19-r0 postgresql13-dev=13.15-r0 git=2.40.3-r0 bash=5.2.15-r5
# Bundler version should be the same version as what the Gemfile.lock was bundled with
RUN gem install bundler:2.3.14 --no-document

201
app/controllers/merge_requests_controller.rb

@ -1,66 +1,77 @@
class MergeRequestsController < ApplicationController
before_action :find_resource, only: %i[
update
organisations
update_organisations
remove_merging_organisation
absorbing_organisation
confirm_telephone_number
new_organisation_name
new_organisation_address
new_organisation_telephone_number
new_organisation_type
merge_date
]
before_action :find_resource, exclude: %i[create new]
before_action :authenticate_user!
before_action :authenticate_scope!, except: [:create]
before_action :authenticate_scope!
before_action :set_organisations_answer_options, only: %i[merging_organisations absorbing_organisation update_merging_organisations remove_merging_organisation update]
def absorbing_organisation; end
def confirm_telephone_number; end
def new_organisation_name; end
def new_organisation_address; end
def new_organisation_telephone_number; end
def new_organisation_type; end
def merge_date; end
def existing_absorbing_organisation; end
def helpdesk_ticket; end
def merge_start_confirmation; end
def user_outcomes; end
def relationship_outcomes; end
def scheme_outcomes; end
def logs_outcomes; end
def create
ActiveRecord::Base.transaction do
@merge_request = MergeRequest.create!(merge_request_params.merge(status: :unsubmitted))
MergeRequestOrganisation.create!({ merge_request: @merge_request, merging_organisation: @merge_request.requesting_organisation })
@merge_request = MergeRequest.create!(merge_request_params.merge(status: :incomplete, requester: current_user))
end
redirect_to organisations_merge_request_path(@merge_request)
redirect_to absorbing_organisation_merge_request_path(@merge_request)
rescue ActiveRecord::RecordInvalid
render_not_found
end
def organisations
@answer_options = organisations_answer_options
end
def update
validate_response
if @merge_request.errors.blank? && @merge_request.update(merge_request_params)
add_merging_organsations if page == "merging_organisations"
remove_absorbing_org_from_merging_organisations if page == "absorbing_organisation" && @merge_request.absorbing_organisation_id.present?
redirect_to next_page_path
else
render previous_template, status: :unprocessable_entity
end
end
def update_organisations
def update_merging_organisations
@new_merging_org_ids = params["merge_request"]["new_merging_org_ids"].split(" ")
merge_request_organisation = MergeRequestOrganisation.new(merge_request_organisation_params)
@answer_options = organisations_answer_options
if merge_request_organisation.save
render :organisations
if merge_request_organisation.valid?
@new_merging_org_ids.push(merge_request_organisation_params[:merging_organisation_id])
render :merging_organisations
else
render :organisations, status: :unprocessable_entity
render :merging_organisations, status: :unprocessable_entity
end
end
def remove_merging_organisation
@new_merging_org_ids = params["merge_request"]["new_merging_org_ids"] || []
org_id_to_remove = merge_request_organisation_params[:merging_organisation_id]
@new_merging_org_ids.delete(org_id_to_remove)
MergeRequestOrganisation.find_by(merge_request_organisation_params)&.destroy!
@answer_options = organisations_answer_options
render :organisations
render :merging_organisations
end
def delete
@merge_request.discard!
flash[:notice] = "The merge request has been deleted."
redirect_to organisations_path(tab: "merge-requests")
end
def merging_organisations
@new_merging_org_ids = []
end
def start_merge
if @merge_request.status == "ready_to_merge"
@merge_request.start_merge!
ProcessMergeRequestJob.perform_later(merge_request: @merge_request)
end
redirect_to merge_request_path(@merge_request)
end
private
@ -70,23 +81,19 @@ private
end
def next_page_path
return merge_request_path if is_referrer_type?("check_answers")
case page
when "absorbing_organisation"
if create_new_organisation?
new_organisation_name_merge_request_path(@merge_request)
else
confirm_telephone_number_merge_request_path(@merge_request)
end
when "organisations"
absorbing_organisation_merge_request_path(@merge_request)
when "confirm_telephone_number"
merging_organisations_merge_request_path(@merge_request)
when "merging_organisations"
merge_date_merge_request_path(@merge_request)
when "new_organisation_name"
new_organisation_address_merge_request_path(@merge_request)
when "new_organisation_address"
new_organisation_telephone_number_merge_request_path(@merge_request)
when "new_organisation_telephone_number"
new_organisation_type_merge_request_path(@merge_request)
when "merge_date"
existing_absorbing_organisation_merge_request_path(@merge_request)
when "existing_absorbing_organisation"
helpdesk_ticket_merge_request_path(@merge_request)
when "helpdesk_ticket"
merge_request_path(@merge_request)
end
end
@ -94,50 +101,29 @@ private
page
end
def create_new_organisation?
params.dig(:merge_request, :absorbing_organisation_id) == "other"
end
def organisations_answer_options
def set_organisations_answer_options
answer_options = { "" => "Select an option" }
Organisation.all.pluck(:id, :name).each do |organisation|
answer_options[organisation[0]] = organisation[1]
if current_user.support?
Organisation.all.pluck(:id, :name).each do |organisation|
answer_options[organisation[0]] = organisation[1]
end
end
answer_options
@answer_options = answer_options
end
def merge_request_params
merge_params = params.fetch(:merge_request, {}).permit(
:requesting_organisation_id,
:other_merging_organisations,
:helpdesk_ticket,
:status,
:absorbing_organisation_id,
:telephone_number_correct,
:new_telephone_number,
:new_organisation_name,
:new_organisation_address_line1,
:new_organisation_address_line2,
:new_organisation_postcode,
:new_organisation_telephone_number,
:merge_date,
:existing_absorbing_organisation,
)
if merge_params[:requesting_organisation_id].present? && (current_user.data_coordinator? || current_user.data_provider?)
merge_params[:requesting_organisation_id] = current_user.organisation.id
end
if merge_params[:absorbing_organisation_id].present?
if create_new_organisation?
merge_params[:new_absorbing_organisation] = true
merge_params[:absorbing_organisation_id] = nil
else
merge_params[:new_absorbing_organisation] = false
end
end
if merge_params[:telephone_number_correct] == "true"
merge_params[:new_telephone_number] = nil
end
merge_params[:requesting_organisation_id] = current_user.organisation.id
merge_params
end
@ -145,18 +131,25 @@ private
def validate_response
case page
when "absorbing_organisation"
if merge_request_params[:absorbing_organisation_id].blank? && merge_request_params[:new_absorbing_organisation].blank?
if merge_request_params[:absorbing_organisation_id].blank?
@merge_request.errors.add(:absorbing_organisation_id, :blank)
end
when "confirm_telephone_number"
if merge_request_params[:telephone_number_correct].blank?
@merge_request.errors.add(:telephone_number_correct, :blank) if @merge_request.absorbing_organisation.phone.present?
@merge_request.errors.add(:new_telephone_number, :blank) if @merge_request.absorbing_organisation.phone.blank?
when "merge_date"
day = merge_request_params["merge_date(3i)"]
month = merge_request_params["merge_date(2i)"]
year = merge_request_params["merge_date(1i)"]
return @merge_request.errors.add(:merge_date, :blank) if [day, month, year].all?(&:blank?)
if [day, month, year].none?(&:blank?) && Date.valid_date?(year.to_i, month.to_i, day.to_i)
merge_request_params["merge_date"] = Time.zone.local(year.to_i, month.to_i, day.to_i)
else
@merge_request.errors.add(:merge_date, :invalid)
end
when "existing_absorbing_organisation"
if merge_request_params[:existing_absorbing_organisation].nil?
@merge_request.errors.add(:existing_absorbing_organisation, :blank)
end
when "new_organisation_name"
@merge_request.errors.add(:new_organisation_name, :blank) if merge_request_params[:new_organisation_name].blank?
when "new_organisation_telephone_number"
@merge_request.errors.add(:new_organisation_telephone_number, :blank) if merge_request_params[:new_organisation_telephone_number].blank?
end
end
@ -168,12 +161,42 @@ private
end
def find_resource
return if params[:id].blank?
@merge_request = MergeRequest.find(params[:id])
end
def authenticate_scope!
if current_user.organisation != @merge_request.requesting_organisation && !current_user.support?
unless current_user.support?
render_not_found
end
end
def is_referrer_type?(referrer_type)
from_referrer_query("referrer") == referrer_type
end
def from_referrer_query(query_param)
referrer = request.headers["HTTP_REFERER"]
return unless referrer
query_params = URI.parse(referrer).query
return unless query_params
parsed_params = CGI.parse(query_params)
parsed_params[query_param]&.first
end
def add_merging_organsations
new_merging_org_ids = params["merge_request"]["new_merging_org_ids"].split(" ")
new_merging_org_ids.each do |org_id|
MergeRequestOrganisation.create!(merge_request: @merge_request, merging_organisation_id: org_id)
end
end
def remove_absorbing_org_from_merging_organisations
if @merge_request.merge_request_organisations.where(merging_organisation_id: @merge_request.absorbing_organisation_id).exists?
MergeRequestOrganisation.find_by(merge_request: @merge_request, merging_organisation_id: @merge_request.absorbing_organisation_id).destroy!
end
end
end

4
app/controllers/organisations_controller.rb

@ -16,6 +16,9 @@ class OrganisationsController < ApplicationController
all_organisations = Organisation.order(:name)
@pagy, @organisations = pagy(filtered_collection(all_organisations.visible, search_term))
@merge_requests = MergeRequest.visible
.joins("LEFT JOIN organisations ON organisations.id = merge_requests.absorbing_organisation_id")
.order("organisations.name, merge_requests.merge_date DESC NULLS LAST, merge_requests.id")
@searched = search_term.presence
@total_count = all_organisations.visible.size
end
@ -235,6 +238,7 @@ class OrganisationsController < ApplicationController
def merge_request
@merge_request = MergeRequest.new
render "merge_requests/merge_request"
end
def data_sharing_agreement

19
app/controllers/schemes_controller.rb

@ -51,17 +51,24 @@ class SchemesController < ApplicationController
end
def deactivate_confirm
@affected_logs = @scheme.lettings_logs.visible.after_date(params[:deactivation_date])
if @affected_logs.count.zero?
@deactivation_date = Time.zone.parse(params[:deactivation_date])
@affected_logs = @scheme.lettings_logs.visible.after_date(@deactivation_date)
@deactivation_date_type = params[:deactivation_date_type]
scheme_locations = @scheme.locations.confirmed
@affected_locations = scheme_locations.select do |location|
%i[active deactivating_soon reactivating_soon activating_soon].include?(location.status_at(@deactivation_date))
end
if @affected_logs.count.zero? && @affected_locations.count.zero?
deactivate
else
@deactivation_date = params[:deactivation_date]
@deactivation_date_type = params[:deactivation_date_type]
end
end
def deactivate
if @scheme.open_deactivation&.update!(deactivation_date: params[:deactivation_date]) || @scheme.scheme_deactivation_periods.create!(deactivation_date: params[:deactivation_date])
deactivation_date = params[:deactivation_date]
if @scheme.open_deactivation&.update!(deactivation_date:) || @scheme.scheme_deactivation_periods.create!(deactivation_date:)
logs = reset_location_and_scheme_for_logs!
flash[:notice] = deactivate_success_notice

4
app/frontend/controllers/index.js

@ -16,6 +16,9 @@ import NumericQuestionController from './numeric_question_controller.js'
import SearchController from './search_controller.js'
import FilterLayoutController from './filter_layout_controller.js'
import TabsController from './tabs_controller.js'
application.register('accessible-autocomplete', AccessibleAutocompleteController)
application.register('conditional-filter', ConditionalFilterController)
application.register('conditional-question', ConditionalQuestionController)
@ -23,3 +26,4 @@ application.register('govukfrontend', GovukfrontendController)
application.register('numeric-question', NumericQuestionController)
application.register('filter-layout', FilterLayoutController)
application.register('search', SearchController)
application.register('tabs', TabsController)

35
app/frontend/controllers/tabs_controller.js

@ -0,0 +1,35 @@
document.addEventListener('DOMContentLoaded', function () {
const urlParams = new URLSearchParams(window.location.search)
let tab = urlParams.get('tab')
if (!tab && window.location.hash) {
tab = window.location.hash.substring(1)
urlParams.set('tab', tab)
window.history.replaceState(null, null, `${window.location.pathname}?${urlParams.toString()}`)
}
function activateTab (tabId) {
const tabElement = document.getElementById(tabId)
if (tabElement) {
tabElement.click()
}
window.history.replaceState(null, null, `${window.location.pathname}?${urlParams.toString()}`)
}
function handleTabClick (event) {
event.preventDefault()
const targetId = this.getAttribute('href').substring(1)
activateTab(targetId)
urlParams.set('tab', targetId)
window.history.replaceState(null, null, `${window.location.pathname}?${urlParams.toString()}`)
}
if (tab) {
activateTab(`tab_${tab}`)
}
window.scrollTo(0, 0)
document.querySelectorAll('.govuk-tabs__tab').forEach(tabElement => {
tabElement.addEventListener('click', handleTabClick)
})
})

8
app/helpers/deactivate_confirm_helper.rb

@ -0,0 +1,8 @@
module DeactivateConfirmHelper
def affected_title(affected_logs, affected_locations)
title_parts = []
title_parts << pluralize(affected_logs.count, "log") if affected_logs.count.positive?
title_parts << pluralize(affected_locations.count, "location") if affected_locations.count.positive?
"This change will affect #{title_parts.join(' and ')}."
end
end

31
app/helpers/locations_helper.rb

@ -70,7 +70,7 @@ module LocationsHelper
def toggle_location_link(location)
return govuk_button_link_to "Deactivate this location", scheme_location_new_deactivation_path(location.scheme, location), warning: true if location.active? || location.deactivates_in_a_long_time?
return govuk_button_link_to "Reactivate this location", scheme_location_new_reactivation_path(location.scheme, location) if location.deactivated?
return govuk_button_link_to "Reactivate this location", scheme_location_new_reactivation_path(location.scheme, location) if location.deactivated? && !location.deactivated_by_scheme?
end
def delete_location_link(location)
@ -100,14 +100,37 @@ private
ActivePeriod = Struct.new(:from, :to)
def location_active_periods(location)
periods = [ActivePeriod.new(location.available_from, nil)]
location_deactivation_periods = location_deactivation_periods(location)
scheme_deactivation_periods = scheme_deactivation_periods(location, location_deactivation_periods)
sorted_deactivation_periods = remove_nested_periods(location.location_deactivation_periods.sort_by(&:deactivation_date))
combined_deactivation_periods = location_deactivation_periods + scheme_deactivation_periods
sorted_deactivation_periods = combined_deactivation_periods.sort_by(&:deactivation_date)
update_periods_with_deactivations(periods, sorted_deactivation_periods)
remove_overlapping_and_empty_periods(periods)
end
def location_deactivation_periods(location)
periods = remove_nested_periods(location.location_deactivation_periods.sort_by(&:deactivation_date))
periods.last&.deactivation_date if periods.last&.reactivation_date.nil?
periods
end
def scheme_deactivation_periods(location, location_deactivation_periods)
return [] unless location.scheme.scheme_deactivation_periods.any?
location_deactivation_date = location_deactivation_periods.last&.deactivation_date
periods = remove_nested_periods(location.scheme.scheme_deactivation_periods.sort_by(&:deactivation_date))
periods.select do |period|
period.deactivation_date >= location.available_from && (location_deactivation_date.nil? || period.deactivation_date <= location_deactivation_date)
end
end
def update_periods_with_deactivations(periods, sorted_deactivation_periods)
sorted_deactivation_periods.each do |deactivation|
periods.last.to = deactivation.deactivation_date
periods << ActivePeriod.new(deactivation.reactivation_date, nil)
end
remove_overlapping_and_empty_periods(periods)
end
def remove_overlapping_and_empty_periods(periods)

279
app/helpers/merge_requests_helper.rb

@ -0,0 +1,279 @@
module MergeRequestsHelper
include GovukLinkHelper
include GovukVisuallyHiddenHelper
def display_value_or_placeholder(value, placeholder = "You didn't answer this question")
value.presence || content_tag(:span, placeholder, class: "app-!-colour-muted")
end
def request_details(merge_request)
[
{ label: "Requester", value: display_value_or_placeholder(merge_request.requester&.name) },
{ label: "Helpdesk ticket", value: merge_request.helpdesk_ticket.present? ? link_to("#{merge_request.helpdesk_ticket} (opens in a new tab)", "https://dluhcdigital.atlassian.net/browse/#{merge_request.helpdesk_ticket}", target: "_blank", rel: "noopener noreferrer") : display_value_or_placeholder(nil), action: merge_request_action(merge_request, "helpdesk_ticket") },
{ label: "Status", value: status_tag(merge_request.status) },
]
end
def merge_details(merge_request)
[
{ label: "Absorbing organisation", value: display_value_or_placeholder(merge_request.absorbing_organisation_name), action: merge_request_action(merge_request, "absorbing_organisation") },
{ label: "Merging organisations", value: merge_request.merge_request_organisations.any? ? merge_request.merge_request_organisations.map(&:merging_organisation_name).join("<br>").html_safe : display_value_or_placeholder(nil), action: merge_request_action(merge_request, "merging_organisations") },
{ label: "Merge date", value: display_value_or_placeholder(merge_request.merge_date), action: merge_request_action(merge_request, "merge_date") },
{ label: "Absorbing organisation already active?", value: display_value_or_placeholder(merge_request.existing_absorbing_organisation_label), action: merge_request_action(merge_request, "existing_absorbing_organisation") },
]
end
def merge_outcomes(merge_request)
[
{ label: "Total users after merge", value: display_value_or_placeholder(merge_request.total_users_label), action: merge_outcome_action(merge_request, "user_outcomes") },
{ label: "Total schemes after merge", value: display_value_or_placeholder(merge_request.total_schemes_label), action: merge_outcome_action(merge_request, "scheme_outcomes") },
{ label: "Total logs after merge", value: display_value_or_placeholder(merge_request.total_logs_label), action: merge_outcome_action(merge_request, "logs_outcomes") },
{ label: "Total stock owners & managing agents after merge", value: display_value_or_placeholder(merge_request.total_stock_owners_managing_agents_label), action: merge_outcome_action(merge_request, "relationship_outcomes") },
]
end
def ordered_merging_organisations(merge_request, new_merging_org_ids)
Organisation.where(id: new_merging_org_ids) + merge_request.merge_request_organisations.order(created_at: :desc).map(&:merging_organisation)
end
def submit_merge_request_button_text(referrer)
if accessed_from_check_answers?(referrer)
"Save changes"
else
"Save and continue"
end
end
def secondary_merge_request_link_text(referrer, skip_for_now: false)
if accessed_from_check_answers?(referrer)
"Cancel"
elsif skip_for_now
"Skip for now"
else
""
end
end
def accessed_from_check_answers?(referrer)
%w[check_answers].include?(referrer)
end
def merge_request_back_link(merge_request, page, referrer)
return merge_request_path(merge_request) if accessed_from_check_answers?(referrer)
case page
when "absorbing_organisation"
organisations_path(tab: "merge-requests")
when "merging_organisations"
absorbing_organisation_merge_request_path(merge_request)
when "merge_date"
merging_organisations_merge_request_path(merge_request)
when "existing_absorbing_organisation"
merge_date_merge_request_path(merge_request)
when "helpdesk_ticket"
existing_absorbing_organisation_merge_request_path(merge_request)
end
end
def merge_request_action(merge_request, page)
unless merge_request.status == "request_merged" || merge_request.status == "processing"
{ text: "Change", href: send("#{page}_merge_request_path", merge_request, referrer: "check_answers"), visually_hidden_text: page.humanize }
end
end
def merge_outcome_action(merge_request, page)
unless merge_request.status == "request_merged" || merge_request.status == "processing"
{ text: "View", href: send("#{page}_merge_request_path", merge_request), visually_hidden_text: page.humanize }
end
end
def submit_merge_request_url(referrer)
referrer == "check_answers" ? merge_request_path(referrer: "check_answers") : merge_request_path
end
def merging_organisations_without_users_text(organisations)
return "" unless organisations.count.positive?
if organisations.count == 1
"#{organisations.first.name} has no users."
else
"#{organisations.map(&:name).to_sentence} have no users."
end
end
def link_to_merging_organisation_users(organisation)
count_text = organisation.users.count == 1 ? "1 #{organisation.name} user" : "all #{organisation.users.count} #{organisation.name} users"
govuk_link_to "View #{count_text} (opens in a new tab)", users_organisation_path(organisation), target: "_blank"
end
def total_users_after_merge_text(merge_request)
count = merge_request.total_visible_users_after_merge
"#{"#{count} user".pluralize(count)} after merge"
end
def total_stock_owners_after_merge_text(merge_request)
count = merge_request.total_stock_owners_after_merge
"#{"#{count} stock owner".pluralize(count)} after merge"
end
def total_managing_agents_after_merge_text(merge_request)
count = merge_request.total_managing_agents_after_merge
"#{"#{count} managing agent".pluralize(count)} after merge"
end
def related_organisations(merge_request, relationship_type)
organisations = merge_request.absorbing_organisation.send(relationship_type.pluralize).visible + merge_request.merging_organisations.flat_map { |org| org.send(relationship_type.pluralize).visible }
organisations += [merge_request.absorbing_organisation] + merge_request.merging_organisations
organisations.group_by { |relationship| relationship }.select { |_, occurrences| occurrences.size > 1 }.keys
end
def related_organisations_text(merge_request, relationship_type)
if related_organisations(merge_request, relationship_type).any?
"Some of the organisations merging have common #{relationship_type.humanize(capitalize: false).pluralize}.<br><br>"
else
""
end
end
def organisations_without_relationships(merge_request, relationship_type)
([merge_request.absorbing_organisation] + merge_request.merging_organisations).select { |org| org.send(relationship_type.pluralize).visible.empty? }
end
def organisations_without_relationships_text(organisations_without_relationships, relationship_type)
return "" unless organisations_without_relationships.any?
org_names = organisations_without_relationships.map(&:name).to_sentence
verb = organisations_without_relationships.count > 1 ? "have" : "has"
"#{org_names} #{verb} no #{relationship_type.humanize(capitalize: false).pluralize}.<br><br>"
end
def generate_organisation_link_text(organisation_count, org, relationship_type)
"View #{organisation_count == 1 ? 'the' : 'all'} #{organisation_count} #{org.name} #{relationship_type.humanize(capitalize: false).pluralize(organisation_count)} (opens in a new tab)"
end
def relationship_text(merge_request, relationship_type, organisation_path_helper)
text = ""
organisations_without_relationships = organisations_without_relationships(merge_request, relationship_type)
text += related_organisations_text(merge_request, relationship_type)
text += organisations_without_relationships_text(organisations_without_relationships, relationship_type)
([merge_request.absorbing_organisation] + merge_request.merging_organisations).each do |org|
organisation_count = org.send(relationship_type.pluralize).visible.count
next if organisation_count.zero?
link_text = generate_organisation_link_text(organisation_count, org, relationship_type)
text += "#{govuk_link_to(link_text, send(organisation_path_helper, org), target: '_blank')}<br><br>"
end
text.html_safe
end
def stock_owners_text(merge_request)
relationship_text(merge_request, "stock_owner", :stock_owners_organisation_path)
end
def managing_agent_text(merge_request)
relationship_text(merge_request, "managing_agent", :managing_agents_organisation_path)
end
def merging_organisations_without_schemes_text(organisations)
return "" unless organisations.count.positive?
if organisations.count == 1
"#{organisations.first.name} has no schemes."
else
"#{organisations.map(&:name).to_sentence} have no schemes."
end
end
def link_to_merging_organisation_schemes(organisation)
count_text = organisation.owned_schemes.count == 1 ? "1 #{organisation.name} scheme" : "all #{organisation.owned_schemes.count} #{organisation.name} schemes"
govuk_link_to "View #{count_text} (opens in a new tab)", schemes_organisation_path(organisation), target: "_blank"
end
def total_schemes_after_merge_text(merge_request)
count = merge_request.total_visible_schemes_after_merge
"#{"#{count} scheme".pluralize(count)} after merge"
end
def total_lettings_logs_after_merge_text(merge_request)
count = merge_request.total_visible_lettings_logs_after_merge
"#{"#{count} lettings log".pluralize(count)} after merge"
end
def total_sales_logs_after_merge_text(merge_request)
count = merge_request.total_visible_sales_logs_after_merge
"#{"#{count} sales log".pluralize(count)} after merge"
end
def merging_organisations_lettings_logs_outcomes_text(merge_request)
merging_organisations_logs_outcomes_text(merge_request, "lettings")
end
def merging_organisations_sales_logs_outcomes_text(merge_request)
merging_organisations_logs_outcomes_text(merge_request, "sales")
end
def merging_organisations_logs_outcomes_text(merge_request, type)
text = ""
if any_organisations_have_logs?(merge_request.merging_organisations, type)
managed_or_reported = type == "lettings" ? "managed" : "reported"
merging_organisations = merge_request.merging_organisations.count == 1 ? "merging organisation" : "merging organisations"
text += "#{merge_request.absorbing_organisation.name} users will have access to all #{type} logs owned or #{managed_or_reported} by the #{merging_organisations} after the merge.<br><br>"
if any_organisations_have_logs_after_merge_date?(merge_request.merging_organisations, type, merge_request.merge_date)
startdate = type == "lettings" ? "tenancy start date" : "sale completion date"
text += "#{type.capitalize} logs that are owned or #{managed_or_reported} by the #{merging_organisations} and have a #{startdate} after the merge date will have their owning or managing organisation changed to #{merge_request.absorbing_organisation.name}.<br><br>"
end
if any_organisations_share_logs?(merge_request.merging_organisations, type)
text += "Some logs are owned and #{managed_or_reported} by different organisations in this merge. They appear in the list for both the owning and the managing organisation.<br><br>"
end
end
organisations_without_logs, organisations_with_logs = merge_request.merging_organisations.partition { |organisation| organisation.send("#{type}_logs").count.zero? }
if merge_request.absorbing_organisation.send("#{type}_logs").count.zero?
organisations_without_logs = [merge_request.absorbing_organisation] + organisations_without_logs
else
organisations_with_logs = [merge_request.absorbing_organisation] + organisations_with_logs
end
if organisations_without_logs.any?
text += "#{organisations_without_logs.map(&:name).to_sentence} #{organisations_without_logs.count == 1 ? 'has' : 'have'} no #{type} logs.<br><br>"
end
organisations_with_logs.each do |organisation|
text += "#{link_to_merging_organisation_logs(organisation, type)}<br><br>"
end
text.html_safe
end
def link_to_merging_organisation_logs(organisation, type)
count_text = organisation.send("#{type}_logs").count == 1 ? "1 #{organisation.name} #{type} log" : "all #{organisation.send("#{type}_logs").count} #{organisation.name} #{type} logs"
govuk_link_to "View #{count_text} (opens in a new tab)", send("#{type}_logs_organisation_path", organisation), target: "_blank"
end
def lettings_logs_outcomes_header_text(merge_request)
count = merge_request.total_visible_lettings_logs_after_merge
"#{count} #{'lettings log'.pluralize(count)} after merge"
end
def sales_logs_outcomes_header_text(merge_request)
count = merge_request.total_visible_sales_logs_after_merge
"#{count} #{'sales log'.pluralize(count)} after merge"
end
def any_organisations_have_logs?(organisations, type)
organisations.any? { |organisation| organisation.send("#{type}_logs").count.positive? }
end
def any_organisations_have_logs_after_merge_date?(organisations, type, merge_date)
organisations.any? { |organisation| organisation.send("#{type}_logs").after_date(merge_date).exists? }
end
def any_organisations_share_logs?(organisations, type)
organisations.any? { |organisation| organisation.send("#{type}_logs").filter_by_managing_organisation(organisations.where.not(id: organisation.id)).exists? }
end
end

11
app/helpers/schemes_helper.rb

@ -12,7 +12,7 @@ module SchemesHelper
def toggle_scheme_link(scheme)
return govuk_button_link_to "Deactivate this scheme", scheme_new_deactivation_path(scheme), warning: true if scheme.active? || scheme.deactivates_in_a_long_time?
return govuk_button_link_to "Reactivate this scheme", scheme_new_reactivation_path(scheme) if scheme.deactivated?
return govuk_button_link_to "Reactivate this scheme", scheme_new_reactivation_path(scheme) if scheme.deactivated? || scheme.deactivating_soon?
end
def delete_scheme_link(scheme)
@ -76,6 +76,15 @@ module SchemesHelper
end
end
def scheme_status_hint(scheme)
case scheme.status
when :deactivating_soon
"This scheme deactivates on #{scheme.last_deactivation_date.to_formatted_s(:govuk_date)}. Any locations you add will be deactivated on the same date. Reactivate the scheme to add locations active after this date."
when :deactivated
"This scheme deactivated on #{scheme.last_deactivation_date.to_formatted_s(:govuk_date)}. Any locations you add will be deactivated on the same date. Reactivate the scheme to add locations active after this date."
end
end
private
ActivePeriod = Struct.new(:from, :to)

8
app/helpers/tag_helper.rb

@ -15,6 +15,10 @@ module TagHelper
deleted: "Deleted",
merged: "Merged",
unconfirmed: "Unconfirmed",
merge_issues: "Merge issues",
request_merged: "Merged",
ready_to_merge: "Ready to merge",
processing: "Processing",
}.freeze
COLOUR = {
@ -31,6 +35,10 @@ module TagHelper
deleted: "red",
merged: "orange",
unconfirmed: "blue",
merge_issues: "orange",
request_merged: "green",
ready_to_merge: "blue",
processing: "yellow",
}.freeze
def status_tag(status, classes = [])

15
app/jobs/process_merge_request_job.rb

@ -0,0 +1,15 @@
class ProcessMergeRequestJob < ApplicationJob
queue_as :default
def perform(merge_request:)
absorbing_organisation_id = merge_request.absorbing_organisation_id
merging_organisation_ids = merge_request.merging_organisations.pluck(:id)
merge_date = merge_request.merge_date
absorbing_organisation_active_from_merge_date = !merge_request.existing_absorbing_organisation unless merge_request.existing_absorbing_organisation.nil?
Merge::MergeOrganisationsService.new(absorbing_organisation_id:, merging_organisation_ids:, merge_date:, absorbing_organisation_active_from_merge_date:).call
merge_request.update!(request_merged: true, last_failed_attempt: nil)
rescue StandardError
merge_request.set_back_to_ready_to_merge!
end
end

70
app/models/location.rb

@ -40,6 +40,7 @@ class Location < ApplicationRecord
filtered_records = filtered_records
.left_outer_joins(:location_deactivation_periods)
.joins(scheme: [:owning_organisation])
.left_outer_joins(scheme: :scheme_deactivation_periods)
.order("location_deactivation_periods.created_at DESC")
.merge(scopes.reduce(&:or))
end
@ -52,30 +53,47 @@ class Location < ApplicationRecord
.or(where(confirmed: nil))
}
scope :deactivated, lambda {
scope :deactivated, lambda { |date = Time.zone.now|
deactivated_by_organisation
.or(deactivated_directly)
.or(deactivated_directly(date))
.or(deactivated_by_scheme(date))
}
scope :deactivated_by_organisation, lambda {
merge(Organisation.filter_by_inactive)
}
scope :deactivated_by_scheme, lambda { |date = Time.zone.now|
merge(Scheme.deactivated_directly(date))
}
scope :deactivated_directly, lambda { |date = Time.zone.now|
merge(LocationDeactivationPeriod.deactivations_without_reactivation)
.where("location_deactivation_periods.deactivation_date <= ?", date)
}
scope :deactivating_soon, lambda { |date = Time.zone.now|
scope :deactivating_soon_directly, lambda { |date = Time.zone.now|
merge(LocationDeactivationPeriod.deactivations_without_reactivation)
.where("location_deactivation_periods.deactivation_date > ?", date)
.where.not(id: joins(scheme: [:owning_organisation]).deactivated_by_organisation.pluck(:id))
.where("location_deactivation_periods.deactivation_date > ?", date)
}
scope :deactivating_soon, lambda { |date = Time.zone.now|
deactivating_soon_directly
.or(deactivating_soon_by_scheme(date))
}
scope :deactivating_soon_by_scheme, lambda { |date = Time.zone.now|
merge(Scheme.deactivating_soon(date))
}
scope :reactivating_soon, lambda { |date = Time.zone.now|
where.not("location_deactivation_periods.reactivation_date IS NULL")
.where("location_deactivation_periods.reactivation_date > ?", date)
.where.not(id: joins(scheme: [:owning_organisation]).deactivated_by_organisation.pluck(:id))
.where("location_deactivation_periods.reactivation_date > ?", date)
.where.not(id: joins(scheme: [:owning_organisation]).deactivated_by_organisation.pluck(:id))
}
scope :reactivating_soon_by_scheme, lambda { |date = Time.zone.now|
merge(Scheme.reactivating_soon(date))
}
scope :activating_soon, lambda { |date = Time.zone.now|
@ -84,19 +102,21 @@ class Location < ApplicationRecord
scope :active_status, lambda {
where.not(id: joins(:location_deactivation_periods).reactivating_soon.pluck(:id))
.where.not(id: joins(scheme: [:owning_organisation]).deactivated_by_organisation.pluck(:id))
.where.not(id: joins(:location_deactivation_periods).deactivated_directly.pluck(:id))
.where.not(id: incomplete.pluck(:id))
.where.not(id: joins(:location_deactivation_periods).deactivating_soon.pluck(:id))
.where.not(id: activating_soon.pluck(:id))
.where.not(id: joins(scheme: [:scheme_deactivation_periods]).reactivating_soon_by_scheme.pluck(:id))
.where.not(id: joins(:location_deactivation_periods).merge(Location.deactivated_directly).pluck(:id))
.where.not(id: joins(scheme: [:scheme_deactivation_periods]).merge(Location.deactivated_by_scheme).pluck(:id))
.where.not(id: joins(scheme: [:owning_organisation]).merge(Location.deactivated_by_organisation).pluck(:id))
.where.not(id: incomplete.pluck(:id))
.where.not(id: joins(:location_deactivation_periods).merge(Location.deactivating_soon_directly).pluck(:id))
.where.not(id: joins(scheme: %i[owning_organisation scheme_deactivation_periods]).merge(Location.deactivating_soon_by_scheme).pluck(:id))
.where.not(id: activating_soon.pluck(:id))
}
scope :active, lambda { |date = Time.zone.now|
where.not(id: joins(:location_deactivation_periods).reactivating_soon(date).pluck(:id))
.where.not(id: joins(scheme: [:owning_organisation]).deactivated_by_organisation.pluck(:id))
.where.not(id: joins(:location_deactivation_periods).deactivated_directly(date).pluck(:id))
.where.not(id: incomplete.pluck(:id))
where.not(id: joins(:location_deactivation_periods).merge(Location.deactivated_directly(date)).pluck(:id))
.where.not(id: incomplete.pluck(:id))
.where.not(id: activating_soon(date).pluck(:id))
.where(scheme: Scheme.active(date))
}
scope :visible, -> { where(discarded_at: nil) }
@ -163,10 +183,10 @@ class Location < ApplicationRecord
return :deleted if discarded_at.present?
return :incomplete unless confirmed
return :deactivated if scheme.owning_organisation.status_at(date) == :deactivated ||
open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date
open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date || scheme.status_at(date) == :deactivated
return :deactivating_soon if open_deactivation&.deactivation_date.present? && date < open_deactivation.deactivation_date || scheme.status_at(date) == :deactivating_soon
return :activating_soon if startdate.present? && date < startdate
return :deactivating_soon if open_deactivation&.deactivation_date.present? && date < open_deactivation.deactivation_date
return :reactivating_soon if last_deactivation_before(date)&.reactivation_date.present? && date < last_deactivation_before(date).reactivation_date
return :reactivating_soon if last_deactivation_before(date)&.reactivation_date.present? && date < last_deactivation_before(date).reactivation_date || scheme.status_at(date) == :reactivating_soon
:active
end
@ -187,6 +207,18 @@ class Location < ApplicationRecord
status_at(6.months.from_now) == :deactivating_soon
end
def deactivated_by_scheme?
status == :deactivated && scheme.status == :deactivated
end
def deactivating_soon_by_scheme?
status == :deactivating_soon && scheme.status == :deactivating_soon
end
def reactivating_soon_by_scheme?
status == :reactivating_soon && scheme.status == :reactivating_soon
end
def validate_postcode
if !postcode&.match(POSTCODE_REGEXP)
error_message = I18n.t("validations.postcode")

178
app/models/merge_request.rb

@ -3,18 +3,182 @@ class MergeRequest < ApplicationRecord
has_many :merge_request_organisations
belongs_to :absorbing_organisation, class_name: "Organisation", optional: true
has_many :merging_organisations, through: :merge_request_organisations, source: :merging_organisation
validate :organisation_name_uniqueness, if: :new_organisation_name
validates :new_telephone_number, presence: true, if: -> { telephone_number_correct == false }
belongs_to :requester, class_name: "User", optional: true
STATUS = {
"unsubmitted" => 0,
"submitted" => 1,
merge_issues: "merge_issues",
incomplete: "incomplete",
ready_to_merge: "ready_to_merge",
processing: "processing",
request_merged: "request_merged",
deleted: "deleted",
}.freeze
enum status: STATUS
def organisation_name_uniqueness
if Organisation.where("lower(name) = ?", new_organisation_name&.downcase).exists?
errors.add(:new_organisation_name, :invalid)
scope :not_merged, -> { where(request_merged: [false, nil]) }
scope :merged, -> { where(request_merged: true) }
scope :visible, lambda {
open_collection_period_start_date = FormHandler.instance.start_date_of_earliest_open_collection_period
merged.where("merge_requests.merge_date >= ?", open_collection_period_start_date).or(not_merged).where(discarded_at: nil)
}
def absorbing_organisation_name
absorbing_organisation&.name || ""
end
def dpo_user
absorbing_organisation.data_protection_officers.filter_by_active.first
end
def discard!
update!(discarded_at: Time.zone.now)
end
def status
return STATUS[:deleted] if discarded_at.present?
return STATUS[:request_merged] if request_merged
return STATUS[:processing] if processing
return STATUS[:incomplete] unless required_questions_answered?
return STATUS[:ready_to_merge] if absorbing_organisation_signed_dsa?
STATUS[:merge_issues]
end
def required_questions_answered?
absorbing_organisation_id.present? &&
merge_date.present? &&
!existing_absorbing_organisation.nil? &&
merging_organisations.count.positive? &&
errors.empty?
end
def absorbing_organisation_signed_dsa?
absorbing_organisation&.data_protection_confirmed?
end
def total_visible_users_after_merge
return total_users if status == STATUS[:request_merged] || status == STATUS[:processing]
absorbing_organisation.users.visible.count + merging_organisations.sum { |org| org.users.visible.count }
end
def total_users_label
"#{total_visible_users_after_merge} #{'user'.pluralize(total_visible_users_after_merge)}"
end
def organisations_with_users
return [] unless absorbing_organisation.present? && merging_organisations.any?
([absorbing_organisation] + merging_organisations).select(&:has_visible_users?)
end
def organisations_without_users
return [] unless absorbing_organisation.present? && merging_organisations.any?
([absorbing_organisation] + merging_organisations).reject(&:has_visible_users?)
end
def total_visible_schemes_after_merge
return total_schemes if status == STATUS[:request_merged] || status == STATUS[:processing]
absorbing_organisation.owned_schemes.visible.count + merging_organisations.sum { |org| org.owned_schemes.visible.count }
end
def total_schemes_label
"#{total_visible_schemes_after_merge} #{'scheme'.pluralize(total_visible_schemes_after_merge)}"
end
def organisations_with_schemes
return [] unless absorbing_organisation.present? && merging_organisations.any?
([absorbing_organisation] + merging_organisations).select(&:has_visible_schemes?)
end
def organisations_without_schemes
return [] unless absorbing_organisation.present? && merging_organisations.any?
([absorbing_organisation] + merging_organisations).reject(&:has_visible_schemes?)
end
def existing_absorbing_organisation_label
return if existing_absorbing_organisation.nil?
existing_absorbing_organisation ? "Yes" : "No"
end
def filter_relationships(absorbing_relationships, merging_relationships, absorbing_organisation, merging_organisations)
unique_relationships = (absorbing_relationships + merging_relationships).uniq
unique_relationships.reject do |relationship|
merging_organisations.include?(relationship) || relationship == absorbing_organisation
end
end
def total_stock_owners_after_merge
return total_stock_owners if status == STATUS[:request_merged] || status == STATUS[:processing]
absorbing_stock_owners = absorbing_organisation.stock_owners.visible
merging_stock_owners = merging_organisations.flat_map { |org| org.stock_owners.visible }
total_filtered_stock_owners = filter_relationships(absorbing_stock_owners, merging_stock_owners, absorbing_organisation, merging_organisations)
total_filtered_stock_owners.count
end
def total_managing_agents_after_merge
return total_managing_agents if status == STATUS[:request_merged] || status == STATUS[:processing]
absorbing_managing_agents = absorbing_organisation.managing_agents.visible
merging_managing_agents = merging_organisations.flat_map { |org| org.managing_agents.visible }
total_filtered_managing_agents = filter_relationships(absorbing_managing_agents, merging_managing_agents, absorbing_organisation, merging_organisations)
total_filtered_managing_agents.count
end
def total_stock_owners_managing_agents_label
stock_owners_count = total_stock_owners_after_merge
managing_agents_count = total_managing_agents_after_merge
"#{stock_owners_count} #{'stock owner'.pluralize(stock_owners_count)}\n#{managing_agents_count} #{'managing agent'.pluralize(managing_agents_count)}"
end
def total_visible_sales_logs_after_merge
return total_sales_logs if status == STATUS[:request_merged] || status == STATUS[:processing]
(absorbing_organisation.sales_logs.visible.pluck(:id) + merging_organisations.map { |org| org.sales_logs.visible.pluck(:id) }.flatten).uniq.count
end
def total_visible_lettings_logs_after_merge
return total_lettings_logs if status == STATUS[:request_merged] || status == STATUS[:processing]
(absorbing_organisation.lettings_logs.visible.pluck(:id) + merging_organisations.map { |org| org.lettings_logs.visible.pluck(:id) }.flatten).uniq.count
end
def total_logs_label
"#{total_visible_lettings_logs_after_merge} lettings logs<br>#{total_visible_sales_logs_after_merge} sales logs"
end
def start_merge!
update!(
processing: true,
last_failed_attempt: nil,
total_users: total_visible_users_after_merge,
total_schemes: total_visible_schemes_after_merge,
total_stock_owners: total_stock_owners_after_merge,
total_managing_agents: total_managing_agents_after_merge,
total_lettings_logs: total_visible_lettings_logs_after_merge,
total_sales_logs: total_visible_sales_logs_after_merge,
)
end
def set_back_to_ready_to_merge!
update!(
last_failed_attempt: Time.zone.now,
processing: false,
total_users: nil,
total_schemes: nil,
total_stock_owners: nil,
total_managing_agents: nil,
total_lettings_logs: nil,
total_sales_logs: nil,
)
end
end

13
app/models/merge_request_organisation.rb

@ -5,11 +5,15 @@ class MergeRequestOrganisation < ApplicationRecord
validates :merging_organisation, presence: { message: I18n.t("validations.merge_request.merging_organisation_id.blank") }
validate :validate_merging_organisations
scope :not_unsubmitted, -> { joins(:merge_request).where.not(merge_requests: { status: "unsubmitted" }) }
scope :merged, -> { joins(:merge_request).where(merge_requests: { request_merged: true }) }
scope :with_merging_organisation, ->(merging_organisation) { where(merging_organisation:) }
has_paper_trail
def merging_organisation_name
merging_organisation.name || ""
end
private
def validate_merging_organisations
@ -17,12 +21,7 @@ private
errors.add(:merging_organisation, I18n.t("validations.merge_request.organisation_part_of_another_merge"))
end
if MergeRequestOrganisation.not_unsubmitted.with_merging_organisation(merging_organisation).count.positive?
errors.add(:merging_organisation, I18n.t("validations.merge_request.organisation_part_of_another_merge"))
merge_request.errors.add(:merging_organisation, I18n.t("validations.merge_request.organisation_part_of_another_merge"))
end
if MergeRequest.not_unsubmitted.where.not(id: merge_request_id).where(requesting_organisation: merging_organisation).count.positive?
if MergeRequestOrganisation.merged.with_merging_organisation(merging_organisation).count.positive?
errors.add(:merging_organisation, I18n.t("validations.merge_request.organisation_part_of_another_merge"))
merge_request.errors.add(:merging_organisation, I18n.t("validations.merge_request.organisation_part_of_another_merge"))
end

8
app/models/organisation.rb

@ -191,4 +191,12 @@ class Organisation < ApplicationRecord
def label
status == :deleted ? "#{name} (deleted)" : name
end
def has_visible_users?
users.visible.count.positive?
end
def has_visible_schemes?
owned_schemes.visible.count.positive?
end
end

38
app/models/scheme.rb

@ -61,25 +61,27 @@ class Scheme < ApplicationRecord
merge(Organisation.filter_by_inactive)
}
scope :deactivated_directly, lambda {
scope :deactivated_directly, lambda { |date = Time.zone.now|
merge(SchemeDeactivationPeriod.deactivations_without_reactivation)
.where("scheme_deactivation_periods.deactivation_date <= ?", Time.zone.now)
.where("scheme_deactivation_periods.deactivation_date <= ?", date)
}
scope :deactivating_soon, lambda {
scope :deactivating_soon, lambda { |date = Time.zone.now|
merge(SchemeDeactivationPeriod.deactivations_without_reactivation)
.where("scheme_deactivation_periods.deactivation_date > ? AND scheme_deactivation_periods.deactivation_date < ? ", Time.zone.now, 6.months.from_now)
.where("scheme_deactivation_periods.deactivation_date > ? AND scheme_deactivation_periods.deactivation_date < ? ", date, 6.months.from_now)
.where.not(id: joins(:owning_organisation).deactivated_by_organisation.pluck(:id))
}
scope :reactivating_soon, lambda {
where.not("scheme_deactivation_periods.reactivation_date IS NULL")
.where("scheme_deactivation_periods.reactivation_date > ?", Time.zone.now)
.where.not(id: joins(:owning_organisation).deactivated_by_organisation.pluck(:id))
scope :reactivating_soon, lambda { |date = Time.zone.now|
merge(SchemeDeactivationPeriod.deactivations_with_reactivation)
.where.not("scheme_deactivation_periods.reactivation_date IS NULL")
.where("scheme_deactivation_periods.reactivation_date > ?", date)
.where("scheme_deactivation_periods.deactivation_date <= ?", date)
.where.not(id: joins(:owning_organisation).deactivated_by_organisation.pluck(:id))
}
scope :activating_soon, lambda {
where("schemes.startdate > ?", Time.zone.now)
scope :activating_soon, lambda { |date = Time.zone.now|
where("schemes.startdate > ?", date)
}
scope :active_status, lambda {
@ -91,6 +93,14 @@ class Scheme < ApplicationRecord
.where.not(id: activating_soon.pluck(:id))
}
scope :active, lambda { |date = Time.zone.now|
where.not(id: joins(:scheme_deactivation_periods).reactivating_soon(date).pluck(:id))
.where.not(id: incomplete.pluck(:id))
.where.not(id: joins(:owning_organisation).deactivated_by_organisation.pluck(:id))
.where.not(id: joins(:owning_organisation).joins(:scheme_deactivation_periods).deactivated_directly(date).pluck(:id))
.where.not(id: activating_soon(date).pluck(:id))
}
scope :visible, -> { where(discarded_at: nil) }
validate :validate_confirmed
@ -275,6 +285,10 @@ class Scheme < ApplicationRecord
scheme_deactivation_periods.where("deactivation_date <= ?", date).order("created_at").last
end
def last_deactivation_date
scheme_deactivation_periods.order(deactivation_date: :desc).first&.deactivation_date
end
def status
@status ||= status_at(Time.zone.now)
end
@ -313,6 +327,10 @@ class Scheme < ApplicationRecord
status == :deactivated
end
def deactivating_soon?
status == :deactivating_soon
end
def deactivates_in_a_long_time?
status_at(6.months.from_now) == :deactivating_soon
end

1
app/models/scheme_deactivation_period.rb

@ -48,4 +48,5 @@ class SchemeDeactivationPeriod < ApplicationRecord
attr_accessor :deactivation_date_type, :reactivation_date_type
scope :deactivations_without_reactivation, -> { where(reactivation_date: nil) }
scope :deactivations_with_reactivation, -> { where.not(reactivation_date: nil) }
end

4
app/models/validations/sales/sale_information_validations.rb

@ -299,7 +299,7 @@ module Validations::Sales::SaleInformationValidations
return unless record.mortgage
if over_tolerance?(record.mortgage_and_deposit_total, record.stairbought_part_of_value, 1)
%i[mortgage value deposit stairbought type].each do |field|
%i[mortgage value deposit stairbought].each do |field|
record.errors.add field, I18n.t("validations.sale_information.staircasing_mortgage.mortgage_used",
mortgage: record.field_formatted_as_currency("mortgage"),
deposit: record.field_formatted_as_currency("deposit"),
@ -315,7 +315,7 @@ module Validations::Sales::SaleInformationValidations
stairbought_part_of_value: record.field_formatted_as_currency("stairbought_part_of_value")).html_safe
end
elsif over_tolerance?(record.deposit, record.stairbought_part_of_value, 1)
%i[mortgageused value deposit stairbought type].each do |field|
%i[mortgageused value deposit stairbought].each do |field|
record.errors.add field, I18n.t("validations.sale_information.staircasing_mortgage.mortgage_not_used",
deposit: record.field_formatted_as_currency("deposit"),
value: record.field_formatted_as_currency("value"),

6
app/models/validations/shared_validations.rb

@ -102,7 +102,11 @@ module Validations::SharedValidations
return unless %i[reactivating_soon activating_soon deactivated].include?(status)
closest_reactivation = resource.last_deactivation_before(date)
open_deactivation = resource.open_deactivation
open_deactivation = if resource.is_a?(Location)
resource.open_deactivation || resource.scheme.open_deactivation
else
resource.open_deactivation
end
date = case status
when :reactivating_soon then closest_reactivation.reactivation_date

7
app/services/bulk_upload/lettings/year2024/csv_parser.rb

@ -30,12 +30,15 @@ class BulkUpload::Lettings::Year2024::CsvParser
end
def row_parsers
@row_parsers ||= body_rows.map do |row|
@row_parsers ||= body_rows.map { |row|
next if row.empty?
stripped_row = row[col_offset..]
hash = Hash[field_numbers.zip(stripped_row)]
BulkUpload::Lettings::Year2024::RowParser.new(hash)
end
}.compact
end
def body_rows

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

@ -513,6 +513,8 @@ class BulkUpload::Lettings::Year2024::RowParser
end
def log_already_exists?
return false if blank_row?
@log_already_exists ||= LettingsLog
.where(status: %w[not_started in_progress completed])
.exists?(duplicate_check_fields.index_with { |field| log.public_send(field) })

6
app/services/bulk_upload/sales/year2024/csv_parser.rb

@ -30,12 +30,14 @@ class BulkUpload::Sales::Year2024::CsvParser
end
def row_parsers
@row_parsers ||= body_rows.map do |row|
@row_parsers ||= body_rows.map { |row|
next if row.empty?
stripped_row = row[col_offset..]
hash = Hash[field_numbers.zip(stripped_row)]
BulkUpload::Sales::Year2024::RowParser.new(hash)
end
}.compact
end
def body_rows

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

@ -539,6 +539,8 @@ class BulkUpload::Sales::Year2024::RowParser
end
def log_already_exists?
return false if blank_row?
@log_already_exists ||= SalesLog
.where(status: %w[not_started in_progress completed])
.exists?(duplicate_check_fields.index_with { |field| log.public_send(field) })

4
app/views/bulk_upload_shared/guidance.html.erb

@ -81,8 +81,8 @@
<%= accordion.with_section(heading_text: "Next steps") do %>
<p class="govuk-body">Once you've saved your CSV file, you can upload it via a button at the top of the lettings and sales logs pages.</p>
<p class="govuk-body">When your file is done processing, you will receive an email explaining your next steps. If all your data is valid, your logs will be created. If some data is invalid, you’ll receive an email with instructions about how to resolve the errors.</p>
<p class="govuk-body">If your file has errors on fields 1 through 17, you must fix these in the CSV. This is because we need to know these answers to validate the rest of the data. Any errors in these fields will be featured in the error report’s summary tab.</p>
<p class="govuk-body">If none of your errors are in fields 1 through 17, you can choose how to fix the errors. You can either fix them in the CSV and reupload, or create partially complete logs and answer the remaining questions on the CORE site. Any errors that affect a significant number of logs will be featured in the error report’s summary tab to help you decide.</p>
<p class="govuk-body">If your file has errors on fields 1 through 15 for lettings, or 1 through 18 for sales, you must fix these in the CSV. This is because we need to know these answers to validate the rest of the data. Any errors in these fields will be featured in the error report’s summary tab.</p>
<p class="govuk-body">If none of your errors are in fields 1 through 15 for lettings, or 1 through 18 for sales, you can choose how to fix the errors. You can either fix them in the CSV and reupload, or create partially complete logs and answer the remaining questions on the CORE site. Any errors that affect a significant number of logs will be featured in the error report’s summary tab to help you decide.</p>
<p class="govuk-body"></p>
<% end %>

3
app/views/layouts/application.html.erb

@ -126,7 +126,8 @@
<%= govuk_notification_banner(
title_text: "Success",
success: true, title_heading_level: 3,
title_id: "flash-notice"
title_id: "flash-notice",
role: "alert"
) do |notification_banner|
notification_banner.with_heading(text: flash.notice.html_safe)
if flash[:notification_banner_body]

13
app/views/locations/index.html.erb

@ -56,10 +56,17 @@
<% end %>
<% end %>
<% if status_hint_message = scheme_status_hint(@scheme) %>
<div class="govuk-hint">
<%= status_hint_message %>
</div>
<br>
<% end %>
<% if LocationPolicy.new(current_user, @scheme.locations.new).create? %>
<%= govuk_button_to "Add a location", scheme_locations_path(@scheme), method: "post", secondary: true %>
<%= govuk_button_to "Add a location", scheme_locations_path(@scheme), method: "post" %>
<% end %>
<br>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "locations" } %>
</div>
</div>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "locations" } %>

33
app/views/merge_requests/_details_list.html.erb

@ -0,0 +1,33 @@
<%= govuk_summary_list do |summary_list| %>
<% details.each do |detail| %>
<% summary_list.with_row do |row| %>
<% row.with_key { detail[:label] } %>
<% row.with_value do %>
<% if detail[:value].html_safe? %>
<div class="govuk-!-margin-left-8 govuk-!-margin-right-4">
<%= raw(detail[:value]) %>
</div>
<% elsif detail[:value].is_a?(Date) || detail[:value].is_a?(Time) %>
<div class="govuk-!-margin-left-8 govuk-!-margin-right-4">
<%= detail[:value].strftime("%d %B %Y") %>
</div>
<% else %>
<%= simple_format(
detail[:value],
wrapper_tag: "span",
class: "govuk-!-margin-left-8 govuk-!-margin-right-4",
) %>
<% end %>
<% end %>
<% if detail[:action].present? %>
<% row.with_action(
text: detail[:action][:text],
href: detail[:action][:href],
visually_hidden_text: detail[:action][:visually_hidden_text],
) %>
<% end %>
<% end %>
<% end %>
<% end %>

46
app/views/merge_requests/_merge_request_list.html.erb

@ -0,0 +1,46 @@
<section class="app-table-group" tabindex="0" aria-labelledby="<%= title.dasherize %>">
<% if @merge_requests.empty? %>
<p>No merge requests</p>
<% else %>
<%= govuk_table do |table| %>
<%= table.with_caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do %>
<strong><%= @merge_requests.not_merged.count %></strong> unresolved merge <%= @merge_requests.not_merged.count == 1 ? "request" : "requests" %>
<% end %>
<%= table.with_head do |head| %>
<%= head.with_row do |row| %>
<% row.with_cell(header: true, text: "Absorbing organisation", html_attributes: {
scope: "col",
}) %>
<% row.with_cell(header: true, text: "Merge date", html_attributes: {
scope: "col",
}) %>
<% row.with_cell(header: true, text: "Status", html_attributes: {
scope: "col",
}) %>
<% row.with_cell(header: true, text: "", html_attributes: {
scope: "col",
}) %>
<% end %>
<% end %>
<% @merge_requests.each do |merge_request| %>
<%= table.with_body do |body| %>
<%= body.with_row do |row| %>
<%= row.with_cell(html_attributes: { scope: "row" }) do %>
<%= display_value_or_placeholder(merge_request.absorbing_organisation_name) %>
<% end %>
<% merge_date = merge_request.merge_date %>
<%= row.with_cell(html_attributes: { scope: "row" }) do %>
<%= display_value_or_placeholder(merge_date&.strftime("%d %B %Y")) %>
<% end %>
<% row.with_cell(text: status_tag(merge_request.status)) %>
<% row.with_cell(html_attributes: {
scope: "row",
}) do %>
<%= govuk_link_to("View details", merge_request_path(merge_request.id)) %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
</section>

21
app/views/merge_requests/_notification_banners.html.erb

@ -0,0 +1,21 @@
<% unless @merge_request.absorbing_organisation_signed_dsa? || @merge_request.absorbing_organisation_id.blank? %>
<%= govuk_notification_banner(title_text: "Important") do %>
<p class="govuk-notification-banner__heading govuk-!-width-full" style="max-width: fit-content">
The absorbing organisation must accept the Data Sharing Agreement before merging.
</p>
<% if @merge_request.dpo_user %>
Contact the Data Protection Officer: <%= link_to @merge_request.dpo_user.name, user_path(@merge_request.dpo_user.id) %>
<% else %>
<%= @merge_request.absorbing_organisation_name %> does not have a Data Protection Officer. You can assign one on the <%= link_to "users page", "#{organisation_path(@merge_request.absorbing_organisation_id)}/users" %>.
<% end %>
<% end %>
<% end %>
<% if @merge_request.last_failed_attempt.present? %>
<%= govuk_notification_banner(title_text: "Important") do %>
<p class="govuk-notification-banner__heading govuk-!-width-full" style="max-width: fit-content">
An error occurred while processing the merge.
</p>
No changes have been made. Try beginning the merge again.
<% end %>
<% end %>

8
app/views/merge_requests/_summary_card.html.erb

@ -0,0 +1,8 @@
<div class="govuk-summary-card govuk-!-margin-bottom-6 govuk-!-width-three-quarters">
<div class="govuk-summary-card__title-wrapper">
<h3 class="govuk-summary-card__title govuk-!-font-weight-regular"><%= title %></h3>
</div>
<div class="govuk-summary-card__content">
<%= render partial: "merge_requests/details_list", locals: { details: } %>
</div>
</div>

40
app/views/merge_requests/absorbing_organisation.html.erb

@ -1,36 +1,34 @@
<% content_for :before_content do %>
<% title = "Tell us if your organisation is merging" %>
<% content_for :title, title %>
<%= govuk_back_link href: organisations_merge_request_path(id: @merge_request) %>
<%= govuk_back_link href: merge_request_back_link(@merge_request, "absorbing_organisation", request.query_parameters["referrer"]) %>
<% end %>
<%= form_with model: @merge_request, url: merge_request_path, method: :patch do |f| %>
<%= form_with model: @merge_request, url: submit_merge_request_url(request.query_parameters["referrer"]), method: :patch do |f| %>
<%= f.govuk_error_summary %>
<h2 class="govuk-heading-l">Which organisation is absorbing the others?</h2>
<h1 class="govuk-heading-l">Which organisation is absorbing the others?</h1>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<p class="govuk-body">Select the organisation that the other organisations are merging into.</p>
<%= f.govuk_radio_buttons_fieldset(
:absorbing_organisation_id,
hint: { text: "For example, if Skype and Yammer merged into Microsoft, you would select Microsoft." },
legend: nil,
) do %>
<% @merge_request.merging_organisations.order(:name).each do |org| %>
<%= f.govuk_radio_button(
:absorbing_organisation_id,
org.id,
label: { text: org.name },
) %>
<p class="govuk-hint">If organisations are merging into a new organisation, <%= govuk_link_to "create the new organisation", new_organisation_path %> first and then select it here.</p>
<br>
<%= f.govuk_select(:absorbing_organisation_id,
label: { text: "Select organisation name", class: "govuk-label--m" },
"data-controller": "accessible-autocomplete") do %>
<% @answer_options.map { |id, name| OpenStruct.new(id:, name:) }.each do |answer| %>
<option value="<%= answer.id %>"
data-synonyms="<%= answer_option_synonyms(answer.resource) %>"
data-append="<%= answer_option_append(answer.resource) %>"
data-hint="<%= answer_option_hint(answer.resource) %>"
<%= @merge_request.absorbing_organisation_id == answer.id ? "selected" : "" %>><%= answer.name || answer.resource %></option>
<% end %>
<% end %>
<%= f.govuk_radio_divider %>
<%= f.govuk_radio_button :absorbing_organisation_id, "other", checked: @merge_request.new_absorbing_organisation?, label: { text: "These organisations are merging into a new one" } %>
<% end %>
<%= f.hidden_field :page, value: "absorbing_organisation" %>
<%= f.govuk_submit %>
<div class="govuk-button-group">
<%= f.govuk_submit submit_merge_request_button_text(request.query_parameters["referrer"]) %>
<%= govuk_link_to(secondary_merge_request_link_text(request.query_parameters["referrer"]), merge_request_path(@merge_request)) %>
</div>
<% end %>
</div>
</div>

41
app/views/merge_requests/confirm_telephone_number.html.erb

@ -1,41 +0,0 @@
<% content_for :before_content do %>
<% title = "Tell us if your organisation is merging" %>
<% content_for :title, title %>
<%= govuk_back_link href: absorbing_organisation_merge_request_path(id: @merge_request) %>
<% end %>
<%= form_with model: @merge_request, url: merge_request_path, method: :patch do |f| %>
<%= f.govuk_error_summary %>
<h2 class="govuk-heading-l">What is <%= @merge_request.absorbing_organisation.name %>'s telephone number?</h2>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<% if @merge_request.absorbing_organisation.phone.present? %>
<p class="govuk-body">Confirm the telephone number on file, or enter a new one.</p>
<div class="govuk-inset-text">
<%= @merge_request.absorbing_organisation.phone %>
</div>
<% end %>
<% if @merge_request.absorbing_organisation.phone.present? %>
<%= f.govuk_radio_buttons_fieldset(:telephone_number_correct, legend: nil) do %>
<%= f.govuk_radio_button :telephone_number_correct, true, checked: @merge_request.telephone_number_correct?, label: { text: "This telephone number is correct" }, link_errors: true %>
<%= f.govuk_radio_button(
:telephone_number_correct,
false,
checked: (@merge_request.new_telephone_number.present? || @merge_request.errors.key?(:new_telephone_number)),
label: { text: "Enter a new phone number" },
) do %>
<%= f.govuk_text_field :new_telephone_number, label: { text: "Telephone number" }, width: "two-thirds" %>
<% end %>
<%= f.hidden_field :page, value: "confirm_telephone_number" %>
<% end %>
<% else %>
<%= f.govuk_text_field :new_telephone_number, label: nil, width: "two-thirds" %>
<%= f.hidden_field :page, value: "confirm_telephone_number" %>
<% end %>
<%= f.govuk_submit %>
<% end %>
</div>
</div>

23
app/views/merge_requests/delete_confirmation.html.erb

@ -0,0 +1,23 @@
<% content_for :before_content do %>
<% content_for :title, "Are you sure you want to delete this merge request?" %>
<%= govuk_back_link(href: :back) %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<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 merge request",
delete_merge_request_path(@merge_request),
method: :delete,
) %>
<%= govuk_button_link_to "Cancel", merge_request_path(@merge_request), secondary: true %>
</div>
</div>
</div>

27
app/views/merge_requests/existing_absorbing_organisation.html.erb

@ -0,0 +1,27 @@
<% content_for :before_content do %>
<% title = "Tell us if your organisation is merging" %>
<% content_for :title, title %>
<%= govuk_back_link href: merge_request_back_link(@merge_request, "existing_absorbing_organisation", request.query_parameters["referrer"]) %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<%= form_with model: @merge_request, url: submit_merge_request_url(request.query_parameters["referrer"]), method: :patch do |f| %>
<%= f.govuk_error_summary %>
<%= f.govuk_radio_buttons_fieldset :existing_absorbing_organisation,
legend: { text: "Was #{@merge_request.absorbing_organisation&.name} already active before the merge date?", size: "l" } do %>
<%= f.govuk_radio_button :existing_absorbing_organisation,
"true",
label: { text: "Yes, this organisation existed before the merge" } %>
<%= f.govuk_radio_button :existing_absorbing_organisation,
"false",
label: { text: "No, it is a new organisation created by this merge" } %>
<% end %>
<%= f.hidden_field :page, value: "existing_absorbing_organisation" %>
<div class="govuk-button-group">
<%= f.govuk_submit submit_merge_request_button_text(request.query_parameters["referrer"]) %>
<%= govuk_link_to(secondary_merge_request_link_text(request.query_parameters["referrer"]), merge_request_path(@merge_request)) %>
</div>
<% end %>
</div>
</div>

27
app/views/merge_requests/helpdesk_ticket.html.erb

@ -0,0 +1,27 @@
<% content_for :before_content do %>
<% title = "Which helpdesk ticket reported this merge?" %>
<% content_for :title, title %>
<%= govuk_back_link href: merge_request_back_link(@merge_request, "helpdesk_ticket", request.query_parameters["referrer"]) %>
<% end %>
<%= form_with model: @merge_request, url: submit_merge_request_url(request.query_parameters["referrer"]), method: :patch do |f| %>
<%= f.govuk_error_summary %>
<h1 class="govuk-heading-l">Which helpdesk ticket reported this merge?</h1>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<p class="govuk-hint">If this merge was reported via a helpdesk ticket, provide the ticket number.<br>The ticket will be linked to the merge request for reference.</p>
<br>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<%= f.govuk_text_field :helpdesk_ticket, caption: { text: "Ticket number", class: "govuk-label govuk-label--s" }, label: { text: "For example, MSD-12345", class: "app-!-colour-muted" } %>
<%= f.hidden_field :page, value: "helpdesk_ticket" %>
<div class="govuk-button-group">
<%= f.govuk_submit submit_merge_request_button_text(request.query_parameters["referrer"]) %>
<%= govuk_link_to(secondary_merge_request_link_text(request.query_parameters["referrer"], skip_for_now: true), merge_request_path(@merge_request)) %>
</div>
</div>
</div>
<% end %>
</div>
</div>

27
app/views/merge_requests/logs_outcomes.html.erb

@ -0,0 +1,27 @@
<% content_for :before_content do %>
<% title = "Users" %>
<% content_for :title, title %>
<%= govuk_back_link href: merge_request_path(@merge_request) %>
<% end %>
<h1 class="govuk-heading-l">
<span class="govuk-caption-l"><%= @merge_request.absorbing_organisation_name %></span>
Logs
</h1>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<% unless @merge_request.status == "request_merged" || @merge_request.status == "processing" %>
<h2 class="govuk-heading-m"><%= total_lettings_logs_after_merge_text(@merge_request) %></h2>
<p class="govuk-body">
<%= merging_organisations_lettings_logs_outcomes_text(@merge_request) %>
</p>
<hr class="govuk-section-break govuk-section-break--m govuk-section-break--visible govuk-!-margin-bottom-7 govuk-!-width-three-quarters ">
<h2 class="govuk-heading-m"><%= total_sales_logs_after_merge_text(@merge_request) %></h2>
<p class="govuk-body">
<%= merging_organisations_sales_logs_outcomes_text(@merge_request) %>
</p>
<% end %>
</div>
</div>

24
app/views/merge_requests/merge_date.html.erb

@ -1,8 +1,26 @@
<% content_for :before_content do %>
<% title = "Tell us if your organisation is merging" %>
<% content_for :title, title %>
<%# TODO: Update this backlink to also work with the create org flow %>
<%= govuk_back_link href: confirm_telephone_number_merge_request_path(@merge_request) %>
<%= govuk_back_link href: merge_request_back_link(@merge_request, "merge_date", request.query_parameters["referrer"]) %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<%= form_with model: @merge_request, url: submit_merge_request_url(request.query_parameters["referrer"]), method: :patch do |f| %>
<%= f.govuk_error_summary %>
<h2 class="govuk-heading-l">What is the merge date?</h2>
<h2 class="govuk-heading-l">What is the merge date?</h2>
<p class="govuk-hint">
Enter the official merge date. Log and organisation page data will show the new organisation name from this date. <br><br>
For example, <%= date_mid_collection_year_formatted(Time.zone.now) %></p>
<%= f.govuk_date_field :merge_date,
legend: { hidden: true },
width: 20 do %>
<% end %>
<%= f.hidden_field :page, value: "merge_date" %>
<div class="govuk-button-group">
<%= f.govuk_submit submit_merge_request_button_text(request.query_parameters["referrer"]) %>
<%= govuk_link_to(secondary_merge_request_link_text(request.query_parameters["referrer"]), merge_request_path(@merge_request)) %>
</div>
<% end %>
</div>
</div>

0
app/views/organisations/merge_request.html.erb → app/views/merge_requests/merge_request.html.erb

27
app/views/merge_requests/merge_start_confirmation.html.erb

@ -0,0 +1,27 @@
<% content_for :before_content do %>
<% content_for :title, "Are you sure you want to begin this merge?" %>
<%= govuk_back_link href: merge_request_path(@merge_request) %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<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(
"Begin merge",
start_merge_merge_request_path(@merge_request),
method: :patch,
) %>
<%= govuk_button_link_to(
"Cancel",
merge_request_path(@merge_request),
secondary: true,
) %>
</div>
</div>
</div>

50
app/views/merge_requests/merging_organisations.html.erb

@ -0,0 +1,50 @@
<% content_for :before_content do %>
<% title = "Tell us if your organisation is merging" %>
<% content_for :title, title %>
<%= govuk_back_link href: merge_request_back_link(@merge_request, "merging_organisations", request.query_parameters["referrer"]) %>
<% end %>
<%= form_with model: @merge_request, url: merging_organisations_merge_request_path(referrer: request.query_parameters["referrer"]), method: :patch do |f| %>
<%= f.govuk_error_summary %>
<h2 class="govuk-heading-l">Which organisations are merging into <%= @merge_request.absorbing_organisation&.name %>?</h2>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<p class="govuk-hint">Add all organisations that are merging.</p>
<br>
<%= render partial: "organisation_relationships/related_organisation_select_question", locals: {
label: { text: "Select an organisation", class: "govuk-label--m" },
field: :merging_organisation,
question: Form::Question.new("", { "answer_options" => @answer_options.reject { |id, _org_name| id != "" && id == @merge_request.absorbing_organisation_id } }, nil),
f:,
} %>
<%= f.hidden_field :new_merging_org_ids, value: @new_merging_org_ids %>
<%= f.govuk_submit "Add organisation", secondary: true, classes: "govuk-button--secondary" %>
<%= govuk_table do |table| %>
<% ordered_merging_organisations(@merge_request, @new_merging_org_ids).each do |merging_organisation| %>
<%= table.with_body do |body| %>
<%= body.with_row do |row| %>
<% row.with_cell(text: merging_organisation.name) %>
<% row.with_cell(html_attributes: {
scope: "row",
class: "govuk-!-text-align-right",
}) do %>
<%= govuk_link_to("Remove", merging_organisations_remove_merge_request_path(merge_request: { merging_organisation: merging_organisation.id, new_merging_org_ids: @new_merging_org_ids })) %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<%= form_with model: @merge_request, url: merge_request_path(id: @merge_request.id), method: :patch do |f| %>
<%= f.hidden_field :page, value: "merging_organisations" %>
<%= f.hidden_field :new_merging_org_ids, value: @new_merging_org_ids %>
<div class="govuk-button-group">
<% if @merge_request.merging_organisations.count.positive? || @new_merging_org_ids.count.positive? %>
<%= f.govuk_submit submit_merge_request_button_text(request.query_parameters["referrer"]) %>
<%= govuk_link_to secondary_merge_request_link_text(request.query_parameters["referrer"]), merge_request_path(@merge_request) %>
<% end %>
</div>
<% end %>
</div>
</div>

33
app/views/merge_requests/new_organisation_address.html.erb

@ -1,33 +0,0 @@
<% content_for :before_content do %>
<% title = "New organisation address" %>
<% content_for :title, title %>
<%= govuk_back_link href: new_organisation_name_merge_request_path(@merge_request) %>
<% end %>
<%= form_with model: @merge_request, url: merge_request_path, method: :patch do |f| %>
<%= f.govuk_error_summary %>
<h2 class="govuk-heading-l">What is <%= @merge_request.new_organisation_name.possessive %> address?</h2>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<%= f.govuk_text_field :new_organisation_address_line1,
label: { text: "Address line 1", size: "m" },
autocomplete: "address-line1" %>
<%= f.govuk_text_field :new_organisation_address_line2,
label: { text: "Address line 2", size: "m" },
autocomplete: "address-line2" %>
<%= f.govuk_text_field :new_organisation_postcode,
label: { text: "Postcode", size: "m" },
autocomplete: "postal-code",
width: 10 %>
<%= f.hidden_field :page, value: "new_organisation_address" %>
<div class="govuk-button-group">
<%= f.govuk_submit %>
<%= govuk_link_to("Skip for now", new_organisation_telephone_number_merge_request_path(@merge_request)) %>
</div>
</div>
</div>
<% end %>

19
app/views/merge_requests/new_organisation_name.html.erb

@ -1,19 +0,0 @@
<% content_for :before_content do %>
<% title = "New organisation name" %>
<% content_for :title, title %>
<%= govuk_back_link href: absorbing_organisation_merge_request_path(id: @merge_request) %>
<% end %>
<%= form_with model: @merge_request, url: merge_request_path, method: :patch do |f| %>
<%= f.govuk_error_summary %>
<h2 class="govuk-heading-l">What is the new organisation called?</h2>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<%= f.govuk_text_field :new_organisation_name, label: nil %>
<%= f.hidden_field :page, value: "new_organisation_name" %>
<%= f.govuk_submit %>
<% end %>
</div>
</div>

20
app/views/merge_requests/new_organisation_telephone_number.html.erb

@ -1,20 +0,0 @@
<% content_for :before_content do %>
<% title = "New organisation telephone number" %>
<% content_for :title, title %>
<%= govuk_back_link href: new_organisation_address_merge_request_path(@merge_request) %>
<% end %>
<%= form_with model: @merge_request, url: merge_request_path, method: :patch do |f| %>
<%= f.govuk_error_summary %>
<h2 class="govuk-heading-l">What is <%= @merge_request.new_organisation_name.possessive %> telephone number?</h2>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<%= f.govuk_text_field :new_organisation_telephone_number, label: nil, width: "two-thirds" %>
<%= f.hidden_field :page, value: "new_organisation_telephone_number" %>
<div class="govuk-button-group">
<%= f.govuk_submit %>
</div>
</div>
</div>
<% end %>

5
app/views/merge_requests/new_organisation_type.html.erb

@ -1,5 +0,0 @@
<% content_for :before_content do %>
<% title = "New organisation type" %>
<% content_for :title, title %>
<%= govuk_back_link href: new_organisation_telephone_number_merge_request_path(@merge_request) %>
<% end %>

51
app/views/merge_requests/organisations.html.erb

@ -1,51 +0,0 @@
<% content_for :before_content do %>
<% title = "Tell us if your organisation is merging" %>
<% content_for :title, title %>
<%= govuk_back_link href: merge_request_organisation_path(id: @merge_request.requesting_organisation_id) %>
<% end %>
<%= form_with model: @merge_request, url: organisations_merge_request_path, method: :patch do |f| %>
<%= f.govuk_error_summary %>
<h2 class="govuk-heading-l">Which organisations are merging?</h2>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<p class="govuk-body">
Add all organisations to be merged - we have already added your own.
</p>
<p class="govuk-body">Start typing to search</p>
<%= render partial: "organisation_relationships/related_organisation_select_question", locals: {
field: :merging_organisation,
question: Form::Question.new("", { "answer_options" => @answer_options }, nil),
f:,
} %>
<%= f.govuk_submit "Add organisation", classes: "govuk-button--secondary" %>
<%= govuk_table do |table| %>
<% @merge_request.merging_organisations.order(:name).each do |merging_organisation| %>
<%= table.with_body do |body| %>
<%= body.with_row do |row| %>
<% row.with_cell(text: merging_organisation.name) %>
<% row.with_cell(html_attributes: {
scope: "row",
class: "govuk-!-text-align-right",
}) do %>
<% if @merge_request.requesting_organisation != merging_organisation %>
<%= govuk_link_to("Remove", organisations_remove_merge_request_path(merge_request: { merging_organisation: merging_organisation.id })) %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<%= form_with model: @merge_request, url: merge_request_path(id: @merge_request.id), method: :patch do |f| %>
<%= govuk_details(summary_text: "I cannot find an organisation on the list") do %>
<%= f.govuk_text_area :other_merging_organisations, label: { text: "Other organisations" }, hint: { text: "List other organisations that are part of the merge but not registered on CORE." }, rows: 9 %>
<% end %>
<% if @merge_request.merging_organisations.count > 1 %>
<%= f.hidden_field :page, value: "organisations" %>
<%= f.govuk_submit "Continue" %>
<% end %>
<% end %>
</div>

25
app/views/merge_requests/relationship_outcomes.html.erb

@ -0,0 +1,25 @@
<% content_for :before_content do %>
<% title = "Stock owners & managing agents".html_safe %>
<% content_for :title, title %>
<%= govuk_back_link href: merge_request_path(@merge_request) %>
<% end %>
<h1 class="govuk-heading-l">
<span class="govuk-caption-l"><%= @merge_request.absorbing_organisation_name %></span>
Stock owners & managing agents
</h1>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<% unless @merge_request.status == "request_merged" || @merge_request.status == "processing" %>
<h2 class="govuk-heading-m"><%= total_stock_owners_after_merge_text(@merge_request) %></h2>
<p class="govuk-body">
<%= stock_owners_text(@merge_request) %>
</p>
<hr class="govuk-section-break govuk-section-break--m govuk-section-break--visible govuk-!-margin-bottom-7 govuk-!-width-three-quarters ">
<h2 class="govuk-heading-m"><%= total_managing_agents_after_merge_text(@merge_request) %></h2>
<p class="govuk-body">
<%= managing_agent_text(@merge_request) %>
</p>
<% end %>
</div>
</div>

23
app/views/merge_requests/scheme_outcomes.html.erb

@ -0,0 +1,23 @@
<% content_for :before_content do %>
<% title = "Schemes" %>
<% content_for :title, title %>
<%= govuk_back_link href: merge_request_path(@merge_request) %>
<% end %>
<h1 class="govuk-heading-l">
<span class="govuk-caption-l"><%= @merge_request.absorbing_organisation_name %></span>
Schemes
</h1>
<% unless @merge_request.status == "request_merged" || @merge_request.status == "processing" %>
<h2 class="govuk-heading-m"><%= total_schemes_after_merge_text(@merge_request) %></h2>
<p class="govuk-body">
<%= merging_organisations_without_schemes_text(@merge_request.organisations_without_schemes) %>
</p>
<% @merge_request.organisations_with_schemes.map do |org| %>
<p class="govuk-body">
<%= link_to_merging_organisation_schemes(org) %>
</p>
<% end %>
<% end %>

26
app/views/merge_requests/show.html.erb

@ -0,0 +1,26 @@
<% content_for :before_content do %>
<% title = "Merge request: #{@merge_request.absorbing_organisation_name}" %>
<% content_for :title, title %>
<%= govuk_back_link href: organisations_path(tab: "merge-requests") %>
<% end %>
<%= render partial: "notification_banners" %>
<h1 class="govuk-heading-l">
<span class="govuk-caption-l">Merge request</span>
<%= display_value_or_placeholder(@merge_request.absorbing_organisation_name) %>
</h1>
<% unless @merge_request.status == "request_merged" || @merge_request.status == "processing" %>
<div class="govuk-button-group">
<%= govuk_button_link_to "Begin merge", merge_start_confirmation_merge_request_path(@merge_request), disabled: @merge_request.status != "ready_to_merge" %>
<%= govuk_button_link_to "Delete merge request", delete_confirmation_merge_request_path(@merge_request), warning: true %>
</div>
<% end %>
<%= render partial: "merge_requests/summary_card", locals: { title: "Request details", details: request_details(@merge_request) } %>
<%= render partial: "merge_requests/summary_card", locals: { title: "Merge details", details: merge_details(@merge_request) } %>
<% unless @merge_request.status == "incomplete" %>
<%= render partial: "merge_requests/summary_card", locals: { title: "Merge outcomes", details: merge_outcomes(@merge_request) } %>
<% end %>

23
app/views/merge_requests/user_outcomes.html.erb

@ -0,0 +1,23 @@
<% content_for :before_content do %>
<% title = "Users" %>
<% content_for :title, title %>
<%= govuk_back_link href: merge_request_path(@merge_request) %>
<% end %>
<h1 class="govuk-heading-l">
<span class="govuk-caption-l"><%= @merge_request.absorbing_organisation_name %></span>
Users
</h1>
<% unless @merge_request.status == "request_merged" || @merge_request.status == "processing" %>
<h2 class="govuk-heading-m"><%= total_users_after_merge_text(@merge_request) %></h2>
<p class="govuk-body">
<%= merging_organisations_without_users_text(@merge_request.organisations_without_users) %>
</p>
<% @merge_request.organisations_with_users.map do |org| %>
<p class="govuk-body">
<%= link_to_merging_organisation_users(org) %>
</p>
<% end %>
<% end %>

2
app/views/organisation_relationships/_related_organisation_select_question.html.erb

@ -1,3 +1,3 @@
<% answers = question.answer_options.map { |key, value| OpenStruct.new(id: key, name: value) } %>
<%= f.govuk_collection_select field, answers, :id, :name, label: { hidden: true }, "data-controller": "accessible-autocomplete" do %>
<%= f.govuk_collection_select field, answers, :id, :name, label:, "data-controller": "accessible-autocomplete" do %>
<% end %>

1
app/views/organisation_relationships/add_managing_agent.html.erb

@ -19,6 +19,7 @@
<% end %>
<%= render partial: "organisation_relationships/related_organisation_select_question", locals: {
field: :child_organisation_id,
label: { hidden: true },
question: Form::Question.new("", { "answer_options" => answer_options }, nil),
f:,
} %>

1
app/views/organisation_relationships/add_stock_owner.html.erb

@ -19,6 +19,7 @@
<% end %>
<%= render partial: "organisation_relationships/related_organisation_select_question", locals: {
field: :parent_organisation_id,
label: { hidden: true },
question: Form::Question.new("", { "answer_options" => answer_options }, nil),
f:,
} %>

25
app/views/organisations/index.html.erb

@ -5,13 +5,18 @@
<%= render partial: "organisations/headings", locals: request.path == organisations_path ? { main: "Organisations", sub: nil } : { main: @organisation.name, sub: "Organisations" } %>
<% if current_user.support? %>
<%= govuk_button_link_to "Create a new organisation", new_organisation_path, html: { method: :get } %>
<% end %>
<%= render SearchComponent.new(current_user:, search_label: "Search by organisation name", value: @searched) %>
<%= govuk_section_break(visible: true, size: "m") %>
<%= render partial: "organisation_list", locals: { organisations: @organisations, title: "Organisations", pagy: @pagy, searched: @searched, item_label:, total_count: @total_count } %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "organisations" } %>
<div class="app-tab__list-view" data-controller="tabs">
<%= govuk_tabs(title: "Collection resources", classes: %w[app-tab__large-headers]) do |c| %>
<% c.with_tab(label: "All organisations") do %>
<%= govuk_button_link_to "Create a new organisation", new_organisation_path, html: { method: :get } %>
<%= render SearchComponent.new(current_user:, search_label: "Search by organisation name", value: @searched) %>
<%= govuk_section_break(visible: true, size: "m") %>
<%= render partial: "organisation_list", locals: { organisations: @organisations, title: "Organisations", pagy: @pagy, searched: @searched, item_label:, total_count: @total_count } %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "organisations" } %>
<% end %>
<% c.with_tab(label: "Merge requests") do %>
<%= govuk_button_to "Create new merge request", merge_requests_path, html: { method: :post } %>
<%= render partial: "merge_requests/merge_request_list", locals: { merge_requests: @merge_requests, title: "Merge requests", pagy: @pagy, searched: @searched, item_label:, total_count: @total_count } %>
<% end %>
<% end %>
</div>

41
app/views/schemes/deactivate_confirm.html.erb

@ -2,16 +2,35 @@
<% content_for :before_content do %>
<%= govuk_back_link(href: :back) %>
<% end %>
<h1 class="govuk-heading-l">
<span class="govuk-caption-l"><%= @scheme.service_name %></span>
This change will affect <%= @affected_logs.count %> logs
</h1>
<%= govuk_warning_text text: I18n.t("warnings.scheme.deactivate.review_logs") %>
<%= f.hidden_field :confirm, value: true %>
<%= f.hidden_field :deactivation_date, value: @deactivation_date %>
<%= f.hidden_field :deactivation_date_type, value: @deactivation_date_type %>
<div class="govuk-button-group">
<%= f.govuk_submit "Deactivate this scheme" %>
<%= govuk_button_link_to "Cancel", scheme_details_path(@scheme), html: { method: :get }, secondary: true %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-l">
<span class="govuk-caption-l"><%= @scheme.service_name %></span>
<%= affected_title(@affected_logs, @affected_locations) %>
</h1>
<% if @affected_logs.count > 0 %>
<p>
<%= pluralize(@affected_logs.count, "existing log") %> using this scheme <%= @affected_logs.count == 1 ? "has" : "have" %> a tenancy start date after <%= @deactivation_date.to_formatted_s(:govuk_date) %>.
</p>
<%= govuk_warning_text text: I18n.t("warnings.scheme.deactivate.review_logs"), html_attributes: { class: "" } %>
<% end %>
<% if @affected_locations.count > 0 %>
<p>
This scheme has <%= pluralize(@affected_locations.count, "location") %> active on <%= @deactivation_date.to_formatted_s(:govuk_date) %>. <%= @affected_locations.count == 1 ? "This location" : "These locations" %> will deactivate on that date. If the scheme is ever reactivated, <%= @affected_locations.count == 1 ? "this location" : "these locations" %> will reactivate as well.
</p>
<br>
<% end %>
<%= f.hidden_field :confirm, value: true %>
<%= f.hidden_field :deactivation_date, value: @deactivation_date %>
<%= f.hidden_field :deactivation_date_type, value: @deactivation_date_type %>
<div class="govuk-button-group">
<%= f.govuk_submit "Deactivate this scheme" %>
<%= govuk_button_link_to "Cancel", scheme_details_path(@scheme), html: { method: :get }, secondary: true %>
</div>
</div>
</div>
<% end %>

13
app/views/schemes/toggle_active.html.erb

@ -3,7 +3,7 @@
<% content_for :before_content do %>
<%= govuk_back_link(
href: scheme_details_path(@scheme),
href: scheme_path(@scheme),
) %>
<% end %>
@ -12,11 +12,12 @@
<div class="govuk-grid-column-two-thirds">
<% start_date = FormHandler.instance.earliest_open_for_editing_collection_start_date %>
<%= f.govuk_error_summary %>
<span class="govuk-caption-m"><%= title %></span>
<h1 class="govuk-heading-m"><%= I18n.t("questions.scheme.toggle_active.apply_from") %></h1>
<%= govuk_warning_text text: I18n.t("warnings.scheme.#{action}.existing_logs") %>
<p class="govuk-hint"><%= I18n.t("hints.scheme.toggle_active", date: start_date.to_formatted_s(:govuk_date)) %></p>
<%= f.govuk_radio_buttons_fieldset date_type_question(action),
legend: { text: I18n.t("questions.scheme.toggle_active.apply_from") },
caption: { text: title },
hint: { text: I18n.t("hints.scheme.toggle_active", date: start_date.to_formatted_s(:govuk_date)) } do %>
<%= govuk_warning_text text: I18n.t("warnings.scheme.#{action}.existing_logs") %>
legend: { text: I18n.t("questions.scheme.toggle_active.apply_from"), hidden: true } do %>
<%= f.govuk_radio_button date_type_question(action),
"default",
label: { text: "From the start of the open collection period (#{start_date.to_formatted_s(:govuk_date)})" } %>
@ -26,7 +27,7 @@
**basic_conditional_html_attributes({ "deactivation_date" => %w[other] }, "scheme") do %>
<%= f.govuk_date_field date_question(action),
legend: { text: "Date", size: "m" },
hint: { text: "For example, 27 3 2022" },
hint: { text: "For example, 27 3 2025" },
width: 20 %>
<% end %>
<% end %>

17
config/locales/en.yml

@ -180,17 +180,12 @@ en:
merge_request:
attributes:
absorbing_organisation_id:
blank: "Select the organisation absorbing the others"
telephone_number_correct:
blank: "Select to confirm or enter a new telephone number"
invalid: "Enter a valid telephone number"
new_telephone_number:
blank: "Enter a valid telephone number"
new_organisation_name:
blank: "Enter an organisation name"
invalid: "An organisation with this name already exists"
new_organisation_telephone_number:
blank: "Enter a valid telephone number"
blank: "Select the absorbing organisation"
merge_date:
blank: "Enter a merge date"
invalid: "Enter a valid merge date"
existing_absorbing_organisation:
blank: "You must answer absorbing organisation already active?"
notification:
attributes:
title:

21
config/routes.rb

@ -206,16 +206,21 @@ Rails.application.routes.draw do
resources :merge_requests, path: "/merge-request" do
member do
get "organisations"
patch "organisations", to: "merge_requests#update_organisations"
get "organisations/remove", to: "merge_requests#remove_merging_organisation"
get "merging-organisations"
patch "merging-organisations", to: "merge_requests#update_merging_organisations"
get "merging-organisations/remove", to: "merge_requests#remove_merging_organisation"
get "absorbing-organisation"
get "confirm-telephone-number"
get "new-organisation-name"
get "new-organisation-address"
get "new-organisation-telephone-number"
get "new-organisation-type"
get "merge-date"
get "existing-absorbing-organisation"
get "helpdesk-ticket"
get "merge-start-confirmation"
get "user-outcomes"
get "relationship-outcomes"
get "scheme-outcomes"
get "logs-outcomes"
get "delete-confirmation", to: "merge_requests#delete_confirmation"
delete "delete", to: "merge_requests#delete"
patch "start-merge", to: "merge_requests#start_merge"
end
end

5
db/migrate/20240808134014_add_merge_date_to_merge_requests.rb

@ -0,0 +1,5 @@
class AddMergeDateToMergeRequests < ActiveRecord::Migration[7.0]
def change
add_column :merge_requests, :merge_date, :datetime
end
end

16
db/migrate/20240809154241_add_additional_fields_to_merge_requests.rb

@ -0,0 +1,16 @@
class AddAdditionalFieldsToMergeRequests < ActiveRecord::Migration[7.0]
def change
change_table :merge_requests, bulk: true do |t|
t.integer :requester_id
t.string :helpdesk_ticket
t.integer :total_users
t.integer :total_schemes
t.integer :total_lettings_logs
t.integer :total_sales_logs
t.integer :total_stock_owners
t.integer :total_managing_agents
t.boolean :signed_dsa, default: false
t.datetime :discarded_at
end
end
end

5
db/migrate/20240813072041_remove_other_merging_org_field.rb

@ -0,0 +1,5 @@
class RemoveOtherMergingOrgField < ActiveRecord::Migration[7.0]
def change
remove_column :merge_requests, :other_merging_organisations, :string
end
end

27
db/migrate/20240813112119_remove_new_org_merge_request_fields.rb

@ -0,0 +1,27 @@
class RemoveNewOrgMergeRequestFields < ActiveRecord::Migration[7.0]
def up
change_table :merge_requests, bulk: true do |t|
t.remove :new_absorbing_organisation
t.remove :telephone_number_correct
t.remove :new_telephone_number
t.remove :new_organisation_name
t.remove :new_organisation_address_line1
t.remove :new_organisation_address_line2
t.remove :new_organisation_postcode
t.remove :new_organisation_telephone_number
end
end
def down
change_table :merge_requests, bulk: true do |t|
t.column :new_absorbing_organisation, :boolean
t.column :telephone_number_correct, :boolean
t.column :new_telephone_number, :string
t.column :new_organisation_name, :string
t.column :new_organisation_address_line1, :string
t.column :new_organisation_address_line2, :string
t.column :new_organisation_postcode, :string
t.column :new_organisation_telephone_number, :string
end
end
end

5
db/migrate/20240814083017_add_last_failed_attempt.rb

@ -0,0 +1,5 @@
class AddLastFailedAttempt < ActiveRecord::Migration[7.0]
def change
add_column :merge_requests, :last_failed_attempt, :datetime
end
end

17
db/migrate/20240819100411_update_merge_request_fields_for_status.rb

@ -0,0 +1,17 @@
class UpdateMergeRequestFieldsForStatus < ActiveRecord::Migration[7.0]
def up
change_table :merge_requests, bulk: true do |t|
t.column :request_merged, :boolean
t.column :processing, :boolean
t.remove :status
end
end
def down
change_table :merge_requests, bulk: true do |t|
t.remove :request_merged
t.remove :processing
t.column :status, :string
end
end
end

5
db/migrate/20240822080228_add_existing_absorbing_organisation_field.rb

@ -0,0 +1,5 @@
class AddExistingAbsorbingOrganisationField < ActiveRecord::Migration[7.0]
def change
add_column :merge_requests, :existing_absorbing_organisation, :boolean
end
end

25
db/schema.rb

@ -429,19 +429,24 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_04_135306) do
create_table "merge_requests", force: :cascade do |t|
t.integer "requesting_organisation_id"
t.text "other_merging_organisations"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "status"
t.integer "absorbing_organisation_id"
t.boolean "new_absorbing_organisation"
t.boolean "telephone_number_correct"
t.string "new_telephone_number"
t.string "new_organisation_name"
t.string "new_organisation_address_line1"
t.string "new_organisation_address_line2"
t.string "new_organisation_postcode"
t.string "new_organisation_telephone_number"
t.datetime "merge_date"
t.integer "requester_id"
t.string "helpdesk_ticket"
t.integer "total_users"
t.integer "total_schemes"
t.integer "total_lettings_logs"
t.integer "total_sales_logs"
t.integer "total_stock_owners"
t.integer "total_managing_agents"
t.boolean "signed_dsa", default: false
t.datetime "discarded_at"
t.datetime "last_failed_attempt"
t.boolean "request_merged"
t.boolean "processing"
t.boolean "existing_absorbing_organisation"
end
create_table "notifications", force: :cascade do |t|

24
db/seeds.rb

@ -107,7 +107,7 @@ unless Rails.env.test?
role: "data_provider",
is_dpo: true,
) do |user|
user.password = "password"
user.password = ENV["REVIEW_APP_USER_PASSWORD"]
user.confirmed_at = Time.zone.now
create_data_protection_confirmation(user)
end
@ -119,7 +119,7 @@ unless Rails.env.test?
role: "data_coordinator",
is_dpo: true,
) do |user|
user.password = "password"
user.password = ENV["REVIEW_APP_USER_PASSWORD"]
user.confirmed_at = Time.zone.now
create_data_protection_confirmation(user)
end
@ -131,7 +131,7 @@ unless Rails.env.test?
role: "data_provider",
is_dpo: true,
) do |user|
user.password = "password"
user.password = ENV["REVIEW_APP_USER_PASSWORD"]
user.confirmed_at = Time.zone.now
create_data_protection_confirmation(user)
end
@ -142,7 +142,7 @@ unless Rails.env.test?
organisation: standalone_no_stock,
role: "data_coordinator",
) do |user|
user.password = "password"
user.password = ENV["REVIEW_APP_USER_PASSWORD"]
user.confirmed_at = Time.zone.now
end
@ -153,7 +153,7 @@ unless Rails.env.test?
role: "data_coordinator",
is_dpo: true,
) do |user|
user.password = "password"
user.password = ENV["REVIEW_APP_USER_PASSWORD"]
user.confirmed_at = Time.zone.now
create_data_protection_confirmation(user)
end
@ -165,7 +165,7 @@ unless Rails.env.test?
role: "data_coordinator",
is_dpo: true,
) do |user|
user.password = "password"
user.password = ENV["REVIEW_APP_USER_PASSWORD"]
user.confirmed_at = Time.zone.now
create_data_protection_confirmation(user)
end
@ -177,7 +177,7 @@ unless Rails.env.test?
role: "data_coordinator",
is_dpo: true,
) do |user|
user.password = "password"
user.password = ENV["REVIEW_APP_USER_PASSWORD"]
user.confirmed_at = Time.zone.now
create_data_protection_confirmation(user)
end
@ -189,7 +189,7 @@ unless Rails.env.test?
role: "data_coordinator",
is_dpo: true,
) do |user|
user.password = "password"
user.password = ENV["REVIEW_APP_USER_PASSWORD"]
user.confirmed_at = Time.zone.now
create_data_protection_confirmation(user)
end
@ -218,7 +218,7 @@ unless Rails.env.test?
organisation: org,
role: "data_provider",
) do |user|
user.password = "password"
user.password = ENV["REVIEW_APP_USER_PASSWORD"]
user.confirmed_at = Time.zone.now
end
@ -229,7 +229,7 @@ unless Rails.env.test?
role: "data_coordinator",
is_dpo: true,
) do |user|
user.password = "password"
user.password = ENV["REVIEW_APP_USER_PASSWORD"]
user.confirmed_at = Time.zone.now
user.is_dpo = true
create_data_protection_confirmation(user)
@ -242,7 +242,7 @@ unless Rails.env.test?
role: "support",
is_dpo: true,
) do |user|
user.password = "password"
user.password = ENV["REVIEW_APP_USER_PASSWORD"]
user.confirmed_at = Time.zone.now
create_data_protection_confirmation(user)
end
@ -310,7 +310,7 @@ unless Rails.env.test?
role: "data_provider",
is_dpo: true,
) do |user|
user.password = "password"
user.password = ENV["REVIEW_APP_USER_PASSWORD"]
user.confirmed_at = Time.zone.now
create_data_protection_confirmation(user)
end

8
spec/factories/merge_request.rb

@ -0,0 +1,8 @@
FactoryBot.define do
factory :merge_request do
status { "incomplete" }
merge_date { nil }
helpdesk_ticket { "MSD-99999" }
association :requesting_organisation, factory: :organisation
end
end

6
spec/factories/merge_request_organisation.rb

@ -0,0 +1,6 @@
FactoryBot.define do
factory :merge_request_organisation do
association :merging_organisation, factory: :organisation
association :merge_request, factory: :merge_request
end
end

2
spec/features/schemes_spec.rb

@ -766,8 +766,10 @@ RSpec.describe "Schemes scheme Features" do
before do
Timecop.freeze(Time.zone.local(2023, 10, 10))
Singleton.__init__(FormHandler)
FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), location: deactivated_location)
Timecop.unfreeze
Singleton.__init__(FormHandler)
click_link(scheme.service_name)
end

2
spec/fixtures/files/locations_csv_export.csv vendored

@ -1,2 +1,2 @@
scheme_code,location_code,location_postcode,location_name,location_status,location_local_authority,location_units,location_type_of_unit,location_mobility_type,location_active_dates
,,SW1A 2AA,Downing Street,deactivating_soon,Westminster,20,Self-contained house,Fitted with equipment and adaptations,"Active from 1 April 2022 to 25 December 2023, Deactivated on 26 December 2023"
,,SW1A 2AA,Downing Street,deactivating_soon,Westminster,20,Self-contained house,Fitted with equipment and adaptations,"Active from 1 April 2023 to 25 December 2023, Deactivated on 26 December 2023"

1 scheme_code location_code location_postcode location_name location_status location_local_authority location_units location_type_of_unit location_mobility_type location_active_dates
2 SW1A 2AA Downing Street deactivating_soon Westminster 20 Self-contained house Fitted with equipment and adaptations Active from 1 April 2022 to 25 December 2023, Deactivated on 26 December 2023 Active from 1 April 2023 to 25 December 2023, Deactivated on 26 December 2023

2
spec/fixtures/files/schemes_and_locations_csv_export.csv vendored

@ -1,2 +1,2 @@
scheme_code,scheme_service_name,scheme_status,scheme_confidential,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_support_services_provided_by,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,scheme_active_dates,location_code,location_postcode,location_name,location_status,location_local_authority,location_units,location_type_of_unit,location_mobility_type,location_active_dates
,Test name,active,Yes,Housing for older people,No,MHCLG,The same organisation that owns the housing stock,People with alcohol problems,Yes,Older people with support needs,High level,Medium stay,2021-04-01T00:00:00+01:00,"Active from 1 April 2020 to 31 March 2022, Deactivated on 1 April 2022, Active from 1 April 2023",,SW1A 2AA,Downing Street,deactivating_soon,Westminster,20,Self-contained house,Fitted with equipment and adaptations,"Active from 1 April 2022 to 25 December 2023, Deactivated on 26 December 2023"
,Test name,active,Yes,Housing for older people,No,MHCLG,The same organisation that owns the housing stock,People with alcohol problems,Yes,Older people with support needs,High level,Medium stay,2021-04-01T00:00:00+01:00,"Active from 1 April 2020 to 31 March 2022, Deactivated on 1 April 2022, Active from 1 April 2023",,SW1A 2AA,Downing Street,deactivating_soon,Westminster,20,Self-contained house,Fitted with equipment and adaptations,"Active from 1 April 2023 to 25 December 2023, Deactivated on 26 December 2023"

1 scheme_code scheme_service_name scheme_status scheme_confidential scheme_type scheme_registered_under_care_act scheme_owning_organisation_name scheme_support_services_provided_by scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at scheme_active_dates location_code location_postcode location_name location_status location_local_authority location_units location_type_of_unit location_mobility_type location_active_dates
2 Test name active Yes Housing for older people No MHCLG The same organisation that owns the housing stock People with alcohol problems Yes Older people with support needs High level Medium stay 2021-04-01T00:00:00+01:00 Active from 1 April 2020 to 31 March 2022, Deactivated on 1 April 2022, Active from 1 April 2023 SW1A 2AA Downing Street deactivating_soon Westminster 20 Self-contained house Fitted with equipment and adaptations Active from 1 April 2022 to 25 December 2023, Deactivated on 26 December 2023 Active from 1 April 2023 to 25 December 2023, Deactivated on 26 December 2023

272
spec/helpers/merge_requests_helper_spec.rb

@ -0,0 +1,272 @@
require "rails_helper"
RSpec.describe MergeRequestsHelper do
describe "#merging_organisations_without_users_text" do
context "with 1 organisation" do
let(:organisation) { build(:organisation, name: "Org 1") }
it "returns the correct text" do
expect(merging_organisations_without_users_text([organisation])).to eq("Org 1 has no users.")
end
end
context "with 2 organisations" do
let(:organisation) { build(:organisation, name: "Org 1") }
let(:organisation_2) { build(:organisation, name: "Org 2") }
it "returns the correct text" do
expect(merging_organisations_without_users_text([organisation, organisation_2])).to eq("Org 1 and Org 2 have no users.")
end
end
context "with 3 organisations" do
let(:organisation) { build(:organisation, name: "Org 1") }
let(:organisation_2) { build(:organisation, name: "Org 2") }
let(:organisation_3) { build(:organisation, name: "Org 3") }
it "returns the correct text" do
expect(merging_organisations_without_users_text([organisation, organisation_2, organisation_3])).to eq("Org 1, Org 2, and Org 3 have no users.")
end
end
end
describe "#link_to_merging_organisation_users" do
context "with 1 organisation user" do
let(:organisation) { create(:organisation, name: "Org 1") }
it "returns the correct link" do
expect(link_to_merging_organisation_users(organisation)).to include("View 1 Org 1 user (opens in a new tab)")
expect(link_to_merging_organisation_users(organisation)).to include(users_organisation_path(organisation))
end
end
context "with multiple organisation users" do
let(:organisation) { create(:organisation, name: "Org 1") }
before do
create(:user, organisation:)
end
it "returns the correct link" do
expect(link_to_merging_organisation_users(organisation)).to include("View all 2 Org 1 users (opens in a new tab)")
expect(link_to_merging_organisation_users(organisation)).to include(users_organisation_path(organisation))
end
end
end
describe "#merging_organisations_without_schemes_text" do
context "with 1 organisation" do
let(:organisation) { build(:organisation, name: "Org 1") }
it "returns the correct text" do
expect(merging_organisations_without_schemes_text([organisation])).to eq("Org 1 has no schemes.")
end
end
context "with 2 organisations" do
let(:organisation) { build(:organisation, name: "Org 1") }
let(:organisation_2) { build(:organisation, name: "Org 2") }
it "returns the correct text" do
expect(merging_organisations_without_schemes_text([organisation, organisation_2])).to eq("Org 1 and Org 2 have no schemes.")
end
end
context "with 3 organisations" do
let(:organisation) { build(:organisation, name: "Org 1") }
let(:organisation_2) { build(:organisation, name: "Org 2") }
let(:organisation_3) { build(:organisation, name: "Org 3") }
it "returns the correct text" do
expect(merging_organisations_without_schemes_text([organisation, organisation_2, organisation_3])).to eq("Org 1, Org 2, and Org 3 have no schemes.")
end
end
end
describe "#link_to_merging_organisation_schemes" do
context "with 1 organisation scheme" do
let(:organisation) { create(:organisation, name: "Org 1") }
before do
create(:scheme, owning_organisation: organisation)
end
it "returns the correct link" do
expect(link_to_merging_organisation_schemes(organisation)).to include("View 1 Org 1 scheme (opens in a new tab)")
expect(link_to_merging_organisation_schemes(organisation)).to include(schemes_organisation_path(organisation))
end
end
context "with multiple organisation schemes" do
let(:organisation) { create(:organisation, name: "Org 1") }
before do
create_list(:scheme, 2, owning_organisation: organisation)
end
it "returns the correct link" do
expect(link_to_merging_organisation_schemes(organisation)).to include("View all 2 Org 1 schemes (opens in a new tab)")
expect(link_to_merging_organisation_schemes(organisation)).to include(schemes_organisation_path(organisation))
end
end
end
describe "when creating relationship outcomes content" do
let(:stock_owner1) { create(:organisation, name: "Stock owner 1") }
let(:stock_owner2) { create(:organisation, name: "Stock owner 2") }
let(:managing_agent1) { create(:organisation, name: "Managing agent 1") }
let(:managing_agent2) { create(:organisation, name: "Managing agent 2") }
let(:absorbing_organisation) { create(:organisation, name: "Absorbing Org") }
let(:merging_organisations) { create_list(:organisation, 2) { |org, i| org.name = "Dummy Org #{i + 1}" } }
let(:merge_request) { create(:merge_request, absorbing_organisation:, merging_organisations:) }
context "when there are no relationships" do
it "returns text stating there are no stock owners" do
expect(stock_owners_text(merge_request)).to eq("Absorbing Org, Dummy Org 1, and Dummy Org 2 have no stock owners.<br><br>")
end
it "returns text stating there are no managing agents" do
expect(managing_agent_text(merge_request)).to eq("Absorbing Org, Dummy Org 1, and Dummy Org 2 have no managing agents.<br><br>")
end
end
context "when there are stock owners" do
before do
create(:organisation_relationship, child_organisation: absorbing_organisation, parent_organisation: stock_owner1)
create(:organisation_relationship, child_organisation: merging_organisations.first, parent_organisation: stock_owner2)
create(:organisation_relationship, child_organisation: merging_organisations.first, parent_organisation: stock_owner1)
end
it "returns text stating the relationships" do
expect(stock_owners_text(merge_request)).to include("Some of the organisations merging have common stock owners.")
expect(stock_owners_text(merge_request)).to include("Dummy Org 2 has no stock owners.")
expect(stock_owners_text(merge_request)).to include("<a class=\"govuk-link\" target=\"_blank\" href=\"/organisations/#{merging_organisations.first.id}/stock-owners\">View all 2 Dummy Org 1 stock owners (opens in a new tab)</a>")
end
end
context "when there are managing agents" do
before do
create(:organisation_relationship, parent_organisation: absorbing_organisation, child_organisation: managing_agent1)
create(:organisation_relationship, parent_organisation: absorbing_organisation, child_organisation: managing_agent2)
create(:organisation_relationship, parent_organisation: merging_organisations.first, child_organisation: managing_agent2)
end
it "returns text stating the relationships" do
expect(managing_agent_text(merge_request)).to include("Some of the organisations merging have common managing agents.")
expect(managing_agent_text(merge_request)).to include("Dummy Org 2 has no managing agents.")
expect(managing_agent_text(merge_request)).to include("<a class=\"govuk-link\" target=\"_blank\" href=\"/organisations/#{merging_organisations.first.id}/managing-agents\">View the 1 Dummy Org 1 managing agent (opens in a new tab)</a>")
end
end
end
describe "logs outcomes summary" do
let(:organisation) { create(:organisation, name: "Org 1") }
let(:merging_organisation) { create(:organisation, name: "Org 2") }
let(:merging_organisation_2) { create(:organisation, name: "Org 3") }
let(:merge_request) { create(:merge_request, absorbing_organisation: organisation, merge_date: Time.zone.today) }
before do
create(:merge_request_organisation, merge_request:, merging_organisation:)
end
context "when merging organisations don't have logs" do
it "returns the correct merging_organisations_lettings_logs_outcomes_text text" do
outcome_text = merging_organisations_lettings_logs_outcomes_text(merge_request)
expect(outcome_text).not_to include("Org 1 users will have access to all lettings logs owned or managed by the merging organisation after the merge.")
expect(outcome_text).not_to include("Lettings logs that are owned or managed by the merging organisation and have a tenancy start date after the merge date will have their owning or managing organisation changed to Org 1.")
expect(outcome_text).not_to include("Some logs are owned and managed by different organisations in this merge. They appear in the list for both the owning and the managing organisation.")
expect(outcome_text).to include("Org 1 and Org 2 have no lettings logs.")
end
it "returns correct lettings_logs_outcomes_header_text" do
expect(lettings_logs_outcomes_header_text(merge_request)).to eq("0 lettings logs after merge")
end
it "returns the correct merging_organisations_sales_logs_outcomes_text text" do
outcome_text = merging_organisations_sales_logs_outcomes_text(merge_request)
expect(outcome_text).not_to include("Org 1 users will have access to all sales logs owned or reported by the merging organisation after the merge.")
expect(outcome_text).not_to include("Sales logs that are owned or reported by the merging organisation and have a sale completion date after the merge date will have their owning or managing organisation changed to Org 1.")
expect(outcome_text).not_to include("Some logs are owned and reported by different organisation in this merge. They appear in the list for both the owning and the managing organisation.")
expect(outcome_text).to include("Org 1 and Org 2 have no sales logs.")
end
it "returns correct sales_logs_outcomes_header_text" do
expect(sales_logs_outcomes_header_text(merge_request)).to eq("0 sales logs after merge")
end
end
context "when merging organisations have logs" do
before do
create(:lettings_log, owning_organisation: organisation)
create(:lettings_log, owning_organisation: merging_organisation, startdate: Time.zone.tomorrow)
create(:lettings_log, owning_organisation: merging_organisation, startdate: Time.zone.yesterday)
create(:sales_log, owning_organisation: organisation)
create(:sales_log, owning_organisation: merging_organisation, saledate: Time.zone.tomorrow)
create(:sales_log, owning_organisation: merging_organisation, saledate: Time.zone.yesterday)
end
it "returns the correct merging_organisations_lettings_logs_outcomes_text text" do
outcome_text = merging_organisations_lettings_logs_outcomes_text(merge_request)
expect(outcome_text).to include("Org 1 users will have access to all lettings logs owned or managed by the merging organisation after the merge.")
expect(outcome_text).to include("Lettings logs that are owned or managed by the merging organisation and have a tenancy start date after the merge date will have their owning or managing organisation changed to Org 1.")
expect(outcome_text).not_to include("Some logs are owned and managed by different organisations in this merge. They appear in the list for both the owning and the managing organisation.")
expect(outcome_text).not_to include("Org 2 has no lettings logs.")
expect(outcome_text).to include("View all 2 Org 2 lettings logs (opens in a new tab)")
end
it "returns correct lettings_logs_outcomes_header_text" do
expect(lettings_logs_outcomes_header_text(merge_request)).to eq("3 lettings logs after merge")
end
it "returns the correct merging_organisations_sales_logs_outcomes_text text" do
outcome_text = merging_organisations_sales_logs_outcomes_text(merge_request)
expect(outcome_text).to include("Org 1 users will have access to all sales logs owned or reported by the merging organisation after the merge.")
expect(outcome_text).to include("Sales logs that are owned or reported by the merging organisation and have a sale completion date after the merge date will have their owning or managing organisation changed to Org 1.")
expect(outcome_text).not_to include("Some logs are owned and reported by different organisations in this merge. They appear in the list for both the owning and the managing organisation.")
expect(outcome_text).not_to include("Org 2 has no sales logs.")
expect(outcome_text).to include("View all 2 Org 2 sales logs (opens in a new tab)")
end
it "returns correct sales_logs_outcomes_header_text" do
expect(sales_logs_outcomes_header_text(merge_request)).to eq("3 sales logs after merge")
end
context "when logs are owned and managed by organisations in the same merge" do
before do
create(:organisation_relationship, parent_organisation: merging_organisation_2, child_organisation: merging_organisation)
create(:merge_request_organisation, merge_request:, merging_organisation: merging_organisation_2)
create(:lettings_log, assigned_to: merging_organisation_2.users.first, owning_organisation: merging_organisation_2, managing_organisation: merging_organisation, startdate: Time.zone.yesterday)
create(:sales_log, assigned_to: merging_organisation_2.users.first, owning_organisation: merging_organisation_2, managing_organisation: merging_organisation, saledate: Time.zone.yesterday)
end
it "returns the correct merging_organisations_lettings_logs_outcomes_text text" do
outcome_text = merging_organisations_lettings_logs_outcomes_text(merge_request)
expect(outcome_text).to include("Org 1 users will have access to all lettings logs owned or managed by the merging organisations after the merge.")
expect(outcome_text).to include("Lettings logs that are owned or managed by the merging organisations and have a tenancy start date after the merge date will have their owning or managing organisation changed to Org 1.")
expect(outcome_text).to include("Some logs are owned and managed by different organisations in this merge. They appear in the list for both the owning and the managing organisation.")
expect(outcome_text).not_to include("Org 2 has no lettings logs.")
expect(outcome_text).to include("View all 3 Org 2 lettings logs (opens in a new tab)")
expect(outcome_text).to include("View 1 Org 3 lettings log (opens in a new tab)")
end
it "returns correct lettings_logs_outcomes_header_text" do
expect(lettings_logs_outcomes_header_text(merge_request)).to eq("4 lettings logs after merge")
end
it "returns the correct merging_organisations_sales_logs_outcomes_text text" do
outcome_text = merging_organisations_sales_logs_outcomes_text(merge_request)
expect(outcome_text).to include("Org 1 users will have access to all sales logs owned or reported by the merging organisations after the merge.")
expect(outcome_text).to include("Sales logs that are owned or reported by the merging organisations and have a sale completion date after the merge date will have their owning or managing organisation changed to Org 1.")
expect(outcome_text).to include("Some logs are owned and reported by different organisations in this merge. They appear in the list for both the owning and the managing organisation.")
expect(outcome_text).not_to include("Org 2 has no sales logs.")
expect(outcome_text).to include("View all 3 Org 2 sales logs (opens in a new tab)")
expect(outcome_text).to include("View 1 Org 3 sales log (opens in a new tab)")
end
it "returns correct sales_logs_outcomes_header_text" do
expect(sales_logs_outcomes_header_text(merge_request)).to eq("4 sales logs after merge")
end
end
end
end
end

65
spec/jobs/process_merge_request_job_spec.rb

@ -0,0 +1,65 @@
require "rails_helper"
describe ProcessMergeRequestJob do
let(:job) { described_class.new }
let(:merge_organisations_service) { instance_double(Merge::MergeOrganisationsService) }
before do
allow(Merge::MergeOrganisationsService).to receive(:new).and_return(merge_organisations_service)
allow(merge_organisations_service).to receive(:call).and_return(nil)
end
context "when processing a merge request" do
let(:organisation) { create(:organisation) }
let(:merging_organisation) { create(:organisation) }
let(:other_merging_organisation) { create(:organisation) }
let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, absorbing_organisation: organisation, merge_date: Time.zone.local(2022, 3, 3), total_users: 5, total_schemes: 5, total_lettings_logs: 2, total_sales_logs: 8, total_managing_agents: 2, total_stock_owners: 1, existing_absorbing_organisation: true) }
before do
create(:merge_request_organisation, merge_request:, merging_organisation:)
create(:merge_request_organisation, merge_request:, merging_organisation: other_merging_organisation)
end
it "calls the merge organisations service with correct arguments" do
expect(Merge::MergeOrganisationsService).to receive(:new).with(absorbing_organisation_id: organisation.id, merging_organisation_ids: [merging_organisation.id, other_merging_organisation.id], merge_date: Time.zone.local(2022, 3, 3), absorbing_organisation_active_from_merge_date: false)
job.perform(merge_request:)
expect(merge_request.reload.status).to eq("request_merged")
end
context "with new absorbing organisation" do
let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, absorbing_organisation: organisation, merge_date: Time.zone.local(2022, 3, 3), existing_absorbing_organisation: false) }
it "calls the merge organisations service with correct arguments" do
expect(Merge::MergeOrganisationsService).to receive(:new).with(absorbing_organisation_id: organisation.id, merging_organisation_ids: [merging_organisation.id, other_merging_organisation.id], merge_date: Time.zone.local(2022, 3, 3), absorbing_organisation_active_from_merge_date: true)
job.perform(merge_request:)
expect(merge_request.reload.status).to eq("request_merged")
end
end
it "clears last_failed_attempt value" do
merge_request.update!(last_failed_attempt: Time.zone.now)
job.perform(merge_request:)
expect(merge_request.reload.last_failed_attempt).to be_nil
end
it "sets last_failed_attempt value, sets processing to false and clears all outcomes if there's an error" do
allow(merge_organisations_service).to receive(:call).and_raise(ActiveRecord::Rollback)
expect(merge_request.last_failed_attempt).to be_nil
job.perform(merge_request:)
merge_request.reload
expect(merge_request.last_failed_attempt).to be_within(10.seconds).of(Time.zone.now)
expect(merge_request.processing).to eq(false)
expect(merge_request.total_users).to be_nil
expect(merge_request.total_schemes).to be_nil
expect(merge_request.total_managing_agents).to be_nil
expect(merge_request.total_stock_owners).to be_nil
expect(merge_request.total_lettings_logs).to be_nil
expect(merge_request.total_sales_logs).to be_nil
end
end
end

33
spec/models/location_spec.rb

@ -930,6 +930,39 @@ RSpec.describe Location, type: :model do
expect(location.status).to eq(:activating_soon)
end
end
context "when the scheme the location belongs to is deactivated" do
before do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.yesterday, scheme: location.scheme)
location.save!
end
it "returns deactivated" do
expect(location.status).to eq(:deactivated)
end
end
context "when the scheme the location belongs to is deactivating soon" do
before do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.today + 1.month, scheme: location.scheme)
location.save!
end
it "returns deactivating soon" do
expect(location.status).to eq(:deactivating_soon)
end
end
context "when the scheme the location belongs to is reactivating soon" do
before do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.yesterday, reactivation_date: Time.zone.tomorrow, scheme: location.scheme)
location.save!
end
it "returns reactivating soon" do
expect(location.status).to eq(:reactivating_soon)
end
end
end
describe "status_at" do

451
spec/models/merge_request_spec.rb

@ -0,0 +1,451 @@
require "rails_helper"
RSpec.describe MergeRequest, type: :model do
describe ".visible" do
let(:open_collection_period_start_date) { 1.year.ago }
let!(:merged_recent) { create(:merge_request, request_merged: true, merge_date: 3.months.ago) }
let!(:merged_old) { create(:merge_request, request_merged: true, merge_date: 18.months.ago) }
let!(:not_merged) { create(:merge_request, request_merged: false) }
before do
allow(FormHandler.instance).to receive(:start_date_of_earliest_open_collection_period).and_return(open_collection_period_start_date)
end
it "includes merged requests with merge dates after the open collection period start date" do
expect(described_class.visible).to include(merged_recent)
end
it "excludes merged requests with merge dates before the open collection period start date" do
expect(described_class.visible).not_to include(merged_old)
end
it "includes not_merged requests" do
expect(described_class.visible).to include(not_merged)
end
end
describe "#discard!" do
let(:merge_request) { create(:merge_request) }
it "sets the discarded_at field" do
merge_request.discard!
expect(merge_request.discarded_at).not_to be_nil
end
it "does not delete the record" do
merge_request.discard!
expect(merge_request).to be_persisted
end
it "is not visible in the visible scope" do
merge_request.discard!
expect(described_class.visible).not_to include(merge_request)
end
end
describe "#status" do
it "returns the correct status for deleted merge request" do
merge_request = build(:merge_request, id: 1, discarded_at: Time.zone.today)
expect(merge_request.status).to eq MergeRequest::STATUS[:deleted]
end
it "returns the correct status for a merged request" do
merge_request = build(:merge_request, id: 1, request_merged: true)
expect(merge_request.status).to eq MergeRequest::STATUS[:request_merged]
end
it "returns the correct status for a ready to merge request" do
merge_request = build(:merge_request, id: 1, absorbing_organisation: create(:organisation), merge_date: Time.zone.today, existing_absorbing_organisation: true)
create(:merge_request_organisation, merge_request:)
expect(merge_request.status).to eq MergeRequest::STATUS[:ready_to_merge]
end
it "returns the correct status for a ready to merge request when existing_absorbing_organisation is false" do
merge_request = build(:merge_request, id: 1, absorbing_organisation: create(:organisation), merge_date: Time.zone.today, existing_absorbing_organisation: false)
create(:merge_request_organisation, merge_request:)
expect(merge_request.status).to eq MergeRequest::STATUS[:ready_to_merge]
end
it "returns the merge issues if dsa is not signed for absorbing organisation" do
merge_request = build(:merge_request, id: 1, absorbing_organisation: create(:organisation, with_dsa: false), merge_date: Time.zone.today, existing_absorbing_organisation: true)
create(:merge_request_organisation, merge_request:)
expect(merge_request.status).to eq MergeRequest::STATUS[:merge_issues]
end
it "returns the incomplete if absorbing organisation is missing" do
merge_request = build(:merge_request, id: 1, absorbing_organisation: nil, merge_date: Time.zone.today)
create(:merge_request_organisation, merge_request:)
expect(merge_request.status).to eq MergeRequest::STATUS[:incomplete]
end
it "returns the incomplete if merge requests organisation is missing" do
merge_request = build(:merge_request, id: 1, absorbing_organisation: create(:organisation), merge_date: Time.zone.today)
expect(merge_request.status).to eq MergeRequest::STATUS[:incomplete]
end
it "returns the incomplete if merge date is missing" do
merge_request = build(:merge_request, id: 1, absorbing_organisation: create(:organisation))
create(:merge_request_organisation, merge_request:)
expect(merge_request.status).to eq MergeRequest::STATUS[:incomplete]
end
it "returns the incomplete if existing absorbing organisation is missing" do
merge_request = build(:merge_request, id: 1, absorbing_organisation: create(:organisation, with_dsa: false), merge_date: Time.zone.today)
create(:merge_request_organisation, merge_request:)
expect(merge_request.status).to eq MergeRequest::STATUS[:incomplete]
end
it "returns processing if merge is processing" do
merge_request = build(:merge_request, id: 1, absorbing_organisation: create(:organisation), processing: true)
create(:merge_request_organisation, merge_request:)
expect(merge_request.status).to eq MergeRequest::STATUS[:processing]
end
end
describe "#organisations_with_users" do
context "when absorbing organisation has users" do
let(:merge_request) { create(:merge_request, absorbing_organisation:) }
let(:absorbing_organisation) { create(:organisation) }
before do
create(:merge_request_organisation, merge_request:, merging_organisation: merging_organisation_1)
create(:merge_request_organisation, merge_request:, merging_organisation: merging_organisation_2)
end
context "and some merging organisations have users" do
let(:merging_organisation_1) { create(:organisation) }
let(:merging_organisation_2) { create(:organisation, with_dsa: false) }
it "returns correct organisations with users" do
expect(absorbing_organisation.users.count).to eq(1)
expect(merging_organisation_1.users.count).to eq(1)
expect(merging_organisation_2.users.count).to eq(0)
expect(merge_request.organisations_with_users.count).to eq(2)
expect(merge_request.organisations_with_users).to include(merging_organisation_1)
expect(merge_request.organisations_with_users).to include(absorbing_organisation)
end
end
context "and no merging organisations have users" do
let(:merging_organisation_1) { create(:organisation, with_dsa: false) }
let(:merging_organisation_2) { create(:organisation, with_dsa: false) }
it "returns correct organisations with users" do
expect(absorbing_organisation.users.count).to eq(1)
expect(merging_organisation_1.users.count).to eq(0)
expect(merging_organisation_2.users.count).to eq(0)
expect(merge_request.organisations_with_users.count).to eq(1)
expect(merge_request.organisations_with_users).to include(absorbing_organisation)
end
end
end
context "when absorbing organisation has no users" do
let(:merge_request) { create(:merge_request, absorbing_organisation:) }
let(:absorbing_organisation) { create(:organisation, with_dsa: false) }
before do
create(:merge_request_organisation, merge_request:, merging_organisation: merging_organisation_1)
create(:merge_request_organisation, merge_request:, merging_organisation: merging_organisation_2)
end
context "and some merging organisations have users" do
let(:merging_organisation_1) { create(:organisation) }
let(:merging_organisation_2) { create(:organisation, with_dsa: false) }
it "returns correct organisations with users" do
expect(merging_organisation_1.users.count).to eq(1)
expect(absorbing_organisation.users.count).to eq(0)
expect(merging_organisation_2.users.count).to eq(0)
expect(merge_request.organisations_with_users.count).to eq(1)
expect(merge_request.organisations_with_users).to include(merging_organisation_1)
end
end
context "and no merging organisations have users" do
let(:merging_organisation_1) { create(:organisation, with_dsa: false) }
let(:merging_organisation_2) { create(:organisation, with_dsa: false) }
it "returns correct organisations with users" do
expect(absorbing_organisation.users.count).to eq(0)
expect(merging_organisation_1.users.count).to eq(0)
expect(merging_organisation_2.users.count).to eq(0)
expect(merge_request.organisations_with_users.count).to eq(0)
end
end
end
end
describe "#organisations_with_schemes" do
let(:merge_request) { create(:merge_request, absorbing_organisation:) }
let(:absorbing_organisation) { create(:organisation) }
let(:merging_organisation_1) { create(:organisation) }
let(:merging_organisation_2) { create(:organisation) }
before do
create(:merge_request_organisation, merge_request:, merging_organisation: merging_organisation_1)
create(:merge_request_organisation, merge_request:, merging_organisation: merging_organisation_2)
end
context "when absorbing organisation has schemes" do
before do
create(:scheme, owning_organisation: absorbing_organisation)
end
context "and some merging organisations have schemes" do
before do
create(:scheme, owning_organisation: merging_organisation_1)
end
it "returns correct organisations with schemes" do
expect(absorbing_organisation.owned_schemes.count).to eq(1)
expect(merging_organisation_1.owned_schemes.count).to eq(1)
expect(merging_organisation_2.owned_schemes.count).to eq(0)
expect(merge_request.organisations_with_schemes.count).to eq(2)
expect(merge_request.organisations_with_schemes).to include(merging_organisation_1)
expect(merge_request.organisations_with_schemes).to include(absorbing_organisation)
end
end
context "and no merging organisations have schemes" do
it "returns correct organisations with schemes" do
expect(absorbing_organisation.owned_schemes.count).to eq(1)
expect(merging_organisation_1.owned_schemes.count).to eq(0)
expect(merging_organisation_2.owned_schemes.count).to eq(0)
expect(merge_request.organisations_with_schemes.count).to eq(1)
expect(merge_request.organisations_with_schemes).to include(absorbing_organisation)
end
end
end
context "when absorbing organisation has no schemes" do
context "and some merging organisations have schemes" do
before do
create(:scheme, owning_organisation: merging_organisation_1)
end
it "returns correct organisations with schemes" do
expect(merging_organisation_1.owned_schemes.count).to eq(1)
expect(absorbing_organisation.owned_schemes.count).to eq(0)
expect(merging_organisation_2.owned_schemes.count).to eq(0)
expect(merge_request.organisations_with_schemes.count).to eq(1)
expect(merge_request.organisations_with_schemes).to include(merging_organisation_1)
end
end
context "and no merging organisations have schemes" do
it "returns correct organisations with schemes" do
expect(absorbing_organisation.owned_schemes.count).to eq(0)
expect(merging_organisation_1.owned_schemes.count).to eq(0)
expect(merging_organisation_2.owned_schemes.count).to eq(0)
expect(merge_request.organisations_with_schemes.count).to eq(0)
end
end
end
end
describe "#organisations_without_users" do
context "when absorbing organisation has users" do
let(:merge_request) { create(:merge_request, absorbing_organisation:) }
let(:absorbing_organisation) { create(:organisation) }
before do
create(:merge_request_organisation, merge_request:, merging_organisation: merging_organisation_1)
create(:merge_request_organisation, merge_request:, merging_organisation: merging_organisation_2)
end
context "and some merging organisations have users" do
let(:merging_organisation_1) { create(:organisation) }
let(:merging_organisation_2) { create(:organisation, with_dsa: false) }
it "returns correct organisations with users" do
expect(absorbing_organisation.users.count).to eq(1)
expect(merging_organisation_1.users.count).to eq(1)
expect(merging_organisation_2.users.count).to eq(0)
expect(merge_request.organisations_without_users.count).to eq(1)
expect(merge_request.organisations_without_users).to include(merging_organisation_2)
end
end
context "and no merging organisations have users" do
let(:merging_organisation_1) { create(:organisation, with_dsa: false) }
let(:merging_organisation_2) { create(:organisation, with_dsa: false) }
it "returns correct organisations with users" do
expect(absorbing_organisation.users.count).to eq(1)
expect(merging_organisation_1.users.count).to eq(0)
expect(merging_organisation_2.users.count).to eq(0)
expect(merge_request.organisations_without_users.count).to eq(2)
expect(merge_request.organisations_without_users).to include(merging_organisation_1)
expect(merge_request.organisations_without_users).to include(merging_organisation_2)
end
end
end
context "when absorbing organisation has no users" do
let(:merge_request) { create(:merge_request, absorbing_organisation:) }
let(:absorbing_organisation) { create(:organisation, with_dsa: false) }
before do
create(:merge_request_organisation, merge_request:, merging_organisation: merging_organisation_1)
create(:merge_request_organisation, merge_request:, merging_organisation: merging_organisation_2)
end
context "and some merging organisations have users" do
let(:merging_organisation_1) { create(:organisation) }
let(:merging_organisation_2) { create(:organisation, with_dsa: false) }
it "returns correct organisations with users" do
expect(merging_organisation_1.users.count).to eq(1)
expect(absorbing_organisation.users.count).to eq(0)
expect(merging_organisation_2.users.count).to eq(0)
expect(merge_request.organisations_without_users.count).to eq(2)
expect(merge_request.organisations_without_users).to include(absorbing_organisation)
expect(merge_request.organisations_without_users).to include(merging_organisation_2)
end
end
context "and no merging organisations have users" do
let(:merging_organisation_1) { create(:organisation, with_dsa: false) }
let(:merging_organisation_2) { create(:organisation, with_dsa: false) }
it "returns correct organisations with users" do
expect(absorbing_organisation.users.count).to eq(0)
expect(merging_organisation_1.users.count).to eq(0)
expect(merging_organisation_2.users.count).to eq(0)
expect(merge_request.organisations_without_users.count).to eq(3)
expect(merge_request.organisations_without_users).to include(absorbing_organisation)
expect(merge_request.organisations_without_users).to include(merging_organisation_1)
expect(merge_request.organisations_without_users).to include(merging_organisation_2)
end
end
end
end
describe "#organisations_without_schemes" do
let(:merge_request) { create(:merge_request, absorbing_organisation:) }
let(:absorbing_organisation) { create(:organisation) }
let(:merging_organisation_1) { create(:organisation) }
let(:merging_organisation_2) { create(:organisation) }
before do
create(:merge_request_organisation, merge_request:, merging_organisation: merging_organisation_1)
create(:merge_request_organisation, merge_request:, merging_organisation: merging_organisation_2)
end
context "when absorbing organisation has schemes" do
before do
create(:scheme, owning_organisation: absorbing_organisation)
end
context "and some merging organisations have schemes" do
before do
create(:scheme, owning_organisation: merging_organisation_1)
end
it "returns correct organisations with schemes" do
expect(absorbing_organisation.owned_schemes.count).to eq(1)
expect(merging_organisation_1.owned_schemes.count).to eq(1)
expect(merging_organisation_2.owned_schemes.count).to eq(0)
expect(merge_request.organisations_without_schemes.count).to eq(1)
expect(merge_request.organisations_without_schemes).to include(merging_organisation_2)
end
end
context "and no merging organisations have schemes" do
it "returns correct organisations with schemes" do
expect(absorbing_organisation.owned_schemes.count).to eq(1)
expect(merging_organisation_1.owned_schemes.count).to eq(0)
expect(merging_organisation_2.owned_schemes.count).to eq(0)
expect(merge_request.organisations_without_schemes.count).to eq(2)
expect(merge_request.organisations_without_schemes).to include(merging_organisation_1)
expect(merge_request.organisations_without_schemes).to include(merging_organisation_2)
end
end
end
context "when absorbing organisation has no schemes" do
context "and some merging organisations have schemes" do
before do
create(:scheme, owning_organisation: merging_organisation_1)
end
it "returns correct organisations with schemes" do
expect(merging_organisation_1.owned_schemes.count).to eq(1)
expect(absorbing_organisation.owned_schemes.count).to eq(0)
expect(merging_organisation_2.owned_schemes.count).to eq(0)
expect(merge_request.organisations_without_schemes.count).to eq(2)
expect(merge_request.organisations_without_schemes).to include(absorbing_organisation)
expect(merge_request.organisations_without_schemes).to include(merging_organisation_2)
end
end
context "and no merging organisations have schemes" do
it "returns correct organisations with schemes" do
expect(absorbing_organisation.owned_schemes.count).to eq(0)
expect(merging_organisation_1.owned_schemes.count).to eq(0)
expect(merging_organisation_2.owned_schemes.count).to eq(0)
expect(merge_request.organisations_without_schemes.count).to eq(3)
expect(merge_request.organisations_without_schemes).to include(absorbing_organisation)
expect(merge_request.organisations_without_schemes).to include(merging_organisation_1)
expect(merge_request.organisations_without_schemes).to include(merging_organisation_2)
end
end
end
end
describe "relationship outcomes" do
let(:stock_owner1) { create(:organisation, name: "Stock owner 1") }
let(:stock_owner2) { create(:organisation, name: "Stock owner 2") }
let(:stock_owner3) { create(:organisation, name: "Stock owner 3") }
let(:managing_agent1) { create(:organisation, name: "Managing agent 1") }
let(:managing_agent2) { create(:organisation, name: "Managing agent 2") }
let(:absorbing_organisation) { create(:organisation, name: "Absorbing Org") }
let(:merging_organisations) { create_list(:organisation, 2) { |org, i| org.name = "Dummy Org #{i + 1}" } }
let(:merge_request) { create(:merge_request, absorbing_organisation:, merging_organisations:) }
before do
create(:organisation_relationship, child_organisation: absorbing_organisation, parent_organisation: stock_owner1)
create(:organisation_relationship, child_organisation: merging_organisations.first, parent_organisation: stock_owner2)
create(:organisation_relationship, child_organisation: merging_organisations.first, parent_organisation: stock_owner1)
create(:organisation_relationship, child_organisation: merging_organisations.first, parent_organisation: stock_owner3)
create(:organisation_relationship, parent_organisation: absorbing_organisation, child_organisation: managing_agent1)
create(:organisation_relationship, parent_organisation: absorbing_organisation, child_organisation: managing_agent2)
create(:organisation_relationship, parent_organisation: merging_organisations.first, child_organisation: managing_agent2)
end
describe "#total_stock_owners_after_merge" do
it "returns the correct count of stock owners after merge" do
expect(merge_request.total_stock_owners_after_merge).to eq(3)
end
end
describe "#total_managing_agents_after_merge" do
it "returns the correct count of managing agents after merge" do
expect(merge_request.total_managing_agents_after_merge).to eq(2)
end
end
describe "#total_stock_owners_managing_agents_label" do
it "returns the correct label" do
expect(merge_request.total_stock_owners_managing_agents_label).to eq("3 stock owners\n2 managing agents")
end
end
end
end

28
spec/requests/merge_request_spec.rb

@ -0,0 +1,28 @@
require "rails_helper"
RSpec.describe MergeRequest, type: :request do
let(:user) { create(:user, :data_coordinator) }
let(:organisation) { user.organisation }
let(:merge_request) { create(:merge_request) }
let(:support_user) { create(:user, :support, organisation:) }
let(:page) { Capybara::Node::Simple.new(response.body) }
before do
allow(support_user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in support_user
end
context "when deleting a merge request" do
it "discards the merge request" do
delete delete_merge_request_path(merge_request)
expect(merge_request.reload.discarded_at).not_to be_nil
end
it "redirects to the merge request list" do
delete delete_merge_request_path(merge_request)
expect(response).to redirect_to(organisations_path(tab: "merge-requests"))
follow_redirect!
expect(page).to have_content("Merge requests")
end
end
end

815
spec/requests/merge_requests_controller_spec.rb

File diff suppressed because it is too large Load Diff

13
spec/requests/organisations_controller_spec.rb

@ -1043,6 +1043,19 @@ RSpec.describe OrganisationsController, type: :request do
expect(page).to have_field("search", type: "search")
end
it "shows the merge request list" do
expect(page).to have_content("Merge requests")
end
it "has a create new merge request button" do
expect(page).to have_button("Create new merge request")
end
it "displays 'No merge requests' when @merge_requests is empty" do
allow(MergeRequest).to receive(:visible).and_return(nil)
expect(page).to have_content("No merge requests")
end
context "when viewing a specific organisation's lettings logs" do
let(:parent_organisation) { create(:organisation) }
let(:child_organisation) { create(:organisation) }

31
spec/requests/schemes_controller_spec.rb

@ -612,9 +612,9 @@ RSpec.describe SchemesController, type: :request do
context "with scheme that's deactivating soon" do
let(:scheme_deactivation_period) { create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 10, 12), scheme:) }
it "does not render toggle scheme link" do
it "does render the reactivate this scheme button" do
expect(response).to have_http_status(:ok)
expect(page).not_to have_link("Reactivate this scheme")
expect(page).to have_link("Reactivate this scheme")
expect(page).not_to have_link("Deactivate this scheme")
end
end
@ -680,9 +680,9 @@ RSpec.describe SchemesController, type: :request do
context "with scheme that's deactivating soon" do
let(:scheme_deactivation_period) { create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 10, 12), scheme: specific_scheme) }
it "does not render toggle scheme link" do
it "does render the reactivate this scheme button" do
expect(response).to have_http_status(:ok)
expect(page).not_to have_link("Reactivate this scheme")
expect(page).to have_link("Reactivate this scheme")
expect(page).not_to have_link("Deactivate this scheme")
end
end
@ -784,6 +784,7 @@ RSpec.describe SchemesController, type: :request do
context "and associated logs in editable collection period" do
before do
create(:location, scheme:)
create(:lettings_log, :sh, scheme:, startdate: Time.zone.local(2022, 9, 9), owning_organisation: user.organisation)
get "/schemes/#{scheme.id}"
end
@ -2533,14 +2534,18 @@ RSpec.describe SchemesController, type: :request do
it "redirects to the confirmation page" do
follow_redirect!
expect(response).to have_http_status(:ok)
expect(page).to have_content("This change will affect #{scheme.lettings_logs.count} logs")
expect(page).to have_content("This change will affect #{scheme.lettings_logs.count} log and 1 location.")
end
end
context "and no affected logs" do
let(:setup_schemes) { scheme.lettings_logs.update(scheme: nil) }
it "redirects to the location page and updates the deactivation period" do
before do
create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 4, 1), reactivation_date: nil, location:)
end
it "redirects to the scheme page and updates the deactivation period" do
follow_redirect!
follow_redirect!
follow_redirect!
@ -2560,14 +2565,18 @@ RSpec.describe SchemesController, type: :request do
it "redirects to the confirmation page" do
follow_redirect!
expect(response).to have_http_status(:ok)
expect(page).to have_content("This change will affect #{scheme.lettings_logs.count} logs")
expect(page).to have_content("This change will affect #{scheme.lettings_logs.count} log and 1 location.")
end
end
context "and no affected logs" do
let(:setup_schemes) { scheme.lettings_logs.update(scheme: nil) }
it "redirects to the location page and updates the deactivation period" do
before do
create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 4, 5), reactivation_date: nil, location:)
end
it "redirects to the scheme page and updates the deactivation period" do
follow_redirect!
follow_redirect!
follow_redirect!
@ -2751,6 +2760,10 @@ RSpec.describe SchemesController, type: :request do
let(:params) { { scheme_deactivation_period: { deactivation_date_type: "other", "deactivation_date(3i)": "8", "deactivation_date(2i)": "9", "deactivation_date(1i)": "2023" } } }
let(:add_deactivations) { create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2023, 6, 5), reactivation_date: nil, scheme:) }
before do
create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 4, 5), reactivation_date: nil, location:)
end
it "redirects to the scheme page and updates the existing deactivation period" do
follow_redirect!
follow_redirect!
@ -2771,7 +2784,7 @@ RSpec.describe SchemesController, type: :request do
it "redirects to the confirmation page" do
follow_redirect!
expect(response).to have_http_status(:ok)
expect(page).to have_content("This change will affect 1 logs")
expect(page).to have_content("This change will affect 1 log and 1 location.")
end
end
end

5
spec/services/bulk_upload/lettings/year2024/csv_parser_spec.rb

@ -41,6 +41,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
file.write("Duplicate check field?\n")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2024_field_numbers_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2024_csv_row)
file.write("\n")
file.rewind
end
@ -52,6 +53,10 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
it "parses csv correctly" do
expect(service.row_parsers[0].field_13).to eql(log.tenancycode)
end
it "does not parse the last empty row" do
expect(service.row_parsers.count).to eq(1)
end
end
context "when parsing csv with headers in arbitrary order" do

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

@ -1843,6 +1843,18 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
end
end
end
describe "log_already_exists?" do
let(:attributes) { { bulk_upload: } }
before do
build(:lettings_log, owning_organisation: nil, startdate: nil, tenancycode: nil, location: nil, age1: nil, sex1: nil, ecstat1: nil, brent: nil, scharge: nil, pscharge: nil, supcharg: nil).save(validate: false)
end
it "does not add duplicate logs validation to the blank row" do
expect(parser.log_already_exists?).to eq(false)
end
end
end
describe "#log" do

5
spec/services/bulk_upload/sales/year2024/csv_parser_spec.rb

@ -17,6 +17,7 @@ RSpec.describe BulkUpload::Sales::Year2024::CsvParser do
file.write("Duplicate check field?\n")
file.write(BulkUpload::SalesLogToCsv.new(log:).default_2024_field_numbers_row)
file.write(BulkUpload::SalesLogToCsv.new(log:).to_2024_csv_row)
file.write("\n")
file.rewind
end
@ -32,6 +33,10 @@ RSpec.describe BulkUpload::Sales::Year2024::CsvParser do
it "counts the number of valid field numbers correctly" do
expect(service).to be_correct_field_count
end
it "does not parse the last empty row" do
expect(service.row_parsers.count).to eq(1)
end
end
context "when parsing csv with headers in arbitrary order" do

12
spec/services/bulk_upload/sales/year2024/row_parser_spec.rb

@ -1417,6 +1417,18 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
end
end
end
describe "log_already_exists?" do
let(:attributes) { { bulk_upload: } }
before do
build(:sales_log, owning_organisation: nil, saledate: nil, purchid: nil, age1: nil, sex1: nil, ecstat1: nil).save(validate: false)
end
it "does not add duplicate logs validation to the blank row" do
expect(parser.log_already_exists?).to eq(false)
end
end
end
describe "#log" do

93
spec/views/merge_requests/show.html.erb_spec.rb

@ -0,0 +1,93 @@
require "rails_helper"
RSpec.describe "merge_requests/show.html.erb", type: :view do
let(:absorbing_organisation) { create(:organisation, name: "Absorbing Org", with_dsa: false) }
let(:dpo_user) { create(:user, name: "DPO User", is_dpo: true, organisation: absorbing_organisation) }
let(:merge_request) { create(:merge_request, absorbing_organisation_id: absorbing_organisation.id, signed_dsa: false) }
before do
assign(:merge_request, merge_request)
render
end
it "displays the correct title" do
expect(rendered).to have_selector("h1.govuk-heading-l") do |h1|
expect(h1).to have_selector("span.govuk-caption-l", text: "Merge request")
expect(h1).to have_content("Absorbing Org")
end
end
it "displays the notification banner when DSA is not signed" do
expect(rendered).to have_selector(".govuk-notification-banner")
expect(rendered).to have_content("The absorbing organisation must accept the Data Sharing Agreement before merging.")
end
it "displays the requester details" do
expect(rendered).to have_selector("dt", text: "Requester")
expect(rendered).to have_selector("dd", text: merge_request.requester&.name || "You didn't answer this question")
end
it "displays the helpdesk ticket details" do
expect(rendered).to have_selector("dt", text: "Helpdesk ticket")
if merge_request.helpdesk_ticket.present?
expect(rendered).to have_link(merge_request.helpdesk_ticket, href: "https://dluhcdigital.atlassian.net/browse/#{merge_request.helpdesk_ticket}")
else
expect(rendered).to have_selector("dd", text: "You didn't answer this question")
end
end
it "displays the status details" do
expect(rendered).to have_selector("dt", text: "Status")
expect(rendered).to have_selector("dd", text: "Incomplete")
end
it "displays the absorbing organisation details" do
expect(rendered).to have_selector("dt", text: "Absorbing organisation")
expect(rendered).to have_selector("dd", text: merge_request.absorbing_organisation_name)
end
it "displays the merge date details" do
expect(rendered).to have_selector("dt", text: "Merge date")
expect(rendered).to have_selector("dd", text: merge_request.merge_date || "You didn't answer this question")
end
context "when the merge request is complete" do
before do
merge_request.update!(request_merged: true, signed_dsa: true, total_users: 10, total_schemes: 5, total_lettings_logs: 20, total_sales_logs: 30, total_stock_owners: 40, total_managing_agents: 50)
assign(:merge_request, merge_request)
render
end
it "has status of 'Merged'" do
expect(rendered).to have_selector("dd", text: "Merged")
end
it "displays the total users after merge details" do
expect(rendered).to have_selector("dt", text: "Total users after merge")
expect(rendered).to have_selector("dd", text: merge_request.total_users)
end
it "displays the total schemes after merge details" do
expect(rendered).to have_selector("dt", text: "Total schemes after merge")
expect(rendered).to have_selector("dd", text: merge_request.total_schemes)
end
it "displays the total logs after merge details" do
expect(rendered).to have_selector("dt", text: "Total logs after merge")
if merge_request.total_lettings_logs.present? || merge_request.total_sales_logs.present?
combined_text = []
combined_text << "#{merge_request.total_lettings_logs} lettings logs" if merge_request.total_lettings_logs.present?
combined_text << "#{merge_request.total_sales_logs} sales logs" if merge_request.total_sales_logs.present?
expect(rendered).to have_selector("dd", text: combined_text.join(""))
end
end
it "displays the total stock owners & managing agents after merge details" do
expect(rendered).to have_selector("dt", text: "Total stock owners & managing agents after merge")
combined_text = []
combined_text << "#{merge_request.total_stock_owners} stock owners" if merge_request.total_stock_owners.present?
combined_text << "#{merge_request.total_managing_agents} managing agents" if merge_request.total_managing_agents.present?
expect(rendered).to have_selector("dd", text: combined_text.join("\n"))
end
end
end

46
spec/views/schemes/deactivate_confirm.html.erb_spec.rb

@ -0,0 +1,46 @@
require "rails_helper"
RSpec.describe "schemes/deactivate_confirm.html.erb", type: :view do
let(:scheme) { create(:scheme, service_name: "ABCScheme") }
let(:deactivation_date) { Time.zone.today + 1.month }
let(:affected_logs) { create_list(:lettings_log, 2, scheme:, status: 1) }
let(:affected_locations) { create_list(:location, 3, scheme:) }
let(:scheme_deactivation_period) { SchemeDeactivationPeriod.new }
before do
assign(:scheme, scheme)
assign(:deactivation_date, deactivation_date)
assign(:affected_logs, affected_logs)
assign(:affected_locations, affected_locations)
assign(:scheme_deactivation_period, scheme_deactivation_period)
render
end
it "displays the service name in the caption" do
expect(rendered).to have_css("span.govuk-caption-l", text: scheme.service_name)
end
it "displays the correct heading" do
expect(rendered).to have_css("h1.govuk-heading-l", text: "This change will affect 2 logs and 3 locations.")
end
it "displays the affected logs count" do
expect(rendered).to have_text("2 existing logs using this scheme have a tenancy start date after #{deactivation_date.to_formatted_s(:govuk_date)}.")
end
it "displays the warning text" do
expect(rendered).to have_css(".govuk-warning-text", text: I18n.t("warnings.scheme.deactivate.review_logs"))
end
it "displays the affected locations count" do
expect(rendered).to have_text("This scheme has 3 locations active on #{deactivation_date.to_formatted_s(:govuk_date)}.")
end
it "renders the submit button" do
expect(rendered).to have_button("Deactivate this scheme")
end
it "renders the cancel button" do
expect(rendered).to have_link("Cancel", href: scheme_details_path(scheme))
end
end
Loading…
Cancel
Save