Browse Source

Merge branch 'main' into cldc-2812-run-app-from-relative-url

cldc-2812-run-app-from-relative-url
Rachael Booth 12 months ago
parent
commit
d0decfcc95
  1. 220
      .github/workflows/paas_only_production_pipeline.yml
  2. 14
      .github/workflows/production_pipeline.yml
  3. 3
      app/components/search_component.html.erb
  4. 10
      app/components/search_result_caption_component.html.erb
  5. 6
      app/components/search_result_caption_component.rb
  6. 9
      app/controllers/application_controller.rb
  7. 4
      app/controllers/delete_logs_controller.rb
  8. 37
      app/controllers/duplicate_logs_controller.rb
  9. 6
      app/controllers/form_controller.rb
  10. 7
      app/controllers/maintenance_controller.rb
  11. 2
      app/controllers/sessions_controller.rb
  12. 29
      app/controllers/users_controller.rb
  13. 53
      app/helpers/duplicate_logs_helper.rb
  14. 35
      app/helpers/filters_helper.rb
  15. 8
      app/helpers/form_page_helper.rb
  16. 13
      app/models/derived_variables/lettings_log_variables.rb
  17. 3
      app/models/form/lettings/pages/address.rb
  18. 1
      app/models/form/lettings/pages/location.rb
  19. 2
      app/models/form/lettings/pages/max_rent_value_check.rb
  20. 2
      app/models/form/lettings/pages/min_rent_value_check.rb
  21. 1
      app/models/form/lettings/questions/location_id.rb
  22. 4
      app/models/form/lettings/questions/managing_organisation.rb
  23. 1
      app/models/form/lettings/questions/referral.rb
  24. 1
      app/models/form/lettings/questions/referral_prp.rb
  25. 1
      app/models/form/lettings/questions/referral_supported_housing.rb
  26. 2
      app/models/form/lettings/questions/referral_supported_housing_prp.rb
  27. 38
      app/models/form/lettings/questions/stock_owner.rb
  28. 4
      app/models/form/sales/pages/address.rb
  29. 4
      app/models/form/sales/pages/uprn.rb
  30. 4
      app/models/form/sales/questions/owning_organisation_id.rb
  31. 23
      app/models/lettings_log.rb
  32. 10
      app/models/location.rb
  33. 7
      app/models/log.rb
  34. 8
      app/models/organisation.rb
  35. 11
      app/models/sales_log.rb
  36. 23
      app/models/scheme.rb
  37. 6
      app/models/user.rb
  38. 20
      app/models/validations/financial_validations.rb
  39. 3
      app/models/validations/household_validations.rb
  40. 8
      app/models/validations/sales/setup_validations.rb
  41. 20
      app/models/validations/setup_validations.rb
  42. 2
      app/models/validations/shared_validations.rb
  43. 45
      app/services/bulk_upload/lettings/year2022/row_parser.rb
  44. 47
      app/services/bulk_upload/lettings/year2023/row_parser.rb
  45. 2
      app/services/bulk_upload/sales/year2022/row_parser.rb
  46. 2
      app/services/bulk_upload/sales/year2023/row_parser.rb
  47. 6
      app/services/csv/sales_log_csv_service.rb
  48. 4
      app/services/feature_toggle.rb
  49. 26
      app/services/merge/merge_organisations_service.rb
  50. 14
      app/views/duplicate_logs/_duplicate_log_check_answers.erb
  51. 4
      app/views/duplicate_logs/index.html.erb
  52. 11
      app/views/duplicate_logs/no_more_duplicates.html.erb
  53. 4
      app/views/form/guidance/_finding_location.erb
  54. 16
      app/views/form/page.html.erb
  55. 12
      app/views/layouts/application.html.erb
  56. 6
      app/views/locations/_location_filters.html.erb
  57. 2
      app/views/locations/index.html.erb
  58. 54
      app/views/logs/_log_filters.html.erb
  59. 29
      app/views/logs/_log_list.html.erb
  60. 6
      app/views/logs/delete_duplicates.html.erb
  61. 2
      app/views/logs/download_csv.html.erb
  62. 2
      app/views/logs/index.html.erb
  63. 11
      app/views/maintenance/service_unavailable.html.erb
  64. 2
      app/views/organisation_relationships/_managing_agent_list.erb
  65. 2
      app/views/organisation_relationships/_stock_owner_list.erb
  66. 34
      app/views/organisations/_merged_organisation_details.html.erb
  67. 2
      app/views/organisations/_organisation_list.html.erb
  68. 2
      app/views/organisations/logs.html.erb
  69. 1
      app/views/organisations/show.html.erb
  70. 6
      app/views/schemes/_scheme_filters.html.erb
  71. 2
      app/views/schemes/_scheme_list.html.erb
  72. 2
      app/views/schemes/support.html.erb
  73. 7
      app/views/users/_user_filters.html.erb
  74. 2
      app/views/users/_user_list.html.erb
  75. 6
      app/views/users/new.html.erb
  76. 12
      config/forms/2021_2022.json
  77. 20
      config/forms/2022_2023.json
  78. 5
      config/initializers/sidekiq.rb
  79. 26
      config/locales/en.yml
  80. 1
      config/routes.rb
  81. 5
      db/migrate/20231023142854_add_available_from_to_org.rb
  82. 3
      db/schema.rb
  83. 2
      docs/adr/index.md
  84. 15
      docs/app_api.md
  85. 2
      docs/documentation_website.md
  86. 2
      docs/form/index.md
  87. 2
      docs/setup.md
  88. 4
      lib/tasks/correct_illness_from_csv.rake
  89. 22
      lib/tasks/import_address_from_csv.rake
  90. 33
      lib/tasks/merge_organisations.rake
  91. 18
      lib/tasks/recalculate_irproduct_values.rake
  92. 13
      lib/tasks/recalculate_lar_values.rake
  93. 35
      lib/tasks/squish_names.rake
  94. 47
      spec/components/search_result_caption_component_spec.rb
  95. 27
      spec/controllers/maintenance_controller_spec.rb
  96. 5
      spec/factories/lettings_log.rb
  97. 1
      spec/factories/user.rb
  98. 14
      spec/features/form/form_navigation_spec.rb
  99. 18
      spec/features/lettings_log_spec.rb
  100. 30
      spec/features/organisation_spec.rb
  101. Some files were not shown because too many files have changed in this diff Show More

220
.github/workflows/paas_only_production_pipeline.yml

