diff --git a/.env.example b/.env.example index 8b966b4f4..329016da2 100644 --- a/.env.example +++ b/.env.example @@ -6,3 +6,4 @@ OTP_SECRET_ENCRYPTION_KEY="" APP_HOST="http://localhost:3000" OS_DATA_KEY=OS_DATA_KEY +REVIEW_APP_USER_PASSWORD=password diff --git a/.env.test b/.env.test index 2ba1222ba..4df1dc339 100644 --- a/.env.test +++ b/.env.test @@ -1,2 +1,3 @@ APP_HOST="http://localhost:3000" OS_DATA_KEY=OS_DATA_KEY +REVIEW_APP_USER_PASSWORD=password diff --git a/Dockerfile b/Dockerfile index b26aad246..a26c80ee9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ RUN apk add --update --no-cache tzdata && \ # build-base: compilation tools for bundle # yarn: node package manager # postgresql-dev: postgres driver and libraries -RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r7 nodejs-current=20.8.1-r0 yarn=1.22.19-r0 postgresql13-dev=13.15-r0 git=2.40.1-r0 bash=5.2.15-r5 +RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r7 nodejs-current=20.8.1-r0 yarn=1.22.19-r0 postgresql13-dev=13.15-r0 git=2.40.3-r0 bash=5.2.15-r5 # Bundler version should be the same version as what the Gemfile.lock was bundled with RUN gem install bundler:2.3.14 --no-document diff --git a/app/controllers/merge_requests_controller.rb b/app/controllers/merge_requests_controller.rb index d717f6ee7..a21d42bbb 100644 --- a/app/controllers/merge_requests_controller.rb +++ b/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 diff --git a/app/controllers/organisations_controller.rb b/app/controllers/organisations_controller.rb index 9d3e63b33..044bab74d 100644 --- a/app/controllers/organisations_controller.rb +++ b/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 diff --git a/app/controllers/schemes_controller.rb b/app/controllers/schemes_controller.rb index c0a36b920..1468bc013 100644 --- a/app/controllers/schemes_controller.rb +++ b/app/controllers/schemes_controller.rb @@ -51,17 +51,24 @@ class SchemesController < ApplicationController end def deactivate_confirm - @affected_logs = @scheme.lettings_logs.visible.after_date(params[:deactivation_date]) - if @affected_logs.count.zero? + @deactivation_date = Time.zone.parse(params[:deactivation_date]) + @affected_logs = @scheme.lettings_logs.visible.after_date(@deactivation_date) + @deactivation_date_type = params[:deactivation_date_type] + + scheme_locations = @scheme.locations.confirmed + + @affected_locations = scheme_locations.select do |location| + %i[active deactivating_soon reactivating_soon activating_soon].include?(location.status_at(@deactivation_date)) + end + + if @affected_logs.count.zero? && @affected_locations.count.zero? deactivate - else - @deactivation_date = params[:deactivation_date] - @deactivation_date_type = params[:deactivation_date_type] end end def deactivate - if @scheme.open_deactivation&.update!(deactivation_date: params[:deactivation_date]) || @scheme.scheme_deactivation_periods.create!(deactivation_date: params[:deactivation_date]) + deactivation_date = params[:deactivation_date] + if @scheme.open_deactivation&.update!(deactivation_date:) || @scheme.scheme_deactivation_periods.create!(deactivation_date:) logs = reset_location_and_scheme_for_logs! flash[:notice] = deactivate_success_notice diff --git a/app/frontend/controllers/index.js b/app/frontend/controllers/index.js index ef29b99ca..944e32e2d 100644 --- a/app/frontend/controllers/index.js +++ b/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) diff --git a/app/frontend/controllers/tabs_controller.js b/app/frontend/controllers/tabs_controller.js new file mode 100644 index 000000000..7cfd65bbb --- /dev/null +++ b/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) + }) +}) diff --git a/app/helpers/deactivate_confirm_helper.rb b/app/helpers/deactivate_confirm_helper.rb new file mode 100644 index 000000000..a7e9edb19 --- /dev/null +++ b/app/helpers/deactivate_confirm_helper.rb @@ -0,0 +1,8 @@ +module DeactivateConfirmHelper + def affected_title(affected_logs, affected_locations) + title_parts = [] + title_parts << pluralize(affected_logs.count, "log") if affected_logs.count.positive? + title_parts << pluralize(affected_locations.count, "location") if affected_locations.count.positive? + "This change will affect #{title_parts.join(' and ')}." + end +end diff --git a/app/helpers/locations_helper.rb b/app/helpers/locations_helper.rb index f963c7040..fc1008926 100644 --- a/app/helpers/locations_helper.rb +++ b/app/helpers/locations_helper.rb @@ -70,7 +70,7 @@ module LocationsHelper def toggle_location_link(location) return govuk_button_link_to "Deactivate this location", scheme_location_new_deactivation_path(location.scheme, location), warning: true if location.active? || location.deactivates_in_a_long_time? - return govuk_button_link_to "Reactivate this location", scheme_location_new_reactivation_path(location.scheme, location) if location.deactivated? + return govuk_button_link_to "Reactivate this location", scheme_location_new_reactivation_path(location.scheme, location) if location.deactivated? && !location.deactivated_by_scheme? end def delete_location_link(location) @@ -100,14 +100,37 @@ private ActivePeriod = Struct.new(:from, :to) def location_active_periods(location) periods = [ActivePeriod.new(location.available_from, nil)] + location_deactivation_periods = location_deactivation_periods(location) + scheme_deactivation_periods = scheme_deactivation_periods(location, location_deactivation_periods) - sorted_deactivation_periods = remove_nested_periods(location.location_deactivation_periods.sort_by(&:deactivation_date)) + combined_deactivation_periods = location_deactivation_periods + scheme_deactivation_periods + sorted_deactivation_periods = combined_deactivation_periods.sort_by(&:deactivation_date) + + update_periods_with_deactivations(periods, sorted_deactivation_periods) + remove_overlapping_and_empty_periods(periods) + end + + def location_deactivation_periods(location) + periods = remove_nested_periods(location.location_deactivation_periods.sort_by(&:deactivation_date)) + periods.last&.deactivation_date if periods.last&.reactivation_date.nil? + periods + end + + def scheme_deactivation_periods(location, location_deactivation_periods) + return [] unless location.scheme.scheme_deactivation_periods.any? + + location_deactivation_date = location_deactivation_periods.last&.deactivation_date + periods = remove_nested_periods(location.scheme.scheme_deactivation_periods.sort_by(&:deactivation_date)) + periods.select do |period| + period.deactivation_date >= location.available_from && (location_deactivation_date.nil? || period.deactivation_date <= location_deactivation_date) + end + end + + def update_periods_with_deactivations(periods, sorted_deactivation_periods) sorted_deactivation_periods.each do |deactivation| periods.last.to = deactivation.deactivation_date periods << ActivePeriod.new(deactivation.reactivation_date, nil) end - - remove_overlapping_and_empty_periods(periods) end def remove_overlapping_and_empty_periods(periods) diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb new file mode 100644 index 000000000..a342ca808 --- /dev/null +++ b/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("
").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}.

" + 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}.

" + 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')}

" + 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.

" + + 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}.

" + 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.

" + 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.

" + end + + organisations_with_logs.each do |organisation| + text += "#{link_to_merging_organisation_logs(organisation, type)}

