Browse Source

CLDC-3382 Add support user functionality to merge organisations (#2566)

* CLDC-2093 Merge organisations page - view all merge requests (#2561)

* CLDC-3585 Update absorbing organisation question (#2564)

* Update user permissions and absorbing org question order

* Update absorbing organisation question and routing

* CLDC 2094: View a merge request (#2565)

* CLDC-3584 Update merging orgs question (#2567)

* Update merging organisations question

* Update rebase tests

* Update routing between CYA and questions

* CLDC-3586 Update merge date merge request question (#2571)

* Remove unused paths and update merge date

* lint

* Create delete merge request functionality (#2568)

* CLDC-3588: Add helpdesk ticket question (#2572)

* Merge request fixes (#2578)

* Add cancel button to merging orgs and update merging orgs selection

* Update submit button text

* Update back buttons

* CLDC-2101 Add begin merge (#2575)

* Calculate merge request status

* Add start merge and merge request job

* Update merge request when it gets processed

* Refactor and display a banner for failed requests

* update test

* Update change links

* Update copy for error message (#2583)

* Do not display cancel button if there's no submit (#2584)

* Update hint date (#2588)

* CLDC-2100++ Add success notification after delete (#2589)

* Update helpdesk_ticket.html.erb

* CLDC-2094++ Merge details page adjustments (#2593)

* Change page name and hide buttons during processing status

* Update test

* Make merge request status dynamic (#2592)

* Make merge request status dynamic

* Update tests

* Keep check answers referrer when validation is hit (#2594)

* Keep check answers referrer when validation is hit

* Remove absorbing org from merging organisations list

* Update text to handle singular merge requests

* Add merge start confirmation page (#2601)

* Set merge request as non processing if it fails (#2598)

* Set merge request to no longer processing if it fails

* clear last_failed_attempt as soon as we start processing the merge

* CLDC-3603 View merged users outcomes (#2602)

* Update user outcomes view link

* Add user outcomes page

* Set user outcomes before merge and on merge fail

* Update hardcoded total user count

* Account for singular user numbers

* CLDC-3603 View scheme outcomes (#2604)

* Update scheme outcomes view link

* Add scheme outcomes page

* Update scheme outcome before merge and on merge fail

* Update back links in merge outcomes pages (#2611)

* CLDC-3605 View relationships page (#2606)

* CLDC-3609 Add existing absorbing organisation merge request question (#2600)

* Add existing absorbing organisation page and field

* Update status calculation and add new question to check answers

* Call correct merge service flow

* Update test

* Update test

* Return correct status when existing_absorbing_organisation is false (#2617)

* Change styling of relationship outcomes page (#2619)

* CLDC-3604 View logs outcomes (#2618)

* Update view logs outcomes link

* Add logs outcomes page

* Set logs outcome before merge and on merge fail

* Update logs outcomes text

* Update line break

* Fix incorrect filtering of relationships and count

* Fix test

* Fix test

* Fix test

* Make example date depend on collection year (#2620)

---------

Co-authored-by: Manny Dinssa <44172848+Dinssa@users.noreply.github.com>
pull/2626/head^2
kosiakkatrina 5 months ago committed by GitHub
parent
commit
e69ed94513
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 201
      app/controllers/merge_requests_controller.rb
  2. 4
      app/controllers/organisations_controller.rb
  3. 4
      app/frontend/controllers/index.js
  4. 35
      app/frontend/controllers/tabs_controller.js
  5. 279
      app/helpers/merge_requests_helper.rb
  6. 8
      app/helpers/tag_helper.rb
  7. 15
      app/jobs/process_merge_request_job.rb
  8. 178
      app/models/merge_request.rb
  9. 13
      app/models/merge_request_organisation.rb
  10. 8
      app/models/organisation.rb
  11. 3
      app/views/layouts/application.html.erb
  12. 33
      app/views/merge_requests/_details_list.html.erb
  13. 46
      app/views/merge_requests/_merge_request_list.html.erb
  14. 21
      app/views/merge_requests/_notification_banners.html.erb
  15. 8
      app/views/merge_requests/_summary_card.html.erb
  16. 40
      app/views/merge_requests/absorbing_organisation.html.erb
  17. 41
      app/views/merge_requests/confirm_telephone_number.html.erb
  18. 23
      app/views/merge_requests/delete_confirmation.html.erb
  19. 27
      app/views/merge_requests/existing_absorbing_organisation.html.erb
  20. 27
      app/views/merge_requests/helpdesk_ticket.html.erb
  21. 27
      app/views/merge_requests/logs_outcomes.html.erb
  22. 24
      app/views/merge_requests/merge_date.html.erb
  23. 0
      app/views/merge_requests/merge_request.html.erb
  24. 27
      app/views/merge_requests/merge_start_confirmation.html.erb
  25. 50
      app/views/merge_requests/merging_organisations.html.erb
  26. 33
      app/views/merge_requests/new_organisation_address.html.erb
  27. 19
      app/views/merge_requests/new_organisation_name.html.erb
  28. 20
      app/views/merge_requests/new_organisation_telephone_number.html.erb
  29. 5
      app/views/merge_requests/new_organisation_type.html.erb
  30. 51
      app/views/merge_requests/organisations.html.erb
  31. 25
      app/views/merge_requests/relationship_outcomes.html.erb
  32. 23
      app/views/merge_requests/scheme_outcomes.html.erb
  33. 26
      app/views/merge_requests/show.html.erb
  34. 23
      app/views/merge_requests/user_outcomes.html.erb
  35. 2
      app/views/organisation_relationships/_related_organisation_select_question.html.erb
  36. 1
      app/views/organisation_relationships/add_managing_agent.html.erb
  37. 1
      app/views/organisation_relationships/add_stock_owner.html.erb
  38. 25
      app/views/organisations/index.html.erb
  39. 17
      config/locales/en.yml
  40. 21
      config/routes.rb
  41. 5
      db/migrate/20240808134014_add_merge_date_to_merge_requests.rb
  42. 16
      db/migrate/20240809154241_add_additional_fields_to_merge_requests.rb
  43. 5
      db/migrate/20240813072041_remove_other_merging_org_field.rb
  44. 27
      db/migrate/20240813112119_remove_new_org_merge_request_fields.rb
  45. 5
      db/migrate/20240814083017_add_last_failed_attempt.rb
  46. 17
      db/migrate/20240819100411_update_merge_request_fields_for_status.rb
  47. 5
      db/migrate/20240822080228_add_existing_absorbing_organisation_field.rb
  48. 27
      db/schema.rb
  49. 8
      spec/factories/merge_request.rb
  50. 6
      spec/factories/merge_request_organisation.rb
  51. 2
      spec/features/schemes_spec.rb
  52. 272
      spec/helpers/merge_requests_helper_spec.rb
  53. 65
      spec/jobs/process_merge_request_job_spec.rb
  54. 451
      spec/models/merge_request_spec.rb
  55. 28
      spec/requests/merge_request_spec.rb
  56. 815
      spec/requests/merge_requests_controller_spec.rb
  57. 13
      spec/requests/organisations_controller_spec.rb
  58. 93
      spec/views/merge_requests/show.html.erb_spec.rb

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

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)
})
})

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

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

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

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]

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>

17
config/locales/en.yml

@ -175,17 +175,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:
logs_deleted:

21
config/routes.rb

@ -203,16 +203,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

27
db/schema.rb

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2024_08_19_143150) do
ActiveRecord::Schema[7.0].define(version: 2024_08_22_080228) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -429,19 +429,24 @@ ActiveRecord::Schema[7.0].define(version: 2024_08_19_143150) 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|

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

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

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) }

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
Loading…
Cancel
Save