diff --git a/app/controllers/organisation_name_changes_controller.rb b/app/controllers/organisation_name_changes_controller.rb new file mode 100644 index 000000000..9ee19c29b --- /dev/null +++ b/app/controllers/organisation_name_changes_controller.rb @@ -0,0 +1,35 @@ +class OrganisationNameChangesController < ApplicationController + before_action :set_organisation, only: %i[create change_name] + before_action :set_previous_name_changes, only: %i[create change_name] + + def create + @organisation_name_change = @organisation.organisation_name_changes.new(organisation_name_change_params) + @organisation_name_change.change_type = "User change" + + if @organisation_name_change.save + notice_message = @organisation_name_change.immediate_change ? "Name change saved successfully." : "Name change scheduled for #{@organisation_name_change.formatted_change_date}." + redirect_to organisation_path(@organisation), notice: notice_message + else + render template: "organisations/change_name", status: :unprocessable_entity + end + end + + def change_name + @organisation_name_change = OrganisationNameChange.new + render "organisations/change_name", layout: "application" + end + +private + + def set_organisation + @organisation = Organisation.find(params[:id]) + end + + def set_previous_name_changes + @previous_name_changes = @organisation.name_changes_with_dates + end + + def organisation_name_change_params + params.require(:organisation_name_change).permit(:name, :change_date, :immediate_change) + end +end diff --git a/app/helpers/tag_helper.rb b/app/helpers/tag_helper.rb index 110198550..5e1d3422e 100644 --- a/app/helpers/tag_helper.rb +++ b/app/helpers/tag_helper.rb @@ -7,10 +7,12 @@ module TagHelper in_progress: "In progress", completed: "Completed", active: "Active", + inactive: "Inactive", incomplete: "Incomplete", deactivating_soon: "Deactivating soon", activating_soon: "Activating soon", reactivating_soon: "Reactivating soon", + scheduled: "Scheduled", deactivated: "Deactivated", deleted: "Deleted", merged: "Merged", @@ -35,10 +37,12 @@ module TagHelper in_progress: "blue", completed: "green", active: "green", + inactive: "grey", incomplete: "red", deactivating_soon: "yellow", activating_soon: "blue", reactivating_soon: "blue", + scheduled: "blue", deactivated: "grey", deleted: "red", merged: "orange", diff --git a/app/models/organisation.rb b/app/models/organisation.rb index c3d0a8ca0..a135c4fa3 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -8,6 +8,7 @@ class Organisation < ApplicationRecord has_many :parent_organisations, through: :parent_organisation_relationships has_many :child_organisation_relationships, foreign_key: :parent_organisation_id, class_name: "OrganisationRelationship" has_many :child_organisations, through: :child_organisation_relationships + has_many :organisation_name_changes, dependent: :destroy has_many :stock_owner_relationships, foreign_key: :child_organisation_id, class_name: "OrganisationRelationship" has_many :stock_owners, through: :stock_owner_relationships, source: :parent_organisation @@ -71,6 +72,49 @@ class Organisation < ApplicationRecord end end + def name(date: Time.zone.now) + name_change = organisation_name_changes.find { |change| change.includes_date?(date) } + name_change&.name || read_attribute(:name) + end + + def name_changes_with_dates + changes = fetch_name_changes_with_dates + + if changes.any? + changes.unshift({ + name: self[:name], + start_date: created_at, + end_date: changes.first[:start_date]&.yesterday, + status: Time.zone.now.to_date < changes.first[:start_date].to_date ? "scheduled" : "inactive", + }) + else + changes << { name: self[:name], start_date: created_at, end_date: nil, status: "active" } + end + + changes.each do |change| + change[:status] ||= if change[:start_date].to_date > Time.zone.now.to_date + "scheduled" + elsif change[:end_date].nil? || change[:end_date].to_date >= Time.zone.now.to_date + "active" + else + "inactive" + end + end + + changes + end + + def fetch_name_changes_with_dates + organisation_name_changes.order(:change_date).map.with_index do |change, index| + next_change = organisation_name_changes.order(:change_date)[index + 1] + { + name: change.name, + start_date: change.change_date, + end_date: next_change&.change_date&.yesterday, + } + end + end + def can_be_managed_by?(organisation:) organisation == self || managing_agents.include?(organisation) end @@ -191,8 +235,9 @@ class Organisation < ApplicationRecord update!(discarded_at: Time.zone.now) end - def label - status == :deleted ? "#{name} (deleted)" : name + def label(date:) + date ||= Time.zone.now + status == :deleted ? "#{name(date:)} (deleted)" : name(date:) end def has_visible_users? diff --git a/app/models/organisation_name_change.rb b/app/models/organisation_name_change.rb new file mode 100644 index 000000000..5684ae213 --- /dev/null +++ b/app/models/organisation_name_change.rb @@ -0,0 +1,68 @@ +class OrganisationNameChange < ApplicationRecord + belongs_to :organisation + + scope :visible, -> { where(discarded_at: nil) } + + validates :name, presence: true + validates :change_date, presence: true, unless: -> { immediate_change } + validate :change_date_must_be_after_last_change_date + validate :name_must_be_different_from_current + + attr_accessor :immediate_change + + before_validation :set_change_date_if_immediate + + has_paper_trail + + def includes_date?(date) + change_date <= date && (next_change&.change_date.nil? || next_change&.change_date > date) + end + + def before_date?(date) + change_date < date + end + + def after_date?(date) + change_date > date + end + + def next_change + organisation.organisation_name_changes.where("change_date > ?", change_date).order(change_date: :asc).first + end + + def previous_change + organisation.organisation_name_changes.where("change_date < ?", change_date).order(change_date: :desc).first + end + + def active?(date = Time.zone.now) + includes_date?(date) + end + + def formatted_change_date(format = :govuk_date) + change_date.to_formatted_s(format) + end + +private + + def set_change_date_if_immediate + self.change_date = Time.zone.now if immediate_change + end + + def change_date_must_be_after_last_change_date + return if change_date.blank? + + last_change_date = organisation.organisation_name_changes.where("change_date < ?", change_date).order(change_date: :desc).first&.change_date + if last_change_date && change_date <= last_change_date + errors.add(:change_date, "Start date must be after the last change date (#{last_change_date}).") + end + end + + def name_must_be_different_from_current + return if name.blank? || change_date.blank? + + if name == organisation.name(date: change_date) + errors.add(:name, "New name must be different from the current name on the change date.") + end + end +end + diff --git a/app/views/organisations/change_name.html.erb b/app/views/organisations/change_name.html.erb index 7b118c1eb..bea97cd68 100644 --- a/app/views/organisations/change_name.html.erb +++ b/app/views/organisations/change_name.html.erb @@ -1,19 +1,66 @@ <% content_for :title, "Change #{@organisation.name}’s name" %> <% content_for :before_content do %> - <%= govuk_back_link(href: :back) %> + <%= govuk_back_link(href: details_organisation_path(@organisation)) %> <% end %> -<%= form_for(@organisation, as: :organisation, html: { method: :patch }) do |f| %> +<%= form_for(@organisation_name_change, url: change_name_organisation_path(@organisation), html: { method: :post }) do |f| %>
+ <%= f.govuk_error_summary %> +