@ -0,0 +1,220 @@
name: PaaS-only Production CI/CD Pipeline
on:
workflow_dispatch:
defaults:
run:
shell: bash
jobs:
test:
name: Tests
runs-on: ubuntu-latest
services:
postgres:
image: postgres:13.5
env:
POSTGRES_PASSWORD: password
POSTGRES_USER: postgres
POSTGRES_DB: data_collector
ports:
- 5432:5432
# Needed because the Postgres container does not provide a health check
# tmpfs makes database faster by using RAM
options: >-
--mount type=tmpfs,destination=/var/lib/postgresql/data
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
RAILS_ENV: test
GEMFILE_RUBY_VERSION: 3.1.1
DB_HOST: localhost
DB_DATABASE: data_collector
DB_USERNAME: postgres
DB_PASSWORD: password
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
PARALLEL_TEST_PROCESSORS: 4
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version: 18
- name: Create database
run: |
bundle exec rake parallel:setup
- name: Compile assets
run: |
bundle exec rake assets:precompile
- name: Run tests
run: |
bundle exec rake parallel:spec['spec\/(?!features)']
feature_test:
name: Feature Tests
runs-on: ubuntu-latest
services:
postgres:
image: postgres:13.5
env:
POSTGRES_PASSWORD: password
POSTGRES_USER: postgres
POSTGRES_DB: data_collector
ports:
- 5432:5432
# Needed because the Postgres container does not provide a health check
# tmpfs makes database faster by using RAM
options: >-
--mount type=tmpfs,destination=/var/lib/postgresql/data
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
RAILS_ENV: test
GEMFILE_RUBY_VERSION: 3.1.1
DB_HOST: localhost
DB_DATABASE: data_collector
DB_USERNAME: postgres
DB_PASSWORD: password
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version: 18
- name: Create database
run: |
bundle exec rake db:prepare
- name: Compile assets
run: |
bundle exec rake assets:precompile
- name: Run tests
run: |
bundle exec rspec spec/features --fail-fast
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version: 18
- name: Install packages and symlink local dependencies
run: |
yarn install --immutable --immutable-cache --check-cache
- name: Lint
run: |
bundle exec rake lint
audit:
name: Audit dependencies
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Audit
run: |
bundle exec bundler-audit
deploy:
name: Deploy
concurrency: "production"
runs-on: ubuntu-latest
environment: "production"
needs: [lint, test, feature_test, audit]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Cloud Foundry CLI
run: |
wget --user-agent "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.3 Safari/605.1.15" -q -O - https://packages.cloudfoundry.org/debian/cli.cloudfoundry.org.key | sudo apt-key add -
echo "deb https://packages.cloudfoundry.org/debian stable main" | sudo tee /etc/apt/sources.list.d/cloudfoundry-cli.list
sudo apt-get update
sudo apt-get install cf8-cli
- name: Deploy
env:
CF_USERNAME: ${{ secrets.CF_USERNAME }}
CF_PASSWORD: ${{ secrets.CF_PASSWORD }}
CF_API_ENDPOINT: ${{ secrets.CF_API_ENDPOINT }}
CF_SPACE: ${{ secrets.CF_SPACE }}
CF_ORG: ${{ secrets.CF_ORG }}
APP_NAME: dluhc-core-production
GOVUK_NOTIFY_API_KEY: ${{ secrets.GOVUK_NOTIFY_API_KEY }}
APP_HOST: ${{ secrets.APP_HOST }}
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
OS_DATA_KEY: ${{ secrets.OS_DATA_KEY }}
IMPORT_PAAS_INSTANCE: ${{ secrets.IMPORT_PAAS_INSTANCE }}
EXPORT_PAAS_INSTANCE: ${{ secrets.EXPORT_PAAS_INSTANCE }}
S3_CONFIG: ${{ secrets.S3_CONFIG }}
CSV_DOWNLOAD_PAAS_INSTANCE: ${{ secrets.CSV_DOWNLOAD_PAAS_INSTANCE }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
run: |
cf api $CF_API_ENDPOINT
cf auth
cf target -o $CF_ORG -s $CF_SPACE
cf set-env $APP_NAME GOVUK_NOTIFY_API_KEY $GOVUK_NOTIFY_API_KEY
cf set-env $APP_NAME APP_HOST $APP_HOST
cf set-env $APP_NAME RAILS_MASTER_KEY $RAILS_MASTER_KEY
cf set-env $APP_NAME OS_DATA_KEY $OS_DATA_KEY
cf set-env $APP_NAME IMPORT_PAAS_INSTANCE $IMPORT_PAAS_INSTANCE
cf set-env $APP_NAME EXPORT_PAAS_INSTANCE $EXPORT_PAAS_INSTANCE
cf set-env $APP_NAME S3_CONFIG $S3_CONFIG
cf set-env $APP_NAME CSV_DOWNLOAD_PAAS_INSTANCE $CSV_DOWNLOAD_PAAS_INSTANCE
cf set-env $APP_NAME SENTRY_DSN $SENTRY_DSN
cf push $APP_NAME --strategy rolling

14
.github/workflows/production_pipeline.yml

@ -167,9 +167,19 @@ jobs:
with:
bundler-cache: true
- name: Rubocop
- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version: 18
- name: Install packages and symlink local dependencies
run: |
yarn install --immutable --immutable-cache --check-cache
- name: Lint
run: |
bundle exec rubocop
bundle exec rake lint
audit:
name: Audit dependencies

3
app/components/search_component.html.erb

@ -10,6 +10,7 @@
autocomplete: "off",
class: "app-search__input" %>
<%= f.govuk_submit "Search", classes: "app-search__button govuk-button--secondary" %>
<%= f.govuk_submit "Search", classes: "app-search__button" %>
<%= govuk_button_link_to "Clear search", path(current_user), secondary: true, class: "app-search__button" %>
</div>
<% end %>

10
app/components/search_result_caption_component.html.erb

@ -1,7 +1,11 @@
<span class="govuk-!-margin-right-4">
<% if searched.present? %>
<strong><%= count %></strong> <%= item_label %> found matching ‘<%= searched %>’ of <strong><%= total_count %></strong> total <%= item %>. <%= govuk_link_to("Clear search", path) %>
<% if searched.present? && filters_count&.positive? %>
<strong><%= count %></strong> <%= item_label.pluralize(count) %> matching search and filters<br>
<% elsif searched.present? %>
<strong><%= count %></strong> <%= item_label.pluralize(count) %> matching search<br>
<% elsif filters_count&.positive? %>
<strong><%= count %></strong> <%= item_label.pluralize(count) %> matching filters<br>
<% else %>
<strong><%= count %></strong> total <%= item %>
<strong><%= count %></strong> matching <%= item %>
<% end %>
</span>

6
app/components/search_result_caption_component.rb

@ -1,13 +1,13 @@
class SearchResultCaptionComponent < ViewComponent::Base
attr_reader :searched, :count, :item_label, :total_count, :item, :path
attr_reader :searched, :count, :item_label, :total_count, :item, :filters_count
def initialize(searched:, count:, item_label:, total_count:, item:, path:)
def initialize(searched:, count:, item_label:, total_count:, item:, filters_count:)
@searched = searched
@count = count
@item_label = item_label
@total_count = total_count
@item = item
@path = path
@filters_count = filters_count
super
end
end

9
app/controllers/application_controller.rb

@ -3,8 +3,17 @@ class ApplicationController < ActionController::Base
rescue_from Pundit::NotAuthorizedError, with: :render_not_authorized
before_action :check_maintenance
before_action :set_paper_trail_whodunnit
def check_maintenance
if FeatureToggle.maintenance_mode_enabled? && !%w[service-unavailable accessibility-statement privacy-notice cookies].include?(request.fullpath.split("?")[0].delete("/"))
redirect_to service_unavailable_path
elsif !FeatureToggle.maintenance_mode_enabled? && request.fullpath.split("?")[0].delete("/") == "service-unavailable"
redirect_back(fallback_location: root_path)
end
end
def render_not_found
render "errors/not_found", status: :not_found
end

4
app/controllers/delete_logs_controller.rb

@ -27,7 +27,7 @@ class DeleteLogsController < ApplicationController
logs = LettingsLog.find(params.require(:ids))
discard logs
if request.referer&.include?("delete-duplicates")
redirect_to lettings_log_duplicate_logs_path(lettings_log_id: params["remaining_log_id"], original_log_id: params["original_log_id"]), notice: I18n.t("notification.duplicate_logs_deleted", count: logs.count, log_ids: duplicate_log_ids(logs))
redirect_to lettings_log_duplicate_logs_path(lettings_log_id: params["remaining_log_id"], original_log_id: params["original_log_id"], referrer: params[:referrer], organisation_id: params[:organisation_id]), notice: I18n.t("notification.duplicate_logs_deleted", count: logs.count, log_ids: duplicate_log_ids(logs))
else
redirect_to lettings_logs_path, notice: I18n.t("notification.logs_deleted", count: logs.count)
end
@ -56,7 +56,7 @@ class DeleteLogsController < ApplicationController
logs = SalesLog.find(params.require(:ids))
discard logs
if request.referer&.include?("delete-duplicates")
redirect_to sales_log_duplicate_logs_path(sales_log_id: params["remaining_log_id"], original_log_id: params["original_log_id"]), notice: I18n.t("notification.duplicate_logs_deleted", count: logs.count, log_ids: duplicate_log_ids(logs))
redirect_to sales_log_duplicate_logs_path(sales_log_id: params["remaining_log_id"], original_log_id: params["original_log_id"], referrer: params[:referrer], organisation_id: params[:organisation_id]), notice: I18n.t("notification.duplicate_logs_deleted", count: logs.count, log_ids: duplicate_log_ids(logs))
else
redirect_to sales_logs_path, notice: I18n.t("notification.logs_deleted", count: logs.count)
end

37
app/controllers/duplicate_logs_controller.rb

@ -5,15 +5,12 @@ class DuplicateLogsController < ApplicationController
before_action :find_resource_by_named_id
before_action :find_duplicates_for_a_log
before_action :find_original_log
before_action :find_organisation, only: [:index]
before_action :find_all_duplicates, only: [:index]
def show
if @log
@all_duplicates = [@log, *@duplicate_logs]
@duplicate_check_questions = duplicate_check_question_ids.map { |question_id|
question = @log.form.get_question(question_id, @log)
question if question.page.routed_to?(@log, current_user)
}.compact
else
render_not_found
end
@ -26,10 +23,8 @@ class DuplicateLogsController < ApplicationController
end
def index
render_not_found if @duplicates.blank?
@duplicate_sets_count = @duplicates[:lettings].count + @duplicates[:sales].count
render_not_found if @duplicate_sets_count.zero?
render "duplicate_logs/no_more_duplicates" if @duplicate_sets_count.zero?
end
private
@ -55,29 +50,9 @@ private
def find_all_duplicates
return @duplicates = duplicates_for_user(current_user) if current_user.data_provider?
organisation = current_user.support? ? Organisation.find(params[:organisation_id]) : current_user.organisation
return unless organisation
return unless @organisation
@duplicates = duplicates_for_organisation(organisation)
end
def duplicate_check_question_ids
if @log.lettings?
["owning_organisation_id",
"startdate",
"tenancycode",
"postcode_full",
"scheme_id",
"location_id",
"age1",
"sex1",
"ecstat1",
@log.household_charge == 1 ? "household_charge" : nil,
"tcharge",
@log.is_carehome? ? "chcharge" : nil].compact
else
%w[owning_organisation_id saledate purchid age1 sex1 ecstat1 postcode_full]
end
@duplicates = duplicates_for_organisation(@organisation)
end
def find_original_log
@ -89,4 +64,8 @@ private
current_user.lettings_logs.find_by(id: original_log_id)
end
end
def find_organisation
@organisation = current_user.support? ? Organisation.find(params[:organisation_id]) : current_user.organisation
end
end

6
app/controllers/form_controller.rb

@ -165,7 +165,7 @@ private
def successful_redirect_path
if FeatureToggle.deduplication_flow_enabled?
if is_referrer_type?("duplicate_logs")
if is_referrer_type?("duplicate_logs") || is_referrer_type?("duplicate_logs_banner")
return correcting_duplicate_logs_redirect_path
end
@ -251,10 +251,10 @@ private
if original_log.present? && current_user.send(class_name.pluralize).duplicate_logs(original_log).count.positive?
flash[:notice] = deduplication_success_banner unless current_user.send(class_name.pluralize).duplicate_logs(@log).count.positive?
send("#{class_name}_duplicate_logs_path", original_log, original_log_id: original_log.id)
send("#{class_name}_duplicate_logs_path", original_log, original_log_id: original_log.id, referrer: params[:referrer], organisation_id: params[:organisation_id])
else
flash[:notice] = deduplication_success_banner
send("#{class_name}_duplicate_logs_path", "#{class_name}_id".to_sym => from_referrer_query("first_remaining_duplicate_id"), original_log_id: from_referrer_query("original_log_id"))
send("#{class_name}_duplicate_logs_path", "#{class_name}_id".to_sym => from_referrer_query("first_remaining_duplicate_id"), original_log_id: from_referrer_query("original_log_id"), referrer: params[:referrer], organisation_id: params[:organisation_id])
end
end

7
app/controllers/maintenance_controller.rb

@ -0,0 +1,7 @@
class MaintenanceController < ApplicationController
def service_unavailable
if current_user
sign_out
end
end
end

2
app/controllers/sessions_controller.rb

@ -3,7 +3,7 @@ class SessionsController < ApplicationController
session[session_name_for(params[:filter_type])] = "{}"
path_params = params[:path_params].presence || {}
redirect_to send("#{params[:filter_type]}_path", scheme_id: path_params[:scheme_id])
redirect_to send("#{params[:filter_type]}_path", scheme_id: path_params[:scheme_id], search: path_params[:search])
end
private

29
app/controllers/users_controller.rb

@ -5,7 +5,7 @@ class UsersController < ApplicationController
include Modules::SearchFilter
before_action :authenticate_user!
before_action :find_resource, except: %i[new create]
before_action :find_user, except: %i[new create]
before_action :authenticate_scope!, except: %i[new]
before_action :session_filters, if: :current_user, only: %i[index]
before_action -> { filter_manager.serialize_filters_to_session }, if: :current_user, only: %i[index]
@ -49,7 +49,8 @@ class UsersController < ApplicationController
end
def update
if @user.update(user_params)
validate_attributes
if @user.errors.empty? && @user.update(user_params)
if @user == current_user
bypass_sign_in @user
flash[:notice] = I18n.t("devise.passwords.updated") if user_params.key?("password")
@ -83,18 +84,18 @@ class UsersController < ApplicationController
def new
@organisation_id = params["organisation_id"]
@resource = User.new
@user = User.new
end
def create
@resource = User.new(user_params.merge(org_params).merge(password_params))
@user = User.new(user_params.merge(org_params).merge(password_params))
validate_attributes
if @resource.errors.empty? && @resource.save
if @user.errors.empty? && @user.save
redirect_to created_user_redirect_path
else
unless @resource.errors[:organisation].empty?
@resource.errors.delete(:organisation)
unless @user.errors[:organisation].empty?
@user.errors.delete(:organisation)
end
render :new, status: :unprocessable_entity
end
@ -124,15 +125,15 @@ class UsersController < ApplicationController
private
def validate_attributes
@resource.validate
@user.validate
if user_params[:role].present? && !current_user.assignable_roles.key?(user_params[:role].to_sym)
@resource.errors.add :role, I18n.t("validations.role.invalid")
@user.errors.add :role, I18n.t("validations.role.invalid")
end
if user_params[:phone].blank?
@resource.errors.add :phone, :blank
elsif !valid_phone_number?(user_params[:phone])
@resource.errors.add :phone
if !user_params[:phone].nil? && user_params[:phone].blank?
@user.errors.add :phone, :blank
elsif !user_params[:phone].nil? && !valid_phone_number?(user_params[:phone])
@user.errors.add :phone
end
end
@ -188,7 +189,7 @@ private
end
end
def find_resource
def find_user
@user = User.find_by(id: params[:user_id]) || User.find_by(id: params[:id]) || current_user
end

53
app/helpers/duplicate_logs_helper.rb

@ -8,10 +8,13 @@ module DuplicateLogsHelper
action: "delete_duplicates",
"#{duplicate_log.class.name.underscore}_id": duplicate_log.id,
original_log_id: original_log.id,
referrer: params[:referrer],
organisation_id: params[:organisation_id],
)
end
if !original_log.deleted?
if params[:referrer] == "duplicate_logs_banner"
current_user.support? ? govuk_button_link_to("Review other duplicates", organisation_duplicates_path(organisation_id: params[:organisation_id], referrer: params[:referrer])) : govuk_button_link_to("Review other duplicates", duplicate_logs_path(referrer: params[:referrer]))
elsif !original_log.deleted?
govuk_button_link_to "Back to Log #{original_log.id}", send("#{original_log.class.name.underscore}_path", original_log)
else
type = duplicate_log.lettings? ? "lettings" : "sales"
@ -25,7 +28,7 @@ module DuplicateLogsHelper
def change_duplicate_logs_action_href(log, page_id, all_duplicates, original_log_id)
first_remaining_duplicate_id = all_duplicates.map(&:id).reject { |id| id == log.id }.first
send("#{log.model_name.param_key}_#{page_id}_path", log, referrer: "duplicate_logs", first_remaining_duplicate_id:, original_log_id:)
send("#{log.model_name.param_key}_#{page_id}_path", log, referrer: params[:referrer] == "duplicate_logs_banner" ? "duplicate_logs_banner" : "duplicate_logs", first_remaining_duplicate_id:, original_log_id:, organisation_id: params[:organisation_id])
end
def duplicates_for_user(user)
@ -50,4 +53,48 @@ module DuplicateLogsHelper
def duplicate_list_header(duplicate_sets_count)
duplicate_sets_count > 1 ? "Review these #{duplicate_sets_count} sets of logs" : "Review this #{duplicate_sets_count} set of logs"
end
def duplicate_log_question_label(question)
if question.id == "uprn"
"Postcode (from UPRN)"
else
get_question_label(question)
end
end
def duplicate_log_answer_label(question, log)
if question.id == "uprn"
postcode_question = log.form.get_question("postcode_full", log)
get_answer_label(postcode_question, log)
else
get_answer_label(question, log)
end
end
def duplicate_log_extra_value(question, log)
if question.id == "uprn"
postcode_question = log.form.get_question("postcode_full", log)
postcode_question.get_extra_check_answer_value(log)
else
question.get_extra_check_answer_value(log)
end
end
def duplicate_log_answer_label_present(question, log, current_user)
if question.id == "uprn"
postcode_question = log.form.get_question("postcode_full", log)
postcode_question.answer_label(log, current_user).present?
else
question.answer_label(log, current_user).present?
end
end
def duplicate_log_inferred_answers(question, log)
if question.id == "uprn"
postcode_question = log.form.get_question("postcode_full", log)
postcode_question.get_inferred_answers(log)
else
question.get_inferred_answers(log)
end
end
end

35
app/helpers/filters_helper.rb

@ -26,6 +26,7 @@ module FiltersHelper
filters["organisation"].present? ||
filters["managing_organisation"].present? ||
filters["status"]&.compact_blank&.any? ||
filters["needstypes"]&.compact_blank&.any? ||
filters["years"]&.compact_blank&.any? ||
filters["bulk_upload_id"].present?
end
@ -56,6 +57,13 @@ module FiltersHelper
}.freeze
end
def needstype_filters
{
"1" => "General needs",
"2" => "Supported housing",
}.freeze
end
def location_status_filters
{
"incomplete" => "Incomplete",
@ -108,12 +116,35 @@ module FiltersHelper
user.support? || org.stock_owners.count > 1 || (org.holds_own_stock? && org.stock_owners.count.positive?)
end
private
def logs_for_both_needstypes_present?(organisation)
return true if current_user.support? && organisation.blank?
return [1, 2].all? { |needstype| organisation.lettings_logs.visible.where(needstype:).count.positive? } if current_user.support?
[1, 2].all? { |needstype| current_user.lettings_logs.visible.where(needstype:).count.positive? }
end
def non_support_with_multiple_owning_orgs?
current_user.organisation.stock_owners.count > 1 && user_lettings_path?
end
def non_support_with_multiple_managing_orgs?
current_user.organisation.managing_agents.count > 1 && user_lettings_path?
end
def user_lettings_path?
request.path == "/lettings-logs"
end
def user_or_org_lettings_path?
request.path.include?("/lettings-logs")
end
def applied_filters_count(filter_type)
filters_count(applied_filters(filter_type))
end
private
def applied_filters(filter_type)
return {} unless session[session_name_for(filter_type)]
@ -126,7 +157,7 @@ private
def filters_count(filters)
filters.each.sum do |category, category_filters|
if %w[status years bulk_upload_id].include?(category)
if %w[years status needstypes bulk_upload_id].include?(category)
category_filters.count(&:present?)
elsif %w[user owning_organisation managing_organisation].include?(category)
1

8
app/helpers/form_page_helper.rb

@ -2,4 +2,12 @@ module FormPageHelper
def action_href(log, page_id, referrer = "check_answers")
send("#{log.model_name.param_key}_#{page_id}_path", log, referrer:)
end
def returning_to_question_page?(page, referrer)
page.interruption_screen? || referrer == "check_answers"
end
def accessed_from_duplicate_logs?(referrer)
%w[duplicate_logs duplicate_logs_banner].include?(referrer)
end
end

13
app/models/derived_variables/lettings_log_variables.rb

@ -45,6 +45,8 @@ module DerivedVariables::LettingsLogVariables
end
self.renttype = RENT_TYPE_MAPPING[rent_type]
self.lettype = get_lettype
self.lar = get_lar
self.irproduct = get_irproduct
self.totchild = get_totchild
self.totelder = get_totelder
self.totadult = get_totadult
@ -307,4 +309,15 @@ private
self.town_or_city = nil
self.county = nil
end
def get_lar
return 1 if rent_type == 2
return 2 if rent_type == 1
end
def get_irproduct
return 1 if rent_type == 3
return 2 if rent_type == 4
return 3 if rent_type == 5
end
end

3
app/models/form/lettings/pages/address.rb

@ -16,9 +16,8 @@ class Form::Lettings::Pages::Address < ::Form::Page
end
def routed_to?(log, _current_user = nil)
return false if log.uprn_known.nil?
return false if log.is_supported_housing?
log.uprn_confirmed != 1 || log.uprn_known.zero?
log.uprn_known.nil? || log.uprn_known.zero? || log.uprn_confirmed&.zero?
end
end

1
app/models/form/lettings/pages/location.rb

@ -7,6 +7,7 @@ class Form::Lettings::Pages::Location < ::Form::Page
"scheme_has_multiple_locations?" => true,
},
]
@header = "Location"
@next_unresolved_page_id = :check_answers
end

2
app/models/form/lettings/pages/max_rent_value_check.rb

@ -19,6 +19,6 @@ class Form::Lettings::Pages::MaxRentValueCheck < ::Form::Page
end
def interruption_screen_question_ids
%w[brent startdate uprn postcode_full la beds rent_type needstype]
%w[brent period startdate uprn postcode_full la beds rent_type needstype]
end
end

2
app/models/form/lettings/pages/min_rent_value_check.rb

@ -19,6 +19,6 @@ class Form::Lettings::Pages::MinRentValueCheck < ::Form::Page
end
def interruption_screen_question_ids
%w[brent startdate uprn postcode_full la beds rent_type needstype]
%w[brent period startdate uprn postcode_full la beds rent_type needstype]
end
end

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

@ -13,6 +13,7 @@ class Form::Lettings::Questions::LocationId < ::Form::Question
}
@question_number = 10
@disable_clearing_if_not_routed_or_dynamic_answer_options = true
@top_guidance_partial = "finding_location"
end
def answer_options

4
app/models/form/lettings/questions/managing_organisation.rb

@ -23,8 +23,8 @@ class Form::Lettings::Questions::ManagingOrganisation < ::Form::Question
if log.owning_organisation.holds_own_stock?
opts[log.owning_organisation.id] = "#{log.owning_organisation.name} (Owning organisation)"
end
elsif user.organisation.absorbed_organisations.exists?
opts[user.organisation.id] = "#{user.organisation.name} (Your organisation, active as of #{user.organisation.created_at.to_fs(:govuk_date)})"
elsif user.organisation.absorbed_organisations.exists? && user.organisation.available_from.present?
opts[user.organisation.id] = "#{user.organisation.name} (Your organisation, active as of #{user.organisation.available_from.to_fs(:govuk_date)})"
else
opts[user.organisation.id] = "#{user.organisation.name} (Your organisation)"
end

