Browse Source

Cldc 1668 multiple location deactivations (#996)

* Add location_deactivations table and remove deactivation_date from locations

* Add location location_deactivations relationship

* Update logs status determination for location

* Update affected logs with deactivated location.

* lint

* Update scheme

* Extract location_deactivation factory

* Update location availability

* lint

* Rename deactivations table

* fix schema

* rebase fixes

* fixes

* refactor
pull/1000/head
kosiakkatrina 2 years ago committed by GitHub
parent
commit
96c792b472
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      app/controllers/locations_controller.rb
  2. 11
      app/helpers/locations_helper.rb
  3. 2
      app/models/form/lettings/questions/location_id.rb
  4. 1
      app/models/lettings_log.rb
  5. 10
      app/models/location.rb
  6. 3
      app/models/location_deactivation_period.rb
  7. 2
      app/views/schemes/check_answers.html.erb
  8. 10
      db/migrate/20221115101732_add_deactivations_table.rb
  9. 13
      db/migrate/20221115113437_remove_deactivatio_date.rb
  10. 12
      db/schema.rb
  11. 5
      spec/factories/location_deactivation_period.rb
  12. 2
      spec/features/schemes_spec.rb
  13. 21
      spec/helpers/locations_helper_spec.rb
  14. 42
      spec/models/location_spec.rb
  15. 41
      spec/requests/locations_controller_spec.rb

8
app/controllers/locations_controller.rb

@ -43,7 +43,7 @@ class LocationsController < ApplicationController
def deactivate def deactivate
@location.run_deactivation_validations! @location.run_deactivation_validations!
if @location.update!(deactivation_date:) if @location.location_deactivation_periods.create!(deactivation_date:) && update_affected_logs
flash[:notice] = deactivate_success_notice flash[:notice] = deactivate_success_notice
end end
redirect_to scheme_location_path(@scheme, @location) redirect_to scheme_location_path(@scheme, @location)
@ -185,10 +185,14 @@ private
when :deactivated when :deactivated
"#{@location.name} has been deactivated" "#{@location.name} has been deactivated"
when :deactivating_soon when :deactivating_soon
"#{@location.name} will deactivate on #{@location.deactivation_date.to_formatted_s(:govuk_date)}" "#{@location.name} will deactivate on #{deactivation_date.to_time.to_formatted_s(:govuk_date)}"
end end
end end
def update_affected_logs
@location.lettings_logs.filter_by_before_startdate(deactivation_date.to_time).update!(location: nil, scheme: nil)
end
def deactivation_date def deactivation_date
if params[:location].blank? if params[:location].blank?
return return

11
app/helpers/locations_helper.rb

@ -32,8 +32,17 @@ module LocationsHelper
{ name: "Common type of unit", value: location.type_of_unit }, { name: "Common type of unit", value: location.type_of_unit },
{ name: "Mobility type", value: location.mobility_type }, { name: "Mobility type", value: location.mobility_type },
{ name: "Code", value: location.location_code }, { name: "Code", value: location.location_code },
{ name: "Availability", value: "Available from #{location.available_from.to_formatted_s(:govuk_date)}" }, { name: "Availability", value: location_availability(location) },
{ name: "Status", value: location.status }, { name: "Status", value: location.status },
] ]
end end
def location_availability(location)
availability = "Active from #{location.available_from.to_formatted_s(:govuk_date)}"
location.location_deactivation_periods.each do |deactivation|
availability << " to #{(deactivation.deactivation_date - 1.day).to_formatted_s(:govuk_date)}\nDeactivated on #{deactivation.deactivation_date.to_formatted_s(:govuk_date)}"
availability << "\nActive from #{deactivation.reactivation_date.to_formatted_s(:govuk_date)}" if deactivation.reactivation_date.present?
end
availability
end
end end

2
app/models/form/lettings/questions/location_id.rb

