Browse Source

Merge branch 'main' into CLDC-3294-address-flow-tests

pull/2805/head
kosiakkatrina 1 year ago committed by GitHub
parent
commit
fb48840070
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      Gemfile
  2. 5
      app/components/create_log_actions_component.html.erb
  3. 8
      app/components/create_log_actions_component.rb
  4. 14
      app/controllers/lettings_logs_controller.rb
  5. 1
      app/controllers/merge_requests_controller.rb
  6. 2
      app/controllers/organisations_controller.rb
  7. 14
      app/controllers/sales_logs_controller.rb
  8. 6
      app/controllers/schemes_controller.rb
  9. 1
      app/controllers/users_controller.rb
  10. 8
      app/helpers/filters_helper.rb
  11. 4
      app/helpers/merge_requests_helper.rb
  12. 20
      app/helpers/schemes_helper.rb
  13. 5
      app/models/derived_variables/lettings_log_variables.rb
  14. 5
      app/models/derived_variables/sales_log_variables.rb
  15. 1
      app/models/form/lettings/subsections/household_needs.rb
  16. 13
      app/models/form/sales/pages/estate_management_fee.rb
  17. 12
      app/models/form/sales/pages/living_before_purchase.rb
  18. 24
      app/models/form/sales/questions/has_management_fee.rb
  19. 12
      app/models/form/sales/questions/management_fee.rb
  20. 10
      app/models/form/sales/sections/sale_information.rb
  21. 48
      app/models/form/sales/subsections/shared_ownership_initial_purchase.rb
  22. 2
      app/models/form/sales/subsections/shared_ownership_scheme.rb
  23. 8
      app/models/location.rb
  24. 7
      app/models/merge_request_organisation.rb
  25. 8
      app/models/scheme.rb
  26. 15
      app/policies/location_policy.rb
  27. 15
      app/policies/scheme_policy.rb
  28. 2
      app/services/bulk_upload/lettings/year2024/csv_parser.rb
  29. 2
      app/services/bulk_upload/sales/year2024/csv_parser.rb
  30. 4
      app/services/feature_toggle.rb
  31. 6
      app/views/form/guidance/_financial_calculations_shared_ownership.html.erb
  32. 15
      app/views/locations/index.html.erb
  33. 2
      app/views/locations/show.html.erb
  34. 8
      app/views/merge_requests/_notification_banners.html.erb
  35. 2
      app/views/merge_requests/show.html.erb
  36. 6
      app/views/schemes/details.html.erb
  37. 2
      app/views/schemes/show.html.erb
  38. 4
      config/locales/en.yml
  39. 21
      config/locales/forms/2025/sales/sale_information.en.yml
  40. 5
      config/routes.rb
  41. 8
      db/migrate/20241114154215_add_management_fee_fields.rb
  42. 2
      db/schema.rb
  43. 2
      db/seeds.rb
  44. 5
      spec/features/accessibility_spec.rb
  45. 2
      spec/features/schemes_spec.rb
  46. 72
      spec/models/form/sales/pages/living_before_purchase_spec.rb
  47. 28
      spec/models/form/sales/sections/sale_information_spec.rb
  48. 95
      spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb
  49. 27
      spec/models/lettings_log_spec.rb
  50. 5
      spec/models/location_spec.rb
  51. 29
      spec/models/sales_log_spec.rb
  52. 5
      spec/models/scheme_spec.rb
  53. 34
      spec/requests/merge_requests_controller_spec.rb
  54. 86
      spec/requests/schemes_controller_spec.rb
  55. 3
      spec/requests/users_controller_spec.rb
  56. 23
      spec/services/bulk_upload/lettings/year2024/csv_parser_spec.rb
  57. 32
      spec/services/bulk_upload/sales/year2024/csv_parser_spec.rb

4
Gemfile

@ -62,6 +62,8 @@ gem "possessive"
# Strip whitespace from active record attributes
gem "auto_strip_attributes"
# Use sidekiq for background processing
gem "factory_bot_rails"
gem "faker"
gem "method_source", "~> 1.1"
gem "rails_admin", "~> 3.1"
gem "ruby-openai"
@ -75,8 +77,6 @@ group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem "byebug", platforms: %i[mri mingw x64_mingw]
gem "dotenv-rails"
gem "factory_bot_rails"
gem "faker"
gem "pry-byebug"
gem "parallel_tests"

5
app/components/create_log_actions_component.html.erb

@ -7,5 +7,10 @@
<% if user.support? %>
<%= govuk_button_link_to view_uploads_button_copy, view_uploads_button_href, secondary: true %>
<% end %>
<% if FeatureToggle.create_test_logs_enabled? %>
<%= govuk_button_link_to "Create test log", create_test_log_href, secondary: true %>
<%= govuk_button_link_to "Create test log (setup only)", create_setup_test_log_href, secondary: true %>
<% end %>
<% end %>
</div>

8
app/components/create_log_actions_component.rb

@ -34,6 +34,14 @@ class CreateLogActionsComponent < ViewComponent::Base
send("bulk_upload_#{log_type}_log_path", id: "start")
end
def create_test_log_href
send("create_test_#{log_type}_log_path")
end
def create_setup_test_log_href
send("create_setup_test_#{log_type}_log_path")
end
def view_uploads_button_copy
"View #{log_type} bulk uploads"
end

14
app/controllers/lettings_logs_controller.rb

@ -149,6 +149,20 @@ class LettingsLogsController < LogsController
end
end
def create_test_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?
log = FactoryBot.create(:lettings_log, :completed, assigned_to: current_user, ppostcode_full: "SW1A 1AA")
redirect_to lettings_log_path(log)
end
def create_setup_test_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?
log = FactoryBot.create(:lettings_log, :setup_completed, assigned_to: current_user)
redirect_to lettings_log_path(log)
end
private
def session_filters

1
app/controllers/merge_requests_controller.rb

@ -143,6 +143,7 @@ private
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)
@merge_request.errors.add(:merge_date, :more_than_year_from_today) if Time.zone.local(year.to_i, month.to_i, day.to_i) - 1.year > Time.zone.today
else
@merge_request.errors.add(:merge_date, :invalid)
end

2
app/controllers/organisations_controller.rb

@ -24,7 +24,7 @@ class OrganisationsController < ApplicationController
end
def schemes
organisation_schemes = Scheme.visible.where(owning_organisation: [@organisation] + @organisation.parent_organisations)
organisation_schemes = Scheme.visible.where(owning_organisation: [@organisation] + @organisation.parent_organisations + @organisation.absorbed_organisations.visible.merged_during_open_collection_period)
@pagy, @schemes = pagy(filter_manager.filtered_schemes(organisation_schemes, search_term, session_filters))
@searched = search_term.presence

14
app/controllers/sales_logs_controller.rb

@ -119,6 +119,20 @@ class SalesLogsController < LogsController
end
end
def create_test_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?
log = FactoryBot.create(:sales_log, :completed, assigned_to: current_user)
redirect_to sales_log_path(log)
end
def create_setup_test_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?
log = FactoryBot.create(:sales_log, :shared_ownership_setup_complete, assigned_to: current_user)
redirect_to sales_log_path(log)
end
private
def session_filters