1
app/models/form/lettings/questions/referral.rb

@ -14,6 +14,7 @@ class Form::Lettings::Questions::Referral < ::Form::Question
ANSWER_OPTIONS = {
"1" => {
"value" => "Internal transfer",
"hint" => "Where the tenant has moved to another social property owned by the same landlord.",
},
"2" => {
"value" => "Tenant applied directly (no referral or nomination)",

1
app/models/form/lettings/questions/referral_prp.rb

@ -14,6 +14,7 @@ class Form::Lettings::Questions::ReferralPrp < ::Form::Question
ANSWER_OPTIONS = {
"1" => {
"value" => "Internal transfer",
"hint" => "Where the tenant has moved to another social property owned by the same landlord.",
},
"2" => {
"value" => "Tenant applied directly (no nomination)",

1
app/models/form/lettings/questions/referral_supported_housing.rb

@ -14,6 +14,7 @@ class Form::Lettings::Questions::ReferralSupportedHousing < ::Form::Question
ANSWER_OPTIONS = {
"1" => {
"value" => "Internal transfer",
"hint" => "Where the tenant has moved to another social property owned by the same landlord.",
},
"2" => {
"value" => "Tenant applied directly (no referral)",

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

@ -12,7 +12,7 @@ class Form::Lettings::Questions::ReferralSupportedHousingPrp < ::Form::Question
end
ANSWER_OPTIONS = {
"1" => { "value" => "Internal transfer" },
"1" => { "value" => "Internal transfer", "hint" => "Where the tenant has moved to another social property owned by the same landlord." },
"2" => { "value" => "Tenant applied directly (no referral or nomination)" },
"3" => { "value" => "Nominated by a local housing authority" },
"4" => { "value" => "Referred by local authority housing department" },

38
app/models/form/lettings/questions/stock_owner.rb

@ -16,20 +16,38 @@ class Form::Lettings::Questions::StockOwner < ::Form::Question
return answer_opts unless log
if log.owning_organisation_id.present?
answer_opts = answer_opts.merge({ log.owning_organisation.id => log.owning_organisation.name })
answer_opts[log.owning_organisation.id] = log.owning_organisation.name
end
recently_absorbed_organisations = user.organisation.absorbed_organisations.merged_during_open_collection_period
if !user.support? && user.organisation.holds_own_stock?
answer_opts[user.organisation.id] = "#{user.organisation.name} (Your organisation)"
answer_opts[user.organisation.id] = if recently_absorbed_organisations.exists? && user.organisation.available_from.present?
"#{user.organisation.name} (Your organisation, active as of #{user.organisation.available_from.to_fs(:govuk_date)})"
else
"#{user.organisation.name} (Your organisation)"
end
end
user_answer_options = if user.support?
Organisation.where(holds_own_stock: true)
else
user.organisation.stock_owners + user.organisation.absorbed_organisations.where(holds_own_stock: true)
end.pluck(:id, :name).to_h
if user.support?
Organisation.where(holds_own_stock: true).find_each do |org|
if org.merge_date.present?
answer_opts[org.id] = "#{org.name} (inactive as of #{org.merge_date.to_fs(:govuk_date)})" if org.merge_date >= FormHandler.instance.start_date_of_earliest_open_for_editing_collection_period
elsif org.absorbed_organisations.merged_during_open_collection_period.exists?
answer_opts[org.id] = "#{org.name} (active as of #{org.created_at.to_fs(:govuk_date)})"
else
answer_opts[org.id] = org.name
end
end
else
user.organisation.stock_owners.each do |stock_owner|
answer_opts[stock_owner.id] = stock_owner.name
end
recently_absorbed_organisations.each do |absorbed_org|
answer_opts[absorbed_org.id] = merged_organisation_label(absorbed_org.name, absorbed_org.merge_date) if absorbed_org.holds_own_stock?
end
end
answer_opts.merge(user_answer_options)
answer_opts
end
def displayed_answer_options(log, user = nil)
@ -71,4 +89,8 @@ private
def selected_answer_option_is_derived?(_log)
true
end
def merged_organisation_label(name, merge_date)
"#{name} (inactive as of #{merge_date.to_fs(:govuk_date)})"
end
end

4
app/models/form/sales/pages/address.rb

@ -16,8 +16,6 @@ class Form::Sales::Pages::Address < ::Form::Page
end
def routed_to?(log, _current_user = nil)
return false if log.uprn_known.nil?
log.uprn_known.zero? || log.uprn_confirmed&.zero?
log.uprn_known.nil? || log.uprn_known.zero? || log.uprn_confirmed&.zero?
end
end

4
app/models/form/sales/pages/uprn.rb

@ -11,6 +11,10 @@ class Form::Sales::Pages::Uprn < ::Form::Page
]
end
def routed_to?(_log, _current_user)
true
end
def skip_text
"Enter address instead"
end

4
app/models/form/sales/questions/owning_organisation_id.rb

@ -22,8 +22,8 @@ class Form::Sales::Questions::OwningOrganisationId < ::Form::Question
recently_absorbed_organisations = user.organisation.absorbed_organisations.merged_during_open_collection_period
if !user.support? && user.organisation.holds_own_stock?
answer_opts[user.organisation.id] = if recently_absorbed_organisations.exists?
"#{user.organisation.name} (Your organisation, active as of #{user.organisation.created_at.to_fs(:govuk_date)})"
answer_opts[user.organisation.id] = if recently_absorbed_organisations.exists? && user.organisation.available_from.present?
"#{user.organisation.name} (Your organisation, active as of #{user.organisation.available_from.to_fs(:govuk_date)})"
else
"#{user.organisation.name} (Your organisation)"
end

23
app/models/lettings_log.rb

@ -43,6 +43,13 @@ class LettingsLog < Log
scope :filter_by_tenant_code, ->(tenant_code) { where("tenancycode ILIKE ?", "%#{tenant_code}%") }
scope :filter_by_propcode, ->(propcode) { where("propcode ILIKE ?", "%#{propcode}%") }
scope :filter_by_location_postcode, ->(postcode_full) { left_joins(:location).where("REPLACE(locations.postcode, ' ', '') ILIKE ?", "%#{postcode_full.delete(' ')}%") }
scope :filter_by_needstype, ->(needstype) { where(needstype:) }
scope :filter_by_needstypes, lambda { |needstypes, _user = nil|
first_needstype = needstypes.shift
query = filter_by_needstype(first_needstype)
needstypes.each { |needstype| query = query.or(filter_by_needstype(needstype)) }
query.all
}
scope :search_by, lambda { |param|
filter_by_location_postcode(param)
.or(filter_by_tenant_code(param))
@ -594,6 +601,22 @@ class LettingsLog < Log
public_send("details_known_#{person_index}") == 1
end
def duplicate_check_question_ids
["owning_organisation_id",
"startdate",
"tenancycode",
form.start_date.year < 2023 || uprn.blank? ? "postcode_full" : nil,
form.start_date.year >= 2023 && uprn.present? ? "uprn" : nil,
"scheme_id",
"location_id",
"age1",
"sex1",
"ecstat1",
household_charge == 1 ? "household_charge" : nil,
"tcharge",
is_carehome? ? "chcharge" : nil].compact
end
private
def reset_invalid_unresolved_log_fields!

10
app/models/location.rb

@ -16,7 +16,7 @@ class Location < ApplicationRecord
before_validation :lookup_postcode!, if: :postcode_changed?
auto_strip_attributes :name
auto_strip_attributes :name, squish: true
scope :search_by_postcode, ->(postcode) { where("REPLACE(postcode, ' ', '') ILIKE ?", "%#{postcode.delete(' ')}%") }
scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") }
@ -110,7 +110,7 @@ class Location < ApplicationRecord
def self.find_by_id_on_multiple_fields(id)
return if id.nil?
where(id:).or(where(old_visible_id: id)).first
where(id:).or(where("ltrim(old_visible_id, '0') = ?", id.to_i.to_s)).first
end
def postcode=(postcode)
@ -131,8 +131,8 @@ class Location < ApplicationRecord
location_deactivation_periods.deactivations_without_reactivation.first
end
def recent_deactivation
location_deactivation_periods.order("created_at").last
def last_deactivation_before(date)
location_deactivation_periods.where("deactivation_date <= ?", date).order("created_at").last
end
def status
@ -143,7 +143,7 @@ class Location < ApplicationRecord
return :incomplete unless confirmed
return :deactivated if 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 recent_deactivation&.reactivation_date.present? && date < recent_deactivation.reactivation_date
return :reactivating_soon if last_deactivation_before(date)&.reactivation_date.present? && date < last_deactivation_before(date).reactivation_date
return :activating_soon if startdate.present? && date < startdate
:active

7
app/models/log.rb

@ -193,6 +193,13 @@ class Log < ApplicationRecord
form.edit_end_date < Time.zone.now || older_than_previous_collection_year?
end
def duplicate_check_questions(current_user)
duplicate_check_question_ids.map { |question_id|
question = form.get_question(question_id, self)
question if question.page.routed_to?(self, current_user)
}.compact
end
private
# Handle logs that are older than previous collection start date

8
app/models/organisation.rb

@ -36,7 +36,7 @@ class Organisation < ApplicationRecord
has_paper_trail
auto_strip_attributes :name
auto_strip_attributes :name, squish: true
PROVIDER_TYPE = {
LA: 1,
@ -140,4 +140,10 @@ class Organisation < ApplicationRecord
def duplicate_sales_logs_sets
sales_logs.duplicate_sets.map { |array_str| array_str ? array_str.map(&:to_i) : [] }
end
def recently_absorbed_organisations_grouped_by_merge_date
return unless absorbed_organisations.present? && absorbed_organisations.merged_during_open_collection_period.present?
absorbed_organisations.merged_during_open_collection_period.group_by(&:merge_date)
end
end

11
app/models/sales_log.rb

@ -436,4 +436,15 @@ class SalesLog < Log
self.pcodenk = nil if errors.attribute_names.include? :postcode_full
end
def duplicate_check_question_ids
["owning_organisation_id",
"saledate",
"purchid",
"age1",
"sex1",
"ecstat1",
form.start_date.year < 2023 || uprn.blank? ? "postcode_full" : nil,
form.start_date.year >= 2023 && uprn.present? ? "uprn" : nil].compact
end
end

23
app/models/scheme.rb

@ -75,7 +75,7 @@ class Scheme < ApplicationRecord
validate :validate_confirmed
validate :validate_owning_organisation
auto_strip_attributes :service_name
auto_strip_attributes :service_name, squish: true
SENSITIVE = {
No: 0,
@ -109,6 +109,7 @@ class Scheme < ApplicationRecord
"Medium level": 3,
"High level": 4,
"Nursing care in a care home": 5,
"Floating support": 6,
}.freeze
enum support_type: SUPPORT_TYPE, _suffix: true
@ -162,13 +163,15 @@ class Scheme < ApplicationRecord
enum arrangement_type: ARRANGEMENT_TYPE, _suffix: true
def self.find_by_id_on_multiple_fields(id)
return if id.nil?
def self.find_by_id_on_multiple_fields(scheme_id, location_id)
return if scheme_id.nil?
if id.start_with?("S")
where(id: id[1..]).first
if scheme_id.start_with?("S")
where(id: scheme_id[1..]).first
elsif location_id.present?
joins(:locations).where("ltrim(schemes.old_visible_id, '0') = ? AND ltrim(locations.old_visible_id, '0') = ?", scheme_id.to_i.to_s, location_id.to_i.to_s).first || where("ltrim(schemes.old_visible_id, '0') = ?", scheme_id.to_i.to_s).first
else
where(old_visible_id: id).first
where("ltrim(old_visible_id, '0') = ?", scheme_id.to_i.to_s).first
end
end
@ -225,7 +228,7 @@ class Scheme < ApplicationRecord
"Medium level": "Staff on site daily or making frequent visits with some out-of-hours cover.",
"High level": "Intensive level of staffing provided on a 24-hour basis.",
}
Scheme.support_types.keys.excluding("Missing").map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize, description: hints[key.to_sym]) }
Scheme.support_types.keys.excluding("Missing").excluding("Floating support").map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize, description: hints[key.to_sym]) }
end
def intended_length_of_stay_options_with_hints
@ -266,8 +269,8 @@ class Scheme < ApplicationRecord
scheme_deactivation_periods.deactivations_without_reactivation.first
end
def recent_deactivation
scheme_deactivation_periods.order("created_at").last
def last_deactivation_before(date)
scheme_deactivation_periods.where("deactivation_date <= ?", date).order("created_at").last
end
def status
@ -278,7 +281,7 @@ class Scheme < ApplicationRecord
return :incomplete unless confirmed && locations.confirmed.any?
return :deactivated if 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 recent_deactivation&.reactivation_date.present? && date < recent_deactivation.reactivation_date
return :reactivating_soon if last_deactivation_before(date)&.reactivation_date.present? && date < last_deactivation_before(date).reactivation_date
:active
end

6
app/models/user.rb

@ -38,7 +38,7 @@ class User < ApplicationRecord
has_one_time_password(encrypted: true)
auto_strip_attributes :name
auto_strip_attributes :name, squish: true
ROLES = {
data_provider: 1,
@ -173,9 +173,9 @@ class User < ApplicationRecord
def logs_filters(specific_org: false)
if (support? && !specific_org) || organisation.has_managing_agents? || organisation.has_stock_owners?
%w[status years assigned_to user managing_organisation owning_organisation bulk_upload_id]
%w[years status needstypes assigned_to user managing_organisation owning_organisation bulk_upload_id]
else
%w[status years assigned_to user bulk_upload_id]
%w[years status needstypes assigned_to user bulk_upload_id]
end
end

20
app/models/validations/financial_validations.rb

@ -146,7 +146,7 @@ module Validations::FinancialValidations
private
CHARGE_MAXIMUMS = {
CHARGE_MAXIMA_PER_WEEK = {
scharge: {
private_registered_provider: {
general_needs: 800,
@ -181,22 +181,30 @@ private
PROVIDER_TYPE = { 1 => :local_authority, 2 => :private_registered_provider }.freeze
NEEDSTYPE_VALUES = { 2 => :supported_housing, 1 => :general_needs }.freeze
CHARGE_NAMES = { scharge: "service charge", pscharge: "personal service charge", supcharg: "support charge" }.freeze
def validate_charges(record)
return unless record.owning_organisation
provider_type = record.owning_organisation.provider_type_before_type_cast
%i[scharge pscharge supcharg].each do |charge|
maximum = CHARGE_MAXIMUMS.dig(charge, PROVIDER_TYPE[provider_type], NEEDSTYPE_VALUES[record.needstype])
maximum_per_week = CHARGE_MAXIMA_PER_WEEK.dig(charge, PROVIDER_TYPE[provider_type], NEEDSTYPE_VALUES[record.needstype])
if maximum.present? && record[:period].present? && record[charge].present? && !weekly_value_in_range(record, charge, 0.0, maximum)
record.errors.add charge, :outside_the_range, message: I18n.t("validations.financial.rent.#{charge}.#{PROVIDER_TYPE[provider_type]}.#{NEEDSTYPE_VALUES[record.needstype]}")
end
next unless maximum_per_week.present? && record[:period].present? && record[charge].present? && !weekly_value_in_range(record, charge, 0.0, maximum_per_week)
charge_name = CHARGE_NAMES[charge]
frequency = record.form.get_question("period", record).label_from_value(record.period).downcase
letting_type = NEEDSTYPE_VALUES[record.needstype].to_s.humanize(capitalize: false)
provider_type_label = PROVIDER_TYPE[provider_type].to_s.humanize(capitalize: false)
maximum_per_period = record.weekly_to_value_per_period(maximum_per_week)
record.errors.add charge, :outside_the_range, message: I18n.t("validations.financial.rent.out_of_range", charge_name:, maximum_per_period:, frequency:, letting_type:, provider_type: provider_type_label)
record.errors.add :period, :outside_the_range, message: I18n.t("validations.financial.rent.out_of_range", charge_name:, maximum_per_period:, frequency:, letting_type:, provider_type: provider_type_label)
end
end
def weekly_value_in_range(record, field, min, max)
record[field].present? && record.weekly_value(record[field]).present? && record.weekly_value(record[field]).between?(min, max)
record.weekly_value(record[field])&.between?(min, max)
end
def validate_rent_range(record)

3
app/models/validations/household_validations.rb

@ -73,7 +73,6 @@ module Validations::HouseholdValidations
# 13 Children's home / Foster Care
# 14 Bed and breakfast
# 19 Rough Sleeping
# 21 Refuge
# 23 Mobile home / Caravan
# 24 Home Office Asylum Support
# 25 Other
@ -81,7 +80,7 @@ module Validations::HouseholdValidations
# 27 Owner occupation (low-cost home ownership)
# 28 Living with Friends or Family
# 29 Prison / Approved Probation Hostel
if record.is_internal_transfer? && [3, 4, 7, 10, 13, 14, 19, 21, 23, 24, 25, 26, 27, 28, 29].include?(record.prevten)
if record.is_internal_transfer? && [3, 4, 7, 10, 13, 14, 19, 23, 24, 25, 26, 27, 28, 29].include?(record.prevten)
label = record.form.get_question("prevten", record).present? ? record.form.get_question("prevten", record).label_from_value(record.prevten) : ""
record.errors.add :prevten, :internal_transfer_non_social_housing, message: I18n.t("validations.household.prevten.internal_transfer", prevten: label)
record.errors.add :referral, :internal_transfer_non_social_housing, message: I18n.t("validations.household.referral.prevten_invalid", prevten: label)

8
app/models/validations/sales/setup_validations.rb

@ -37,7 +37,7 @@ module Validations::Sales::SetupValidations
if absorbing_owning_organisation_inactive?(record)
record.errors.add :saledate, I18n.t("validations.setup.saledate.invalid_absorbing_organisations_saledate",
owning_organisation: record.owning_organisation.name,
owning_organisation_available_from: record.owning_organisation.created_at.to_formatted_s(:govuk_date))
owning_organisation_available_from: record.owning_organisation.available_from.to_formatted_s(:govuk_date))
end
end
@ -50,10 +50,10 @@ module Validations::Sales::SetupValidations
owning_organisation: record.owning_organisation.name,
owning_organisation_merge_date: record.owning_organisation.merge_date.to_formatted_s(:govuk_date),
owning_absorbing_organisation: record.owning_organisation.absorbing_organisation.name)
elsif record.owning_organisation&.absorbed_organisations.present? && record.owning_organisation.created_at.to_date > record.saledate.to_date
elsif record.owning_organisation&.absorbed_organisations.present? && record.owning_organisation.available_from.present? && record.owning_organisation.available_from.to_date > record.saledate.to_date
record.errors.add :owning_organisation_id, I18n.t("validations.setup.owning_organisation.inactive_absorbing_organisation_sales",
owning_organisation: record.owning_organisation.name,
owning_organisation_available_from: record.owning_organisation.created_at.to_formatted_s(:govuk_date))
owning_organisation_available_from: record.owning_organisation.available_from.to_formatted_s(:govuk_date))
end
end
end
@ -104,6 +104,6 @@ private
end
def absorbing_owning_organisation_inactive?(record)
record.owning_organisation&.absorbed_organisations.present? && record.owning_organisation.created_at.to_date > record.saledate.to_date
record.owning_organisation&.absorbed_organisations.present? && record.owning_organisation.available_from.present? && record.owning_organisation.available_from.to_date > record.saledate.to_date
end
end

20
app/models/validations/setup_validations.rb

@ -61,10 +61,10 @@ module Validations::SetupValidations
owning_organisation: record.owning_organisation.name,
owning_organisation_merge_date: record.owning_organisation.merge_date.to_formatted_s(:govuk_date),
owning_absorbing_organisation: record.owning_organisation.absorbing_organisation.name)
elsif owning_organisation&.absorbed_organisations.present? && owning_organisation.created_at.to_date > record.startdate.to_date
elsif owning_organisation&.absorbed_organisations.present? && owning_organisation.available_from.present? && owning_organisation.available_from.to_date > record.startdate.to_date
record.errors.add :owning_organisation_id, I18n.t("validations.setup.owning_organisation.inactive_absorbing_organisation",
owning_organisation: record.owning_organisation.name,
owning_organisation_available_from: record.owning_organisation.created_at.to_formatted_s(:govuk_date))
owning_organisation_available_from: record.owning_organisation.available_from.to_formatted_s(:govuk_date))
end
end
@ -74,10 +74,10 @@ module Validations::SetupValidations
managing_organisation: record.managing_organisation.name,
managing_organisation_merge_date: record.managing_organisation.merge_date.to_formatted_s(:govuk_date),
managing_absorbing_organisation: record.managing_organisation.absorbing_organisation.name)
elsif managing_organisation&.absorbed_organisations.present? && managing_organisation.created_at.to_date > record.startdate.to_date
elsif managing_organisation&.absorbed_organisations.present? && managing_organisation.available_from.present? && managing_organisation.available_from.to_date > record.startdate.to_date
record.errors.add :managing_organisation_id, I18n.t("validations.setup.managing_organisation.inactive_absorbing_organisation",
managing_organisation: record.managing_organisation.name,
managing_organisation_available_from: record.managing_organisation.created_at.to_formatted_s(:govuk_date))
managing_organisation_available_from: record.managing_organisation.available_from.to_formatted_s(:govuk_date))
end
end
end
@ -194,20 +194,20 @@ private
if absorbing_owning_organisation_inactive?(record) && absorbing_managing_organisation_inactive?(record)
record.errors.add :startdate, I18n.t("validations.setup.startdate.invalid_absorbing_organisations_start_date.different_organisations",
owning_organisation: record.owning_organisation.name,
owning_organisation_active_from: record.owning_organisation.created_at.to_formatted_s(:govuk_date),
owning_organisation_active_from: record.owning_organisation.available_from.to_formatted_s(:govuk_date),
managing_organisation: record.managing_organisation.name,
managing_organisation_active_from: record.managing_organisation.created_at.to_formatted_s(:govuk_date))
managing_organisation_active_from: record.managing_organisation.available_from.to_formatted_s(:govuk_date))
else
if absorbing_owning_organisation_inactive?(record)
record.errors.add :startdate, I18n.t("validations.setup.startdate.invalid_absorbing_organisations_start_date.owning_organisation",
owning_organisation: record.owning_organisation.name,
owning_organisation_available_from: record.owning_organisation.created_at.to_formatted_s(:govuk_date))
owning_organisation_available_from: record.owning_organisation.available_from.to_formatted_s(:govuk_date))
end
if absorbing_managing_organisation_inactive?(record)
record.errors.add :startdate, I18n.t("validations.setup.startdate.invalid_absorbing_organisations_start_date.managing_organisation",
managing_organisation: record.managing_organisation.name,
managing_organisation_available_from: record.managing_organisation.created_at.to_formatted_s(:govuk_date))
managing_organisation_available_from: record.managing_organisation.available_from.to_formatted_s(:govuk_date))
end
end
end
@ -221,11 +221,11 @@ private
end
def absorbing_owning_organisation_inactive?(record)
record.owning_organisation&.absorbed_organisations.present? && record.owning_organisation.created_at.to_date > record.startdate.to_date
record.owning_organisation&.absorbed_organisations.present? && record.owning_organisation.available_from.present? && record.owning_organisation.available_from.to_date > record.startdate.to_date
end
def absorbing_managing_organisation_inactive?(record)
record.managing_organisation&.absorbed_organisations.present? && record.managing_organisation.created_at.to_date > record.startdate.to_date
record.managing_organisation&.absorbed_organisations.present? && record.managing_organisation.available_from.present? && record.managing_organisation.available_from.to_date > record.startdate.to_date
end
def organisations_belong_to_same_merge?(organisation_a, organisation_b)