" + 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 diff --git a/app/helpers/schemes_helper.rb b/app/helpers/schemes_helper.rb index f96f9e4c8..0e318d283 100644 --- a/app/helpers/schemes_helper.rb +++ b/app/helpers/schemes_helper.rb @@ -12,7 +12,7 @@ module SchemesHelper def toggle_scheme_link(scheme) return govuk_button_link_to "Deactivate this scheme", scheme_new_deactivation_path(scheme), warning: true if scheme.active? || scheme.deactivates_in_a_long_time? - return govuk_button_link_to "Reactivate this scheme", scheme_new_reactivation_path(scheme) if scheme.deactivated? + return govuk_button_link_to "Reactivate this scheme", scheme_new_reactivation_path(scheme) if scheme.deactivated? || scheme.deactivating_soon? end def delete_scheme_link(scheme) @@ -76,6 +76,15 @@ module SchemesHelper end end + def scheme_status_hint(scheme) + case scheme.status + when :deactivating_soon + "This scheme deactivates on #{scheme.last_deactivation_date.to_formatted_s(:govuk_date)}. Any locations you add will be deactivated on the same date. Reactivate the scheme to add locations active after this date." + when :deactivated + "This scheme deactivated on #{scheme.last_deactivation_date.to_formatted_s(:govuk_date)}. Any locations you add will be deactivated on the same date. Reactivate the scheme to add locations active after this date." + end + end + private ActivePeriod = Struct.new(:from, :to) diff --git a/app/helpers/tag_helper.rb b/app/helpers/tag_helper.rb index c389942dc..3c2e332f6 100644 --- a/app/helpers/tag_helper.rb +++ b/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 = []) diff --git a/app/jobs/process_merge_request_job.rb b/app/jobs/process_merge_request_job.rb new file mode 100644 index 000000000..8c3d1842c --- /dev/null +++ b/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 diff --git a/app/models/location.rb b/app/models/location.rb index 8efa4ee28..19cf5e211 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -40,6 +40,7 @@ class Location < ApplicationRecord filtered_records = filtered_records .left_outer_joins(:location_deactivation_periods) .joins(scheme: [:owning_organisation]) + .left_outer_joins(scheme: :scheme_deactivation_periods) .order("location_deactivation_periods.created_at DESC") .merge(scopes.reduce(&:or)) end @@ -52,30 +53,47 @@ class Location < ApplicationRecord .or(where(confirmed: nil)) } - scope :deactivated, lambda { + scope :deactivated, lambda { |date = Time.zone.now| deactivated_by_organisation - .or(deactivated_directly) + .or(deactivated_directly(date)) + .or(deactivated_by_scheme(date)) } scope :deactivated_by_organisation, lambda { merge(Organisation.filter_by_inactive) } + scope :deactivated_by_scheme, lambda { |date = Time.zone.now| + merge(Scheme.deactivated_directly(date)) + } + scope :deactivated_directly, lambda { |date = Time.zone.now| merge(LocationDeactivationPeriod.deactivations_without_reactivation) .where("location_deactivation_periods.deactivation_date <= ?", date) } - scope :deactivating_soon, lambda { |date = Time.zone.now| + scope :deactivating_soon_directly, lambda { |date = Time.zone.now| merge(LocationDeactivationPeriod.deactivations_without_reactivation) - .where("location_deactivation_periods.deactivation_date > ?", date) - .where.not(id: joins(scheme: [:owning_organisation]).deactivated_by_organisation.pluck(:id)) + .where("location_deactivation_periods.deactivation_date > ?", date) + } + + scope :deactivating_soon, lambda { |date = Time.zone.now| + deactivating_soon_directly + .or(deactivating_soon_by_scheme(date)) + } + + scope :deactivating_soon_by_scheme, lambda { |date = Time.zone.now| + merge(Scheme.deactivating_soon(date)) } scope :reactivating_soon, lambda { |date = Time.zone.now| where.not("location_deactivation_periods.reactivation_date IS NULL") - .where("location_deactivation_periods.reactivation_date > ?", date) - .where.not(id: joins(scheme: [:owning_organisation]).deactivated_by_organisation.pluck(:id)) + .where("location_deactivation_periods.reactivation_date > ?", date) + .where.not(id: joins(scheme: [:owning_organisation]).deactivated_by_organisation.pluck(:id)) + } + + scope :reactivating_soon_by_scheme, lambda { |date = Time.zone.now| + merge(Scheme.reactivating_soon(date)) } scope :activating_soon, lambda { |date = Time.zone.now| @@ -84,19 +102,21 @@ class Location < ApplicationRecord scope :active_status, lambda { where.not(id: joins(:location_deactivation_periods).reactivating_soon.pluck(:id)) - .where.not(id: joins(scheme: [:owning_organisation]).deactivated_by_organisation.pluck(:id)) - .where.not(id: joins(:location_deactivation_periods).deactivated_directly.pluck(:id)) - .where.not(id: incomplete.pluck(:id)) - .where.not(id: joins(:location_deactivation_periods).deactivating_soon.pluck(:id)) - .where.not(id: activating_soon.pluck(:id)) + .where.not(id: joins(scheme: [:scheme_deactivation_periods]).reactivating_soon_by_scheme.pluck(:id)) + .where.not(id: joins(:location_deactivation_periods).merge(Location.deactivated_directly).pluck(:id)) + .where.not(id: joins(scheme: [:scheme_deactivation_periods]).merge(Location.deactivated_by_scheme).pluck(:id)) + .where.not(id: joins(scheme: [:owning_organisation]).merge(Location.deactivated_by_organisation).pluck(:id)) + .where.not(id: incomplete.pluck(:id)) + .where.not(id: joins(:location_deactivation_periods).merge(Location.deactivating_soon_directly).pluck(:id)) + .where.not(id: joins(scheme: %i[owning_organisation scheme_deactivation_periods]).merge(Location.deactivating_soon_by_scheme).pluck(:id)) + .where.not(id: activating_soon.pluck(:id)) } scope :active, lambda { |date = Time.zone.now| - where.not(id: joins(:location_deactivation_periods).reactivating_soon(date).pluck(:id)) - .where.not(id: joins(scheme: [:owning_organisation]).deactivated_by_organisation.pluck(:id)) - .where.not(id: joins(:location_deactivation_periods).deactivated_directly(date).pluck(:id)) - .where.not(id: incomplete.pluck(:id)) + where.not(id: joins(:location_deactivation_periods).merge(Location.deactivated_directly(date)).pluck(:id)) + .where.not(id: incomplete.pluck(:id)) .where.not(id: activating_soon(date).pluck(:id)) + .where(scheme: Scheme.active(date)) } scope :visible, -> { where(discarded_at: nil) } @@ -163,10 +183,10 @@ class Location < ApplicationRecord return :deleted if discarded_at.present? return :incomplete unless confirmed return :deactivated if scheme.owning_organisation.status_at(date) == :deactivated || - open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date + open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date || scheme.status_at(date) == :deactivated + return :deactivating_soon if open_deactivation&.deactivation_date.present? && date < open_deactivation.deactivation_date || scheme.status_at(date) == :deactivating_soon return :activating_soon if startdate.present? && date < startdate - return :deactivating_soon if open_deactivation&.deactivation_date.present? && date < open_deactivation.deactivation_date - return :reactivating_soon if last_deactivation_before(date)&.reactivation_date.present? && date < last_deactivation_before(date).reactivation_date + return :reactivating_soon if last_deactivation_before(date)&.reactivation_date.present? && date < last_deactivation_before(date).reactivation_date || scheme.status_at(date) == :reactivating_soon :active end @@ -187,6 +207,18 @@ class Location < ApplicationRecord status_at(6.months.from_now) == :deactivating_soon end + def deactivated_by_scheme? + status == :deactivated && scheme.status == :deactivated + end + + def deactivating_soon_by_scheme? + status == :deactivating_soon && scheme.status == :deactivating_soon + end + + def reactivating_soon_by_scheme? + status == :reactivating_soon && scheme.status == :reactivating_soon + end + def validate_postcode if !postcode&.match(POSTCODE_REGEXP) error_message = I18n.t("validations.postcode") diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index e62b1d39d..24fa26bde 100644 --- a/app/models/merge_request.rb +++ b/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
#{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 diff --git a/app/models/merge_request_organisation.rb b/app/models/merge_request_organisation.rb index 08c1d6d45..6dda8b35e 100644 --- a/app/models/merge_request_organisation.rb +++ b/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 diff --git a/app/models/organisation.rb b/app/models/organisation.rb index 65b35c24e..e769f7b3d 100644 --- a/app/models/organisation.rb +++ b/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 diff --git a/app/models/scheme.rb b/app/models/scheme.rb index 07ec14731..6d3524723 100644 --- a/app/models/scheme.rb +++ b/app/models/scheme.rb @@ -61,25 +61,27 @@ class Scheme < ApplicationRecord merge(Organisation.filter_by_inactive) } - scope :deactivated_directly, lambda { + scope :deactivated_directly, lambda { |date = Time.zone.now| merge(SchemeDeactivationPeriod.deactivations_without_reactivation) - .where("scheme_deactivation_periods.deactivation_date <= ?", Time.zone.now) + .where("scheme_deactivation_periods.deactivation_date <= ?", date) } - scope :deactivating_soon, lambda { + scope :deactivating_soon, lambda { |date = Time.zone.now| merge(SchemeDeactivationPeriod.deactivations_without_reactivation) - .where("scheme_deactivation_periods.deactivation_date > ? AND scheme_deactivation_periods.deactivation_date < ? ", Time.zone.now, 6.months.from_now) + .where("scheme_deactivation_periods.deactivation_date > ? AND scheme_deactivation_periods.deactivation_date < ? ", date, 6.months.from_now) .where.not(id: joins(:owning_organisation).deactivated_by_organisation.pluck(:id)) } - scope :reactivating_soon, lambda { - where.not("scheme_deactivation_periods.reactivation_date IS NULL") - .where("scheme_deactivation_periods.reactivation_date > ?", Time.zone.now) - .where.not(id: joins(:owning_organisation).deactivated_by_organisation.pluck(:id)) + scope :reactivating_soon, lambda { |date = Time.zone.now| + merge(SchemeDeactivationPeriod.deactivations_with_reactivation) + .where.not("scheme_deactivation_periods.reactivation_date IS NULL") + .where("scheme_deactivation_periods.reactivation_date > ?", date) + .where("scheme_deactivation_periods.deactivation_date <= ?", date) + .where.not(id: joins(:owning_organisation).deactivated_by_organisation.pluck(:id)) } - scope :activating_soon, lambda { - where("schemes.startdate > ?", Time.zone.now) + scope :activating_soon, lambda { |date = Time.zone.now| + where("schemes.startdate > ?", date) } scope :active_status, lambda { @@ -91,6 +93,14 @@ class Scheme < ApplicationRecord .where.not(id: activating_soon.pluck(:id)) } + scope :active, lambda { |date = Time.zone.now| + where.not(id: joins(:scheme_deactivation_periods).reactivating_soon(date).pluck(:id)) + .where.not(id: incomplete.pluck(:id)) + .where.not(id: joins(:owning_organisation).deactivated_by_organisation.pluck(:id)) + .where.not(id: joins(:owning_organisation).joins(:scheme_deactivation_periods).deactivated_directly(date).pluck(:id)) + .where.not(id: activating_soon(date).pluck(:id)) + } + scope :visible, -> { where(discarded_at: nil) } validate :validate_confirmed @@ -275,6 +285,10 @@ class Scheme < ApplicationRecord scheme_deactivation_periods.where("deactivation_date <= ?", date).order("created_at").last end + def last_deactivation_date + scheme_deactivation_periods.order(deactivation_date: :desc).first&.deactivation_date + end + def status @status ||= status_at(Time.zone.now) end @@ -313,6 +327,10 @@ class Scheme < ApplicationRecord status == :deactivated end + def deactivating_soon? + status == :deactivating_soon + end + def deactivates_in_a_long_time? status_at(6.months.from_now) == :deactivating_soon end diff --git a/app/models/scheme_deactivation_period.rb b/app/models/scheme_deactivation_period.rb index 176d15211..cb27534f7 100644 --- a/app/models/scheme_deactivation_period.rb +++ b/app/models/scheme_deactivation_period.rb @@ -48,4 +48,5 @@ class SchemeDeactivationPeriod < ApplicationRecord attr_accessor :deactivation_date_type, :reactivation_date_type scope :deactivations_without_reactivation, -> { where(reactivation_date: nil) } + scope :deactivations_with_reactivation, -> { where.not(reactivation_date: nil) } end diff --git a/app/models/validations/sales/sale_information_validations.rb b/app/models/validations/sales/sale_information_validations.rb index 8bfa46783..c5febb693 100644 --- a/app/models/validations/sales/sale_information_validations.rb +++ b/app/models/validations/sales/sale_information_validations.rb @@ -299,7 +299,7 @@ module Validations::Sales::SaleInformationValidations return unless record.mortgage if over_tolerance?(record.mortgage_and_deposit_total, record.stairbought_part_of_value, 1) - %i[mortgage value deposit stairbought type].each do |field| + %i[mortgage value deposit stairbought].each do |field| record.errors.add field, I18n.t("validations.sale_information.staircasing_mortgage.mortgage_used", mortgage: record.field_formatted_as_currency("mortgage"), deposit: record.field_formatted_as_currency("deposit"), @@ -315,7 +315,7 @@ module Validations::Sales::SaleInformationValidations stairbought_part_of_value: record.field_formatted_as_currency("stairbought_part_of_value")).html_safe end elsif over_tolerance?(record.deposit, record.stairbought_part_of_value, 1) - %i[mortgageused value deposit stairbought type].each do |field| + %i[mortgageused value deposit stairbought].each do |field| record.errors.add field, I18n.t("validations.sale_information.staircasing_mortgage.mortgage_not_used", deposit: record.field_formatted_as_currency("deposit"), value: record.field_formatted_as_currency("value"), diff --git a/app/models/validations/shared_validations.rb b/app/models/validations/shared_validations.rb index ab9f9d7a8..217b2c170 100644 --- a/app/models/validations/shared_validations.rb +++ b/app/models/validations/shared_validations.rb @@ -102,7 +102,11 @@ module Validations::SharedValidations return unless %i[reactivating_soon activating_soon deactivated].include?(status) closest_reactivation = resource.last_deactivation_before(date) - open_deactivation = resource.open_deactivation + open_deactivation = if resource.is_a?(Location) + resource.open_deactivation || resource.scheme.open_deactivation + else + resource.open_deactivation + end date = case status when :reactivating_soon then closest_reactivation.reactivation_date diff --git a/app/services/bulk_upload/lettings/year2024/csv_parser.rb b/app/services/bulk_upload/lettings/year2024/csv_parser.rb index 061f3bbbd..d8d430755 100644 --- a/app/services/bulk_upload/lettings/year2024/csv_parser.rb +++ b/app/services/bulk_upload/lettings/year2024/csv_parser.rb @@ -30,12 +30,15 @@ class BulkUpload::Lettings::Year2024::CsvParser end def row_parsers - @row_parsers ||= body_rows.map do |row| + @row_parsers ||= body_rows.map { |row| + next if row.empty? + stripped_row = row[col_offset..] + hash = Hash[field_numbers.zip(stripped_row)] BulkUpload::Lettings::Year2024::RowParser.new(hash) - end + }.compact end def body_rows diff --git a/app/services/bulk_upload/lettings/year2024/row_parser.rb b/app/services/bulk_upload/lettings/year2024/row_parser.rb index 913e5d9e5..8fc913055 100644 --- a/app/services/bulk_upload/lettings/year2024/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2024/row_parser.rb @@ -513,6 +513,8 @@ class BulkUpload::Lettings::Year2024::RowParser end def log_already_exists? + return false if blank_row? + @log_already_exists ||= LettingsLog .where(status: %w[not_started in_progress completed]) .exists?(duplicate_check_fields.index_with { |field| log.public_send(field) }) diff --git a/app/services/bulk_upload/sales/year2024/csv_parser.rb b/app/services/bulk_upload/sales/year2024/csv_parser.rb index 2dc9d38a1..9ba99c19b 100644 --- a/app/services/bulk_upload/sales/year2024/csv_parser.rb +++ b/app/services/bulk_upload/sales/year2024/csv_parser.rb @@ -30,12 +30,14 @@ class BulkUpload::Sales::Year2024::CsvParser end def row_parsers - @row_parsers ||= body_rows.map do |row| + @row_parsers ||= body_rows.map { |row| + next if row.empty? + stripped_row = row[col_offset..] hash = Hash[field_numbers.zip(stripped_row)] BulkUpload::Sales::Year2024::RowParser.new(hash) - end + }.compact end def body_rows diff --git a/app/services/bulk_upload/sales/year2024/row_parser.rb b/app/services/bulk_upload/sales/year2024/row_parser.rb index d39715f30..8be08d62f 100644 --- a/app/services/bulk_upload/sales/year2024/row_parser.rb +++ b/app/services/bulk_upload/sales/year2024/row_parser.rb @@ -539,6 +539,8 @@ class BulkUpload::Sales::Year2024::RowParser end def log_already_exists? + return false if blank_row? + @log_already_exists ||= SalesLog .where(status: %w[not_started in_progress completed]) .exists?(duplicate_check_fields.index_with { |field| log.public_send(field) }) diff --git a/app/views/bulk_upload_shared/guidance.html.erb b/app/views/bulk_upload_shared/guidance.html.erb index c32295b13..e530aa5b5 100644 --- a/app/views/bulk_upload_shared/guidance.html.erb +++ b/app/views/bulk_upload_shared/guidance.html.erb @@ -81,8 +81,8 @@ <%= accordion.with_section(heading_text: "Next steps") do %>

Once you've saved your CSV file, you can upload it via a button at the top of the lettings and sales logs pages.

When your file is done processing, you will receive an email explaining your next steps. If all your data is valid, your logs will be created. If some data is invalid, you’ll receive an email with instructions about how to resolve the errors.

-

If your file has errors on fields 1 through 17, you must fix these in the CSV. This is because we need to know these answers to validate the rest of the data. Any errors in these fields will be featured in the error report’s summary tab.

-

If none of your errors are in fields 1 through 17, you can choose how to fix the errors. You can either fix them in the CSV and reupload, or create partially complete logs and answer the remaining questions on the CORE site. Any errors that affect a significant number of logs will be featured in the error report’s summary tab to help you decide.

+

If your file has errors on fields 1 through 15 for lettings, or 1 through 18 for sales, you must fix these in the CSV. This is because we need to know these answers to validate the rest of the data. Any errors in these fields will be featured in the error report’s summary tab.

+

If none of your errors are in fields 1 through 15 for lettings, or 1 through 18 for sales, you can choose how to fix the errors. You can either fix them in the CSV and reupload, or create partially complete logs and answer the remaining questions on the CORE site. Any errors that affect a significant number of logs will be featured in the error report’s summary tab to help you decide.