6
app/controllers/schemes_controller.rb

@ -118,6 +118,10 @@ class SchemesController < ApplicationController
validation_errors scheme_params
if @scheme.errors.empty? && @scheme.save
if @scheme.owning_organisation.merge_date.present?
deactivation = SchemeDeactivationPeriod.new(scheme: @scheme, deactivation_date: @scheme.owning_organisation.merge_date)
deactivation.save!(validate: false)
end
redirect_to scheme_primary_client_group_path(@scheme)
else
if @scheme.errors.any? { |error| error.attribute == :owning_organisation }
@ -152,7 +156,7 @@ class SchemesController < ApplicationController
flash[:notice] = if scheme_previously_confirmed
"#{@scheme.service_name} has been updated."
else
"#{@scheme.service_name} has been created. It does not require helpdesk approval."
"#{@scheme.service_name} has been created."
end
redirect_to scheme_path(@scheme)
end

1
app/controllers/users_controller.rb

@ -114,6 +114,7 @@ class UsersController < ApplicationController
validate_attributes
if @user.errors.empty? && @user.save
flash[:notice] = "Invitation sent to #{@user.email}"
redirect_to created_user_redirect_path
else
unless @user.errors[:organisation].empty?

8
app/helpers/filters_helper.rb

@ -192,9 +192,15 @@ module FiltersHelper
end
def show_scheme_managing_org_filter?(user)
return true if user.support?
org = user.organisation
stock_owners = org.stock_owners.count
recently_absorbed_with_stock = org.absorbed_organisations.visible.merged_during_open_collection_period.where(holds_own_stock: true).count
relevant_orgs_count = stock_owners + recently_absorbed_with_stock + (org.holds_own_stock? ? 1 : 0)
user.support? || org.stock_owners.count > 1 || (org.holds_own_stock? && org.stock_owners.count.positive?)
relevant_orgs_count > 1
end
def logs_for_both_needstypes_present?(organisation)

4
app/helpers/merge_requests_helper.rb

@ -276,4 +276,8 @@ module MergeRequestsHelper
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
def begin_merge_disabled?(merge_request)
merge_request.status != "ready_to_merge" || merge_request.merge_date.future?
end
end

20
app/helpers/schemes_helper.rb

@ -20,11 +20,14 @@ module SchemesHelper
end
def owning_organisation_options(current_user)
all_orgs = Organisation.visible.map { |org| OpenStruct.new(id: org.id, name: org.name) }
user_org = [OpenStruct.new(id: current_user.organisation_id, name: current_user.organisation.name)]
stock_owners = current_user.organisation.stock_owners.visible.map { |org| OpenStruct.new(id: org.id, name: org.name) }
merged_organisations = current_user.organisation.absorbed_organisations.visible.merged_during_open_collection_period.map { |org| OpenStruct.new(id: org.id, name: org.name) }
current_user.support? ? all_orgs : user_org + stock_owners + merged_organisations
if current_user.support?
Organisation.visible.map { |org| OpenStruct.new(id: org.id, name: org.name) }
else
user_org = [current_user.organisation]
stock_owners = current_user.organisation.stock_owners.visible.filter { |org| org.status == :active || (org.status == :merged && org.merge_date >= FormHandler.instance.start_date_of_earliest_open_for_editing_collection_period) }
merged_organisations = current_user.organisation.absorbed_organisations.visible.merged_during_open_collection_period
(user_org + stock_owners + merged_organisations).map { |org| OpenStruct.new(id: org.id, name: org.name) }
end
end
def null_option
@ -81,7 +84,12 @@ module SchemesHelper
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."
case scheme.owning_organisation.status
when :active
"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."
when :merged
"This scheme has been deactivated due to #{scheme.owning_organisation.name} merging into #{scheme.owning_organisation.absorbing_organisation.name} on #{scheme.owning_organisation.merge_date.to_formatted_s(:govuk_date)}. Any locations you add will be deactivated on the same date. Use the after merge organisation for schemes and locations active after this date."
end
end
end

5
app/models/derived_variables/lettings_log_variables.rb

@ -133,6 +133,11 @@ module DerivedVariables::LettingsLogVariables
self.nationality_all = nationality_all_group if nationality_uk_or_prefers_not_to_say?
if startdate_changed? && !LocalAuthority.active(startdate).where(code: la).exists?
self.la = nil
self.is_la_inferred = false
end
reset_address_fields! if is_supported_housing?
end

5
app/models/derived_variables/sales_log_variables.rb

@ -84,6 +84,11 @@ module DerivedVariables::SalesLogVariables
self.nationality_all = nationality_all_group if nationality_uk_or_prefers_not_to_say?
self.nationality_all_buyer2 = nationality_all_buyer2_group if nationality2_uk_or_prefers_not_to_say?
if saledate_changed? && !LocalAuthority.active(saledate).where(code: la).exists?
self.la = nil
self.is_la_inferred = false
end
set_encoded_derived_values!(DEPENDENCIES)
end

1
app/models/form/lettings/subsections/household_needs.rb

@ -2,7 +2,6 @@ class Form::Lettings::Subsections::HouseholdNeeds < ::Form::Subsection
def initialize(id, hsh, section)
super
@id = "household_needs"
@copy_key = "lettings.household_needs.housingneeds_type"
@label = "Household needs"
@depends_on = [{ "non_location_setup_questions_completed?" => true }]
end

13
app/models/form/sales/pages/estate_management_fee.rb

@ -0,0 +1,13 @@
class Form::Sales::Pages::EstateManagementFee < ::Form::Page
def initialize(id, hsh, subsection)
super
@copy_key = "sales.sale_information.management_fee"
end
def questions
@questions ||= [
Form::Sales::Questions::HasManagementFee.new(nil, nil, self),
Form::Sales::Questions::ManagementFee.new(nil, nil, self),
]
end
end

12
app/models/form/sales/pages/living_before_purchase.rb

@ -19,11 +19,17 @@ class Form::Sales::Pages::LivingBeforePurchase < ::Form::Page
end
end
def depends_on
def routed_to?(log, _user)
super && page_routed_to?(log)
end
def page_routed_to?(log)
return false if form.start_year_2025_or_later? && log.resale != 2
if @joint_purchase
[{ "joint_purchase?" => true }]
log.joint_purchase?
else
[{ "not_joint_purchase?" => true }, { "jointpur" => nil }]
log.not_joint_purchase? || log.jointpur.nil?
end
end
end

24
app/models/form/sales/questions/has_management_fee.rb

@ -0,0 +1,24 @@
class Form::Sales::Questions::HasManagementFee < ::Form::Question
def initialize(id, hsh, subsection)
super
@id = "has_management_fee"
@copy_key = "sales.sale_information.management_fee.has_management_fee"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@conditional_for = {
"management_fee" => [1],
}
@hidden_in_check_answers = {
"depends_on" => [
{
"has_management_fee" => 1,
},
],
}
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"0" => { "value" => "No" },
}.freeze
end