@ -23,7 +23,7 @@ class Form::Lettings::Questions::LocationId < ::Form::Question
end end
end end
def displayed_answer_options(lettings_log) def displayed_answer_options(lettings_log, _user = nil)
return {} unless lettings_log.scheme return {} unless lettings_log.scheme
scheme_location_ids = lettings_log.scheme.locations.pluck(:id) scheme_location_ids = lettings_log.scheme.locations.pluck(:id)

1
app/models/lettings_log.rb

@ -45,6 +45,7 @@ class LettingsLog < Log
.or(filter_by_postcode(param)) .or(filter_by_postcode(param))
.or(filter_by_id(param)) .or(filter_by_id(param))
} }
scope :filter_by_before_startdate, ->(date) { left_joins(:location).where("lettings_logs.startdate >= ?", date) }
AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at].freeze AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at].freeze
OPTIONAL_FIELDS = %w[first_time_property_let_as_social_housing tenancycode propcode].freeze OPTIONAL_FIELDS = %w[first_time_property_let_as_social_housing tenancycode propcode].freeze

10
app/models/location.rb

@ -4,6 +4,7 @@ class Location < ApplicationRecord
validates :units, :type_of_unit, :mobility_type, presence: true validates :units, :type_of_unit, :mobility_type, presence: true
belongs_to :scheme belongs_to :scheme
has_many :lettings_logs, class_name: "LettingsLog" has_many :lettings_logs, class_name: "LettingsLog"
has_many :location_deactivation_periods, class_name: "LocationDeactivationPeriod"
has_paper_trail has_paper_trail
@ -11,7 +12,7 @@ class Location < ApplicationRecord
auto_strip_attributes :name auto_strip_attributes :name
attr_accessor :add_another_location, :deactivation_date_type, :run_deactivation_validations attr_accessor :add_another_location, :deactivation_date_type, :deactivation_date, :run_deactivation_validations
scope :search_by_postcode, ->(postcode) { where("REPLACE(postcode, ' ', '') ILIKE ?", "%#{postcode.delete(' ')}%") } scope :search_by_postcode, ->(postcode) { where("REPLACE(postcode, ' ', '') ILIKE ?", "%#{postcode.delete(' ')}%") }
scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") } scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") }
@ -374,8 +375,9 @@ class Location < ApplicationRecord
end end
def status def status
return :active if deactivation_date.blank? recent_deactivation = location_deactivation_periods.deactivations_without_reactivation.first
return :deactivating_soon if Time.zone.now < deactivation_date return :active if recent_deactivation.blank?
return :deactivating_soon if Time.zone.now < recent_deactivation.deactivation_date
:deactivated :deactivated
end end
@ -403,7 +405,7 @@ class Location < ApplicationRecord
end end
else else
collection_start_date = FormHandler.instance.current_collection_start_date collection_start_date = FormHandler.instance.current_collection_start_date
unless deactivation_date.between?(collection_start_date, Date.new(2200, 1, 1)) unless deactivation_date.between?(collection_start_date, Time.zone.local(2200, 1, 1))
errors.add(:deactivation_date, message: I18n.t("validations.location.deactivation_date.out_of_range", date: collection_start_date.to_formatted_s(:govuk_date))) errors.add(:deactivation_date, message: I18n.t("validations.location.deactivation_date.out_of_range", date: collection_start_date.to_formatted_s(:govuk_date)))
end end
end end

3
app/models/location_deactivation_period.rb

@ -0,0 +1,3 @@
class LocationDeactivationPeriod < ApplicationRecord
scope :deactivations_without_reactivation, -> { where(reactivation_date: nil) }
end

2
app/views/schemes/check_answers.html.erb

@ -73,7 +73,7 @@
<% row.cell(text: simple_format("<span>#{location.type_of_unit}</span>")) %> <% row.cell(text: simple_format("<span>#{location.type_of_unit}</span>")) %>
<% row.cell(text: location.mobility_type) %> <% row.cell(text: location.mobility_type) %>
<% row.cell(text: simple_format(location_cell_location_admin_district(location, get_location_change_link_href_location_admin_district(@scheme, location)), wrapper_tag: "div")) %> <% row.cell(text: simple_format(location_cell_location_admin_district(location, get_location_change_link_href_location_admin_district(@scheme, location)), wrapper_tag: "div")) %>
<% row.cell(text: location.startdate&.to_formatted_s(:govuk_date)) %> <% row.cell(text: location_availability(location)) %>
<% end %> <% end %>
<% end %> <% end %>
<% end %> <% end %>