2
app/models/validations/shared_validations.rb

@ -90,7 +90,7 @@ module Validations::SharedValidations
status = resource.status_at(date)
return unless %i[reactivating_soon activating_soon deactivated].include?(status)
closest_reactivation = resource.recent_deactivation
closest_reactivation = resource.last_deactivation_before(date)
open_deactivation = resource.open_deactivation
date = case status

45
app/services/bulk_upload/lettings/year2022/row_parser.rb

@ -354,12 +354,10 @@ class BulkUpload::Lettings::Year2022::RowParser
validate :validate_managing_org_exists, on: :after_log
validate :validate_managing_org_related, on: :after_log
validate :validate_scheme_related, on: :after_log
validate :validate_scheme_exists, on: :after_log
validate :validate_related_scheme_exists, on: :after_log
validate :validate_scheme_data_given, on: :after_log
validate :validate_location_related, on: :after_log
validate :validate_location_exists, on: :after_log
validate :validate_related_location_exists, on: :after_log
validate :validate_location_data_given, on: :after_log
validate :validate_created_by_exists, on: :after_log
@ -513,7 +511,7 @@ private
end
def created_by
@created_by ||= User.find_by(email: field_112)
@created_by ||= User.where("lower(email) = ?", field_112&.downcase).first
end
def duplicate_check_fields
@ -530,53 +528,36 @@ private
].compact
end
def validate_location_related
return if scheme.blank? || location.blank?
unless location.scheme == scheme
block_log_creation!
errors.add(:field_5, "Scheme code must relate to a location that is owned by owning organisation or managing organisation")
end
end
def location
return if scheme.nil?
@location ||= scheme.locations.find_by_id_on_multiple_fields(field_5)
end
def validate_location_exists
def validate_related_location_exists
if scheme && field_5.present? && location.nil?
errors.add(:field_5, "Location could not be found with the provided scheme code", category: :setup)
block_log_creation!
errors.add(:field_5, "Scheme code must relate to a scheme that is owned by the owning organisation or managing organisation", category: :setup)
end
end
def validate_location_data_given
if bulk_upload.supported_housing? && field_5.blank?
block_log_creation!
errors.add(:field_5, I18n.t("validations.not_answered", question: "scheme code"), category: :setup)
end
end
def validate_scheme_related
return unless field_4.present? && scheme.present?
owned_by_owning_org = owning_organisation && scheme.owning_organisation == owning_organisation
owned_by_managing_org = managing_organisation && scheme.owning_organisation == managing_organisation
unless owned_by_owning_org || owned_by_managing_org
def validate_related_scheme_exists
if field_4.present? && owning_organisation.present? && managing_organisation.present? && scheme.nil?
block_log_creation!
errors.add(:field_4, "This management group code does not belong to your organisation, or any of your stock owners / managing agents", category: :setup)
end
end
def validate_scheme_exists
if field_4.present? && scheme.nil?
errors.add(:field_4, "The management group code is not correct", category: :setup)
errors.add(:field_4, "This management group code does not belong to the owning organisation or managing organisation", category: :setup)
end
end
def validate_scheme_data_given
if bulk_upload.supported_housing? && field_4.blank?
block_log_creation!
errors.add(:field_4, I18n.t("validations.not_answered", question: "management group code"), category: :setup)
end
end
@ -1488,6 +1469,8 @@ private
end
def scheme
@scheme ||= Scheme.find_by_id_on_multiple_fields(field_4)
return if field_4.nil? || owning_organisation.nil? || managing_organisation.nil?
@scheme ||= Scheme.where(id: (owning_organisation.owned_schemes + managing_organisation.owned_schemes).map(&:id)).find_by_id_on_multiple_fields(field_4, field_5)
end
end

47
app/services/bulk_upload/lettings/year2023/row_parser.rb

@ -387,12 +387,10 @@ class BulkUpload::Lettings::Year2023::RowParser
validate :validate_managing_org_exists, on: :after_log
validate :validate_managing_org_related, on: :after_log
validate :validate_scheme_related, on: :after_log
validate :validate_scheme_exists, on: :after_log
validate :validate_related_scheme_exists, on: :after_log
validate :validate_scheme_data_given, on: :after_log
validate :validate_location_related, on: :after_log
validate :validate_location_exists, on: :after_log
validate :validate_related_location_exists, on: :after_log
validate :validate_location_data_given, on: :after_log
validate :validate_created_by_exists, on: :after_log
@ -402,7 +400,7 @@ class BulkUpload::Lettings::Year2023::RowParser
validate :validate_nulls, on: :after_log
validate :validate_uprn_exists_if_any_key_address_fields_are_blank, on: :after_log
validate :validate_uprn_exists_if_any_key_address_fields_are_blank, on: :after_log, unless: -> { supported_housing? }
validate :validate_incomplete_soft_validations, on: :after_log
@ -542,7 +540,7 @@ private
end
def created_by
@created_by ||= User.find_by(email: field_3)
@created_by ||= User.where("lower(email) = ?", field_3&.downcase).first
end
def validate_uprn_exists_if_any_key_address_fields_are_blank
@ -746,47 +744,30 @@ private
end
end
def validate_location_related
return if scheme.blank? || location.blank?
if location.scheme != scheme && location_field.present?
block_log_creation!
errors.add(location_field, "#{scheme_or_management_group.capitalize} code must relate to a #{location_or_scheme} that is owned by owning organisation or managing organisation", category: :setup)
end
end
def validate_location_exists
def validate_related_location_exists
if scheme && location_id.present? && location.nil? && location_field.present?
errors.add(location_field, "#{location_or_scheme.capitalize} could not be found with the provided #{scheme_or_management_group} code", category: :setup)
block_log_creation!
errors.add(location_field, "#{location_or_scheme.capitalize} code must relate to a #{location_or_scheme} that is owned by the owning organisation or managing organisation", category: :setup)
end
end
def validate_location_data_given
if supported_housing? && location_id.blank? && location_field.present?
block_log_creation!
errors.add(location_field, I18n.t("validations.not_answered", question: "#{location_or_scheme} code"), category: "setup")
end
end
def validate_scheme_related
return unless scheme_id.present? && scheme.present?
owned_by_owning_org = owning_organisation && scheme.owning_organisation == owning_organisation
owned_by_managing_org = managing_organisation && scheme.owning_organisation == managing_organisation
if !(owned_by_owning_org || owned_by_managing_org) && scheme_field.present?
def validate_related_scheme_exists
if scheme_id.present? && scheme_field.present? && owning_organisation.present? && managing_organisation.present? && scheme.nil?
block_log_creation!
errors.add(scheme_field, "This #{scheme_or_management_group} code does not belong to your organisation, or any of your stock owners / managing agents", category: :setup)
end
end
def validate_scheme_exists
if scheme_id.present? && scheme_field.present? && scheme.nil?
errors.add(scheme_field, "The #{scheme_or_management_group} code is not correct", category: :setup)
errors.add(scheme_field, "This #{scheme_or_management_group} code does not belong to the owning organisation or managing organisation", category: :setup)
end
end
def validate_scheme_data_given
if supported_housing? && scheme_field.present? && scheme_id.blank?
block_log_creation!
errors.add(scheme_field, I18n.t("validations.not_answered", question: "#{scheme_or_management_group} code"), category: "setup")
end
end
@ -1276,9 +1257,9 @@ private
end
def scheme
return if field_16.nil?
return if scheme_id.nil? || owning_organisation.nil? || managing_organisation.nil?
@scheme ||= Scheme.find_by_id_on_multiple_fields(scheme_id)
@scheme ||= Scheme.where(id: (owning_organisation.owned_schemes + managing_organisation.owned_schemes).map(&:id)).find_by_id_on_multiple_fields(scheme_id, location_id)
end
def location