12
app/models/form/sales/questions/management_fee.rb

@ -0,0 +1,12 @@
class Form::Sales::Questions::ManagementFee < ::Form::Question
def initialize(id, hsh, subsection)
super
@id = "management_fee"
@copy_key = "sales.sale_information.management_fee.management_fee"
@type = "numeric"
@min = 1
@step = 0.01
@width = 5
@prefix = "£"
end
end

10
app/models/form/sales/sections/sale_information.rb

@ -5,9 +5,17 @@ class Form::Sales::Sections::SaleInformation < ::Form::Section
@label = "Sale information"
@description = ""
@subsections = [
Form::Sales::Subsections::SharedOwnershipScheme.new(nil, nil, self),
shared_ownership_scheme_subsection,
Form::Sales::Subsections::DiscountedOwnershipScheme.new(nil, nil, self),
Form::Sales::Subsections::OutrightSale.new(nil, nil, self),
] || []
end
def shared_ownership_scheme_subsection
if form.start_year_2025_or_later?
Form::Sales::Subsections::SharedOwnershipInitialPurchase.new(nil, nil, self)
else
Form::Sales::Subsections::SharedOwnershipScheme.new(nil, nil, self)
end
end
end

48
app/models/form/sales/subsections/shared_ownership_initial_purchase.rb

@ -0,0 +1,48 @@
class Form::Sales::Subsections::SharedOwnershipInitialPurchase < ::Form::Subsection
def initialize(id, hsh, section)
super
@id = "shared_ownership_initial_purchase"
@label = "Shared ownership - initial purchase"
@depends_on = [{ "ownershipsch" => 1, "setup_completed?" => true, "staircase" => 2 }]
end
def pages
@pages ||= [
Form::Sales::Pages::Resale.new(nil, nil, self),
Form::Sales::Pages::LivingBeforePurchase.new("living_before_purchase_shared_ownership_joint_purchase", nil, self, ownershipsch: 1, joint_purchase: true),
Form::Sales::Pages::LivingBeforePurchase.new("living_before_purchase_shared_ownership", nil, self, ownershipsch: 1, joint_purchase: false),
Form::Sales::Pages::HandoverDate.new(nil, nil, self),
Form::Sales::Pages::HandoverDateCheck.new(nil, nil, self),
Form::Sales::Pages::BuyerPrevious.new("buyer_previous_joint_purchase", nil, self, joint_purchase: true),
Form::Sales::Pages::BuyerPrevious.new("buyer_previous_not_joint_purchase", nil, self, joint_purchase: false),
Form::Sales::Pages::PreviousBedrooms.new(nil, nil, self),
Form::Sales::Pages::PreviousPropertyType.new(nil, nil, self),
Form::Sales::Pages::PreviousTenure.new(nil, nil, self),
Form::Sales::Pages::ValueSharedOwnership.new(nil, nil, self),
Form::Sales::Pages::AboutPriceValueCheck.new("about_price_shared_ownership_value_check", nil, self),
Form::Sales::Pages::Equity.new(nil, nil, self),
Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_equity_value_check", nil, self),
Form::Sales::Pages::Mortgageused.new("mortgage_used_shared_ownership", nil, self, ownershipsch: 1),
Form::Sales::Pages::MortgageValueCheck.new("mortgage_used_mortgage_value_check", nil, self),
Form::Sales::Pages::MortgageAmount.new("mortgage_amount_shared_ownership", nil, self, ownershipsch: 1),
Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_mortgage_amount_value_check", nil, self),
Form::Sales::Pages::MortgageValueCheck.new("mortgage_amount_mortgage_value_check", nil, self),
Form::Sales::Pages::MortgageLength.new("mortgage_length_shared_ownership", nil, self, ownershipsch: 1),
Form::Sales::Pages::Deposit.new("deposit_shared_ownership", nil, self, ownershipsch: 1, optional: false),
Form::Sales::Pages::Deposit.new("deposit_shared_ownership_optional", nil, self, ownershipsch: 1, optional: true),
Form::Sales::Pages::DepositValueCheck.new("deposit_joint_purchase_value_check", nil, self, joint_purchase: true),
Form::Sales::Pages::DepositValueCheck.new("deposit_value_check", nil, self, joint_purchase: false),
Form::Sales::Pages::DepositDiscount.new("deposit_discount", nil, self, optional: false),
Form::Sales::Pages::DepositDiscount.new("deposit_discount_optional", nil, self, optional: true),
Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_deposit_value_check", nil, self),
Form::Sales::Pages::MonthlyRent.new(nil, nil, self),
Form::Sales::Pages::LeaseholdCharges.new("leasehold_charges_shared_ownership", nil, self, ownershipsch: 1),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_shared_ownership_value_check", nil, self),
Form::Sales::Pages::EstateManagementFee.new("estate_management_fee", nil, self),
].compact
end
def displayed_in_tasklist?(log)
log.staircase == 2 && (log.ownershipsch.nil? || log.ownershipsch == 1)
end
end

2
app/models/form/sales/subsections/shared_ownership_scheme.rb