<% end %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 1ed5014c3..9e68fa7b4 100644 --- a/app/views/layouts/application.html.erb +++ b/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] diff --git a/app/views/locations/index.html.erb b/app/views/locations/index.html.erb index 78a362332..64d9bf286 100644 --- a/app/views/locations/index.html.erb +++ b/app/views/locations/index.html.erb @@ -56,10 +56,17 @@ <% end %> <% end %> + <% if status_hint_message = scheme_status_hint(@scheme) %> +
+ <%= status_hint_message %> +
+
+ <% end %> + <% if LocationPolicy.new(current_user, @scheme.locations.new).create? %> - <%= govuk_button_to "Add a location", scheme_locations_path(@scheme), method: "post", secondary: true %> + <%= govuk_button_to "Add a location", scheme_locations_path(@scheme), method: "post" %> <% end %> +
+ <%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "locations" } %> - -<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "locations" } %> diff --git a/app/views/merge_requests/_details_list.html.erb b/app/views/merge_requests/_details_list.html.erb new file mode 100644 index 000000000..e93278b08 --- /dev/null +++ b/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? %> +
+ <%= raw(detail[:value]) %> +
+ <% elsif detail[:value].is_a?(Date) || detail[:value].is_a?(Time) %> +
+ <%= detail[:value].strftime("%d %B %Y") %> +
+ <% 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 %> diff --git a/app/views/merge_requests/_merge_request_list.html.erb b/app/views/merge_requests/_merge_request_list.html.erb new file mode 100644 index 000000000..e73a1aa02 --- /dev/null +++ b/app/views/merge_requests/_merge_request_list.html.erb @@ -0,0 +1,46 @@ +
+ <% if @merge_requests.empty? %> +

No merge requests

+ <% else %> + <%= govuk_table do |table| %> + <%= table.with_caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do %> + <%= @merge_requests.not_merged.count %> 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 %> +
diff --git a/app/views/merge_requests/_notification_banners.html.erb b/app/views/merge_requests/_notification_banners.html.erb new file mode 100644 index 000000000..38c05dbcd --- /dev/null +++ b/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 %> +

+ The absorbing organisation must accept the Data Sharing Agreement before merging. +

+ <% 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 %> +

+ An error occurred while processing the merge. +

+ No changes have been made. Try beginning the merge again. + <% end %> +<% end %> diff --git a/app/views/merge_requests/_summary_card.html.erb b/app/views/merge_requests/_summary_card.html.erb new file mode 100644 index 000000000..4a372f0a0 --- /dev/null +++ b/app/views/merge_requests/_summary_card.html.erb @@ -0,0 +1,8 @@ +
+
+

<%= title %>

+
+
+ <%= render partial: "merge_requests/details_list", locals: { details: } %> +
+
diff --git a/app/views/merge_requests/absorbing_organisation.html.erb b/app/views/merge_requests/absorbing_organisation.html.erb index e63154bac..804e9f03b 100644 --- a/app/views/merge_requests/absorbing_organisation.html.erb +++ b/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 %> -

Which organisation is absorbing the others?

+

Which organisation is absorbing the others?

-

Select the organisation that the other organisations are merging into.

- - <%= 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 }, - ) %> +

If organisations are merging into a new organisation, <%= govuk_link_to "create the new organisation", new_organisation_path %> first and then select it here.

+
+ <%= 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| %> + + <% 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 %> +
+ <%= 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 %>
diff --git a/app/views/merge_requests/confirm_telephone_number.html.erb b/app/views/merge_requests/confirm_telephone_number.html.erb deleted file mode 100644 index 8b3a354b4..000000000 --- a/app/views/merge_requests/confirm_telephone_number.html.erb +++ /dev/null @@ -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 %> - -

What is <%= @merge_request.absorbing_organisation.name %>'s telephone number?

- -
-
- <% if @merge_request.absorbing_organisation.phone.present? %> -

Confirm the telephone number on file, or enter a new one.

-
- <%= @merge_request.absorbing_organisation.phone %> -
- <% 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 %> -
-
diff --git a/app/views/merge_requests/delete_confirmation.html.erb b/app/views/merge_requests/delete_confirmation.html.erb new file mode 100644 index 000000000..d95ebba63 --- /dev/null +++ b/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 %> + +
+
+

+ <%= content_for(:title) %> +

+ + <%= govuk_warning_text(text: "You will not be able to undo this action.") %> + +
+ <%= 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 %> +
+
+
diff --git a/app/views/merge_requests/existing_absorbing_organisation.html.erb b/app/views/merge_requests/existing_absorbing_organisation.html.erb new file mode 100644 index 000000000..ce6628a35 --- /dev/null +++ b/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 %> +
+
+ <%= 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" %> +
+ <%= 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 %> +
+
diff --git a/app/views/merge_requests/helpdesk_ticket.html.erb b/app/views/merge_requests/helpdesk_ticket.html.erb new file mode 100644 index 000000000..4ebd11395 --- /dev/null +++ b/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 %> + +

Which helpdesk ticket reported this merge?

+
+
+

If this merge was reported via a helpdesk ticket, provide the ticket number.
The ticket will be linked to the merge request for reference.

+
+
+
+ <%= 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" %> +
+ <%= 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)) %> +
+
+
+ <% end %> +
+
diff --git a/app/views/merge_requests/logs_outcomes.html.erb b/app/views/merge_requests/logs_outcomes.html.erb new file mode 100644 index 000000000..5b9d32359 --- /dev/null +++ b/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 %> + +

+ <%= @merge_request.absorbing_organisation_name %> + Logs +

+
+
+ <% unless @merge_request.status == "request_merged" || @merge_request.status == "processing" %> +

<%= total_lettings_logs_after_merge_text(@merge_request) %>

+

+ <%= merging_organisations_lettings_logs_outcomes_text(@merge_request) %> +

+ +
+ +

<%= total_sales_logs_after_merge_text(@merge_request) %>

+

+ <%= merging_organisations_sales_logs_outcomes_text(@merge_request) %> +

+ <% end %> +
+
diff --git a/app/views/merge_requests/merge_date.html.erb b/app/views/merge_requests/merge_date.html.erb index 35d9ef36d..637426446 100644 --- a/app/views/merge_requests/merge_date.html.erb +++ b/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 %> +
+
+ <%= form_with model: @merge_request, url: submit_merge_request_url(request.query_parameters["referrer"]), method: :patch do |f| %> + <%= f.govuk_error_summary %> -

What is the merge date?

+

What is the merge date?

+

+ Enter the official merge date. Log and organisation page data will show the new organisation name from this date.

+ For example, <%= date_mid_collection_year_formatted(Time.zone.now) %>

+ <%= f.govuk_date_field :merge_date, + legend: { hidden: true }, + width: 20 do %> + <% end %> + <%= f.hidden_field :page, value: "merge_date" %> +
+ <%= 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 %> +
+
diff --git a/app/views/organisations/merge_request.html.erb b/app/views/merge_requests/merge_request.html.erb similarity index 100% rename from app/views/organisations/merge_request.html.erb rename to app/views/merge_requests/merge_request.html.erb diff --git a/app/views/merge_requests/merge_start_confirmation.html.erb b/app/views/merge_requests/merge_start_confirmation.html.erb new file mode 100644 index 000000000..c85223f3a --- /dev/null +++ b/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 %> + +
+
+

+ <%= content_for(:title) %> +

+ + <%= govuk_warning_text(text: "You will not be able to undo this action.") %> + +
+ <%= 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, + ) %> +
+
+
diff --git a/app/views/merge_requests/merging_organisations.html.erb b/app/views/merge_requests/merging_organisations.html.erb new file mode 100644 index 000000000..02df05032 --- /dev/null +++ b/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 %> +

Which organisations are merging into <%= @merge_request.absorbing_organisation&.name %>?

+ +
+
+

Add all organisations that are merging.

+
+ <%= 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 %> +
+ <% 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 %> +
+ <% end %> +
+
diff --git a/app/views/merge_requests/new_organisation_address.html.erb b/app/views/merge_requests/new_organisation_address.html.erb deleted file mode 100644 index 7dd331507..000000000 --- a/app/views/merge_requests/new_organisation_address.html.erb +++ /dev/null @@ -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 %> - -

What is <%= @merge_request.new_organisation_name.possessive %> address?

-
-
- <%= 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" %> -
- <%= f.govuk_submit %> - <%= govuk_link_to("Skip for now", new_organisation_telephone_number_merge_request_path(@merge_request)) %> -
-
-
-<% end %> diff --git a/app/views/merge_requests/new_organisation_name.html.erb b/app/views/merge_requests/new_organisation_name.html.erb deleted file mode 100644 index 8b391e735..000000000 --- a/app/views/merge_requests/new_organisation_name.html.erb +++ /dev/null @@ -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 %> - -

What is the new organisation called?

- -
-
- <%= f.govuk_text_field :new_organisation_name, label: nil %> - <%= f.hidden_field :page, value: "new_organisation_name" %> - <%= f.govuk_submit %> - <% end %> -
-
diff --git a/app/views/merge_requests/new_organisation_telephone_number.html.erb b/app/views/merge_requests/new_organisation_telephone_number.html.erb deleted file mode 100644 index 6c0c1d81e..000000000 --- a/app/views/merge_requests/new_organisation_telephone_number.html.erb +++ /dev/null @@ -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 %> - -

What is <%= @merge_request.new_organisation_name.possessive %> telephone number?

-
-
- <%= f.govuk_text_field :new_organisation_telephone_number, label: nil, width: "two-thirds" %> - <%= f.hidden_field :page, value: "new_organisation_telephone_number" %> -
- <%= f.govuk_submit %> -
-
-
-<% end %> diff --git a/app/views/merge_requests/new_organisation_type.html.erb b/app/views/merge_requests/new_organisation_type.html.erb deleted file mode 100644 index 2e22071e7..000000000 --- a/app/views/merge_requests/new_organisation_type.html.erb +++ /dev/null @@ -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 %> diff --git a/app/views/merge_requests/organisations.html.erb b/app/views/merge_requests/organisations.html.erb deleted file mode 100644 index 8a47a8641..000000000 --- a/app/views/merge_requests/organisations.html.erb +++ /dev/null @@ -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 %> -

Which organisations are merging?

- -
-
-

- Add all organisations to be merged - we have already added your own. -

- -

Start typing to search

- <%= 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 %> -
diff --git a/app/views/merge_requests/relationship_outcomes.html.erb b/app/views/merge_requests/relationship_outcomes.html.erb new file mode 100644 index 000000000..6d1eba0d7 --- /dev/null +++ b/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 %> + +

+ <%= @merge_request.absorbing_organisation_name %> + Stock owners & managing agents +

+
+
+ <% unless @merge_request.status == "request_merged" || @merge_request.status == "processing" %> +

<%= total_stock_owners_after_merge_text(@merge_request) %>

+

+ <%= stock_owners_text(@merge_request) %> +

+
+

<%= total_managing_agents_after_merge_text(@merge_request) %>

+

+ <%= managing_agent_text(@merge_request) %> +

+ <% end %> +
+
diff --git a/app/views/merge_requests/scheme_outcomes.html.erb b/app/views/merge_requests/scheme_outcomes.html.erb new file mode 100644 index 000000000..ff8a8ffd3 --- /dev/null +++ b/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 %> + +

+ <%= @merge_request.absorbing_organisation_name %> + Schemes +

+ +<% unless @merge_request.status == "request_merged" || @merge_request.status == "processing" %> +

<%= total_schemes_after_merge_text(@merge_request) %>

+

+ <%= merging_organisations_without_schemes_text(@merge_request.organisations_without_schemes) %> +

+ + <% @merge_request.organisations_with_schemes.map do |org| %> +

+ <%= link_to_merging_organisation_schemes(org) %> +

+ <% end %> +<% end %> diff --git a/app/views/merge_requests/show.html.erb b/app/views/merge_requests/show.html.erb new file mode 100644 index 000000000..0fbde7621 --- /dev/null +++ b/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" %> + +

+ Merge request + <%= display_value_or_placeholder(@merge_request.absorbing_organisation_name) %> +

+<% unless @merge_request.status == "request_merged" || @merge_request.status == "processing" %> +
+ <%= 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 %> +
+<% 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 %> diff --git a/app/views/merge_requests/user_outcomes.html.erb b/app/views/merge_requests/user_outcomes.html.erb new file mode 100644 index 000000000..411b78216 --- /dev/null +++ b/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 %> + +

+ <%= @merge_request.absorbing_organisation_name %> + Users +

+ +<% unless @merge_request.status == "request_merged" || @merge_request.status == "processing" %> +

<%= total_users_after_merge_text(@merge_request) %>

+

+ <%= merging_organisations_without_users_text(@merge_request.organisations_without_users) %> +

+ + <% @merge_request.organisations_with_users.map do |org| %> +

+ <%= link_to_merging_organisation_users(org) %> +

+ <% end %> +<% end %> diff --git a/app/views/organisation_relationships/_related_organisation_select_question.html.erb b/app/views/organisation_relationships/_related_organisation_select_question.html.erb index 551bce5ed..f0d1aa2eb 100644 --- a/app/views/organisation_relationships/_related_organisation_select_question.html.erb +++ b/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 %> diff --git a/app/views/organisation_relationships/add_managing_agent.html.erb b/app/views/organisation_relationships/add_managing_agent.html.erb index b3e3a4a42..25c7c53a2 100644 --- a/app/views/organisation_relationships/add_managing_agent.html.erb +++ b/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:, } %> diff --git a/app/views/organisation_relationships/add_stock_owner.html.erb b/app/views/organisation_relationships/add_stock_owner.html.erb index b74d812ec..042442125 100644 --- a/app/views/organisation_relationships/add_stock_owner.html.erb +++ b/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:, } %> diff --git a/app/views/organisations/index.html.erb b/app/views/organisations/index.html.erb index 72d1d2f43..3b96288f1 100644 --- a/app/views/organisations/index.html.erb +++ b/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" } %> +
+ <%= 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 %> +
diff --git a/app/views/schemes/deactivate_confirm.html.erb b/app/views/schemes/deactivate_confirm.html.erb index b5dda160e..a73208e75 100644 --- a/app/views/schemes/deactivate_confirm.html.erb +++ b/app/views/schemes/deactivate_confirm.html.erb @@ -2,16 +2,35 @@ <% content_for :before_content do %> <%= govuk_back_link(href: :back) %> <% end %> -