2
app/services/bulk_upload/sales/year2022/row_parser.rb

@ -934,7 +934,7 @@ private
end
def created_by
@created_by ||= User.find_by(email: field_93)
@created_by ||= User.where("lower(email) = ?", field_93&.downcase).first
end
def hhregres

2
app/services/bulk_upload/sales/year2023/row_parser.rb

@ -1112,7 +1112,7 @@ private
end
def created_by
@created_by ||= User.find_by(email: field_2)
@created_by ||= User.where("lower(email) = ?", field_2&.downcase).first
end
def previous_la_known

6
app/services/csv/sales_log_csv_service.rb

@ -58,12 +58,6 @@ module Csv
end
}.freeze
AGE_KNOWN_FIELDS = {}.tap { |hash|
(1..6).each do |i|
hash["age#{i}"] = { "age_known_field" => "age#{i}_known" }
end
}.freeze
FIELDS_ALWAYS_EXPORTED_AS_CODES = %w[
la
prevloc

4
app/services/feature_toggle.rb

@ -37,4 +37,8 @@ class FeatureToggle
def self.duplicate_summary_enabled?
!Rails.env.production?
end
def self.maintenance_mode_enabled?
false
end
end

26
app/services/merge/merge_organisations_service.rb

@ -1,7 +1,9 @@
class Merge::MergeOrganisationsService
def initialize(absorbing_organisation_id:, merging_organisation_ids:)
def initialize(absorbing_organisation_id:, merging_organisation_ids:, merge_date: Time.zone.today, absorbing_organisation_active_from_merge_date: false)
@absorbing_organisation = Organisation.find(absorbing_organisation_id)
@merging_organisations = Organisation.find(merging_organisation_ids)
@merge_date = merge_date || Time.zone.today
@absorbing_organisation_active_from_merge_date = absorbing_organisation_active_from_merge_date
end
def call
@ -18,6 +20,7 @@ class Merge::MergeOrganisationsService
merge_sales_logs(merging_organisation)
mark_organisation_as_merged(merging_organisation)
end
@absorbing_organisation.available_from = @merge_date if @absorbing_organisation_active_from_merge_date
@absorbing_organisation.save!
log_success_message
rescue ActiveRecord::RecordInvalid => e
@ -65,17 +68,17 @@ private
merging_organisation.owned_schemes.each do |scheme|
next if scheme.deactivated?
new_scheme = Scheme.create!(scheme.attributes.except("id", "owning_organisation_id").merge(owning_organisation: @absorbing_organisation))
new_scheme = Scheme.create!(scheme.attributes.except("id", "owning_organisation_id", "old_id", "old_visible_id").merge(owning_organisation: @absorbing_organisation))
scheme.locations.each do |location|
new_scheme.locations << Location.new(location.attributes.except("id", "scheme_id")) unless location.deactivated?
new_scheme.locations << Location.new(location.attributes.except("id", "scheme_id", "old_id", "old_visible_id")) unless location.deactivated?
end
@merged_schemes[merging_organisation.name] << { name: new_scheme.service_name, code: new_scheme.id }
SchemeDeactivationPeriod.create!(scheme:, deactivation_date: Time.zone.now)
SchemeDeactivationPeriod.create!(scheme:, deactivation_date: @merge_date)
end
end
def merge_lettings_logs(merging_organisation)
merging_organisation.owned_lettings_logs.after_date(Time.zone.today).each do |lettings_log|
merging_organisation.owned_lettings_logs.after_date(@merge_date.to_time).each do |lettings_log|
if lettings_log.scheme.present?
scheme_to_set = @absorbing_organisation.owned_schemes.find_by(service_name: lettings_log.scheme.service_name)
location_to_set = scheme_to_set.locations.find_by(name: lettings_log.location&.name, postcode: lettings_log.location&.postcode)
@ -84,22 +87,23 @@ private
lettings_log.location = location_to_set if location_to_set.present?
end
lettings_log.owning_organisation = @absorbing_organisation
lettings_log.save!
lettings_log.save!(validate: false)
end
merging_organisation.managed_lettings_logs.after_date(Time.zone.today).each do |lettings_log|
merging_organisation.managed_lettings_logs.after_date(@merge_date.to_time).each do |lettings_log|
lettings_log.managing_organisation = @absorbing_organisation
lettings_log.save!
lettings_log.save!(validate: false)
end
end
def merge_sales_logs(merging_organisation)
merging_organisation.sales_logs.after_date(Time.zone.today).each do |sales_log|
sales_log.update(owning_organisation: @absorbing_organisation)
merging_organisation.sales_logs.after_date(@merge_date.to_time).each do |sales_log|
sales_log.owning_organisation = @absorbing_organisation
sales_log.save!(validate: false)
end
end
def mark_organisation_as_merged(merging_organisation)
merging_organisation.update(merge_date: Time.zone.today, absorbing_organisation: @absorbing_organisation)
merging_organisation.update(merge_date: @merge_date, absorbing_organisation: @absorbing_organisation)
end
def log_success_message

14
app/views/duplicate_logs/_duplicate_log_check_answers.erb

@ -1,28 +1,26 @@
<div class="x-govuk-summary-card govuk-!-margin-bottom-6">
<div class="x-govuk-summary-card__body">
<%= govuk_summary_list do |summary_list| %>
<% @duplicate_check_questions.each do |question| %>
<% log.duplicate_check_questions(current_user).each do |question| %>
<% summary_list.row do |row| %>
<% row.key { get_question_label(question) } %>
<% row.key { duplicate_log_question_label(question) } %>
<% row.value do %>
<%= simple_format(
get_answer_label(question, log),
duplicate_log_answer_label(question, log),
wrapper_tag: "span",
class: "govuk-!-margin-right-4",
) %>
<% extra_value = question.get_extra_check_answer_value(log) %>
<% if extra_value && question.answer_label(log, current_user).present? %>
<% if duplicate_log_extra_value(question, log) && duplicate_log_answer_label_present(question, log, current_user) %>
<%= simple_format(
extra_value,
duplicate_log_extra_value(question, log),
wrapper_tag: "span",
class: "govuk-!-font-weight-regular app-!-colour-muted",
) %>
<% end %>
<% question.get_inferred_answers(log).each do |inferred_answer| %>
<% duplicate_log_inferred_answers(question, log).each do |inferred_answer| %>
<span class="govuk-!-font-weight-regular app-!-colour-muted"><%= inferred_answer %></span>
<% end %>
<% end %>

4
app/views/duplicate_logs/index.html.erb

@ -24,7 +24,7 @@
<% row.cell text: "Lettings" %>
<% row.cell text: duplicate_set.map { |id| "Log #{id}" }.join(", ") %>
<% row.cell do %>
<%= govuk_link_to "Review logs", lettings_log_duplicate_logs_path(duplicate_set.first, original_log_id: duplicate_set.first) %>
<%= govuk_link_to "Review logs", lettings_log_duplicate_logs_path(duplicate_set.first, original_log_id: duplicate_set.first, referrer: params[:referrer], organisation_id: params[:organisation_id]) %>
<% end %>
<% end %>
<% end %>
@ -33,7 +33,7 @@
<% row.cell text: "Sales" %>
<% row.cell text: duplicate_set.map { |id| "Log #{id}" }.join(", ") %>
<% row.cell do %>
<%= govuk_link_to "Review logs", sales_log_duplicate_logs_path(duplicate_set.first, original_log_id: duplicate_set.first) %>
<%= govuk_link_to "Review logs", sales_log_duplicate_logs_path(duplicate_set.first, original_log_id: duplicate_set.first, referrer: params[:referrer], organisation_id: params[:organisation_id]) %>
<% end %>
<% end %>
<% end %>

11
app/views/duplicate_logs/no_more_duplicates.html.erb

@ -0,0 +1,11 @@
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-l">There are no more duplicate logs</h1>
</div>
</div>
<p class="govuk-body">
You have either changed or deleted all the duplicate logs.
</p>
<%= govuk_button_link_to "Back to all logs", lettings_logs_path %>

4
app/views/form/guidance/_finding_location.erb

@ -0,0 +1,4 @@
<%= govuk_details(summary_text: "What is a location?") do %>
<p class="govuk-body">A location is a postcode where supported housing is provided under a scheme. A scheme can have multiple locations, and a location can have multiple units at the same postcode.</p>
<p class="govuk-body"><%= govuk_link_to("Read more about schemes and locations", scheme_changes_path) %></p>
<% end %>

16
app/views/form/page.html.erb

@ -63,17 +63,19 @@
<%= f.hidden_field :interruption_page_referrer_type, value: @interruption_page_referrer_type %>
<div class="govuk-button-group">
<% if !@page.interruption_screen? && if request.query_parameters["referrer"] != "check_answers" %>
<%= f.govuk_submit "Save and continue" %>
<% if accessed_from_duplicate_logs?(request.query_parameters["referrer"]) %>
<%= f.govuk_submit "Save changes" %>
<%= govuk_link_to "Cancel", send("#{@log.class.name.underscore}_duplicate_logs_path", @log, original_log_id: request.query_parameters["original_log_id"]) %>
<% elsif returning_to_question_page?(@page, request.query_parameters["referrer"]) %>
<%= f.govuk_submit "Save changes" %>
<%= govuk_link_to "Cancel", send(@log.form.cancel_path(@page, @log), @log) %>
<% else %>
<%= f.govuk_submit "Save and continue" %>
<%= govuk_link_to(
(@page.skip_text || "Skip for now"),
(@page.skip_href(@log) || send(@log.form.next_page_redirect_path(@page, @log, current_user), @log)),
) %>
<% else %>
<%= f.govuk_submit "Save changes" %>
<%= govuk_link_to "Cancel", send(@log.form.cancel_path(@page, @log), @log) %>
<% end %>
<% end %>
<% end %>
</div>
</div>
</div>

12
app/views/layouts/application.html.erb

@ -91,11 +91,13 @@
navigation_classes: "govuk-header__navigation--end",
) do |component|
component.product_name(name: t("service_name"))
if current_user.nil?
component.navigation_item(text: "Sign in", href: user_session_path)
else
component.navigation_item(text: "Your account", href: account_path)
component.navigation_item(text: "Sign out", href: destroy_user_session_path)
unless FeatureToggle.maintenance_mode_enabled?
if current_user.nil?
component.navigation_item(text: "Sign in", href: user_session_path)
else
component.navigation_item(text: "Your account", href: account_path)
component.navigation_item(text: "Sign out", href: destroy_user_session_path)
end
end
end %>

6
app/views/locations/_location_filters.html.erb

@ -11,7 +11,7 @@
<%= filters_applied_text(@filter_type) %>
</p>
<p class="govuk-!-text-align-right govuk-grid-column-one-half">
<%= reset_filters_link(@filter_type, { scheme_id: @scheme.id }) %>
<%= reset_filters_link(@filter_type, { scheme_id: @scheme.id, search: request.params["search"] }.compact) %>
</p>
</div>
@ -23,6 +23,10 @@
category: "status",
} %>
<% if request.params["search"].present? %>
<%= f.hidden_field :search, value: request.params["search"] %>
<% end %>
<%= f.govuk_submit "Apply filters", class: "govuk-!-margin-bottom-0" %>
<% end %>
</div>

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

@ -24,7 +24,7 @@
<%= govuk_table do |table| %>
<%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %>
<%= render(SearchResultCaptionComponent.new(searched: @searched, count: @pagy.count, item_label:, total_count: @total_count, item: "locations", path: request.path)) %>
<%= render(SearchResultCaptionComponent.new(searched: @searched, count: @pagy.count, item_label:, total_count: @total_count, item: "locations", filters_count: applied_filters_count(@filter_type))) %>
<% end %>
<%= table.head do |head| %>
<%= head.row do |row| %>

54
app/views/logs/_log_filters.html.erb

@ -12,7 +12,7 @@
<%= filters_applied_text(@filter_type) %>
</p>
<p class="govuk-!-text-align-right govuk-grid-column-one-half">
<%= reset_filters_link(@filter_type) %>
<%= reset_filters_link(@filter_type, { search: request.params["search"] }.compact) %>
</p>
</div>
<% if bulk_upload_options(@bulk_upload).present? %>
@ -41,29 +41,39 @@
label: "Status",
category: "status",
} %>
<% if logs_for_both_needstypes_present?(@organisation) && user_or_org_lettings_path? %>
<%= render partial: "filters/checkbox_filter",
locals: {
f:,
options: needstype_filters,
label: "Needs type",
category: "needstypes",
} %>
<% end %>
<% end %>
<%= render partial: "filters/radio_filter",
locals: {
f:,
options: {
"all": { label: "Any user" },
"you": { label: "You" },
"specific_user": {
label: "Specific user",
conditional_filter: {
type: "select",
label: "User",
category: "user",
options: assigned_to_filter_options(current_user),
},
<%= render partial: "filters/radio_filter",
locals: {
f:,
options: {
"all": { label: "Any user" },
"you": { label: "You" },
"specific_user": {
label: "Specific user",
conditional_filter: {
type: "select",
label: "User",
category: "user",
options: assigned_to_filter_options(current_user),
},
},
label: "Assigned to",
category: "assigned_to",
} %>
},
label: "Assigned to",
category: "assigned_to",
} %>
<% if current_user.support? || current_user.organisation.stock_owners.count > 1 && request.path == "/lettings-logs" %>
<% if current_user.support? || non_support_with_multiple_owning_orgs? %>
<%= render partial: "filters/radio_filter", locals: {
f:,
options: {
@ -83,7 +93,7 @@
} %>
<% end %>
<% if (current_user.support? || current_user.organisation.managing_agents.count > 1) && request.path == "/lettings-logs" %>
<% if (current_user.support? || non_support_with_multiple_managing_orgs?) && user_or_org_lettings_path? %>
<%= render partial: "filters/radio_filter", locals: {
f:,
options: {
@ -103,6 +113,10 @@
} %>
<% end %>
<% if request.params["search"].present? %>
<%= f.hidden_field :search, value: request.params["search"] %>
<% end %>
<%= f.govuk_submit "Apply filters", class: "govuk-!-margin-bottom-0" %>
<% end %>
</div>

29
app/views/logs/_log_list.html.erb

@ -1,19 +1,22 @@
<h2 class="govuk-body">
<div class="govuk-grid-row">
<div class="govuk-grid-column-three-quarters">
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "logs", path: request.path)) %>
<% if logs&.any? %>
<%= govuk_link_to "Download (CSV)", csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4" %>
<% if @current_user.support? %>
<%= govuk_link_to "Download (CSV, codes only)", csv_codes_only_download_url, type: "text/csv", class: "govuk-!-margin-right-4" %>
<div class="govuk-grid-column-three-quarters">
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "logs", filters_count: applied_filters_count(@filter_type))) %>
<% if logs&.any? %>
<%= govuk_link_to "Download (CSV)", csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4" %>
<% if @current_user.support? %>
<%= govuk_link_to "Download (CSV, codes only)", csv_codes_only_download_url, type: "text/csv", class: "govuk-!-margin-right-4" %>
<% end %>
<% end %>
<% end %>
</div>
<div class="govuk-grid-column-one-quarter govuk-!-text-align-right">
<% if logs&.any? && (display_delete_logs?(@current_user, searched, filter_type) || in_organisations_tab?) %>
<%= govuk_link_to "Delete logs", delete_logs_path, class: "app-!-colour-red" %>
<% end %>
</div>
</div>
<div class="govuk-grid-column-one-quarter govuk-!-text-align-right">
<% if searched || applied_filters_count(@filter_type).positive? %>
<br>
<% end %>
<% if logs&.any? && (display_delete_logs?(@current_user, searched, filter_type) || in_organisations_tab?) %>
<%= govuk_link_to "Delete logs", delete_logs_path, class: "app-!-colour-red" %>
<% end %>
</div>
</div>
</h2>
<% logs.map do |log| %>