@ -11,7 +11,7 @@ class Form::Sales::Subsections::SharedOwnershipScheme < ::Form::Subsection
@pages ||= [
Form::Sales::Pages::LivingBeforePurchase.new("living_before_purchase_shared_ownership_joint_purchase", nil, self, ownershipsch: 1, joint_purchase: true),
Form::Sales::Pages::LivingBeforePurchase.new("living_before_purchase_shared_ownership", nil, self, ownershipsch: 1, joint_purchase: false),
(Form::Sales::Pages::Staircase.new(nil, nil, self) unless form.start_year_2025_or_later?),
Form::Sales::Pages::Staircase.new(nil, nil, self),
Form::Sales::Pages::AboutStaircase.new("about_staircasing_joint_purchase", nil, self, joint_purchase: true),
Form::Sales::Pages::AboutStaircase.new("about_staircasing_not_joint_purchase", nil, self, joint_purchase: false),
Form::Sales::Pages::StaircaseBoughtValueCheck.new(nil, nil, self),

8
app/models/location.rb

@ -54,13 +54,13 @@ class Location < ApplicationRecord
}
scope :deactivated, lambda { |date = Time.zone.now|
deactivated_by_organisation
deactivated_by_organisation(date)
.or(deactivated_directly(date))
.or(deactivated_by_scheme(date))
}
scope :deactivated_by_organisation, lambda {
merge(Organisation.filter_by_inactive)
scope :deactivated_by_organisation, lambda { |date = Time.zone.now|
merge(Organisation.filter_by_inactive.or(Organisation.where("merge_date <= ?", date)))
}
scope :deactivated_by_scheme, lambda { |date = Time.zone.now|
@ -206,7 +206,7 @@ class Location < ApplicationRecord
def status_at(date)
return :deleted if discarded_at.present?
return :incomplete unless confirmed
return :deactivated if scheme.owning_organisation.status_at(date) == :deactivated ||
return :deactivated if scheme.owning_organisation.status_at(date) == :deactivated || scheme.owning_organisation.status_at(date) == :merged ||
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

7
app/models/merge_request_organisation.rb

@ -29,5 +29,12 @@ private
if merging_organisation_id.blank? || !Organisation.where(id: merging_organisation_id).exists?
merge_request.errors.add(:merging_organisation, I18n.t("validations.merge_request.organisation_not_selected"))
end
existing_merges = MergeRequestOrganisation.with_merging_organisation(merging_organisation)
if existing_merges.count.positive?
existing_merge_request = existing_merges.first.merge_request
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_incomplete_merge", organisation: merging_organisation.name, absorbing_organisation: existing_merge_request.absorbing_organisation&.name, merge_date: existing_merge_request.merge_date&.to_fs(:govuk_date)))
end
end
end

8
app/models/scheme.rb

@ -57,8 +57,8 @@ class Scheme < ApplicationRecord
.or(deactivated_directly)
}
scope :deactivated_by_organisation, lambda {
merge(Organisation.filter_by_inactive)
scope :deactivated_by_organisation, lambda { |date = Time.zone.now|
merge(Organisation.filter_by_inactive.or(Organisation.where("merge_date <= ?", date)))
}
scope :deactivated_directly, lambda { |date = Time.zone.now|
@ -96,7 +96,7 @@ class Scheme < ApplicationRecord
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).deactivated_by_organisation(date).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))
}
@ -314,7 +314,7 @@ class Scheme < ApplicationRecord
def status_at(date)
return :deleted if discarded_at.present?
return :incomplete unless confirmed && locations.confirmed.any?
return :deactivated if owning_organisation.status_at(date) == :deactivated ||
return :deactivated if owning_organisation.status_at(date) == :deactivated || owning_organisation.status_at(date) == :merged ||
(open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date)
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

15
app/policies/location_policy.rb

@ -16,14 +16,14 @@ class LocationPolicy
if location == Location
user.data_coordinator?
else
user.data_coordinator? && scheme_owned_by_user_org_or_stock_owner
user.data_coordinator? && scheme_owned_by_user_org_or_stock_owner_or_recently_absorbed_org
end
end
def update?
return true if user.support?
user.data_coordinator? && scheme_owned_by_user_org_or_stock_owner
user.data_coordinator? && scheme_owned_by_user_org_or_stock_owner_or_recently_absorbed_org
end
def delete_confirmation?
@ -62,7 +62,7 @@ class LocationPolicy
define_method method_name do
return true if user.support?
user.data_coordinator? && scheme_owned_by_user_org_or_stock_owner
user.data_coordinator? && scheme_owned_by_user_org_or_stock_owner_or_recently_absorbed_org
end
end
@ -73,7 +73,7 @@ class LocationPolicy
define_method method_name do
return true if user.support?
scheme_owned_by_user_org_or_stock_owner
scheme_owned_by_user_org_or_stock_owner_or_recently_absorbed_org
end
end
@ -83,8 +83,11 @@ private
location.scheme
end
def scheme_owned_by_user_org_or_stock_owner
scheme&.owning_organisation == user.organisation || user.organisation.stock_owners.exists?(scheme&.owning_organisation_id)
def scheme_owned_by_user_org_or_stock_owner_or_recently_absorbed_org
scheme_owned_by_user_org = scheme&.owning_organisation == user.organisation
scheme_owned_by_stock_owner = user.organisation.stock_owners.exists?(scheme&.owning_organisation_id)
scheme_owned_by_recently_absorbed_org = user.organisation.absorbed_organisations.visible.merged_during_open_collection_period.exists?(scheme&.owning_organisation_id)
scheme_owned_by_user_org || scheme_owned_by_stock_owner || scheme_owned_by_recently_absorbed_org
end
def has_any_logs_in_editable_collection_period

15
app/policies/scheme_policy.rb

@ -12,7 +12,7 @@ class SchemePolicy
if scheme == Scheme
true
else
scheme_owned_by_user_org_or_stock_owner
scheme_owned_by_user_org_or_stock_owner_or_recently_absorbed_org
end
end
@ -27,7 +27,7 @@ class SchemePolicy
def update?
return true if user.support?
user.data_coordinator? && scheme_owned_by_user_org_or_stock_owner
user.data_coordinator? && scheme_owned_by_user_org_or_stock_owner_or_recently_absorbed_org
end
def changes?
@ -41,7 +41,7 @@ class SchemePolicy
define_method method_name do
return true if user.support?
scheme_owned_by_user_org_or_stock_owner
scheme_owned_by_user_org_or_stock_owner_or_recently_absorbed_org
end
end
@ -61,7 +61,7 @@ class SchemePolicy
define_method method_name do
return true if user.support?
user.data_coordinator? && scheme_owned_by_user_org_or_stock_owner
user.data_coordinator? && scheme_owned_by_user_org_or_stock_owner_or_recently_absorbed_org
end
end
@ -78,8 +78,11 @@ class SchemePolicy
private
def scheme_owned_by_user_org_or_stock_owner
scheme&.owning_organisation == user.organisation || user.organisation.stock_owners.exists?(scheme&.owning_organisation_id)
def scheme_owned_by_user_org_or_stock_owner_or_recently_absorbed_org
scheme_owned_by_user_org = scheme&.owning_organisation == user.organisation
scheme_owned_by_stock_owner = user.organisation.stock_owners.exists?(scheme&.owning_organisation_id)
scheme_owned_by_recently_absorbed_org = user.organisation.absorbed_organisations.visible.merged_during_open_collection_period.exists?(scheme&.owning_organisation_id)
scheme_owned_by_user_org || scheme_owned_by_stock_owner || scheme_owned_by_recently_absorbed_org
end
def has_any_logs_in_editable_collection_period

2
app/services/bulk_upload/lettings/year2024/csv_parser.rb

@ -15,7 +15,7 @@ class BulkUpload::Lettings::Year2024::CsvParser
def row_offset
if with_headers?
rows.find_index { |row| row[0].match(/field number/i) } + 1
rows.find_index { |row| row[0].present? && row[0].match(/field number/i) } + 1
else
0
end

2
app/services/bulk_upload/sales/year2024/csv_parser.rb

@ -15,7 +15,7 @@ class BulkUpload::Sales::Year2024::CsvParser
def row_offset
if with_headers?
rows.find_index { |row| row[0].match(/field number/i) } + 1
rows.find_index { |row| row[0].present? && row[0].match(/field number/i) } + 1
else
0
end

4
app/services/feature_toggle.rb

@ -46,4 +46,8 @@ class FeatureToggle
def self.managing_resources_enabled?
!Rails.env.production?
end
def self.create_test_logs_enabled?
Rails.env.development? || Rails.env.review?
end
end

6
app/views/form/guidance/_financial_calculations_shared_ownership.html.erb