10
db/migrate/20221115101732_add_deactivations_table.rb

@ -0,0 +1,10 @@
class AddDeactivationsTable < ActiveRecord::Migration[7.0]
def change
create_table :location_deactivation_periods do |t|
t.datetime :deactivation_date
t.datetime :reactivation_date
t.belongs_to :location
t.timestamps
end
end
end

13
db/migrate/20221115113437_remove_deactivatio_date.rb

@ -0,0 +1,13 @@
class RemoveDeactivatioDate < ActiveRecord::Migration[7.0]
def up
change_table :locations, bulk: true do |t|
t.remove :deactivation_date
end
end
def down
change_table :locations, bulk: true do |t|
t.column :deactivation_date, :datetime
end
end
end

12
db/schema.rb

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2022_11_11_102656) do ActiveRecord::Schema[7.0].define(version: 2022_11_15_113437) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -245,6 +245,15 @@ ActiveRecord::Schema[7.0].define(version: 2022_11_11_102656) do
t.index ["scheme_id"], name: "index_lettings_logs_on_scheme_id" t.index ["scheme_id"], name: "index_lettings_logs_on_scheme_id"
end end
create_table "location_deactivation_periods", force: :cascade do |t|
t.datetime "deactivation_date"
t.datetime "reactivation_date"
t.bigint "location_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["location_id"], name: "index_location_deactivation_periods_on_location_id"
end
create_table "locations", force: :cascade do |t| create_table "locations", force: :cascade do |t|
t.string "location_code" t.string "location_code"
t.string "postcode" t.string "postcode"
@ -260,7 +269,6 @@ ActiveRecord::Schema[7.0].define(version: 2022_11_11_102656) do
t.datetime "startdate", precision: nil t.datetime "startdate", precision: nil
t.string "location_admin_district" t.string "location_admin_district"
t.boolean "confirmed" t.boolean "confirmed"
t.datetime "deactivation_date"
t.index ["old_id"], name: "index_locations_on_old_id", unique: true t.index ["old_id"], name: "index_locations_on_old_id", unique: true
t.index ["scheme_id"], name: "index_locations_on_scheme_id" t.index ["scheme_id"], name: "index_locations_on_scheme_id"
end end

5
spec/factories/location_deactivation_period.rb

@ -0,0 +1,5 @@
FactoryBot.define do
factory :location_deactivation_period do
reactivation_date { nil }
end
end

2
spec/features/schemes_spec.rb

@ -766,7 +766,7 @@ RSpec.describe "Schemes scheme Features" do
expect(page).to have_content(location.type_of_unit) expect(page).to have_content(location.type_of_unit)
expect(page).to have_content(location.mobility_type) expect(page).to have_content(location.mobility_type)
expect(page).to have_content(location.location_code) expect(page).to have_content(location.location_code)
expect(page).to have_content("Available from 4 April 2022") expect(page).to have_content("Active from 4 April 2022")
expect(page).to have_content("Active") expect(page).to have_content("Active")
end end

21
spec/helpers/locations_helper_spec.rb