6
app/views/logs/delete_duplicates.html.erb

@ -1,6 +1,6 @@
<% content_for :before_content do %>
<% content_for :title, "Are you sure you want to delete #{@duplicate_logs.count == 1 ? 'this duplicate log' : 'these duplicate logs'}?" %>
<%= govuk_back_link href: @log.lettings? ? lettings_log_duplicate_logs_path(@original_log, original_log_id: @original_log.id) : sales_log_duplicate_logs_path(@original_log, original_log_id: @original_log.id) %>
<%= govuk_back_link href: @log.lettings? ? lettings_log_duplicate_logs_path(@original_log, original_log_id: @original_log.id, referrer: params[:referrer], organisation_id: params[:organisation_id]) : sales_log_duplicate_logs_path(@original_log, original_log_id: @original_log.id, referrer: params[:referrer], organisation_id: params[:organisation_id]) %>
<% end %>
<div class="govuk-grid-row">
@ -29,10 +29,10 @@
<%= govuk_button_to @duplicate_logs.count == 1 ? "Delete this log" : "Delete these logs",
send("delete_logs_#{@log.class.name.underscore}s_path"),
method: "delete",
params: { ids: @duplicate_logs.map(&:id), original_log_id: @original_log.id, remaining_log_id: @log.id } %>
params: { ids: @duplicate_logs.map(&:id), original_log_id: @original_log.id, remaining_log_id: @log.id, referrer: params[:referrer], organisation_id: params[:organisation_id] } %>
<%= govuk_button_link_to(
"Cancel",
send("#{@log.class.name.underscore}_duplicate_logs_path", @original_log, original_log_id: @original_log.id),
send("#{@log.class.name.underscore}_duplicate_logs_path", @original_log, original_log_id: @original_log.id, referrer: params[:referrer], organisation_id: params[:organisation_id]),
secondary: true,
) %>
</div>

2
app/views/logs/download_csv.html.erb

@ -6,7 +6,7 @@
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-l">Download CSV</h2>
<h1 class="govuk-heading-l">Download CSV</h1>
<p class="govuk-body">We'll send a secure download link to your email address <strong><%= @current_user.email %></strong>.</p>
<p class="govuk-body">You've selected <%= count %> logs.</p>

2
app/views/logs/index.html.erb

@ -9,7 +9,7 @@
) %>
<% if @duplicate_sets_count&.positive? %>
<%= govuk_notification_banner(title_text: "Important", text: govuk_link_to("Review logs", duplicate_logs_path)) do |banner| %>
<%= govuk_notification_banner(title_text: "Important", text: govuk_link_to("Review logs", duplicate_logs_path(referrer: "duplicate_logs_banner"))) do |banner| %>
<% banner.with_heading(text: I18n.t("notification.duplicate_sets", count: @duplicate_sets_count)) %>
<% end %>
<% end %>

11
app/views/maintenance/service_unavailable.html.erb

@ -0,0 +1,11 @@
<h1 class="govuk-heading-l govuk-!-width-two-thirds">
Sorry, the service is unavailable
</h1>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<p class="govuk-body">You will be able to use the service from 9am on Thursday 16 November 2023.</p>
<p class="govuk-body">Changes from the page you were on have not been saved. Changes on pages where you have selected 'save and continue' have been saved.</p>
<p class="govuk-body"><%= govuk_link_to "Contact the helpdesk", "https://dluhcdigital.atlassian.net/servicedesk/customer/portal/6/group/11" %> if you need help.</p>
</div>
</div>

2
app/views/organisation_relationships/_managing_agent_list.erb

@ -1,7 +1,7 @@
<section class="app-table-group" tabindex="0" aria-labelledby="<%= title.dasherize %>">
<%= govuk_table do |table| %>
<%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %>
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "agents", path: request.path)) %>
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "agents", filters_count: 0)) %>
<% end %>
<% @managing_agents.each do |managing_agent| %>
<%= table.body do |body| %>

2
app/views/organisation_relationships/_stock_owner_list.erb

@ -1,7 +1,7 @@
<section class="app-table-group" tabindex="0" aria-labelledby="<%= title.dasherize %>">
<%= govuk_table do |table| %>
<%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %>
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "stock owners", path: request.path)) %>
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "stock owners", filters_count: 0)) %>
<% end %>
<% @stock_owners.each do |stock_owner| %>
<%= table.body do |body| %>

34
app/views/organisations/_merged_organisation_details.html.erb

@ -0,0 +1,34 @@
<% if @organisation.recently_absorbed_organisations_grouped_by_merge_date.present? %>
<%= govuk_details(summary_text: "View all organisations that were merged into #{@organisation.name}") do %>
<% @organisation.recently_absorbed_organisations_grouped_by_merge_date.each do |merge_date, organisations| %>
<p><strong>Merge date:</strong> <%= merge_date&.to_formatted_s(:govuk_date) %></p>
<%= govuk_table do |table| %>
<%= table.head do |head| %>
<%= head.row do |row| %>
<% row.cell(header: true, text: "Organisation name", html_attributes: { scope: "col", class: "govuk-!-width-one-half" }) %>
<% row.cell(header: true, text: "Organisation ID", html_attributes: { scope: "col", class: "govuk-!-width-one-half" }) %>
<% end %>
<% end %>
<% organisations.each do |absorbed_org| %>
<%= table.body do |body| %>
<%= body.row do |row| %>
<% if current_user.support? %>
<% row.cell(text: simple_format(govuk_link_to(absorbed_org.name, organisation_path(absorbed_org)), { class: "govuk-!-font-weight-bold scheme-name-cell" }, wrapper_tag: "div")) %>
<% else %>
<% row.cell(text: absorbed_org.name) %>
<% end %>
<% row.cell(text: "ORG#{absorbed_org.id}") %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% if @organisation.absorbing_organisation.present? %>
<% if current_user.support? %>
<p><%= @organisation.name %> was merged into <%= govuk_link_to(@organisation.absorbing_organisation.name, organisation_path(@organisation.absorbing_organisation)) %><%= @organisation.merge_date ? " on #{@organisation.merge_date.to_formatted_s(:govuk_date)}" : "" %>.</p>
<% else %>
<p><%= @organisation.name %> was merged into <%= @organisation.absorbing_organisation.name %><%= @organisation.merge_date ? " on #{@organisation.merge_date.to_formatted_s(:govuk_date)}" : "" %>.</p>
<% end %>
<% end %>

2
app/views/organisations/_organisation_list.html.erb

@ -1,7 +1,7 @@
<section class="app-table-group" tabindex="0" aria-labelledby="<%= title.dasherize %>">
<%= govuk_table do |table| %>
<%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %>
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "organisations", path: request.path)) %>
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "organisations", filters_count: applied_filters_count(@filter_type))) %>
<% end %>
<%= table.head do |head| %>
<%= head.row do |row| %>

2
app/views/organisations/logs.html.erb

@ -11,7 +11,7 @@
) %>
<% if @duplicate_sets_count&.positive? %>
<%= govuk_notification_banner(title_text: "Important", text: govuk_link_to("Review logs", organisation_duplicates_path(@organisation))) do |banner| %>
<%= govuk_notification_banner(title_text: "Important", text: govuk_link_to("Review logs", organisation_duplicates_path(@organisation, referrer: "duplicate_logs_banner"))) do |banner| %>
<% banner.with_heading(text: I18n.t("notification.duplicate_sets", count: @duplicate_sets_count)) %>
<% end %>
<% end %>

1
app/views/organisations/show.html.erb

@ -39,6 +39,7 @@
<% if FeatureToggle.merge_organisations_enabled? %>
<p>To report a merge or update your organisation details, <%= govuk_link_to "contact the helpdesk", "https://dluhcdigital.atlassian.net/servicedesk/customer/portal/6/group/11" %>.</p>
<% end %>
<%= render partial: "organisations/merged_organisation_details" %>
</div>
<div class="govuk-grid-column-one-third-from-desktop">

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

@ -11,7 +11,7 @@
<%= filters_applied_text(@filter_type) %>
</p>
<p class="govuk-!-text-align-right govuk-grid-column-one-half">
<%= reset_filters_link(@filter_type) %>
<%= reset_filters_link(@filter_type, { search: request.params["search"] }.compact) %>
</p>
</div>
@ -43,6 +43,10 @@
} %>
<% end %>
<% if request.params["search"].present? %>
<%= f.hidden_field :search, value: request.params["search"] %>
<% end %>
<%= f.govuk_submit "Apply filters", class: "govuk-!-margin-bottom-0" %>
<% end %>
</div>

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

@ -1,7 +1,7 @@
<section class="app-table-group" tabindex="0" aria-labelledby="<%= title.dasherize %>">
<%= govuk_table do |table| %>
<%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %>
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "schemes", path: request.path)) %>
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "schemes", filters_count: applied_filters_count(@filter_type))) %>
<% end %>
<%= table.head do |head| %>
<%= head.row do |row| %>

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

@ -15,7 +15,7 @@
<% support_level_options_hints = { "Low level": "Staff visiting once a week, fortnightly or less.", "Medium level": "Staff on site daily or making frequent visits with some out-of-hours cover.", "High level": "Intensive level of staffing provided on a 24-hour basis." } %>
<% support_level_options_with_hints = Scheme.support_types.keys.excluding("Missing").map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize, description: support_level_options_hints[key.to_sym]) } %>
<% support_level_options_with_hints = Scheme.support_types.keys.excluding("Missing").excluding("Floating support").map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize, description: support_level_options_hints[key.to_sym]) } %>
<%= f.govuk_collection_radio_buttons :support_type,
support_level_options_with_hints,

7
app/views/users/_user_filters.html.erb

@ -11,7 +11,7 @@
<%= filters_applied_text(@filter_type) %>
</p>
<p class="govuk-!-text-align-right govuk-grid-column-one-half">
<%= reset_filters_link(@filter_type) %>
<%= reset_filters_link(@filter_type, { search: request.params["search"] }.compact) %>
</p>
</div>
@ -22,6 +22,11 @@
label: "Status",
category: "status",
} %>
<% if request.params["search"].present? %>
<%= f.hidden_field :search, value: request.params["search"] %>
<% end %>
<%= f.govuk_submit "Apply filters", class: "govuk-!-margin-bottom-0" %>
<% end %>
</div>

2
app/views/users/_user_list.html.erb

@ -1,7 +1,7 @@
<section class="app-table-group" tabindex="0" aria-labelledby="<%= title.dasherize %>">
<%= govuk_table do |table| %>
<%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %>
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "users", path: request.path)) %>
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "users", filters_count: applied_filters_count(@filter_type))) %>
<% if current_user.support? %>
<% query = searched.present? ? "?search=#{searched}" : nil %>
<%= govuk_link_to "Download (CSV)", "#{request.path}.csv#{query}", type: "text/csv" %>

6
app/views/users/new.html.erb

@ -4,7 +4,7 @@
<%= govuk_back_link(href: :back) %>
<% end %>
<%= form_for(@resource, as: :user, html: { method: :post }) do |f| %>
<%= form_for(@user, as: :user, html: { method: :post }) do |f| %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= f.govuk_error_summary %>
@ -21,13 +21,13 @@
label: { text: "Email address", size: "m" },
autocomplete: "email",
spellcheck: "false",
value: @resource.email %>
value: @user.email %>
<%= f.govuk_phone_field :phone,
label: { text: "Telephone number", size: "m" },
autocomplete: "phone",
spellcheck: "false",
value: @resource.phone %>
value: @user.phone %>
<% if current_user.support? %>
<% null_option = [OpenStruct.new(id: "", name: "Select an option")] %>

12
config/forms/2021_2022.json