@ -20,11 +20,11 @@
<% end %>
must equal
the purchase price <%= question_link("value", log, current_user) %>
<% stairbought_page = log.form.get_question("stairbought", log).page %>
<% if stairbought_page.routed_to?(log, current_user) %>
<% stairbought_page = log.form.get_question("stairbought", log)&.page %>
<% if stairbought_page&.routed_to?(log, current_user) %>
multiplied by the percentage bought <%= question_link("stairbought", log, current_user) %>
<% else %>
multiplied by the percentage equity stake <%= question_link("equity", log, current_user) %>
multiplied by the percentage equity share <%= question_link("equity", log, current_user) %>
<% end %>
</p>
<% end %>

15
app/views/locations/index.html.erb

@ -56,14 +56,13 @@
<% end %>
<% end %>
<% if status_hint_message = scheme_status_hint(@scheme) %>
<div class="govuk-hint">
<%= status_hint_message %>
</div>
<br>
<% end %>
<% if LocationPolicy.new(current_user, @scheme.locations.new).create? %>
<% if LocationPolicy.new(current_user, @scheme.locations.new).create? && [:active, :merged].include?(@scheme.owning_organisation.status) %>
<% if status_hint_message = scheme_status_hint(@scheme) %>
<div class="govuk-hint">
<%= status_hint_message %>
</div>
<br>
<% end %>
<%= govuk_button_to "Add a location", scheme_locations_path(@scheme), method: "post" %>
<% end %>
<br>

2
app/views/locations/show.html.erb

@ -47,7 +47,7 @@
</div>
</div>
<% if @location.scheme.owning_organisation.active? && LocationPolicy.new(current_user, @location).deactivate? %>
<% if @location.scheme.owning_organisation.status == :active && LocationPolicy.new(current_user, @location).deactivate? %>
<%= toggle_location_link(@location) %>
<% end %>

8
app/views/merge_requests/_notification_banners.html.erb

@ -19,3 +19,11 @@
No changes have been made. Try beginning the merge again.
<% end %>
<% end %>
<% if @merge_request.merge_date&.future? %>
<%= govuk_notification_banner(title_text: "Important") do %>
<p class="govuk-notification-banner__heading govuk-!-width-full" style="max-width: fit-content">
This merge is happening in the future. Wait until the merge date to begin this merge.
</p>
<% end %>
<% end %>

2
app/views/merge_requests/show.html.erb

@ -12,7 +12,7 @@
</h1>
<% unless @merge_request.status == "request_merged" || @merge_request.status == "processing" %>
<div class="govuk-button-group">
<%= govuk_button_link_to "Begin merge", merge_start_confirmation_merge_request_path(@merge_request), disabled: @merge_request.status != "ready_to_merge" %>
<%= govuk_button_link_to "Begin merge", merge_start_confirmation_merge_request_path(@merge_request), disabled: begin_merge_disabled?(@merge_request) %>
<%= govuk_button_link_to "Delete merge request", delete_confirmation_merge_request_path(@merge_request), warning: true %>
</div>
<% end %>

6
app/views/schemes/details.html.erb

@ -49,11 +49,13 @@
:description,
legend: { text: "Is this scheme registered under the Care Standards Act 2000?", size: "m" } %>
<% if current_user.data_coordinator? && current_user.organisation.stock_owners.count.zero? && !current_user.organisation.has_recent_absorbed_organisations? %>
<% scheme_owning_organisation_options = owning_organisation_options(current_user) %>
<% if scheme_owning_organisation_options.count == 1 %>
<%= f.hidden_field :owning_organisation_id, value: current_user.organisation.id %>
<% else %>
<%= f.govuk_collection_select :owning_organisation_id,
owning_organisation_options(current_user),
scheme_owning_organisation_options,
:id,
:name,
label: { text: "Which organisation owns the housing stock for this scheme?", size: "m" },

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

@ -52,7 +52,7 @@
</div>
</div>
<% if @scheme.owning_organisation.active? && SchemePolicy.new(current_user, @scheme).deactivate? %>
<% if @scheme.owning_organisation.status == :active && SchemePolicy.new(current_user, @scheme).deactivate? %>
<%= toggle_scheme_link(@scheme) %>
<% end %>

4
config/locales/en.yml

@ -183,8 +183,11 @@ en:
merge_date:
blank: "Enter a merge date."
invalid: "Enter a valid merge date."
more_than_year_from_today: "The merge date must not be later than a year from today’s date."
existing_absorbing_organisation:
blank: "You must answer absorbing organisation already active?"
merging_organisation_id:
part_of_another_merge: "Another merge request records %{organisation} as merging into %{absorbing_organisation} on %{merge_date}. Select another organisation or remove this organisation from the other merge request."
notification:
attributes:
title:
@ -370,6 +373,7 @@ en:
during_deactivated_period: "The location is already deactivated during this date, please enter a different date."
merge_request:
organisation_part_of_another_merge: "This organisation is part of another merge - select a different one."
organisation_part_of_another_incomplete_merge: "Another merge request records %{organisation} as merging into %{absorbing_organisation} on %{merge_date}. Select another organisation or remove this organisation from the other merge request."
organisation_not_selected: "Select an organisation from the search list."
soft_validations:

21
config/locales/forms/2025/sales/sale_information.en.yml

@ -107,9 +107,9 @@ en:
equity:
page_header: "About the price of the property"
check_answer_label: "Initial percentage equity stake"
hint_text: "Enter the amount of initial equity held by the purchaser (for example, 25% or 50%)"
question_text: "What was the initial percentage equity stake purchased?"
check_answer_label: "Initial percentage equity share"
hint_text: "Enter the amount of initial equity share held by the purchaser (for example, 25% or 50%)"
question_text: "What was the initial percentage share purchased?"
mortgageused:
page_header: "Mortgage Amount"
@ -168,9 +168,9 @@ en:
leaseholdcharges:
page_header: ""
has_mscharge:
check_answer_label: "Does the property have any monthly leasehold charges?"
check_answer_label: "Does the property have any service charges?"
hint_text: "For example, service and management charges"
question_text: "Does the property have any monthly leasehold charges?"
question_text: "Does the property have any service charges?"
mscharge:
check_answer_label: "Monthly leasehold charges"
hint_text: ""
@ -199,3 +199,14 @@ en:
check_answer_label: "Amount of any loan, grant or subsidy"
hint_text: "For all schemes except Right to Buy (RTB), Preserved Right to Buy (PRTB), Voluntary Right to Buy (VRTB) and Rent to Buy"
question_text: "What was the amount of any loan, grant, discount or subsidy given?"
management_fee:
page_header: ""
has_management_fee:
check_answer_label: "Does the property have an estate management fee?"
hint_text: "Estate management fees are typically used for the maintenance of communal gardens, payments, private roads, car parks and/or play areas within new build estates."
question_text: "Does the property have an estate management fee?"
management_fee:
check_answer_label: "Monthly estate management fee"
hint_text: ""
question_text: "Enter the total monthly management fee"

5
config/routes.rb