@ -59,18 +59,35 @@ RSpec.describe LocationsHelper do
{ name: "Common type of unit", value: location.type_of_unit }, { name: "Common type of unit", value: location.type_of_unit },
{ name: "Mobility type", value: location.mobility_type }, { name: "Mobility type", value: location.mobility_type },
{ name: "Code", value: location.location_code }, { name: "Code", value: location.location_code },
{ name: "Availability", value: "Available from 8 August 2022" }, { name: "Availability", value: "Active from 8 August 2022" },
{ name: "Status", value: :active }, { name: "Status", value: :active },
] ]
expect(display_location_attributes(location)).to eq(attributes) expect(display_location_attributes(location)).to eq(attributes)
end end
context "when viewing availability" do
context "with are no deactivations" do
it "displays created_at as availability date if startdate is not present" do it "displays created_at as availability date if startdate is not present" do
location.update!(startdate: nil) location.update!(startdate: nil)
availability_attribute = display_location_attributes(location).find { |x| x[:name] == "Availability" }[:value] availability_attribute = display_location_attributes(location).find { |x| x[:name] == "Availability" }[:value]
expect(availability_attribute).to eq("Available from #{location.created_at.to_formatted_s(:govuk_date)}") expect(availability_attribute).to eq("Active from #{location.created_at.to_formatted_s(:govuk_date)}")
end
end
context "with previous deactivations" do
before do
location.location_deactivation_periods << FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 8, 10), reactivation_date: Time.zone.local(2022, 9, 1))
location.location_deactivation_periods << FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 9, 15), reactivation_date: nil)
end
it "displays the timeline of availability" do
availability_attribute = display_location_attributes(location).find { |x| x[:name] == "Availability" }[:value]
expect(availability_attribute).to eq("Active from 8 August 2022 to 9 August 2022\nDeactivated on 10 August 2022\nActive from 1 September 2022 to 14 September 2022\nDeactivated on 15 September 2022")
end
end
end end
end end
end end

42
spec/models/location_spec.rb

@ -119,34 +119,58 @@ RSpec.describe Location, type: :model do
Timecop.freeze(2022, 6, 7) Timecop.freeze(2022, 6, 7)
end end
it "returns active if the location is not deactivated" do context "when there have not been any previous deactivations" do
location.deactivation_date = nil it "returns active if the location has no deactivation records" do
location.deactivation_date_type = nil expect(location.status).to eq(:active)
end
it "returns deactivating soon if deactivation_date is in the future" do
location.location_deactivation_periods << FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 8, 8))
location.save!
expect(location.status).to eq(:deactivating_soon)
end
it "returns deactivated if deactivation_date is in the past" do
location.location_deactivation_periods << FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 6))
location.save!
expect(location.status).to eq(:deactivated)
end
it "returns deactivated if deactivation_date is today" do
location.location_deactivation_periods << FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 7))
location.save! location.save!
expect(location.status).to eq(:deactivated)
end
end
context "when there have been previous deactivations" do
before do
location.location_deactivation_periods << FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), reactivation_date: Time.zone.local(2022, 6, 5))
end
it "returns active if the location has no relevant deactivation records" do
expect(location.status).to eq(:active) expect(location.status).to eq(:active)
end end
it "returns deactivating soon if deactivation_date is in the future" do it "returns deactivating soon if deactivation_date is in the future" do
location.deactivation_date = Time.zone.local(2022, 8, 8) location.location_deactivation_periods << FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 8, 8))
location.deactivation_date_type = "other"
location.save! location.save!
expect(location.status).to eq(:deactivating_soon) expect(location.status).to eq(:deactivating_soon)
end end
it "returns deactivated if deactivation_date is in the past" do it "returns deactivated if deactivation_date is in the past" do
location.deactivation_date = Time.zone.local(2022, 4, 8) location.location_deactivation_periods << FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 6))
location.deactivation_date_type = "other"
location.save! location.save!
expect(location.status).to eq(:deactivated) expect(location.status).to eq(:deactivated)
end end
it "returns deactivated if deactivation_date is today" do it "returns deactivated if deactivation_date is today" do
location.deactivation_date = Time.zone.local(2022, 6, 7) location.location_deactivation_periods << FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 7))
location.deactivation_date_type = "other"
location.save! location.save!
expect(location.status).to eq(:deactivated) expect(location.status).to eq(:deactivated)
end end
end end
end
describe "with deactivation_date (but no deactivation_date_type)" do describe "with deactivation_date (but no deactivation_date_type)" do
let(:location) { FactoryBot.create(:location, deactivation_date: Date.new(2022, 4, 1)) } let(:location) { FactoryBot.create(:location, deactivation_date: Date.new(2022, 4, 1)) }