@ -7074,7 +7074,8 @@
"type": "radio",
"answer_options": {
"1": {
"value": "Internal transfer"
"value": "Internal transfer",
"hint": "Where the tenant has moved to another social property owned by the same landlord."
},
"2": {
"value": "Tenant applied directly (no referral or nomination)"
@ -7131,7 +7132,8 @@
"type": "radio",
"answer_options": {
"1": {
"value": "Internal transfer"
"value": "Internal transfer",
"hint": "Where the tenant has moved to another social property owned by the same landlord."
},
"2": {
"value": "Tenant applied directly (no nomination)"
@ -7194,7 +7196,8 @@
"type": "radio",
"answer_options": {
"1": {
"value": "Internal transfer"
"value": "Internal transfer",
"hint": "Where the tenant has moved to another social property owned by the same landlord."
},
"2": {
"value": "Tenant applied directly (no referral)"
@ -7254,7 +7257,8 @@
"type": "radio",
"answer_options": {
"1": {
"value": "Internal transfer"
"value": "Internal transfer",
"hint": "Where the tenant has moved to another social property owned by the same landlord."
},
"2": {
"value": "Tenant applied directly (no referral or nomination)"

20
config/forms/2022_2023.json

@ -6991,7 +6991,8 @@
"type": "radio",
"answer_options": {
"1": {
"value": "Internal transfer"
"value": "Internal transfer",
"hint": "Where the tenant has moved to another social property owned by the same landlord."
},
"2": {
"value": "Tenant applied directly (no referral or nomination)"
@ -7048,7 +7049,8 @@
"type": "radio",
"answer_options": {
"1": {
"value": "Internal transfer"
"value": "Internal transfer",
"hint": "Where the tenant has moved to another social property owned by the same landlord."
},
"2": {
"value": "Tenant applied directly (no nomination)"
@ -7056,8 +7058,8 @@
"3": {
"value": "Nominated by a local housing authority"
},
"4" : {
"value" : "Referred by local authority housing department"
"4" : {
"value" : "Referred by local authority housing department"
},
"8": {
"value": "Re-located through official housing mobility scheme"
@ -7111,7 +7113,8 @@
"type": "radio",
"answer_options": {
"1": {
"value": "Internal transfer"
"value": "Internal transfer",
"hint": "Where the tenant has moved to another social property owned by the same landlord."
},
"2": {
"value": "Tenant applied directly (no referral)"
@ -7171,7 +7174,8 @@
"type": "radio",
"answer_options": {
"1": {
"value": "Internal transfer"
"value": "Internal transfer",
"hint": "Where the tenant has moved to another social property owned by the same landlord."
},
"2": {
"value": "Tenant applied directly (no referral or nomination)"
@ -8316,7 +8320,7 @@
}
}
},
"interruption_screen_question_ids": ["brent", "startdate", "la", "beds", "rent_type", "needstype"]
"interruption_screen_question_ids": ["brent", "period", "startdate", "la", "beds", "rent_type", "needstype"]
},
"max_rent_value_check": {
"depends_on": [
@ -8361,7 +8365,7 @@
}
}
},
"interruption_screen_question_ids": ["brent", "startdate", "la", "beds", "rent_type", "needstype"]
"interruption_screen_question_ids": ["brent", "period", "startdate", "la", "beds", "rent_type", "needstype"]
},
"scharge_value_check": {
"depends_on": [

5
config/initializers/sidekiq.rb

@ -32,7 +32,10 @@ Redis.silence_deprecations = true
Sidekiq.configure_server do |config|
config.on(:startup) do
Sidekiq::Cron::Job.load_from_hash YAML.load_file("config/sidekiq_cron_schedule.yml")
Sidekiq::Cron::Job.all.each(&:destroy)
unless FeatureToggle.maintenance_mode_enabled?
Sidekiq::Cron::Job.load_from_hash YAML.load_file("config/sidekiq_cron_schedule.yml")
end
end
config.on(:shutdown) do

26
config/locales/en.yml

@ -265,7 +265,7 @@ en:
owning_organisation: "Enter a date when the owning organisation was active. %{owning_organisation} became inactive on %{owning_organisation_merge_date} and was replaced by %{owning_absorbing_organisation}."
managing_organisation: "Enter a date when the managing organisation was active. %{managing_organisation} became inactive on %{managing_organisation_merge_date} and was replaced by %{managing_absorbing_organisation}."
different_merge: "Enter a date when the owning and managing organisations were active. %{owning_organisation} became inactive on %{owning_organisation_merge_date} and was replaced by %{owning_absorbing_organisation}. %{managing_organisation} became inactive on %{managing_organisation_merge_date} and was replaced by %{managing_absorbing_organisation}."
invalid_absorbing_organisations_start_date:
invalid_absorbing_organisations_start_date:
same_organisation: "Enter a date when the owning and managing organisation was active. %{owning_organisation} became active on %{owning_organisation_available_from}."
owning_organisation: "Enter a date when the owning organisation was active. %{owning_organisation} became active on %{owning_organisation_available_from}."
managing_organisation: "Enter a date when the managing organisation was active. %{managing_organisation} became active on %{managing_organisation_available_from}."
@ -367,27 +367,7 @@ en:
negative_currency: "Enter an amount above 0"
rent:
less_than_shortfall: "Enter an amount that is more than the shortfall in basic rent"
scharge:
private_registered_provider:
general_needs: "Enter a value for the service charge between £0 and £800 per week if the landlord is a private registered provider and it is a general needs letting"
supported_housing: "Enter a value for the service charge between £0 and £800 per week if the landlord is a private registered provider and it is a supported housing letting"
local_authority:
general_needs: "Enter a value for the service charge between £0 and £500 per week if the landlord is a local authority and it is a general needs letting"
supported_housing: "Enter a value for the service charge between £0 and £500 per week if the landlord is a local authority and it is a supported housing letting"
pscharge:
private_registered_provider:
general_needs: "Enter a value for the personal service charge between £0 and £700 per week if the landlord is a private registered provider and it is a general needs letting"
supported_housing: "Enter a value for the personal service charge between £0 and £700 per week if the landlord is a private registered provider and it is a supported housing letting"
local_authority:
general_needs: "Enter a value for the personal service charge between £0 and £200 per week if the landlord is a local authority and it is a general needs letting"
supported_housing: "Enter a value for the personal service charge between £0 and £200 per week if the landlord is a local authority and it is a supported housing letting"
supcharg:
private_registered_provider:
general_needs: "Enter a value for the support charge between £0 and £800 per week if the landlord is a private registered provider and it is a general needs letting"
supported_housing: "Enter a value for the support charge between £0 and £800 per week if the landlord is a private registered provider and it is a supported housing letting"
local_authority:
general_needs: "Enter a value for the support charge between £0 and £200 per week if the landlord is a local authority and it is a general needs letting"
supported_housing: "Enter a value for the support charge between £0 and £200 per week if the landlord is a local authority and it is a supported housing letting"
out_of_range: "Enter a value for the %{charge_name} between £0 and %{maximum_per_period} paid %{frequency}. %{maximum_per_period} is the max limit for rent and charges paid %{frequency} for %{letting_type} lettings owned by a %{provider_type}."
ecstat:
over_hard_max: "Net income of %{hard_max} per week is too high given the tenant’s working situation"
brent:
@ -722,7 +702,7 @@ Make sure these answers are correct."
charges:
informative_text: "This is higher than we would expect."
hint_text: "Check the following:<ul class=\"govuk-body-l app-panel--interruption\"><li>the decimal point</li><li>the frequency, for example every week or every calendar month</li><li>the needs type</li></ul>"
devise:
email:

1
config/routes.rb

@ -35,6 +35,7 @@ Rails.application.routes.draw do
get "/accessibility-statement", to: "content#accessibility_statement"
get "/privacy-notice", to: "content#privacy_notice"
get "/data-sharing-agreement", to: "content#data_sharing_agreement"
get "/service-unavailable", to: "maintenance#service_unavailable"
get "/download-23-24-lettings-form", to: "start#download_23_24_lettings_form"
get "/download-22-23-lettings-form", to: "start#download_22_23_lettings_form"

5
db/migrate/20231023142854_add_available_from_to_org.rb

@ -0,0 +1,5 @@
class AddAvailableFromToOrg < ActiveRecord::Migration[7.0]
def change
add_column :organisations, :available_from, :datetime
end
end

3
db/schema.rb

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_09_13_093443) do
ActiveRecord::Schema[7.0].define(version: 2023_10_23_142854) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -440,6 +440,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_13_093443) do
t.string "old_visible_id"
t.datetime "merge_date"
t.bigint "absorbing_organisation_id"
t.datetime "available_from"
t.index ["absorbing_organisation_id"], name: "index_organisations_on_absorbing_organisation_id"
t.index ["old_visible_id"], name: "index_organisations_on_old_visible_id", unique: true
end

2
docs/adr/index.md

@ -1,6 +1,6 @@
---
has_children: true
nav_order: 9
nav_order: 10
---
# Architecture decisions

15
docs/app_api.md

@ -0,0 +1,15 @@
---
nav_order: 8
---
# Using the App API
In order to use the app as an API, you will need to configure requests to the API as so:
* Configure your request with Basic Auth. Set the username to be the same as `API_USER` and password as the `API_KEY` (`API_USER` and `API_KEY` are environment variables that should be set for the application)
* Check the endpoint you are calling is an action that is `create`, `show` or `update`
* Check you are setting the following request headers:
* `Content-Type = application/json`
* `Action = application/json` N.B. If you use `*/*` instead, the request won't be recognised as an API request`
Currently only the logs controller is configured to accept and authenticate API requests, when the above API environment variables are set.

2
docs/documentation_website.md

@ -1,5 +1,5 @@
---
nav_order: 10
nav_order: 11
---
# This documentation website

2
docs/form/index.md

@ -1,6 +1,6 @@
---
has_children: true
nav_order: 8
nav_order: 9
---
# Generating forms

2
docs/setup.md

@ -1,5 +1,5 @@
---
nav_order: 2
nav_order: 3
---
# Local development

4
lib/tasks/correct_illness_from_csv.rake

@ -20,7 +20,9 @@ namespace :correct_illness do
raise "Usage: rake correct_illness:correct_illness_from_csv['csv_file_name']" if file_name.blank?
s3_service = Storage::S3Service.new(PlatformHelper.is_paas? ? Configuration::PaasConfigurationService.new : Configuration::EnvConfigurationService.new, ENV["IMPORT_PAAS_INSTANCE"])
illness_csv = CSV.parse(s3_service.get_file_io(file_name), headers: false)
file_io = s3_service.get_file_io(file_name)
file_io.set_encoding_by_bom
illness_csv = CSV.parse(file_io, headers: false)
illness_csv.each_with_index do |row, index|
next if index < 3

22
lib/tasks/import_address_from_csv.rake

@ -6,7 +6,9 @@ namespace :data_import do
raise "Usage: rake data_import:import_lettings_addresses_from_csv['csv_file_name']" if file_name.blank?
s3_service = Storage::S3Service.new(PlatformHelper.is_paas? ? Configuration::PaasConfigurationService.new : Configuration::EnvConfigurationService.new, ENV["IMPORT_PAAS_INSTANCE"])
addresses_csv = CSV.parse(s3_service.get_file_io(file_name), headers: true)
file_io = s3_service.get_file_io(file_name)
file_io.set_encoding_by_bom
addresses_csv = CSV.parse(file_io, headers: true)
contains_issue_type = addresses_csv.headers.include?("Issue type")
addresses_csv.each do |row|
@ -53,8 +55,11 @@ namespace :data_import do
lettings_log.send("process_postcode_changes!")
lettings_log.values_updated_at = Time.zone.now
lettings_log.save!
Rails.logger.info("Updated lettings log #{lettings_log_id}, with address: #{[lettings_log.address_line1, lettings_log.address_line2, lettings_log.town_or_city, lettings_log.county, lettings_log.postcode_full].join(', ')}")
if lettings_log.save
Rails.logger.info("Updated lettings log #{lettings_log_id}, with address: #{[lettings_log.address_line1, lettings_log.address_line2, lettings_log.town_or_city, lettings_log.county, lettings_log.postcode_full].join(', ')}")
else
Rails.logger.error("Validation failed for lettings log with ID #{lettings_log.id}: #{lettings_log.errors.full_messages.join(', ')}}")
end
end
end
@ -65,7 +70,9 @@ namespace :data_import do
raise "Usage: rake data_import:import_sales_addresses_from_csv['csv_file_name']" if file_name.blank?
s3_service = Storage::S3Service.new(PlatformHelper.is_paas? ? Configuration::PaasConfigurationService.new : Configuration::EnvConfigurationService.new, ENV["IMPORT_PAAS_INSTANCE"])
addresses_csv = CSV.parse(s3_service.get_file_io(file_name), headers: true)
file_io = s3_service.get_file_io(file_name)
file_io.set_encoding_by_bom
addresses_csv = CSV.parse(file_io, headers: true)
contains_issue_type = addresses_csv.headers.include?("Issue type")
addresses_csv.each do |row|
@ -112,8 +119,11 @@ namespace :data_import do
sales_log.send("process_postcode_changes!")
sales_log.values_updated_at = Time.zone.now
sales_log.save!
Rails.logger.info("Updated sales log #{sales_log_id}, with address: #{[sales_log.address_line1, sales_log.address_line2, sales_log.town_or_city, sales_log.county, sales_log.postcode_full].join(', ')}")
if sales_log.save
Rails.logger.info("Updated sales log #{sales_log_id}, with address: #{[sales_log.address_line1, sales_log.address_line2, sales_log.town_or_city, sales_log.county, sales_log.postcode_full].join(', ')}")
else
Rails.logger.error("Validation failed for sales log with ID #{sales_log.id}: #{sales_log.errors.full_messages.join(', ')}}")
end
end
end
end

33
lib/tasks/merge_organisations.rake

@ -1,12 +1,37 @@
namespace :merge do
desc "Merge organisations into one"
task :merge_organisations, %i[absorbing_organisation_id merging_organisation_ids] => :environment do |_task, args|
desc "Merge organisations into an existing organisation"
task :merge_organisations, %i[absorbing_organisation_id merging_organisation_ids merge_date] => :environment do |_task, args|
absorbing_organisation_id = args[:absorbing_organisation_id]
merging_organisation_ids = args[:merging_organisation_ids]&.split(" ")&.map(&:to_i)
begin
merge_date = args[:merge_date].present? ? Date.parse(args[:merge_date]) : nil
rescue StandardError
raise "Usage: rake merge:merge_organisations[absorbing_organisation_id, merging_organisation_ids, merge_date]. Merge date must be in format YYYY-MM-DD"
end
raise "Usage: rake merge:merge_organisations[absorbing_organisation_id, merging_organisation_ids]" if merging_organisation_ids.blank? || absorbing_organisation_id.blank?
if merging_organisation_ids.blank? || absorbing_organisation_id.blank?
raise "Usage: rake merge:merge_organisations[absorbing_organisation_id, merging_organisation_ids, merge_date]"
end
service = Merge::MergeOrganisationsService.new(absorbing_organisation_id:, merging_organisation_ids:)
service = Merge::MergeOrganisationsService.new(absorbing_organisation_id:, merging_organisation_ids:, merge_date:)
service.call
end
desc "Merge organisations into an existing organisation, make the absorbing organisation active from merge date only"
task :merge_organisations_into_new_organisation, %i[absorbing_organisation_id merging_organisation_ids merge_date] => :environment do |_task, args|
absorbing_organisation_id = args[:absorbing_organisation_id]
merging_organisation_ids = args[:merging_organisation_ids]&.split(" ")&.map(&:to_i)
begin
merge_date = args[:merge_date].present? ? Date.parse(args[:merge_date]) : nil
rescue StandardError
raise "Usage: rake merge:merge_organisations_into_new_organisation[absorbing_organisation_id, merging_organisation_ids, merge_date]. Merge date must be in format YYYY-MM-DD"
end
if merging_organisation_ids.blank? || absorbing_organisation_id.blank?
raise "Usage: rake merge:merge_organisations_into_new_organisation[absorbing_organisation_id, merging_organisation_ids, merge_date]"
end
service = Merge::MergeOrganisationsService.new(absorbing_organisation_id:, merging_organisation_ids:, merge_date:, absorbing_organisation_active_from_merge_date: true)
service.call
end
end

18
lib/tasks/recalculate_irproduct_values.rake

@ -0,0 +1,18 @@
desc "Forces to recalculate irproduct values"
task recalculate_irproduct_values: :environment do
LettingsLog.exportable.where(rent_type: [3, 4, 5]).where(irproduct: nil).each do |log| # irproduct was never set
Rails.logger.info("Could not update irproduct for LettingsLog #{log.id}") unless log.update(values_updated_at: Time.zone.now)
end
LettingsLog.exportable.where(rent_type: 3).where.not(irproduct: 1).each do |log| # irproduct was set wrong
Rails.logger.info("Could not update irproduct for LettingsLog #{log.id}") unless log.update(values_updated_at: Time.zone.now)
end
LettingsLog.exportable.where(rent_type: 4).where.not(irproduct: 2).each do |log| # irproduct was set wrong
Rails.logger.info("Could not update irproduct for LettingsLog #{log.id}") unless log.update(values_updated_at: Time.zone.now)
end
LettingsLog.exportable.where(rent_type: 5).where.not(irproduct: 3).each do |log| # irproduct was set wrong
Rails.logger.info("Could not update irproduct for LettingsLog #{log.id}") unless log.update(values_updated_at: Time.zone.now)
end
LettingsLog.exportable.where.not(rent_type: [3, 4, 5]).where.not(irproduct: nil).each do |log| # irproduct was set when it should have been nil
Rails.logger.info("Could not update irproduct for LettingsLog #{log.id}") unless log.update(values_updated_at: Time.zone.now)
end
end

13
lib/tasks/recalculate_lar_values.rake

@ -0,0 +1,13 @@
desc "Forces to recalculate lar values for affordable rent types and clears irrelevant lar values"
task recalculate_lar_values: :environment do
LettingsLog.exportable.where(rent_type: [1, 2], lar: nil).each do |log| # lar was never set
Rails.logger.info("Could not update lar for LettingsLog #{log.id}") unless log.update(values_updated_at: Time.zone.now)
end
LettingsLog.exportable.where(rent_type: 1).where.not(lar: 2).each do |log| # lar was set wrong
Rails.logger.info("Could not update lar for LettingsLog #{log.id}") unless log.update(values_updated_at: Time.zone.now)
end
LettingsLog.exportable.where(rent_type: 2).where.not(lar: 1).each do |log| # lar was set wrong
Rails.logger.info("Could not update lar for LettingsLog #{log.id}") unless log.update(values_updated_at: Time.zone.now)
end
LettingsLog.exportable.where.not(rent_type: [1, 2]).where.not(lar: nil).update_all(lar: nil) # lar was set to 2 but should never have been set
end

35
lib/tasks/squish_names.rake

@ -0,0 +1,35 @@
desc "Squish names of locations, schemes, users, and organisations"
task squish_names: :environment do
Location.where("name LIKE ?", "% %").each do |location|
location.name&.squish!
begin
location.save!
rescue StandardError => e
Sentry.capture_exception(e)
end
end
Scheme.where("service_name LIKE ?", "% %").each do |scheme|
scheme.service_name&.squish!
begin
scheme.save!
rescue StandardError => e
Sentry.capture_exception(e)
end
end
User.where("name LIKE ?", "% %").each do |user|
user.name&.squish!
begin
user.save!
rescue StandardError => e
Sentry.capture_exception(e)
end
end
Organisation.where("name LIKE ?", "% %").each do |organisation|
organisation.name&.squish!
begin
organisation.save!
rescue StandardError => e
Sentry.capture_exception(e)
end
end
end

47
spec/components/search_result_caption_component_spec.rb

@ -6,20 +6,53 @@ RSpec.describe SearchResultCaptionComponent, type: :component do
let(:item_label) { "user" }
let(:total_count) { 3 }
let(:item) { "schemes" }
let(:path) { "path" }
let(:filters_count) { 1 }
let(:result) { render_inline(described_class.new(searched:, count:, item_label:, total_count:, item:, filters_count:)) }
it "renders table caption including the search results and total" do
result = render_inline(described_class.new(searched:, count:, item_label:, total_count:, item:, path:))
expect(result.to_html).to eq("<span class=\"govuk-!-margin-right-4\">\n <strong>#{count}</strong> #{item_label} found matching ‘#{searched}’ of <strong>#{total_count}</strong> total #{item}. <a class=\"govuk-link\" href=\"path\">Clear search</a>\n</span>\n")
context "when search and filter results are found" do
it "renders table caption including the search results and total" do
expect(result.to_html).to eq("<span class=\"govuk-!-margin-right-4\">\n <strong>2</strong> users matching search and filters<br>\n</span>\n")
end
end
context "when search results are found" do
let(:filters_count) { nil }
it "renders table caption including the search results and total" do
expect(result.to_html).to eq("<span class=\"govuk-!-margin-right-4\">\n <strong>2</strong> users matching search<br>\n</span>\n")
end
end
context "when filter results are found" do
let(:searched) { nil }
it "renders table caption including the search results and total" do
expect(result.to_html).to eq("<span class=\"govuk-!-margin-right-4\">\n <strong>2</strong> users matching filters<br>\n</span>\n")
end
end
context "when no search results are found" do
context "when no search/filter is applied" do
let(:searched) { nil }
let(:filters_count) { nil }
it "renders table caption with total count only" do
result = render_inline(described_class.new(searched:, count:, item_label:, total_count:, item:, path:))
expect(result.to_html).to eq("<span class=\"govuk-!-margin-right-4\">\n <strong>#{count}</strong> matching #{item}\n</span>\n")
end
end
context "when nothing is found" do
let(:count) { 0 }
expect(result.to_html).to eq("<span class=\"govuk-!-margin-right-4\">\n <strong>#{count}</strong> total #{item}\n</span>\n")
it "renders table caption with total count only" do
expect(result.to_html).to eq("<span class=\"govuk-!-margin-right-4\">\n <strong>0</strong> users matching search and filters<br>\n</span>\n")
end
end
context "when 1 record is found" do
let(:count) { 1 }
it "renders table caption with total count only" do
expect(result.to_html).to eq("<span class=\"govuk-!-margin-right-4\">\n <strong>1</strong> user matching search and filters<br>\n</span>\n")
end
end
end

27
spec/controllers/maintenance_controller_spec.rb

@ -0,0 +1,27 @@
require "rails_helper"
RSpec.describe MaintenanceController do
let(:user) { FactoryBot.create(:user) }
describe "GET #service_unavailable" do
context "when maintenance mode is enabled" do
it "logs the user out" do
allow(FeatureToggle).to receive(:maintenance_mode_enabled?).and_return(true)
sign_in user
expect(controller).to be_user_signed_in
get :service_unavailable
expect(controller).not_to be_user_signed_in
end
end
context "when maintenance mode is disabled" do
it "doesn't log the user out" do
allow(FeatureToggle).to receive(:maintenance_mode_enabled?).and_return(false)
sign_in user
expect(controller).to be_user_signed_in
get :service_unavailable
expect(controller).to be_user_signed_in
end
end
end
end

5
spec/factories/lettings_log.rb

@ -3,7 +3,10 @@ FactoryBot.define do
created_by { FactoryBot.create(:user) }
owning_organisation { created_by.organisation }
managing_organisation { created_by.organisation }
created_at { Time.zone.today }
updated_at { Time.zone.today }
trait :setup_completed do
startdate_today
renewal { 0 }
needstype { 1 }
rent_type { 1 }
@ -206,7 +209,5 @@ FactoryBot.define do
illness_type_9 { false }
illness_type_10 { false }
end
created_at { Time.zone.today }
updated_at { Time.zone.today }
end
end

1
spec/factories/user.rb

@ -5,6 +5,7 @@ FactoryBot.define do
password { "pAssword1" }
organisation
role { "data_provider" }
phone { "1234512345123" }
trait :data_coordinator do
role { "data_coordinator" }
end

14
spec/features/form/form_navigation_spec.rb

@ -176,4 +176,18 @@ RSpec.describe "Form Navigation" do
end
end
end
describe "fixing duplicate logs" do
it "shows a correct cancel link" do
visit("lettings-logs/#{id}/tenant-code-test?first_remaining_duplicate_id=x&original_log_id=#{id}&referrer=duplicate_logs")
click_link(text: "Cancel")
expect(page).to have_current_path("/lettings-logs/#{id}/duplicate-logs?original_log_id=#{id}")
end
it "shows a correct Save Changes buttons" do
visit("lettings-logs/#{id}/tenant-code-test?first_remaining_duplicate_id=#{id}&original_log_id=#{id}&referrer=duplicate_logs")
click_button(text: "Save changes")
expect(page).to have_current_path("/lettings-logs/#{id}/duplicate-logs?original_log_id=#{id}&referrer=duplicate_logs")
end
end
end

18
spec/features/lettings_log_spec.rb

@ -79,7 +79,7 @@ RSpec.describe "Lettings Log Features" do
context "when no filters are selected" do
it "displays the filters component with no clear button" do
expect(page).to have_content("No filters applied")
expect(page).not_to have_content("Clear")
expect(page).not_to have_link("Clear", href: "/clear-filters?filter_type=lettings_logs")
end
end
@ -97,7 +97,7 @@ RSpec.describe "Lettings Log Features" do
it "displays the filters component with a correct count and clear button" do
expect(page).to have_content("5 filters applied")
expect(page).to have_content("Clear")
expect(page).to have_link("Clear", href: "/clear-filters?filter_type=lettings_logs")
end
context "when clearing the filters" do
@ -107,7 +107,7 @@ RSpec.describe "Lettings Log Features" do
it "clears the filters and displays the filter component as before" do
expect(page).to have_content("No filters applied")
expect(page).not_to have_content("Clear")
expect(page).not_to have_link("Clear", href: "/clear-filters?filter_type=lettings_logs")
end
end
end
@ -467,7 +467,7 @@ RSpec.describe "Lettings Log Features" do
expect(duplicate_log.deleted?).to be true
expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success")
expect(page).to have_content("Log #{duplicate_log.id} has been deleted.")
expect(page).to have_current_path("/lettings-logs/#{lettings_log.id}/duplicate-logs?original_log_id=#{lettings_log.id}")
expect(page).to have_current_path("/lettings-logs/#{lettings_log.id}/duplicate-logs?organisation_id=&original_log_id=#{lettings_log.id}&referrer=")
expect(page).not_to have_content("These logs are duplicates")
expect(page).not_to have_link("Keep this log and delete duplicates")
expect(page).to have_link("Back to Log #{lettings_log.id}", href: "/lettings-logs/#{lettings_log.id}")
@ -491,7 +491,7 @@ RSpec.describe "Lettings Log Features" do
expect(lettings_log.status).to eq("deleted")
expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success")
expect(page).to have_content("Log #{lettings_log.id} has been deleted.")
expect(page).to have_current_path("/lettings-logs/#{duplicate_log.id}/duplicate-logs?original_log_id=#{lettings_log.id}")
expect(page).to have_current_path("/lettings-logs/#{duplicate_log.id}/duplicate-logs?organisation_id=&original_log_id=#{lettings_log.id}&referrer=")
expect(page).not_to have_content("These logs are duplicates")
expect(page).not_to have_link("Keep this log and delete duplicates")
expect(page).to have_link("Back to lettings logs", href: "/lettings-logs")
@ -510,8 +510,8 @@ RSpec.describe "Lettings Log Features" do
expect(page).to have_current_path("/lettings-logs/#{lettings_log.id}/duplicate-logs?original_log_id=#{lettings_log.id}")
click_link("Change", href: "/lettings-logs/#{duplicate_log.id}/tenant-code?first_remaining_duplicate_id=#{lettings_log.id}&original_log_id=#{lettings_log.id}&referrer=duplicate_logs")
fill_in("lettings-log-tenancycode-field", with: "something else")
click_button("Save and continue")
expect(page).to have_current_path("/lettings-logs/#{lettings_log.id}/duplicate-logs?original_log_id=#{lettings_log.id}")
click_button("Save changes")
expect(page).to have_current_path("/lettings-logs/#{lettings_log.id}/duplicate-logs?original_log_id=#{lettings_log.id}&referrer=duplicate_logs")
expect(page).to have_link("Back to Log #{lettings_log.id}", href: "/lettings-logs/#{lettings_log.id}")
expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success")
expect(page).to have_content("Log #{duplicate_log.id} is no longer a duplicate and has been removed from the list")
@ -521,8 +521,8 @@ RSpec.describe "Lettings Log Features" do
it "allows deduplicating logs by changing the answers on the original log" do
click_link("Change", href: "/lettings-logs/#{lettings_log.id}/tenant-code?first_remaining_duplicate_id=#{duplicate_log.id}&original_log_id=#{lettings_log.id}&referrer=duplicate_logs")
fill_in("lettings-log-tenancycode-field", with: "something else")
click_button("Save and continue")
expect(page).to have_current_path("/lettings-logs/#{duplicate_log.id}/duplicate-logs?original_log_id=#{lettings_log.id}")
click_button("Save changes")
expect(page).to have_current_path("/lettings-logs/#{duplicate_log.id}/duplicate-logs?original_log_id=#{lettings_log.id}&referrer=duplicate_logs")
expect(page).to have_link("Back to Log #{lettings_log.id}", href: "/lettings-logs/#{lettings_log.id}")
expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success")
expect(page).to have_content("Log #{lettings_log.id} is no longer a duplicate and has been removed from the list")

30
spec/features/organisation_spec.rb

@ -126,7 +126,8 @@ RSpec.describe "User Features" do
context "when viewing lettings logs for specific organisation" do
let(:first_log) { organisation.lettings_logs.first }
let!(:log_to_search) { FactoryBot.create(:lettings_log, created_by: user) }
let!(:other_logs) { FactoryBot.create_list(:lettings_log, 4, created_by: user) }
let!(:other_general_needs_logs) { FactoryBot.create_list(:lettings_log, 4, created_by: user, needstype: 1) }
let!(:other_supported_housing_logs) { FactoryBot.create_list(:lettings_log, 4, created_by: user, needstype: 2) }
let(:number_of_lettings_logs) { LettingsLog.count }
before do
@ -148,7 +149,7 @@ RSpec.describe "User Features" do
context "when searching for specific logs" do
it "displays the logs belonging to the same organisation" do
expect(page).to have_content(log_to_search.id)
other_logs.each do |log|
(other_general_needs_logs + other_supported_housing_logs).each do |log|
expect(page).to have_content(log.id)
end
end
@ -168,7 +169,7 @@ RSpec.describe "User Features" do
it "displays log matching the log ID" do
expect(page).to have_link(log_to_search.id.to_s)
other_logs.each do |log|
(other_general_needs_logs + other_supported_housing_logs).each do |log|
expect(page).not_to have_link(log.id.to_s)
end
end
@ -187,16 +188,31 @@ RSpec.describe "User Features" do
end
end
it "can filter lettings logs" do
expect(page).to have_content("#{number_of_lettings_logs} total logs")
it "has correct page details" do
expect(page).to have_content("#{number_of_lettings_logs} matching logs")
organisation.lettings_logs.map(&:id).each do |lettings_log_id|
expect(page).to have_link lettings_log_id.to_s, href: "/lettings-logs/#{lettings_log_id}"
end
end
it "can filter lettings logs by year" do
check("years-2021-field")
click_button("Apply filters")
expect(page).to have_current_path("/organisations/#{org_id}/lettings-logs?years[]=&years[]=2021&status[]=&assigned_to=all&user=&owning_organisation_select=all&owning_organisation=")
expect(page).to have_current_path("/organisations/#{org_id}/lettings-logs?years[]=&years[]=2021&status[]=&needstypes[]=&assigned_to=all&user=&owning_organisation_select=all&owning_organisation=&managing_organisation_select=all&managing_organisation=")
expect(page).not_to have_link first_log.id.to_s, href: "/lettings-logs/#{first_log.id}"
end
it "can filter lettings logs by needstype" do
check("needstypes-1-field")
click_button("Apply filters")
expect(page).to have_current_path("/organisations/#{org_id}/lettings-logs?years[]=&status[]=&needstypes[]=&needstypes[]=1&assigned_to=all&user=&owning_organisation_select=all&owning_organisation=&managing_organisation_select=all&managing_organisation=")
other_general_needs_logs.each do |general_needs_log|
expect(page).to have_link general_needs_log.id.to_s, href: "/lettings-logs/#{general_needs_log.id}"
end
other_supported_housing_logs.each do |supported_housing_log|
expect(page).not_to have_link supported_housing_log.id.to_s, href: "/lettings-logs/#{supported_housing_log.id}"
end
end
end
context "when viewing sales logs for specific organisation" do
@ -221,7 +237,7 @@ RSpec.describe "User Features" do
end
it "can filter sales logs" do
expect(page).to have_content("#{number_of_sales_logs} total logs")
expect(page).to have_content("#{number_of_sales_logs} matching logs")
organisation.sales_logs.map(&:id).each do |sales_log_id|
expect(page).to have_link sales_log_id.to_s, href: "/sales-logs/#{sales_log_id}"
end

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save