@ -389,6 +389,11 @@ Rails.application.routes.draw do
end
end
get "create-test-lettings-log", to: "lettings_logs#create_test_log"
get "create-test-sales-log", to: "sales_logs#create_test_log"
get "create-setup-test-lettings-log", to: "lettings_logs#create_setup_test_log"
get "create-setup-test-sales-log", to: "sales_logs#create_setup_test_log"
scope via: :all do
match "/404", to: "errors#not_found"
match "/429", to: "errors#too_many_requests", status: 429

8
db/migrate/20241114154215_add_management_fee_fields.rb

@ -0,0 +1,8 @@
class AddManagementFeeFields < ActiveRecord::Migration[7.0]
def change
change_table :sales_logs, bulk: true do |t|
t.column :has_management_fee, :integer
t.column :management_fee, :decimal, precision: 10, scale: 2
end
end
end

2
db/schema.rb

@ -758,6 +758,8 @@ ActiveRecord::Schema[7.0].define(version: 2024_11_18_104046) do
t.integer "partner_under_16_value_check"
t.integer "multiple_partners_value_check"
t.bigint "created_by_id"
t.integer "has_management_fee"
t.decimal "management_fee", precision: 10, scale: 2
t.index ["assigned_to_id"], name: "index_sales_logs_on_assigned_to_id"
t.index ["bulk_upload_id"], name: "index_sales_logs_on_bulk_upload_id"
t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id"

2
db/seeds.rb

@ -15,6 +15,8 @@ def create_data_protection_confirmation(user)
signed_at: Time.zone.local(2019, 1, 1),
data_protection_officer_email: user.email,
data_protection_officer_name: user.name,
organisation_name: user.organisation.name,
organisation_address: user.organisation.address_row,
)
end

5
spec/features/accessibility_spec.rb

@ -138,15 +138,18 @@ RSpec.describe "Accessibility", js: true do
routes = find_routes("sales-log", sales_log, bulk_upload)
routes.reject { |path|
routes = routes.reject { |path|
path.include?("/edit") || path.include?("/new") || path.include?("*page") ||
path.include?("/sales-logs/bulk-upload-logs/#{bulk_upload.id}") ||
path.include?("bulk-upload-soft-validations-check") || path.include?("filters/update") ||
path == "/sales-logs/bulk-upload-resume/#{bulk_upload.id}" ||
path == "/sales-logs/bulk-upload-logs" ||
path.include?("/check-answers") ||
other_form_page_ids.any? { |page_id| path.include?(page_id.dasherize) } ||
sales_log_pages.any? { |page| path.include?(page.id.dasherize) && !page.routed_to?(sales_log, user) }
}.uniq
routes + sales_log.form.subsections.map(&:id).map { |id| "/sales-logs/#{sales_log.id}/#{id.dasherize}/check-answers" }
end
before do

2
spec/features/schemes_spec.rb

@ -677,7 +677,7 @@ RSpec.describe "Schemes scheme Features" do
end
it "adds scheme to the list of schemes" do
expect(page).to have_content "#{scheme.service_name} has been created. It does not require helpdesk approval."
expect(page).to have_content "#{scheme.service_name} has been created."
click_link "Schemes"
expect(page).to have_content "Supported housing schemes"
expect(page).to have_content scheme.id_to_display

72
spec/models/form/sales/pages/living_before_purchase_spec.rb

@ -5,17 +5,19 @@ RSpec.describe Form::Sales::Pages::LivingBeforePurchase, type: :model do
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
let(:start_year) { 2022 }
let(:form) { Form.new(nil, start_year, [], "sales") }
let(:subsection) { instance_double(Form::Subsection, depends_on: nil, form:) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
describe "questions" do
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date:)) }
let(:subsection) { instance_double(Form::Subsection, form:, depends_on: nil) }
context "when 2022" do
let(:start_date) { Time.utc(2022, 2, 8) }
let(:start_year) { 2022 }
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[proplen])
@ -23,7 +25,7 @@ RSpec.describe Form::Sales::Pages::LivingBeforePurchase, type: :model do
end
context "when 2023" do
let(:start_date) { Time.utc(2023, 2, 8) }
let(:start_year) { 2023 }
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[proplen_asked proplen])
@ -39,15 +41,63 @@ RSpec.describe Form::Sales::Pages::LivingBeforePurchase, type: :model do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "not_joint_purchase?" => true }, { "jointpur" => nil }])
end
context "when routing" do
context "with form before 2025" do
let(:start_year) { 2024 }
context "with joint purchase" do
subject(:page) { described_class.new(page_id, page_definition, subsection, ownershipsch: 1, joint_purchase: true) }
it "routes to the page when joint purchase is true" do
log = build(:sales_log, jointpur: 1)
expect(page.routed_to?(log, nil)).to eq(true)
end
it "does not route to the page when joint purchase is false" do
log = build(:sales_log, jointpur: 2)
expect(page.routed_to?(log, nil)).to eq(false)
end
it "does not route to the page when joint purchase is missing" do
log = build(:sales_log, jointpur: nil)
expect(page.routed_to?(log, nil)).to eq(false)
end
end
context "with non joint purchase" do
subject(:page) { described_class.new(page_id, page_definition, subsection, ownershipsch: 1, joint_purchase: false) }
it "routes to the page when joint purchase is false" do
log = build(:sales_log, jointpur: 2)
expect(page.routed_to?(log, nil)).to eq(true)
end
context "with joint purchase" do
subject(:page) { described_class.new(page_id, page_definition, subsection, ownershipsch: 1, joint_purchase: true) }
it "does not route to the page when joint purchase is true" do
log = build(:sales_log, jointpur: 1)
expect(page.routed_to?(log, nil)).to eq(false)
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "joint_purchase?" => true }])
it "routes to the page when joint purchase is missing" do
log = build(:sales_log, jointpur: nil)
expect(page.routed_to?(log, nil)).to eq(true)
end
end
end
context "with form on or after 2025" do
subject(:page) { described_class.new(page_id, page_definition, subsection, ownershipsch: 1, joint_purchase: true) }
let(:start_year) { 2025 }
it "routes to the page when resale is 2" do
log = build(:sales_log, jointpur: 1, resale: 2)
expect(page.routed_to?(log, nil)).to eq(true)
end
it "does not route to the page when resale is not 2" do
log = build(:sales_log, jointpur: 1, resale: nil)
expect(page.routed_to?(log, nil)).to eq(false)
end
end
end
end

28
spec/models/form/sales/sections/sale_information_spec.rb