<%= content_for(:title) %>

- <%= f.govuk_text_field :name, autocomplete: "name", label: { size: "m" } %> + <%= f.govuk_text_field :name, autocomplete: "name", label: { text: "Enter new name", size: "m" }, value: @organisation.name %> + + <%= govuk_details(summary_text: "View name history") do %> + <%= govuk_table do |table| %> + <%= table.with_head do |head| %> + <% head.with_row do |row| %> + <% row.with_cell(header: true, text: "Name") %> + <% row.with_cell(header: true, text: "Start Date") %> + <% row.with_cell(header: true, text: "End Date") %> + <% row.with_cell(header: true, text: "Status") %> + <% end %> + <% end %> + <% @previous_name_changes.each do |change| %> + <%= table.with_body do |body| %> + <% body.with_row do |row| %> + <% row.with_cell(text: change[:name]) %> + <% row.with_cell(text: change[:start_date]&.to_formatted_s(:govuk_date)) %> + <% row.with_cell(text: change[:end_date]&.to_formatted_s(:govuk_date) || "None") %> + <% row.with_cell text: status_tag(change[:status].to_sym) %> + <% end %> + <% end %> + <% end %> + <% end %> + <% end %> + + <%= f.govuk_radio_buttons_fieldset :immediate_change, + legend: { text: "Does this change take effect starting today?", size: "m" } do %> + <%= f.govuk_radio_button :immediate_change, "true", label: { text: "Yes" } %> + <%= f.govuk_radio_button :immediate_change, "false", + label: { text: "No" }, + "data-controller": "conditional-question", + "data-action": "click->conditional-question#displayConditional", + "data-info": { conditional_questions: { scheduled_date: [false] } }.to_json do %> + <%= render partial: "components/date_picker", locals: { + resource: @organisation_name_change, + question_id: :change_date, + legend: { text: "Set start date", size: "m" }, + resource_type: "organisation_name_change", + hint: "For example, 13/9/2025", + f: f + } %> + <% end %> + <% end %> - <%= f.govuk_submit "Save changes" %> +
+ <%= f.govuk_submit "Save changes" %> + <%= govuk_button_link_to "Cancel", details_organisation_path(@organisation), secondary: true %> +
<% end %> diff --git a/config/routes.rb b/config/routes.rb index e9b94c0bb..36331d60a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -156,7 +156,8 @@ Rails.application.routes.draw do get "details", to: "organisations#details" get "data-sharing-agreement", to: "organisations#data_sharing_agreement" post "data-sharing-agreement", to: "organisations#confirm_data_sharing_agreement" - get "change-name", to: "organisations#change_name", as: "change_name" + get "change-name", to: "organisation_name_changes#change_name", as: "change_name" + post "change-name", to: "organisation_name_changes#create" get "users", to: "organisations#users" get "lettings-logs", to: "organisations#lettings_logs"