- <%= @scheme.service_name %> - This change will affect <%= @affected_logs.count %> logs -

- <%= govuk_warning_text text: I18n.t("warnings.scheme.deactivate.review_logs") %> - <%= f.hidden_field :confirm, value: true %> - <%= f.hidden_field :deactivation_date, value: @deactivation_date %> - <%= f.hidden_field :deactivation_date_type, value: @deactivation_date_type %> -
- <%= f.govuk_submit "Deactivate this scheme" %> - <%= govuk_button_link_to "Cancel", scheme_details_path(@scheme), html: { method: :get }, secondary: true %> +
+
+ +

+ <%= @scheme.service_name %> + <%= affected_title(@affected_logs, @affected_locations) %> +

+ + <% if @affected_logs.count > 0 %> +

+ <%= pluralize(@affected_logs.count, "existing log") %> using this scheme <%= @affected_logs.count == 1 ? "has" : "have" %> a tenancy start date after <%= @deactivation_date.to_formatted_s(:govuk_date) %>. +

+ <%= govuk_warning_text text: I18n.t("warnings.scheme.deactivate.review_logs"), html_attributes: { class: "" } %> + <% end %> + + <% if @affected_locations.count > 0 %> +

+ This scheme has <%= pluralize(@affected_locations.count, "location") %> active on <%= @deactivation_date.to_formatted_s(:govuk_date) %>. <%= @affected_locations.count == 1 ? "This location" : "These locations" %> will deactivate on that date. If the scheme is ever reactivated, <%= @affected_locations.count == 1 ? "this location" : "these locations" %> will reactivate as well. +

+
+ <% end %> + + <%= f.hidden_field :confirm, value: true %> + <%= f.hidden_field :deactivation_date, value: @deactivation_date %> + <%= f.hidden_field :deactivation_date_type, value: @deactivation_date_type %> +
+ <%= f.govuk_submit "Deactivate this scheme" %> + <%= govuk_button_link_to "Cancel", scheme_details_path(@scheme), html: { method: :get }, secondary: true %> +
+
<% end %> diff --git a/app/views/schemes/toggle_active.html.erb b/app/views/schemes/toggle_active.html.erb index b23c9591c..3e2f1d4a8 100644 --- a/app/views/schemes/toggle_active.html.erb +++ b/app/views/schemes/toggle_active.html.erb @@ -3,7 +3,7 @@ <% content_for :before_content do %> <%= govuk_back_link( - href: scheme_details_path(@scheme), + href: scheme_path(@scheme), ) %> <% end %> @@ -12,11 +12,12 @@
<% start_date = FormHandler.instance.earliest_open_for_editing_collection_start_date %> <%= f.govuk_error_summary %> + <%= title %> +

<%= I18n.t("questions.scheme.toggle_active.apply_from") %>

+ <%= govuk_warning_text text: I18n.t("warnings.scheme.#{action}.existing_logs") %> +

<%= I18n.t("hints.scheme.toggle_active", date: start_date.to_formatted_s(:govuk_date)) %>

<%= f.govuk_radio_buttons_fieldset date_type_question(action), - legend: { text: I18n.t("questions.scheme.toggle_active.apply_from") }, - caption: { text: title }, - hint: { text: I18n.t("hints.scheme.toggle_active", date: start_date.to_formatted_s(:govuk_date)) } do %> - <%= govuk_warning_text text: I18n.t("warnings.scheme.#{action}.existing_logs") %> + legend: { text: I18n.t("questions.scheme.toggle_active.apply_from"), hidden: true } do %> <%= f.govuk_radio_button date_type_question(action), "default", label: { text: "From the start of the open collection period (#{start_date.to_formatted_s(:govuk_date)})" } %> @@ -26,7 +27,7 @@ **basic_conditional_html_attributes({ "deactivation_date" => %w[other] }, "scheme") do %> <%= f.govuk_date_field date_question(action), legend: { text: "Date", size: "m" }, - hint: { text: "For example, 27 3 2022" }, + hint: { text: "For example, 27 3 2025" }, width: 20 %> <% end %> <% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 66d7d2531..408e1e04b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -180,17 +180,12 @@ en: merge_request: attributes: absorbing_organisation_id: - blank: "Select the organisation absorbing the others" - telephone_number_correct: - blank: "Select to confirm or enter a new telephone number" - invalid: "Enter a valid telephone number" - new_telephone_number: - blank: "Enter a valid telephone number" - new_organisation_name: - blank: "Enter an organisation name" - invalid: "An organisation with this name already exists" - new_organisation_telephone_number: - blank: "Enter a valid telephone number" + blank: "Select the absorbing organisation" + merge_date: + blank: "Enter a merge date" + invalid: "Enter a valid merge date" + existing_absorbing_organisation: + blank: "You must answer absorbing organisation already active?" notification: attributes: title: diff --git a/config/routes.rb b/config/routes.rb index 73a7239ca..c0b204f02 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -206,16 +206,21 @@ Rails.application.routes.draw do resources :merge_requests, path: "/merge-request" do member do - get "organisations" - patch "organisations", to: "merge_requests#update_organisations" - get "organisations/remove", to: "merge_requests#remove_merging_organisation" + get "merging-organisations" + patch "merging-organisations", to: "merge_requests#update_merging_organisations" + get "merging-organisations/remove", to: "merge_requests#remove_merging_organisation" get "absorbing-organisation" - get "confirm-telephone-number" - get "new-organisation-name" - get "new-organisation-address" - get "new-organisation-telephone-number" - get "new-organisation-type" get "merge-date" + get "existing-absorbing-organisation" + get "helpdesk-ticket" + get "merge-start-confirmation" + get "user-outcomes" + get "relationship-outcomes" + get "scheme-outcomes" + get "logs-outcomes" + get "delete-confirmation", to: "merge_requests#delete_confirmation" + delete "delete", to: "merge_requests#delete" + patch "start-merge", to: "merge_requests#start_merge" end end diff --git a/db/migrate/20240808134014_add_merge_date_to_merge_requests.rb b/db/migrate/20240808134014_add_merge_date_to_merge_requests.rb new file mode 100644 index 000000000..80a2c5650 --- /dev/null +++ b/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 diff --git a/db/migrate/20240809154241_add_additional_fields_to_merge_requests.rb b/db/migrate/20240809154241_add_additional_fields_to_merge_requests.rb new file mode 100644 index 000000000..6f2a37fd0 --- /dev/null +++ b/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 diff --git a/db/migrate/20240813072041_remove_other_merging_org_field.rb b/db/migrate/20240813072041_remove_other_merging_org_field.rb new file mode 100644 index 000000000..eb1ddc2e4 --- /dev/null +++ b/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 diff --git a/db/migrate/20240813112119_remove_new_org_merge_request_fields.rb b/db/migrate/20240813112119_remove_new_org_merge_request_fields.rb new file mode 100644 index 000000000..c7050b54e --- /dev/null +++ b/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 diff --git a/db/migrate/20240814083017_add_last_failed_attempt.rb b/db/migrate/20240814083017_add_last_failed_attempt.rb new file mode 100644 index 000000000..34e0e4046 --- /dev/null +++ b/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 diff --git a/db/migrate/20240819100411_update_merge_request_fields_for_status.rb b/db/migrate/20240819100411_update_merge_request_fields_for_status.rb new file mode 100644 index 000000000..6613d27a4 --- /dev/null +++ b/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 diff --git a/db/migrate/20240822080228_add_existing_absorbing_organisation_field.rb b/db/migrate/20240822080228_add_existing_absorbing_organisation_field.rb new file mode 100644 index 000000000..f1a0d8b1e --- /dev/null +++ b/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 diff --git a/db/schema.rb b/db/schema.rb index ce07c8570..2c06732f4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -429,19 +429,24 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_04_135306) do create_table "merge_requests", force: :cascade do |t| t.integer "requesting_organisation_id" - t.text "other_merging_organisations" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "status" t.integer "absorbing_organisation_id" - t.boolean "new_absorbing_organisation" - t.boolean "telephone_number_correct" - t.string "new_telephone_number" - t.string "new_organisation_name" - t.string "new_organisation_address_line1" - t.string "new_organisation_address_line2" - t.string "new_organisation_postcode" - t.string "new_organisation_telephone_number" + t.datetime "merge_date" + t.integer "requester_id" + t.string "helpdesk_ticket" + t.integer "total_users" + t.integer "total_schemes" + t.integer "total_lettings_logs" + t.integer "total_sales_logs" + t.integer "total_stock_owners" + t.integer "total_managing_agents" + t.boolean "signed_dsa", default: false + t.datetime "discarded_at" + t.datetime "last_failed_attempt" + t.boolean "request_merged" + t.boolean "processing" + t.boolean "existing_absorbing_organisation" end create_table "notifications", force: :cascade do |t| diff --git a/db/seeds.rb b/db/seeds.rb index 2dd8570bc..b58f7e0a8 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -107,7 +107,7 @@ unless Rails.env.test? role: "data_provider", is_dpo: true, ) do |user| - user.password = "password" + user.password = ENV["REVIEW_APP_USER_PASSWORD"] user.confirmed_at = Time.zone.now create_data_protection_confirmation(user) end @@ -119,7 +119,7 @@ unless Rails.env.test? role: "data_coordinator", is_dpo: true, ) do |user| - user.password = "password" + user.password = ENV["REVIEW_APP_USER_PASSWORD"] user.confirmed_at = Time.zone.now create_data_protection_confirmation(user) end @@ -131,7 +131,7 @@ unless Rails.env.test? role: "data_provider", is_dpo: true, ) do |user| - user.password = "password" + user.password = ENV["REVIEW_APP_USER_PASSWORD"] user.confirmed_at = Time.zone.now create_data_protection_confirmation(user) end @@ -142,7 +142,7 @@ unless Rails.env.test? organisation: standalone_no_stock, role: "data_coordinator", ) do |user| - user.password = "password" + user.password = ENV["REVIEW_APP_USER_PASSWORD"] user.confirmed_at = Time.zone.now end @@ -153,7 +153,7 @@ unless Rails.env.test? role: "data_coordinator", is_dpo: true, ) do |user| - user.password = "password" + user.password = ENV["REVIEW_APP_USER_PASSWORD"] user.confirmed_at = Time.zone.now create_data_protection_confirmation(user) end @@ -165,7 +165,7 @@ unless Rails.env.test? role: "data_coordinator", is_dpo: true, ) do |user| - user.password = "password" + user.password = ENV["REVIEW_APP_USER_PASSWORD"] user.confirmed_at = Time.zone.now create_data_protection_confirmation(user) end @@ -177,7 +177,7 @@ unless Rails.env.test? role: "data_coordinator", is_dpo: true, ) do |user| - user.password = "password" + user.password = ENV["REVIEW_APP_USER_PASSWORD"] user.confirmed_at = Time.zone.now create_data_protection_confirmation(user) end @@ -189,7 +189,7 @@ unless Rails.env.test? role: "data_coordinator", is_dpo: true, ) do |user| - user.password = "password" + user.password = ENV["REVIEW_APP_USER_PASSWORD"] user.confirmed_at = Time.zone.now create_data_protection_confirmation(user) end @@ -218,7 +218,7 @@ unless Rails.env.test? organisation: org, role: "data_provider", ) do |user| - user.password = "password" + user.password = ENV["REVIEW_APP_USER_PASSWORD"] user.confirmed_at = Time.zone.now end @@ -229,7 +229,7 @@ unless Rails.env.test? role: "data_coordinator", is_dpo: true, ) do |user| - user.password = "password" + user.password = ENV["REVIEW_APP_USER_PASSWORD"] user.confirmed_at = Time.zone.now user.is_dpo = true create_data_protection_confirmation(user) @@ -242,7 +242,7 @@ unless Rails.env.test? role: "support", is_dpo: true, ) do |user| - user.password = "password" + user.password = ENV["REVIEW_APP_USER_PASSWORD"] user.confirmed_at = Time.zone.now create_data_protection_confirmation(user) end @@ -310,7 +310,7 @@ unless Rails.env.test? role: "data_provider", is_dpo: true, ) do |user| - user.password = "password" + user.password = ENV["REVIEW_APP_USER_PASSWORD"] user.confirmed_at = Time.zone.now create_data_protection_confirmation(user) end diff --git a/spec/factories/merge_request.rb b/spec/factories/merge_request.rb new file mode 100644 index 000000000..19020fce1 --- /dev/null +++ b/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 diff --git a/spec/factories/merge_request_organisation.rb b/spec/factories/merge_request_organisation.rb new file mode 100644 index 000000000..178401fb0 --- /dev/null +++ b/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 diff --git a/spec/features/schemes_spec.rb b/spec/features/schemes_spec.rb index 2d43e9b02..0706a0d7e 100644 --- a/spec/features/schemes_spec.rb +++ b/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 diff --git a/spec/fixtures/files/locations_csv_export.csv b/spec/fixtures/files/locations_csv_export.csv index 51844b6c5..5b80ae024 100644 --- a/spec/fixtures/files/locations_csv_export.csv +++ b/spec/fixtures/files/locations_csv_export.csv @@ -1,2 +1,2 @@ scheme_code,location_code,location_postcode,location_name,location_status,location_local_authority,location_units,location_type_of_unit,location_mobility_type,location_active_dates -,,SW1A 2AA,Downing Street,deactivating_soon,Westminster,20,Self-contained house,Fitted with equipment and adaptations,"Active from 1 April 2022 to 25 December 2023, Deactivated on 26 December 2023" +,,SW1A 2AA,Downing Street,deactivating_soon,Westminster,20,Self-contained house,Fitted with equipment and adaptations,"Active from 1 April 2023 to 25 December 2023, Deactivated on 26 December 2023" diff --git a/spec/fixtures/files/schemes_and_locations_csv_export.csv b/spec/fixtures/files/schemes_and_locations_csv_export.csv index 79c9132f6..d662dceb3 100644 --- a/spec/fixtures/files/schemes_and_locations_csv_export.csv +++ b/spec/fixtures/files/schemes_and_locations_csv_export.csv @@ -1,2 +1,2 @@ scheme_code,scheme_service_name,scheme_status,scheme_confidential,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_support_services_provided_by,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,scheme_active_dates,location_code,location_postcode,location_name,location_status,location_local_authority,location_units,location_type_of_unit,location_mobility_type,location_active_dates -,Test name,active,Yes,Housing for older people,No,MHCLG,The same organisation that owns the housing stock,People with alcohol problems,Yes,Older people with support needs,High level,Medium stay,2021-04-01T00:00:00+01:00,"Active from 1 April 2020 to 31 March 2022, Deactivated on 1 April 2022, Active from 1 April 2023",,SW1A 2AA,Downing Street,deactivating_soon,Westminster,20,Self-contained house,Fitted with equipment and adaptations,"Active from 1 April 2022 to 25 December 2023, Deactivated on 26 December 2023" +,Test name,active,Yes,Housing for older people,No,MHCLG,The same organisation that owns the housing stock,People with alcohol problems,Yes,Older people with support needs,High level,Medium stay,2021-04-01T00:00:00+01:00,"Active from 1 April 2020 to 31 March 2022, Deactivated on 1 April 2022, Active from 1 April 2023",,SW1A 2AA,Downing Street,deactivating_soon,Westminster,20,Self-contained house,Fitted with equipment and adaptations,"Active from 1 April 2023 to 25 December 2023, Deactivated on 26 December 2023" diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb new file mode 100644 index 000000000..0db07e02b --- /dev/null +++ b/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.