@ -5,18 +5,32 @@ RSpec.describe Form::Sales::Sections::SaleInformation, type: :model do
let(:section_id) { nil }
let(:section_definition) { nil }
let(:form) { instance_double(Form) }
let(:form) { instance_double(Form, start_year_2025_or_later?: false) }
it "has correct form" do
expect(sale_information.form).to eq(form)
end
it "has correct subsections" do
expect(sale_information.subsections.map(&:id)).to eq(%w[
shared_ownership_scheme
discounted_ownership_scheme
outright_sale
])
context "when form is before 2025" do
it "has correct subsections" do
expect(sale_information.subsections.map(&:id)).to eq(%w[
shared_ownership_scheme
discounted_ownership_scheme
outright_sale
])
end
end
context "when form is 2025 or later" do
let(:form) { instance_double(Form, start_year_2025_or_later?: true) }
it "has correct subsections" do
expect(sale_information.subsections.map(&:id)).to eq(%w[
shared_ownership_initial_purchase
discounted_ownership_scheme
outright_sale
])
end
end
it "has the correct id" do

95
spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb

@ -0,0 +1,95 @@
require "rails_helper"
RSpec.describe Form::Sales::Subsections::SharedOwnershipInitialPurchase, type: :model do
subject(:shared_ownership_initial_purchase) { described_class.new(subsection_id, subsection_definition, section) }
let(:subsection_id) { nil }
let(:subsection_definition) { nil }
let(:section) { instance_double(Form::Sales::Sections::SaleInformation) }
before do
allow(section).to receive(:form).and_return(instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))
end
it "has correct section" do
expect(shared_ownership_initial_purchase.section).to eq(section)
end
it "has correct pages" do
expect(shared_ownership_initial_purchase.pages.map(&:id)).to eq(
%w[
resale
living_before_purchase_shared_ownership_joint_purchase
living_before_purchase_shared_ownership
handover_date
handover_date_check
buyer_previous_joint_purchase
buyer_previous_not_joint_purchase
previous_bedrooms
previous_property_type
shared_ownership_previous_tenure
value_shared_ownership
about_price_shared_ownership_value_check
equity
shared_ownership_equity_value_check
mortgage_used_shared_ownership
mortgage_used_mortgage_value_check
mortgage_amount_shared_ownership
shared_ownership_mortgage_amount_value_check
mortgage_amount_mortgage_value_check
mortgage_length_shared_ownership
deposit_shared_ownership
deposit_shared_ownership_optional
deposit_joint_purchase_value_check
deposit_value_check
deposit_discount
deposit_discount_optional
shared_ownership_deposit_value_check
monthly_rent
leasehold_charges_shared_ownership
monthly_charges_shared_ownership_value_check
estate_management_fee
],
)
end
it "has the correct id" do
expect(shared_ownership_initial_purchase.id).to eq("shared_ownership_initial_purchase")
end
it "has the correct label" do
expect(shared_ownership_initial_purchase.label).to eq("Shared ownership - initial purchase")
end
it "has the correct depends_on" do
expect(shared_ownership_initial_purchase.depends_on).to eq([
{
"ownershipsch" => 1, "setup_completed?" => true, "staircase" => 2
},
])
end
context "when it is a shared ownership scheme and not staircase" do
let(:log) { FactoryBot.build(:sales_log, ownershipsch: 1, staircase: 2) }
it "is displayed in tasklist" do
expect(shared_ownership_initial_purchase.displayed_in_tasklist?(log)).to eq(true)
end
end
context "when it is not a shared ownership scheme" do
let(:log) { FactoryBot.build(:sales_log, ownershipsch: 2, staircase: 2) }
it "is displayed in tasklist" do
expect(shared_ownership_initial_purchase.displayed_in_tasklist?(log)).to eq(false)
end
end
context "when it is staircase" do
let(:log) { FactoryBot.build(:sales_log, ownershipsch: 1, staircase: 1) }
it "is displayed in tasklist" do
expect(shared_ownership_initial_purchase.displayed_in_tasklist?(log)).to eq(false)
end
end
end

27
spec/models/lettings_log_spec.rb

@ -809,6 +809,21 @@ RSpec.describe LettingsLog do
expect { lettings_log.update!(nationality_all_group: nil, declaration: 1) }.not_to change(lettings_log, :nationality_all)
end
end
context "when form year changes and LA is no longer active" do
before do
LocalAuthority.find_by(code: "E08000003").update!(end_date: Time.zone.today)
end
it "removes the LA" do
lettings_log.update!(startdate: Time.zone.yesterday, la: "E08000003")
expect(lettings_log.reload.la).to eq("E08000003")
lettings_log.update!(startdate: Time.zone.tomorrow)
expect(lettings_log.reload.la).to eq(nil)
expect(lettings_log.reload.is_la_inferred).to eq(false)
end
end
end
describe "optional fields" do
@ -2006,5 +2021,17 @@ RSpec.describe LettingsLog do
end
end
end
describe "#process_address_change!" do
context "when uprn_selection is uprn_not_listed" do
let(:log) { build(:lettings_log, uprn_selection: "uprn_not_listed", address_line1_input: "Address line 1", postcode_full_input: "AA1 1AA") }
it "sets log address fields, including postcode known" do
expect { log.process_address_change! }.to change(log, :address_line1).from(nil).to("Address line 1")
.and change(log, :postcode_full).from(nil).to("AA1 1AA")
.and change(log, :postcode_known).from(nil).to(1)
end
end
end
end
# rubocop:enable RSpec/MessageChain

5
spec/models/location_spec.rb

@ -929,6 +929,11 @@ RSpec.describe Location, type: :model do
expect(location.status).to eq(:deactivated)
end
it "returns deactivated if the owning organisation has been merged" do
location.scheme.owning_organisation.merge_date = 2.days.ago
expect(location.status).to eq(:deactivated)
end
it "returns deactivated if deactivation_date is in the past" do
FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.yesterday, location:)
location.save!

29
spec/models/sales_log_spec.rb

@ -978,5 +978,34 @@ RSpec.describe SalesLog, type: :model do
end
end
end
context "when form year changes and LA is no longer active" do
let!(:sales_log) { create(:sales_log) }
before do
LocalAuthority.find_by(code: "E08000003").update!(end_date: Time.zone.today)
end
it "removes the LA" do
sales_log.update!(saledate: Time.zone.yesterday, la: "E08000003")
expect(sales_log.reload.la).to eq("E08000003")
sales_log.update!(saledate: Time.zone.tomorrow)
expect(sales_log.reload.la).to eq(nil)
expect(sales_log.reload.is_la_inferred).to eq(false)
end
end
describe "#process_address_change!" do
context "when uprn_selection is uprn_not_listed" do
let(:log) { build(:sales_log, uprn_selection: "uprn_not_listed", address_line1_input: "Address line 1", postcode_full_input: "AA1 1AA") }
it "sets log address fields, including postcode known" do
expect { log.process_address_change! }.to change(log, :address_line1).from(nil).to("Address line 1")
.and change(log, :postcode_full).from(nil).to("AA1 1AA")
.and change(log, :pcodenk).from(nil).to(0)
end
end
end
end
# rubocop:enable RSpec/MessageChain

5
spec/models/scheme_spec.rb

@ -363,6 +363,11 @@ RSpec.describe Scheme, type: :model do
expect(scheme.status).to eq(:deactivated)
end
it "returns deactivated if the owning organisation has been merged" do
scheme.owning_organisation.merge_date = 2.days.ago
expect(scheme.status).to eq(:deactivated)
end
it "returns deactivated if deactivation_date is in the past" do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.yesterday, scheme:)
scheme.reload

