Browse Source

Implement organisation name change feature with history tracking

pull/3053/head
Manny Dinssa 3 weeks ago
parent
commit
d59920d619
  1. 35
      app/controllers/organisation_name_changes_controller.rb
  2. 4
      app/helpers/tag_helper.rb
  3. 49
      app/models/organisation.rb
  4. 68
      app/models/organisation_name_change.rb
  5. 55
      app/views/organisations/change_name.html.erb
  6. 3
      config/routes.rb

35
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

4
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",

49
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?

68
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

55
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| %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= f.govuk_error_summary %>
<h1 class="govuk-heading-l">
<%= content_for(:title) %>
</h1>
<%= 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" %>
<div class="govuk-button-group">
<%= f.govuk_submit "Save changes" %>
<%= govuk_button_link_to "Cancel", details_organisation_path(@organisation), secondary: true %>
</div>
</div>
</div>
<% end %>

3
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"

Loading…
Cancel
Save