") + 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.

") + 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("View all 2 Dummy Org 1 stock owners (opens in a new tab)") + 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("View the 1 Dummy Org 1 managing agent (opens in a new tab)") + 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 diff --git a/spec/jobs/process_merge_request_job_spec.rb b/spec/jobs/process_merge_request_job_spec.rb new file mode 100644 index 000000000..72ccbdf26 --- /dev/null +++ b/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 diff --git a/spec/models/location_spec.rb b/spec/models/location_spec.rb index 2f991ab31..4856c5662 100644 --- a/spec/models/location_spec.rb +++ b/spec/models/location_spec.rb @@ -930,6 +930,39 @@ RSpec.describe Location, type: :model do expect(location.status).to eq(:activating_soon) end end + + context "when the scheme the location belongs to is deactivated" do + before do + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.yesterday, scheme: location.scheme) + location.save! + end + + it "returns deactivated" do + expect(location.status).to eq(:deactivated) + end + end + + context "when the scheme the location belongs to is deactivating soon" do + before do + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.today + 1.month, scheme: location.scheme) + location.save! + end + + it "returns deactivating soon" do + expect(location.status).to eq(:deactivating_soon) + end + end + + context "when the scheme the location belongs to is reactivating soon" do + before do + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.yesterday, reactivation_date: Time.zone.tomorrow, scheme: location.scheme) + location.save! + end + + it "returns reactivating soon" do + expect(location.status).to eq(:reactivating_soon) + end + end end describe "status_at" do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb new file mode 100644 index 000000000..0228f71a7 --- /dev/null +++ b/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 diff --git a/spec/requests/merge_request_spec.rb b/spec/requests/merge_request_spec.rb new file mode 100644 index 000000000..c7302b632 --- /dev/null +++ b/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 diff --git a/spec/requests/merge_requests_controller_spec.rb b/spec/requests/merge_requests_controller_spec.rb index 6f7b64f16..dc1dd817d 100644 --- a/spec/requests/merge_requests_controller_spec.rb +++ b/spec/requests/merge_requests_controller_spec.rb @@ -10,115 +10,88 @@ RSpec.describe MergeRequestsController, type: :request do let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation) } let(:other_merge_request) { MergeRequest.create!(requesting_organisation: other_organisation) } - context "when user is signed in with a data coordinator user" do - before { sign_in user } - - describe "#organisations" do - let(:params) { { merge_request: { requesting_organisation_id: "9", status: "unsubmitted" } } } - - context "when creating a new merge request" do - before do - post "/merge-request", headers:, params: - end + context "when user is signed in with a support user" do + before do + allow(support_user).to receive(:need_two_factor_authentication?).and_return(false) + sign_in support_user + end - it "creates merge request with requesting organisation" do - follow_redirect! - expect(page).to have_content("Which organisations are merging?") - expect(page).to have_content(organisation.name) - expect(page).not_to have_link("Remove") - end + context "when creating a new merge request" do + let(:params) { { merge_request: { requesting_organisation_id: support_user.organisation_id } } } - context "when passing a different requesting organisation id" do - let(:params) { { merge_request: { requesting_organisation_id: other_organisation.id, status: "unsubmitted" } } } + before do + post "/merge-request", headers:, params: + end - it "creates merge request with current user organisation" do - follow_redirect! - expect(MergeRequest.count).to eq(1) - expect(MergeRequest.first.requesting_organisation_id).to eq(organisation.id) - expect(MergeRequest.first.merging_organisations.count).to eq(1) - expect(MergeRequest.first.merging_organisations.first.id).to eq(organisation.id) - end - end + it "creates merge request with requesting organisation" do + follow_redirect! + expect(page).to have_content("Which organisation is absorbing the others?") + expect(MergeRequest.first.requesting_organisation_id).to eq(support_user.organisation_id) end - context "when viewing existing merge request" do - before do - get "/merge-request/#{merge_request.id}/organisations", headers:, params: - end + context "when passing a different requesting organisation id" do + let(:params) { { merge_request: { requesting_organisation_id: other_organisation.id } } } - it "shows merge request with requesting organisation" do - expect(page).to have_content("Which organisations are merging?") - expect(page).to have_content(organisation.name) + it "creates merge request with current user organisation" do + follow_redirect! + expect(MergeRequest.count).to eq(1) + expect(MergeRequest.first.requesting_organisation_id).to eq(support_user.organisation_id) + expect(MergeRequest.first.merging_organisations.count).to eq(0) end end + end - context "when viewing existing merge request of a different (unauthorised) organisation" do + describe "#merging-organisations" do + context "when viewing merging organisations page" do before do - get "/merge-request/#{other_merge_request.id}/organisations", headers:, params: + merge_request.update!(absorbing_organisation_id: organisation.id) + get "/merge-request/#{merge_request.id}/merging-organisations", headers: end - it "shows merge request with requesting organisation" do - expect(response).to have_http_status(:not_found) + it "shows the correct content" do + expect(page).to have_content("Which organisations are merging into MHCLG?") end end end - describe "#update_organisations" do - let(:params) { { merge_request: { merging_organisation: other_organisation.id } } } + describe "#update_merging_organisations" do + let(:params) { { merge_request: { merging_organisation: other_organisation.id, new_merging_org_ids: [] } } } - context "when updating a merge request with a new organisation" do + context "when updating a merge request with a new merging organisation" do before do - patch "/merge-request/#{merge_request.id}/organisations", headers:, params: + patch "/merge-request/#{merge_request.id}/merging-organisations", headers:, params: end - it "updates the merge request" do + it "adds merging organisation to the page" do merge_request.reload - expect(merge_request.merging_organisations.count).to eq(1) - expect(page).to have_content("Test Org") + expect(page).to have_content("MHCLG") expect(page).to have_content("Other Test Org") expect(page).to have_link("Remove") end end context "when the user selects an organisation that requested another merge" do - let(:params) { { merge_request: { merging_organisation: other_organisation.id } } } - - before do - MergeRequest.create!(requesting_organisation_id: other_organisation.id, status: "submitted") - patch "/merge-request/#{merge_request.id}/organisations", headers:, params: - end - - it "does not update the merge request" do - merge_request.reload - expect(merge_request.merging_organisations.count).to eq(0) - expect(response).to have_http_status(:unprocessable_entity) - expect(page).to have_content(I18n.t("validations.merge_request.organisation_part_of_another_merge")) - end - end - - context "when the user selects an organisation that has another non submitted merge" do - let(:params) { { merge_request: { merging_organisation: other_organisation.id } } } + let(:params) { { merge_request: { merging_organisation: other_organisation.id, new_merging_org_ids: [] } } } before do - MergeRequest.create!(requesting_organisation_id: other_organisation.id, status: "unsubmitted") - patch "/merge-request/#{merge_request.id}/organisations", headers:, params: + MergeRequest.create!(requesting_organisation_id: other_organisation.id, request_merged: true) + patch "/merge-request/#{merge_request.id}/merging-organisations", headers:, params: end - it "updates the merge request" do + it "does not error" do merge_request.reload - expect(merge_request.merging_organisations.count).to eq(1) expect(page).not_to have_content(I18n.t("validations.merge_request.organisation_part_of_another_merge")) end end context "when the user selects an organisation that is a part of another merge" do let(:another_organisation) { create(:organisation) } - let(:params) { { merge_request: { merging_organisation: another_organisation.id } } } + let(:params) { { merge_request: { merging_organisation: another_organisation.id, new_merging_org_ids: [] } } } before do - existing_merge_request = MergeRequest.create!(requesting_organisation_id: other_organisation.id, status: "submitted") + existing_merge_request = MergeRequest.create!(requesting_organisation_id: other_organisation.id, request_merged: true) MergeRequestOrganisation.create!(merge_request_id: existing_merge_request.id, merging_organisation_id: another_organisation.id) - patch "/merge-request/#{merge_request.id}/organisations", headers:, params: + patch "/merge-request/#{merge_request.id}/merging-organisations", headers:, params: end it "does not update the merge request" do @@ -129,57 +102,42 @@ RSpec.describe MergeRequestsController, type: :request do end end - context "when the user selects an organisation that is a part of another unsubmitted merge" do + context "when the user selects an organisation that is a part of another incomplete merge" do let(:another_organisation) { create(:organisation) } - let(:params) { { merge_request: { merging_organisation: another_organisation.id } } } + let(:params) { { merge_request: { merging_organisation: another_organisation.id, new_merging_org_ids: [] } } } before do - existing_merge_request = MergeRequest.create!(requesting_organisation_id: other_organisation.id, status: "unsubmitted") + existing_merge_request = MergeRequest.create!(requesting_organisation_id: other_organisation.id) MergeRequestOrganisation.create!(merge_request_id: existing_merge_request.id, merging_organisation_id: another_organisation.id) - patch "/merge-request/#{merge_request.id}/organisations", headers:, params: + patch "/merge-request/#{merge_request.id}/merging-organisations", headers:, params: end - it "does not update the merge request" do + it "does not error" do merge_request.reload - expect(merge_request.merging_organisations.count).to eq(1) expect(page).not_to have_content(I18n.t("validations.merge_request.organisation_part_of_another_merge")) end end context "when the user selects an organisation that is a part of current merge" do let(:another_organisation) { create(:organisation) } - let(:params) { { merge_request: { merging_organisation: another_organisation.id } } } + let(:params) { { merge_request: { merging_organisation: another_organisation.id, new_merging_org_ids: [] } } } before do merge_request.merging_organisations << another_organisation - patch "/merge-request/#{merge_request.id}/organisations", headers:, params: - end - - it "does not update the merge request" do - merge_request.reload - expect(merge_request.merging_organisations.count).to eq(1) - end - end - - context "when the user selects an organisation that is requesting this merge" do - let(:params) { { merge_request: { merging_organisation: merge_request.requesting_organisation_id } } } - - before do - patch "/merge-request/#{merge_request.id}/organisations", headers:, params: + patch "/merge-request/#{merge_request.id}/merging-organisations", headers:, params: end it "does not update the merge request" do merge_request.reload - expect(page).not_to have_content(I18n.t("validations.merge_request.organisation_part_of_another_merge")) expect(merge_request.merging_organisations.count).to eq(1) end end context "when the user does not select an organisation" do - let(:params) { { merge_request: { merging_organisation: nil } } } + let(:params) { { merge_request: { merging_organisation: nil, new_merging_org_ids: [] } } } before do - patch "/merge-request/#{merge_request.id}/organisations", headers:, params: + patch "/merge-request/#{merge_request.id}/merging-organisations", headers:, params: end it "does not update the merge request" do @@ -191,10 +149,10 @@ RSpec.describe MergeRequestsController, type: :request do end context "when the user selects non existent id" do - let(:params) { { merge_request: { merging_organisation: "clearly_not_an_id" } } } + let(:params) { { merge_request: { merging_organisation: "clearly_not_an_id", new_merging_org_ids: [] } } } before do - patch "/merge-request/#{merge_request.id}/organisations", headers:, params: + patch "/merge-request/#{merge_request.id}/merging-organisations", headers:, params: end it "does not update the merge request" do @@ -212,7 +170,7 @@ RSpec.describe MergeRequestsController, type: :request do context "when removing an organisation from merge request" do before do MergeRequestOrganisation.create!(merge_request_id: merge_request.id, merging_organisation_id: other_organisation.id) - get "/merge-request/#{merge_request.id}/organisations/remove", headers:, params: + get "/merge-request/#{merge_request.id}/merging-organisations/remove", headers:, params: end it "updates the merge request" do @@ -223,7 +181,7 @@ RSpec.describe MergeRequestsController, type: :request do context "when removing an organisation that is not part of a merge from merge request" do before do - get "/merge-request/#{merge_request.id}/organisations/remove", headers:, params: + get "/merge-request/#{merge_request.id}/merging-organisations/remove", headers:, params: end it "does not throw an error" do @@ -233,65 +191,67 @@ RSpec.describe MergeRequestsController, type: :request do end end - describe "#confirm_telephone_number" do - let(:merge_request) do - MergeRequest.create!( - absorbing_organisation: create(:organisation, phone: phone_number), - requesting_organisation: organisation, - ) - end + describe "#absorbing_organisation" do + let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation) } - before { get "/merge-request/#{merge_request.id}/confirm-telephone-number", headers: } + before { get "/merge-request/#{merge_request.id}/absorbing-organisation", headers: } - context "when org has phone number" do - let(:phone_number) { 123 } + it "asks for the absorbing organisation" do + expect(page).to have_content("Which organisation is absorbing the others?") + expect(page).to have_content("Select organisation name") + end - it "asks to confirm or provide new number" do - expect(page).to have_content("This telephone number is correct") - expect(page).to have_content("Confirm the telephone number on file, or enter a new one.") - expect(page).to have_content(phone_number) - expect(page).to have_content("What is #{merge_request.absorbing_organisation.name}'s telephone number?") - end + it "has the correct back button" do + expect(page).to have_link("Back", href: organisations_path(tab: "merge-requests")) end + end - context "when org does not have a phone number set" do - let(:phone_number) { nil } + describe "#merge_date" do + context "when viewing merge date page" do + before do + merge_request.update!(absorbing_organisation_id: organisation.id) + get "/merge-request/#{merge_request.id}/merge-date", headers: + end - it "asks provide new number" do - expect(page).not_to have_content("This telephone number is correct") - expect(page).not_to have_content("Confirm the telephone number on file, or enter a new one.") - expect(page).to have_content("What is #{merge_request.absorbing_organisation.name}'s telephone number?") + it "shows the correct content" do + expect(page).to have_content("What is the merge date?") end end end - describe "#update" do - before { sign_in user } - - describe "#other_merging_organisations" do - let(:other_merging_organisations) { "A list of other merging organisations" } - let(:params) { { merge_request: { other_merging_organisations:, page: "organisations" } } } - let(:request) do - patch "/merge-request/#{merge_request.id}", headers:, params: + describe "#existing_absorbing_organisation" do + context "when viewing helpdesk ticket page" do + before do + merge_request.update!(absorbing_organisation_id: organisation.id, merge_date: Time.zone.today) + get "/merge-request/#{merge_request.id}/existing-absorbing-organisation", headers: end - context "when adding other merging organisations" do - before do - MergeRequestOrganisation.create!(merge_request_id: merge_request.id, merging_organisation_id: other_organisation.id) - end - - it "updates the merge request" do - expect { request }.to change { merge_request.reload.other_merging_organisations }.from(nil).to(other_merging_organisations) - end + it "shows the correct content" do + expect(page).to have_content("Was #{merge_request.absorbing_organisation.name} already active before the merge date?") + expect(page).to have_content("Yes, this organisation existed before the merge") + expect(page).to have_content("No, it is a new organisation created by this merge") + expect(page).to have_link("Back", href: merge_date_merge_request_path(merge_request)) + expect(page).to have_button("Save and continue") + end + end + end - it "redirects telephone number path" do - request + describe "#helpdesk_ticket" do + context "when viewing helpdesk ticket page" do + before do + merge_request.update!(absorbing_organisation_id: organisation.id, merge_date: Time.zone.today) + get "/merge-request/#{merge_request.id}/helpdesk-ticket", headers: + end - expect(response).to redirect_to(absorbing_organisation_merge_request_path(merge_request)) - end + it "shows the correct content" do + expect(page).to have_content("Which helpdesk ticket reported this merge?") + expect(page).to have_link("Back", href: existing_absorbing_organisation_merge_request_path(merge_request)) + expect(page).to have_button("Save and continue") end end + end + describe "#update" do describe "from absorbing_organisation page" do context "when not answering the question" do let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, absorbing_organisation: other_organisation) } @@ -305,7 +265,7 @@ RSpec.describe MergeRequestsController, type: :request do it "renders the error" do request - expect(page).to have_content("Select the organisation absorbing the others") + expect(page).to have_content("Select the absorbing organisation") end it "does not update the request" do @@ -313,32 +273,8 @@ RSpec.describe MergeRequestsController, type: :request do end end - context "when absorbing_organisation_id set to other" do - let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, absorbing_organisation: other_organisation) } - let(:params) do - { merge_request: { absorbing_organisation_id: "other", page: "absorbing_organisation" } } - end - let(:request) do - patch "/merge-request/#{merge_request.id}", headers:, params: - end - - it "redirects to new org path" do - request - - expect(response).to redirect_to(new_organisation_name_merge_request_path(merge_request)) - end - - it "resets absorbing_organisation and sets new_absorbing_organisation to true" do - expect { request }.to change { - merge_request.reload.absorbing_organisation - }.from(other_organisation).to(nil).and change { - merge_request.reload.new_absorbing_organisation - }.from(nil).to(true) - end - end - context "when absorbing_organisation_id set to id" do - let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, new_absorbing_organisation: true) } + let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation) } let(:params) do { merge_request: { absorbing_organisation_id: other_organisation.id, page: "absorbing_organisation" } } end @@ -347,75 +283,62 @@ RSpec.describe MergeRequestsController, type: :request do patch "/merge-request/#{merge_request.id}", headers:, params: end - it "redirects telephone number path" do + it "redirects to merging organisations path" do request - expect(response).to redirect_to(confirm_telephone_number_merge_request_path(merge_request)) + expect(response).to redirect_to(merging_organisations_merge_request_path(merge_request)) end - it "updates absorbing_organisation_id and sets new_absorbing_organisation to false" do + it "updates absorbing_organisation_id" do expect { request }.to change { merge_request.reload.absorbing_organisation - }.from(nil).to(other_organisation).and change { - merge_request.reload.new_absorbing_organisation - }.from(true).to(false) + }.from(nil).to(other_organisation) end end - end - describe "from confirm_telephone_number page" do - context "when confirming the number" do - let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, new_absorbing_organisation: true, new_telephone_number: "123") } + context "when updating from check_answers page" do + let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation) } let(:params) do - { merge_request: { telephone_number_correct: true, page: "confirm_telephone_number" } } + { merge_request: { absorbing_organisation_id: "", page: "absorbing_organisation" } } end let(:request) do - patch "/merge-request/#{merge_request.id}", headers:, params: + patch "/merge-request/#{merge_request.id}?referrer=check_answers", headers:, params: end - it "redirects telephone number path" do + it "keeps corrent links if validation fails" do request - expect(response).to redirect_to(merge_date_merge_request_path(merge_request)) - end - - it "updates telephone_number_correct and sets new_telephone_number to nil" do - expect { request }.to change { - merge_request.reload.telephone_number_correct - }.from(nil).to(true).and change { - merge_request.reload.new_telephone_number - }.from("123").to(nil) + expect(page).to have_link("Cancel", href: merge_request_path(merge_request)) + expect(page).to have_button("Save changes") end end - context "when setting new number" do - let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, new_absorbing_organisation: true) } + context "when absorbing_organisation_id set to one of the merging organisations" do + let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation) } let(:params) do - { merge_request: { telephone_number_correct: false, new_telephone_number: "123", page: "confirm_telephone_number" } } + { merge_request: { absorbing_organisation_id: other_organisation.id, page: "absorbing_organisation" } } end let(:request) do + MergeRequestOrganisation.create!(merge_request_id: merge_request.id, merging_organisation_id: other_organisation.id) patch "/merge-request/#{merge_request.id}", headers:, params: end - it "redirects telephone number path" do + it "removes organisation from merge request organisations" do request - expect(response).to redirect_to(merge_date_merge_request_path(merge_request)) - end - - it "updates telephone_number_correct and sets new_telephone_number to nil" do - expect { request }.to change { - merge_request.reload.new_telephone_number - }.from(nil).to("123") + merge_request.reload + expect(merge_request.merging_organisations.count).to eq(0) end end + end - context "when not answering the question and the org has phone number" do - let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, absorbing_organisation: create(:organisation, phone: "123")) } + describe "from merge_date page" do + context "when not answering the question" do + let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, absorbing_organisation: other_organisation) } let(:params) do - { merge_request: { page: "confirm_telephone_number" } } + { merge_request: { page: "merge_date" } } end let(:request) do patch "/merge-request/#{merge_request.id}", headers:, params: @@ -424,7 +347,8 @@ RSpec.describe MergeRequestsController, type: :request do it "renders the error" do request - expect(page).to have_content("Select to confirm or enter a new telephone number") + expect(response).to have_http_status(:unprocessable_entity) + expect(page).to have_content("Enter a merge date") end it "does not update the request" do @@ -432,285 +356,410 @@ RSpec.describe MergeRequestsController, type: :request do end end - context "when not answering the question and the org does not have a phone number" do - let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, absorbing_organisation: other_organisation) } + context "when merge date set to an invalid date" do + let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation) } let(:params) do - { merge_request: { page: "confirm_telephone_number" } } + { merge_request: { page: "merge_date", "merge_date(3i)": "10", "merge_date(2i)": "44", "merge_date(1i)": "2022" } } end + let(:request) do patch "/merge-request/#{merge_request.id}", headers:, params: end - it "renders the error" do + it "displays the page with an error message" do request - expect(page).to have_content("Enter a valid telephone number") - end - - it "does not update the request" do - expect { request }.not_to(change { merge_request.reload.attributes }) + expect(response).to have_http_status(:unprocessable_entity) + expect(page).to have_content("Enter a valid merge date") end end - context "when not answering the phone number" do - let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, absorbing_organisation: other_organisation) } + context "when merge date set to a valid date" do + let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation) } let(:params) do - { merge_request: { page: "confirm_telephone_number", telephone_number_correct: false } } + { merge_request: { page: "merge_date", "merge_date(3i)": "10", "merge_date(2i)": "4", "merge_date(1i)": "2022" } } end + let(:request) do patch "/merge-request/#{merge_request.id}", headers:, params: end - it "renders the error" do + it "redirects to existing absorbing organisation path" do request - expect(page).to have_content("Enter a valid telephone number") + expect(response).to redirect_to(existing_absorbing_organisation_merge_request_path(merge_request)) end - it "does not update the request" do - expect { request }.not_to(change { merge_request.reload.attributes }) + it "updates merge_date" do + expect { request }.to change { + merge_request.reload.merge_date + }.from(nil).to(Time.zone.local(2022, 4, 10)) end end end - describe "#new_organisation_name" do - let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, new_absorbing_organisation: true) } - - context "when viewing the new organisation name page" do - before do - get "/merge-request/#{merge_request.id}/new-organisation-name", headers: - end - - it "displays the correct question" do - expect(page).to have_content("What is the new organisation called?") - end - - it "has the correct back button" do - expect(page).to have_link("Back", href: absorbing_organisation_merge_request_path(merge_request)) - end - end - - context "when updating the new organisation name" do + describe "from merging_organisations page" do + context "when the user updates merge request with valid merging organisation ID" do + let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation) } + let(:another_organisation) { create(:organisation) } let(:params) do - { merge_request: { new_organisation_name: "new org name", page: "new_organisation_name" } } + { merge_request: { page: "merging_organisations", new_merging_org_ids: another_organisation.id } } end let(:request) do patch "/merge-request/#{merge_request.id}", headers:, params: end - it "redirects to new organisation address path" do + it "updates the merge request" do request - expect(response).to redirect_to(new_organisation_address_merge_request_path(merge_request)) - end - it "updates new organisation name to the correct name" do - expect { request }.to change { - merge_request.reload.new_organisation_name - }.from(nil).to("new org name") + merge_request.reload + expect(merge_request.merging_organisations.count).to eq(1) + expect(merge_request.merging_organisations.first.id).to eq(another_organisation.id) end end + end - context "when the new organisation name is not answered" do + describe "from existing_absorbing_organisation page" do + context "when not answering the question" do + let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, absorbing_organisation: other_organisation) } let(:params) do - { merge_request: { new_organisation_name: nil, page: "new_organisation_name" } } + { merge_request: { page: "existing_absorbing_organisation" } } end - let(:request) do patch "/merge-request/#{merge_request.id}", headers:, params: end it "renders the error" do request - expect(page).to have_content("Enter an organisation name") + + expect(response).to have_http_status(:unprocessable_entity) + expect(page).to have_content("You must answer absorbing organisation already active?") end - it "does not update the organisation name" do + it "does not update the request" do expect { request }.not_to(change { merge_request.reload.attributes }) end end + end + end - context "when the new organisation name already exists" do - before do - create(:organisation, name: "new org name") - end + describe "#merge_start_confirmation" do + before do + get "/merge-request/#{merge_request.id}/merge-start-confirmation", headers: + end - let(:params) do - { merge_request: { new_organisation_name: "New org name", page: "new_organisation_name" } } - end + it "has correct content" do + expect(page).to have_content("Are you sure you want to begin this merge?") + expect(page).to have_content("You will not be able to undo this action") + expect(page).to have_link("Back", href: merge_request_path(merge_request)) + expect(page).to have_button("Begin merge") + end + end - let(:request) do - patch "/merge-request/#{merge_request.id}", headers:, params: - end + describe "#start_merge" do + let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, absorbing_organisation: organisation, merge_date: Time.zone.local(2022, 3, 3), existing_absorbing_organisation: true) } + let(:merging_organisation) { create(:organisation, name: "Merging Test Org") } - it "renders the error" do - request - expect(page).to have_content("An organisation with this name already exists") - end + before do + allow(ProcessMergeRequestJob).to receive(:perform_later).and_return(nil) + end - it "does not update the organisation name" do - expect { request }.not_to(change { merge_request.reload.attributes }) - end + context "when merge request is ready to merge" do + before do + create(:merge_request_organisation, merge_request:, merging_organisation: other_organisation) + create(:merge_request_organisation, merge_request:, merging_organisation:) + create_list(:scheme, 2, owning_organisation: organisation) + create(:lettings_log, owning_organisation: organisation) + create(:sales_log, owning_organisation: organisation) + end + + it "runs the job with correct merge request" do + expect(merge_request.reload.status).to eq("ready_to_merge") + expect(ProcessMergeRequestJob).to receive(:perform_later).with(merge_request:).once + patch "/merge-request/#{merge_request.id}/start-merge" + expect(merge_request.reload.status).to eq("processing") + expect(merge_request.total_users).to eq(5) + expect(merge_request.total_schemes).to eq(2) + expect(merge_request.total_stock_owners).to eq(0) + expect(merge_request.total_managing_agents).to eq(0) + expect(merge_request.total_lettings_logs).to eq(1) + expect(merge_request.total_sales_logs).to eq(1) end end - describe "#new_organisation_address" do - let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, new_organisation_name: "New name", new_absorbing_organisation: true) } - - context "when viewing the new organisation name page" do - before do - get "/merge-request/#{merge_request.id}/new-organisation-address", headers: - end + context "when merge request is not ready to merge" do + it "does not run the job" do + expect(merge_request.status).to eq("incomplete") + expect(ProcessMergeRequestJob).not_to receive(:perform_later).with(merge_request:) + patch "/merge-request/#{merge_request.id}/start-merge" + expect(merge_request.reload.status).to eq("incomplete") + end + end + end - it "displays the correct question" do - expect(page).to have_content("What is New name’s address?") - end + describe "#show" do + before do + create(:merge_request_organisation, merge_request:, merging_organisation: other_organisation) + create_list(:scheme, 2, owning_organisation: organisation) + create_list(:scheme, 2, owning_organisation: other_organisation) + create(:lettings_log, owning_organisation: organisation) + create(:lettings_log, owning_organisation: other_organisation) + create_list(:sales_log, 2, owning_organisation: other_organisation) + create(:sales_log, owning_organisation: organisation) + get "/merge-request/#{merge_request.id}", headers: + end - it "has the correct back button" do - expect(page).to have_link("Back", href: new_organisation_name_merge_request_path(merge_request)) - end + context "when request has previously failed" do + let(:merge_request) { create(:merge_request, last_failed_attempt: Time.zone.yesterday) } - it "has a skip link" do - expect(page).to have_link("Skip for now", href: new_organisation_telephone_number_merge_request_path(merge_request)) - end + it "shows a banner" do + expect(page).to have_content("An error occurred while processing the merge.") + expect(page).to have_content("No changes have been made. Try beginning the merge again.") end + end - context "when updating the new organisation address" do - let(:params) do - { merge_request: { - new_organisation_address_line1: "first address line", - new_organisation_address_line2: "second address line", - new_organisation_postcode: "new postcode", - page: "new_organisation_address", - } } - end + context "when request has not previously failed" do + let(:merge_request) { create(:merge_request, last_failed_attempt: nil) } - let(:request) do - patch "/merge-request/#{merge_request.id}", headers:, params: - end + it "does not show a banner" do + expect(page).not_to have_content("An error occurred while processing the merge.") + expect(page).not_to have_content("No changes have been made. Try beginning the merge again.") + end + end - it "redirects to new organisation telephone path" do - request - expect(response).to redirect_to(new_organisation_telephone_number_merge_request_path(merge_request)) - end + it "has begin merge button" do + expect(page).to have_link("Begin merge", href: merge_start_confirmation_merge_request_path(merge_request)) + end - it "updates new organisation address line 1 to correct address line" do - expect { request }.to change { - merge_request.reload.new_organisation_address_line1 - }.from(nil).to("first address line") - end + context "with unmerged request" do + let(:merge_request) { create(:merge_request, absorbing_organisation_id: organisation.id, merge_date: Time.zone.today, existing_absorbing_organisation: true) } + + it "shows outcomes count and has links to view merge outcomes" do + expect(page).to have_link("View", href: user_outcomes_merge_request_path(merge_request)) + expect(page).to have_link("View", href: scheme_outcomes_merge_request_path(merge_request)) + expect(page).to have_link("View", href: relationship_outcomes_merge_request_path(merge_request)) + expect(page).to have_link("View", href: logs_outcomes_merge_request_path(merge_request)) + expect(page).to have_content("4 users") + expect(page).to have_content("4 schemes") + expect(page).to have_content("0 stock owners") + expect(page).to have_content("0 managing agents") + expect(page).to have_content("2 lettings logs") + expect(page).to have_content("3 sales logs") + end + end - it "updates new organisation address line 2 to correct address line" do - expect { request }.to change { - merge_request.reload.new_organisation_address_line2 - }.from(nil).to("second address line") - end + context "with a merged request" do + let(:merge_request) { create(:merge_request, request_merged: true, total_users: 34, total_schemes: 12, total_stock_owners: 8, total_managing_agents: 5, total_lettings_logs: 4, total_sales_logs: 5) } - it "updates new organisation postcode to correct address line" do - expect { request }.to change { - merge_request.reload.new_organisation_postcode - }.from(nil).to("new postcode") - end + it "shows saved users count and doesn't have links to view merge outcomes" do + expect(merge_request.status).to eq("request_merged") + expect(page).not_to have_link("View", href: user_outcomes_merge_request_path(merge_request)) + expect(page).to have_content("34 users") end - context "when address is not provided" do - let(:params) do - { merge_request: { - new_organisation_address_line1: nil, - new_organisation_address_line2: nil, - new_organisation_postcode: nil, - page: "new_organisation_address", - } } - end + it "shows saved schemes count and doesn't have links to view merge outcomes" do + expect(merge_request.status).to eq("request_merged") + expect(page).not_to have_link("View", href: scheme_outcomes_merge_request_path(merge_request)) + expect(page).to have_content("12 schemes") + end - let(:request) do - patch "/merge-request/#{merge_request.id}", headers:, params: - end + it "shows stock owners and managing agents count and doesn't have links to view merge outcomes" do + expect(merge_request.status).to eq("request_merged") + expect(page).not_to have_link("View", href: relationship_outcomes_merge_request_path(merge_request)) + expect(page).to have_content("8 stock owners") + expect(page).to have_content("5 managing agents") + end - it "does not throw an error" do - request - expect(response).to redirect_to(new_organisation_telephone_number_merge_request_path(merge_request)) - end + it "shows logs counts and doesn't have links to view merge outcomes" do + expect(merge_request.status).to eq("request_merged") + expect(page).not_to have_link("View", href: logs_outcomes_merge_request_path(merge_request)) + expect(page).to have_content("4 lettings logs") + expect(page).to have_content("5 sales logs") end end - describe "#new_organisation_telephone_number" do - let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, new_organisation_name: "New name", new_absorbing_organisation: true) } + context "with a processing request" do + let(:merge_request) { create(:merge_request, processing: true, total_users: 51, total_schemes: 33, total_stock_owners: 15, total_managing_agents: 20) } - context "when viewing the new organisation telephone number page" do - before do - get "/merge-request/#{merge_request.id}/new-organisation-telephone-number", headers: - end + it "shows saved users count and doesn't have links to view merge outcomes" do + expect(merge_request.status).to eq("processing") + expect(page).not_to have_link("View", href: user_outcomes_merge_request_path(merge_request)) + expect(page).to have_content("51 users") + end - it "displays the correct question" do - expect(page).to have_content("What is New name’s telephone number?") - end + it "shows saved schemes count and doesn't have links to view merge outcomes" do + expect(merge_request.status).to eq("processing") + expect(page).not_to have_link("View", href: scheme_outcomes_merge_request_path(merge_request)) + expect(page).to have_content("33 schemes") + end - it "has the correct back button" do - expect(page).to have_link("Back", href: new_organisation_address_merge_request_path(merge_request)) - end + it "shows stock owners and managing agents count and doesn't have links to view merge outcomes" do + expect(merge_request.status).to eq("processing") + expect(page).not_to have_link("View", href: relationship_outcomes_merge_request_path(merge_request)) + expect(page).to have_content("15 stock owners") + expect(page).to have_content("20 managing agents") end + end + end - context "when updating the new organisation telephone number" do - let(:params) do - { merge_request: { new_organisation_telephone_number: "1234", page: "new_organisation_telephone_number" } } - end + describe "#user_outcomes" do + let(:merge_request) { create(:merge_request, absorbing_organisation: organisation) } + let(:organisation_with_no_users) { create(:organisation, name: "Organisation with no users", with_dsa: false) } + let(:organisation_with_no_users_too) { create(:organisation, name: "Organisation with no users too", with_dsa: false) } + let(:organisation_with_some_users) { create(:organisation, name: "Organisation with some users", with_dsa: false) } + let(:organisation_with_some_more_users) { create(:organisation, name: "Organisation with many users", with_dsa: false) } - let(:request) do - patch "/merge-request/#{merge_request.id}", headers:, params: - end + before do + create_list(:user, 4, organisation: organisation_with_some_users) + create_list(:user, 12, organisation: organisation_with_some_more_users) + create(:merge_request_organisation, merge_request:, merging_organisation: organisation_with_no_users) + create(:merge_request_organisation, merge_request:, merging_organisation: organisation_with_no_users_too) + create(:merge_request_organisation, merge_request:, merging_organisation: organisation_with_some_users) + create(:merge_request_organisation, merge_request:, merging_organisation: organisation_with_some_more_users) + get "/merge-request/#{merge_request.id}/user-outcomes", headers: + end - it "redirects to new organisation type path" do - request - expect(response).to redirect_to(new_organisation_type_merge_request_path(merge_request)) - end + it "shows user outcomes after merge" do + expect(page).to have_link("View all 4 Organisation with some users users (opens in a new tab)", href: users_organisation_path(organisation_with_some_users)) + expect(page).to have_link("View all 12 Organisation with many users users (opens in a new tab)", href: users_organisation_path(organisation_with_some_more_users)) + expect(page).to have_link("View all 3 MHCLG users (opens in a new tab)", href: users_organisation_path(organisation)) + expect(page).to have_content("Organisation with no users and Organisation with no users too have no users.") + expect(page).to have_content("19 users after merge") + end + end - it "updates new organisation name to the correct telephone number" do - expect { request }.to change { - merge_request.reload.new_organisation_telephone_number - }.from(nil).to("1234") - end - end + describe "#scheme_outcomes" do + let(:merge_request) { create(:merge_request, absorbing_organisation: organisation) } + let(:organisation_with_no_schemes) { create(:organisation, name: "Organisation with no schemes") } + let(:organisation_with_no_schemes_too) { create(:organisation, name: "Organisation with no schemes too") } + let(:organisation_with_some_schemes) { create(:organisation, name: "Organisation with some schemes") } + let(:organisation_with_some_more_schemes) { create(:organisation, name: "Organisation with many schemes") } - context "when the new organisation telephone number is not answered" do - let(:params) do - { merge_request: { new_organisation_telephone_number: nil, page: "new_organisation_telephone_number" } } - end + before do + create_list(:scheme, 4, owning_organisation: organisation_with_some_schemes) + create_list(:scheme, 6, owning_organisation: organisation_with_some_more_schemes) + create_list(:scheme, 3, owning_organisation: organisation) + create(:merge_request_organisation, merge_request:, merging_organisation: organisation_with_no_schemes) + create(:merge_request_organisation, merge_request:, merging_organisation: organisation_with_no_schemes_too) + create(:merge_request_organisation, merge_request:, merging_organisation: organisation_with_some_schemes) + create(:merge_request_organisation, merge_request:, merging_organisation: organisation_with_some_more_schemes) + get "/merge-request/#{merge_request.id}/scheme-outcomes", headers: + end - let(:request) do - patch "/merge-request/#{merge_request.id}", headers:, params: - end + it "shows scheme outcomes after merge" do + expect(page).to have_link("View all 4 Organisation with some schemes schemes (opens in a new tab)", href: schemes_organisation_path(organisation_with_some_schemes)) + expect(page).to have_link("View all 6 Organisation with many schemes schemes (opens in a new tab)", href: schemes_organisation_path(organisation_with_some_more_schemes)) + expect(page).to have_link("View all 3 MHCLG schemes (opens in a new tab)", href: schemes_organisation_path(organisation)) + expect(page).to have_content("Organisation with no schemes and Organisation with no schemes too have no schemes.") + expect(page).to have_content("13 schemes after merge") + end + end - it "renders the error" do - request - expect(page).to have_content("Enter a valid telephone number") - end + describe "#logs_outcomes" do + let(:merge_request) { create(:merge_request, absorbing_organisation: organisation) } + let(:organisation_with_no_logs) { create(:organisation, name: "Organisation with no logs") } + let(:organisation_with_no_logs_too) { create(:organisation, name: "Organisation with no logs too") } + let(:organisation_with_some_logs) { create(:organisation, name: "Organisation with some logs") } - it "does not update the organisation telephone number" do - expect { request }.not_to(change { merge_request.reload.attributes }) - end - end + before do + create_list(:lettings_log, 4, owning_organisation: organisation_with_some_logs) + create_list(:sales_log, 2, owning_organisation: organisation_with_some_logs) + create_list(:lettings_log, 2, owning_organisation: organisation) + create_list(:sales_log, 3, owning_organisation: organisation) + create(:merge_request_organisation, merge_request:, merging_organisation: organisation_with_no_logs) + create(:merge_request_organisation, merge_request:, merging_organisation: organisation_with_no_logs_too) + create(:merge_request_organisation, merge_request:, merging_organisation: organisation_with_some_logs) + get "/merge-request/#{merge_request.id}/logs-outcomes", headers: + end + + it "shows logs outcomes after merge" do + expect(page).to have_link("View all 4 Organisation with some logs lettings logs (opens in a new tab)", href: lettings_logs_organisation_path(organisation_with_some_logs)) + expect(page).to have_link("View all 2 Organisation with some logs sales logs (opens in a new tab)", href: sales_logs_organisation_path(organisation_with_some_logs)) + expect(page).to have_link("View all 2 MHCLG lettings logs (opens in a new tab)", href: lettings_logs_organisation_path(organisation)) + expect(page).to have_link("View all 3 MHCLG sales logs (opens in a new tab)", href: sales_logs_organisation_path(organisation)) + expect(page).to have_content("Organisation with no logs and Organisation with no logs too have no lettings logs.") + expect(page).to have_content("Organisation with no logs and Organisation with no logs too have no sales logs.") + expect(page).to have_content("6 lettings logs after merge") + expect(page).to have_content("5 sales logs after merge") end end end - context "when user is signed in as a support user" do + context "when user is signed in with a data coordinator user" do before do - allow(support_user).to receive(:need_two_factor_authentication?).and_return(false) - sign_in support_user + sign_in user end - describe "#organisations" do - let(:params) { { merge_request: { requesting_organisation_id: other_organisation.id, status: "unsubmitted" } } } + describe "#merging_organisations" do + let(:params) { { merge_request: { requesting_organisation_id: other_organisation.id } } } - before do - post "/merge-request", headers:, params: + context "when creating a new merge request" do + before do + post "/merge-request", headers:, params: + end + + it "does not allow creating a new merge request" do + expect(response).to have_http_status(:not_found) + end end - it "creates merge request with requesting organisation" do - follow_redirect! - expect(MergeRequest.count).to eq(1) - expect(MergeRequest.first.requesting_organisation_id).to eq(other_organisation.id) + context "when viewing existing merge request" do + before do + get "/merge-request/#{merge_request.id}/merging-organisations", headers:, params: + end + + it "does not allow viewing a merge request" do + expect(response).to have_http_status(:not_found) + end + end + end + + describe "#update_merging_organisations" do + let(:params) { { merge_request: { merging_organisation: other_organisation.id } } } + + context "when updating a merge request with a new organisation" do + before do + patch "/merge-request/#{merge_request.id}/merging-organisations", headers:, params: + end + + it "does not allow updaing a merge request" do + expect(response).to have_http_status(:not_found) + end + end + end + + describe "#remove_merging_organisation" do + let(:params) { { merge_request: { merging_organisation: other_organisation.id } } } + + context "when removing an organisation from merge request" do + before do + MergeRequestOrganisation.create!(merge_request_id: merge_request.id, merging_organisation_id: other_organisation.id) + get "/merge-request/#{merge_request.id}/merging-organisations/remove", headers:, params: + end + + it "does not allow removing an organisation" do + expect(response).to have_http_status(:not_found) + end + end + end + + describe "#update" do + describe "from absorbing_organisation page" do + context "when absorbing_organisation_id set to id" do + let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation) } + let(:params) do + { merge_request: { absorbing_organisation_id: other_organisation.id, page: "absorbing_organisation" } } + end + + before do + patch "/merge-request/#{merge_request.id}", headers:, params: + end + + it "does not allow updating absorbing organisation" do + expect(response).to have_http_status(:not_found) + end + end end end end diff --git a/spec/requests/organisations_controller_spec.rb b/spec/requests/organisations_controller_spec.rb index cb74ca3ab..91dc07094 100644 --- a/spec/requests/organisations_controller_spec.rb +++ b/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) } diff --git a/spec/requests/schemes_controller_spec.rb b/spec/requests/schemes_controller_spec.rb index 579cfd1a9..d93bc873d 100644 --- a/spec/requests/schemes_controller_spec.rb +++ b/spec/requests/schemes_controller_spec.rb @@ -612,9 +612,9 @@ RSpec.describe SchemesController, type: :request do context "with scheme that's deactivating soon" do let(:scheme_deactivation_period) { create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 10, 12), scheme:) } - it "does not render toggle scheme link" do + it "does render the reactivate this scheme button" do expect(response).to have_http_status(:ok) - expect(page).not_to have_link("Reactivate this scheme") + expect(page).to have_link("Reactivate this scheme") expect(page).not_to have_link("Deactivate this scheme") end end @@ -680,9 +680,9 @@ RSpec.describe SchemesController, type: :request do context "with scheme that's deactivating soon" do let(:scheme_deactivation_period) { create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 10, 12), scheme: specific_scheme) } - it "does not render toggle scheme link" do + it "does render the reactivate this scheme button" do expect(response).to have_http_status(:ok) - expect(page).not_to have_link("Reactivate this scheme") + expect(page).to have_link("Reactivate this scheme") expect(page).not_to have_link("Deactivate this scheme") end end @@ -784,6 +784,7 @@ RSpec.describe SchemesController, type: :request do context "and associated logs in editable collection period" do before do + create(:location, scheme:) create(:lettings_log, :sh, scheme:, startdate: Time.zone.local(2022, 9, 9), owning_organisation: user.organisation) get "/schemes/#{scheme.id}" end @@ -2533,14 +2534,18 @@ RSpec.describe SchemesController, type: :request do it "redirects to the confirmation page" do follow_redirect! expect(response).to have_http_status(:ok) - expect(page).to have_content("This change will affect #{scheme.lettings_logs.count} logs") + expect(page).to have_content("This change will affect #{scheme.lettings_logs.count} log and 1 location.") end end context "and no affected logs" do let(:setup_schemes) { scheme.lettings_logs.update(scheme: nil) } - it "redirects to the location page and updates the deactivation period" do + before do + create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 4, 1), reactivation_date: nil, location:) + end + + it "redirects to the scheme page and updates the deactivation period" do follow_redirect! follow_redirect! follow_redirect! @@ -2560,14 +2565,18 @@ RSpec.describe SchemesController, type: :request do it "redirects to the confirmation page" do follow_redirect! expect(response).to have_http_status(:ok) - expect(page).to have_content("This change will affect #{scheme.lettings_logs.count} logs") + expect(page).to have_content("This change will affect #{scheme.lettings_logs.count} log and 1 location.") end end context "and no affected logs" do let(:setup_schemes) { scheme.lettings_logs.update(scheme: nil) } - it "redirects to the location page and updates the deactivation period" do + before do + create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 4, 5), reactivation_date: nil, location:) + end + + it "redirects to the scheme page and updates the deactivation period" do follow_redirect! follow_redirect! follow_redirect! @@ -2751,6 +2760,10 @@ RSpec.describe SchemesController, type: :request do let(:params) { { scheme_deactivation_period: { deactivation_date_type: "other", "deactivation_date(3i)": "8", "deactivation_date(2i)": "9", "deactivation_date(1i)": "2023" } } } let(:add_deactivations) { create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2023, 6, 5), reactivation_date: nil, scheme:) } + before do + create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 4, 5), reactivation_date: nil, location:) + end + it "redirects to the scheme page and updates the existing deactivation period" do follow_redirect! follow_redirect! @@ -2771,7 +2784,7 @@ RSpec.describe SchemesController, type: :request do it "redirects to the confirmation page" do follow_redirect! expect(response).to have_http_status(:ok) - expect(page).to have_content("This change will affect 1 logs") + expect(page).to have_content("This change will affect 1 log and 1 location.") end end end diff --git a/spec/services/bulk_upload/lettings/year2024/csv_parser_spec.rb b/spec/services/bulk_upload/lettings/year2024/csv_parser_spec.rb index 6db8e1806..d0e5b3692 100644 --- a/spec/services/bulk_upload/lettings/year2024/csv_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2024/csv_parser_spec.rb @@ -41,6 +41,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do file.write("Duplicate check field?\n") file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2024_field_numbers_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2024_csv_row) + file.write("\n") file.rewind end @@ -52,6 +53,10 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do it "parses csv correctly" do expect(service.row_parsers[0].field_13).to eql(log.tenancycode) end + + it "does not parse the last empty row" do + expect(service.row_parsers.count).to eq(1) + end end context "when parsing csv with headers in arbitrary order" do diff --git a/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb index 42a05e33c..3faa0a699 100644 --- a/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb @@ -1843,6 +1843,18 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do end end end + + describe "log_already_exists?" do + let(:attributes) { { bulk_upload: } } + + before do + build(:lettings_log, owning_organisation: nil, startdate: nil, tenancycode: nil, location: nil, age1: nil, sex1: nil, ecstat1: nil, brent: nil, scharge: nil, pscharge: nil, supcharg: nil).save(validate: false) + end + + it "does not add duplicate logs validation to the blank row" do + expect(parser.log_already_exists?).to eq(false) + end + end end describe "#log" do diff --git a/spec/services/bulk_upload/sales/year2024/csv_parser_spec.rb b/spec/services/bulk_upload/sales/year2024/csv_parser_spec.rb index ef90bd834..9440b7e8c 100644 --- a/spec/services/bulk_upload/sales/year2024/csv_parser_spec.rb +++ b/spec/services/bulk_upload/sales/year2024/csv_parser_spec.rb @@ -17,6 +17,7 @@ RSpec.describe BulkUpload::Sales::Year2024::CsvParser do file.write("Duplicate check field?\n") file.write(BulkUpload::SalesLogToCsv.new(log:).default_2024_field_numbers_row) file.write(BulkUpload::SalesLogToCsv.new(log:).to_2024_csv_row) + file.write("\n") file.rewind end @@ -32,6 +33,10 @@ RSpec.describe BulkUpload::Sales::Year2024::CsvParser do it "counts the number of valid field numbers correctly" do expect(service).to be_correct_field_count end + + it "does not parse the last empty row" do + expect(service.row_parsers.count).to eq(1) + end end context "when parsing csv with headers in arbitrary order" do diff --git a/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb b/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb index f19a61d78..e428f7792 100644 --- a/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb +++ b/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb @@ -1417,6 +1417,18 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do end end end + + describe "log_already_exists?" do + let(:attributes) { { bulk_upload: } } + + before do + build(:sales_log, owning_organisation: nil, saledate: nil, purchid: nil, age1: nil, sex1: nil, ecstat1: nil).save(validate: false) + end + + it "does not add duplicate logs validation to the blank row" do + expect(parser.log_already_exists?).to eq(false) + end + end end describe "#log" do diff --git a/spec/views/merge_requests/show.html.erb_spec.rb b/spec/views/merge_requests/show.html.erb_spec.rb new file mode 100644 index 000000000..d03239d2a --- /dev/null +++ b/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 diff --git a/spec/views/schemes/deactivate_confirm.html.erb_spec.rb b/spec/views/schemes/deactivate_confirm.html.erb_spec.rb new file mode 100644 index 000000000..fd1f2a2ac --- /dev/null +++ b/spec/views/schemes/deactivate_confirm.html.erb_spec.rb @@ -0,0 +1,46 @@ +require "rails_helper" + +RSpec.describe "schemes/deactivate_confirm.html.erb", type: :view do + let(:scheme) { create(:scheme, service_name: "ABCScheme") } + let(:deactivation_date) { Time.zone.today + 1.month } + let(:affected_logs) { create_list(:lettings_log, 2, scheme:, status: 1) } + let(:affected_locations) { create_list(:location, 3, scheme:) } + let(:scheme_deactivation_period) { SchemeDeactivationPeriod.new } + + before do + assign(:scheme, scheme) + assign(:deactivation_date, deactivation_date) + assign(:affected_logs, affected_logs) + assign(:affected_locations, affected_locations) + assign(:scheme_deactivation_period, scheme_deactivation_period) + render + end + + it "displays the service name in the caption" do + expect(rendered).to have_css("span.govuk-caption-l", text: scheme.service_name) + end + + it "displays the correct heading" do + expect(rendered).to have_css("h1.govuk-heading-l", text: "This change will affect 2 logs and 3 locations.") + end + + it "displays the affected logs count" do + expect(rendered).to have_text("2 existing logs using this scheme have a tenancy start date after #{deactivation_date.to_formatted_s(:govuk_date)}.") + end + + it "displays the warning text" do + expect(rendered).to have_css(".govuk-warning-text", text: I18n.t("warnings.scheme.deactivate.review_logs")) + end + + it "displays the affected locations count" do + expect(rendered).to have_text("This scheme has 3 locations active on #{deactivation_date.to_formatted_s(:govuk_date)}.") + end + + it "renders the submit button" do + expect(rendered).to have_button("Deactivate this scheme") + end + + it "renders the cancel button" do + expect(rendered).to have_link("Cancel", href: scheme_details_path(scheme)) + end +end