34
spec/requests/merge_requests_controller_spec.rb

@ -84,6 +84,22 @@ RSpec.describe MergeRequestsController, type: :request do
end
end
context "when the user updates merge request with organisation that is already part of another merge" do
let(:another_organisation) { create(:organisation) }
let(:other_merge_request) { create(:merge_request, merge_date: Time.zone.local(2022, 5, 4)) }
let(:params) { { merge_request: { merging_organisation: another_organisation.id, new_merging_org_ids: [] } } }
before do
MergeRequestOrganisation.create!(merge_request_id: other_merge_request.id, merging_organisation_id: another_organisation.id)
patch "/merge-request/#{merge_request.id}/merging-organisations", headers:, params:
end
it "displays the page with an error message" do
expect(response).to have_http_status(:unprocessable_entity)
expect(page).to have_content("Another merge request records #{another_organisation.name} as merging into #{other_merge_request.absorbing_organisation&.name} on 4 May 2022. Select another organisation or remove this organisation from the other merge request.")
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, new_merging_org_ids: [] } } }
@ -396,6 +412,24 @@ RSpec.describe MergeRequestsController, type: :request do
}.from(nil).to(Time.zone.local(2022, 4, 10))
end
end
context "when merge date set to a date more than 1 year in the future" do
let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation) }
let(:params) do
{ merge_request: { page: "merge_date", "merge_date(3i)": (Time.zone.now.day + 1).to_s, "merge_date(2i)": Time.zone.now.month.to_s, "merge_date(1i)": (Time.zone.now.year + 1).to_s } }
end
let(:request) do
patch "/merge-request/#{merge_request.id}", headers:, params:
end
it "displays the page with an error message" do
request
expect(response).to have_http_status(:unprocessable_entity)
expect(page).to have_content("The merge date must not be later than a year from today’s date.")
end
end
end
describe "from merging_organisations page" do

86
spec/requests/schemes_controller_spec.rb

@ -89,9 +89,47 @@ RSpec.describe SchemesController, type: :request do
end
end
context "when a recently absorbed organisation has schemes" do
let(:absorbed_org) { create(:organisation) }
let!(:absorbed_org_schemes) { create_list(:scheme, 2, owning_organisation: absorbed_org) }
before do
absorbed_org.merge_date = 2.days.ago
absorbed_org.absorbing_organisation = user.organisation
absorbed_org.save!
end
it "shows absorbed organisation schemes" do
get "/schemes"
follow_redirect!
absorbed_org_schemes.each do |scheme|
expect(page).to have_content(scheme.id_to_display)
end
end
end
context "when a non-recently absorbed organisation has schemes" do
let(:absorbed_org) { create(:organisation) }
let!(:absorbed_org_schemes) { create_list(:scheme, 2, owning_organisation: absorbed_org) }
before do
absorbed_org.merge_date = 2.years.ago
absorbed_org.absorbing_organisation = user.organisation
absorbed_org.save!
end
it "shows absorbed organisation schemes" do
get "/schemes"
follow_redirect!
absorbed_org_schemes.each do |scheme|
expect(page).not_to have_content(scheme.id_to_display)
end
end
end
context "when filtering" do
context "with owning organisation filter" do
context "when user org does not have owning orgs" do
context "when user org does not have owning orgs or recently absorbed orgs" do
it "does not show filter" do
expect(page).not_to have_content("Owned by")
end
@ -700,6 +738,27 @@ RSpec.describe SchemesController, type: :request do
end
end
context "when coordinator attempts to see scheme belonging to a recently absorbed organisation" do
let(:absorbed_organisation) { create(:organisation) }
let!(:specific_scheme) { create(:scheme, owning_organisation: absorbed_organisation) }
before do
absorbed_organisation.merge_date = 2.days.ago
absorbed_organisation.absorbing_organisation = user.organisation
absorbed_organisation.save!
get "/schemes/#{specific_scheme.id}"
end
it "shows the scheme" do
expect(page).to have_content(specific_scheme.id_to_display)
end
it "allows editing" do
expect(page).to have_link("Change")
end
end
context "when the scheme has all details but no confirmed locations" do
it "shows the scheme as incomplete with text to explain" do
get scheme_path(specific_scheme)
@ -1146,6 +1205,31 @@ RSpec.describe SchemesController, type: :request do
end
end
end
context "when making a scheme in an organisation recently absorbed by the users organisation" do
let(:absorbed_organisation) { create(:organisation) }
let(:params) do
{ scheme: { service_name: " testy ",
sensitive: "1",
scheme_type: "Foyer",
registered_under_care_act: "No",
owning_organisation_id: absorbed_organisation.id,
arrangement_type: "D" } }
end
before do
absorbed_organisation.merge_date = 2.days.ago
absorbed_organisation.absorbing_organisation = user.organisation
absorbed_organisation.save!
end
it "creates a new scheme for this organisation and renders correct page" do
expect { post "/schemes", params: }.to change(Scheme, :count).by(1)
follow_redirect!
expect(response).to have_http_status(:ok)
expect(page).to have_content("What client group is this scheme intended for?")
end
end
end
context "when signed in as a support user" do

3
spec/requests/users_controller_spec.rb

@ -1043,6 +1043,9 @@ RSpec.describe UsersController, type: :request do
it "invites a new user" do
expect { request }.to change(User, :count).by(1)
follow_redirect!
expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success")
expect(page).to have_content("Invitation sent to new_user@example.com")
end
it "sends an invitation email" do

23
spec/services/bulk_upload/lettings/year2024/csv_parser_spec.rb

@ -30,6 +30,29 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
end
end
context "when some csv headers are empty (and we don't care about them)" do
before do
file.write("Question\n")
file.write("Additional info\n")
file.write("Values\n")
file.write("\n")
file.write("Type of letting the question applies to\n")
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.rewind
end
it "returns correct offsets" do
expect(service.row_offset).to eq(7)
expect(service.col_offset).to eq(1)
end
it "parses csv correctly" do
expect(service.row_parsers[0].field_13).to eql(log.tenancycode)
end
end
context "when parsing csv with headers with extra rows" do
before do
file.write("Section\n")

32
spec/services/bulk_upload/sales/year2024/csv_parser_spec.rb

@ -39,6 +39,38 @@ RSpec.describe BulkUpload::Sales::Year2024::CsvParser do
end
end
context "when some csv headers are empty (and we don't care about them)" do
before do
file.write("Question\n")
file.write("Additional info\n")
file.write("Values\n")
file.write("\n")
file.write("Type of letting the question applies to\n")
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
it "returns correct offsets" do
expect(service.row_offset).to eq(7)
expect(service.col_offset).to eq(1)
end
it "parses csv correctly" do
expect(service.row_parsers[0].field_22).to eql(log.uprn)
end
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
let(:seed) { rand }

Loading…
Cancel
Save