41
spec/requests/locations_controller_spec.rb

@ -1239,8 +1239,9 @@ RSpec.describe LocationsController, type: :request do
let(:user) { FactoryBot.create(:user, :data_coordinator) } let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:location) { FactoryBot.create(:location, scheme:) } let!(:location) { FactoryBot.create(:location, scheme:) }
let(:startdate) { Time.utc(2021, 1, 2) }
let(:deactivation_date) { Time.utc(2022, 10, 10) } let(:deactivation_date) { Time.utc(2022, 10, 10) }
let!(:lettings_log) { FactoryBot.create(:lettings_log, :sh, location:, scheme:, startdate:, owning_organisation: user.organisation) }
let(:startdate) { Time.utc(2022, 10, 11) }
before do before do
Timecop.freeze(Time.utc(2022, 10, 10)) Timecop.freeze(Time.utc(2022, 10, 10))
@ -1282,7 +1283,30 @@ RSpec.describe LocationsController, type: :request do
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success") expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success")
location.reload location.reload
expect(location.deactivation_date).to eq(deactivation_date) expect(location.location_deactivation_periods.count).to eq(1)
expect(location.location_deactivation_periods.first.deactivation_date).to eq(deactivation_date)
end
context "and a log startdate is after location deactivation date" do
it "clears the location and scheme answers" do
expect(lettings_log.location).to eq(location)
expect(lettings_log.scheme).to eq(scheme)
lettings_log.reload
expect(lettings_log.location).to eq(nil)
expect(lettings_log.scheme).to eq(nil)
end
end
context "and a log startdate is before location deactivation date" do
let(:startdate) { Time.utc(2022, 10, 9) }
it "does not update the log" do
expect(lettings_log.location).to eq(location)
expect(lettings_log.scheme).to eq(scheme)
lettings_log.reload
expect(lettings_log.location).to eq(location)
expect(lettings_log.scheme).to eq(scheme)
end
end end
end end
@ -1368,19 +1392,18 @@ RSpec.describe LocationsController, type: :request do
let(:user) { FactoryBot.create(:user, :data_coordinator) } let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:location) { FactoryBot.create(:location, scheme:) } let!(:location) { FactoryBot.create(:location, scheme:) }
let(:add_deactivations) { location.location_deactivation_periods << location_deactivation_period }
before do before do
Timecop.freeze(Time.utc(2022, 10, 10)) Timecop.freeze(Time.utc(2022, 10, 10))
sign_in user sign_in user
location.deactivation_date = deactivation_date add_deactivations
location.deactivation_date_type = deactivation_date_type
location.save! location.save!
get "/schemes/#{scheme.id}/locations/#{location.id}" get "/schemes/#{scheme.id}/locations/#{location.id}"
end end
context "with active location" do context "with active location" do
let(:deactivation_date) { nil } let(:add_deactivations) {}
let(:deactivation_date_type) { nil }
it "renders deactivate this location" do it "renders deactivate this location" do
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
@ -1389,8 +1412,7 @@ RSpec.describe LocationsController, type: :request do
end end
context "with deactivated location" do context "with deactivated location" do
let(:deactivation_date) { Time.utc(2022, 10, 9) } let(:location_deactivation_period) { FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 10, 9)) }
let(:deactivation_date_type) { "other" }
it "renders reactivate this location" do it "renders reactivate this location" do
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
@ -1399,8 +1421,7 @@ RSpec.describe LocationsController, type: :request do
end end
context "with location that's deactivating soon" do context "with location that's deactivating soon" do
let(:deactivation_date) { Time.utc(2022, 10, 12) } let(:location_deactivation_period) { FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 10, 12)) }
let(:deactivation_date_type) { "other" }
it "renders reactivate this location" do it "renders reactivate this location" do
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)

Loading…
Cancel
Save