Browse Source

Merge branch 'main' into CLDC-3789-update-frontend-components

pull/2854/head
kosiakkatrina 1 year ago committed by GitHub
parent
commit
5bc619d5d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 411
      .github/workflows/run_tests.yml
  2. 347
      .github/workflows/staging_pipeline.yml
  3. 2
      Gemfile
  4. 124
      Gemfile.lock
  5. 2
      app/components/search_result_caption_component.html.erb
  6. 23
      app/controllers/bulk_upload_lettings_logs_controller.rb
  7. 19
      app/controllers/bulk_upload_sales_logs_controller.rb
  8. 11
      app/controllers/form_controller.rb
  9. 1
      app/controllers/organisations_controller.rb
  10. 5
      app/helpers/form_page_error_helper.rb
  11. 21
      app/mailers/bulk_upload_mailer.rb
  12. 2
      app/models/form/sales/pages/owning_organisation.rb
  13. 6
      app/models/form/sales/questions/buyer2_working_situation.rb
  14. 39
      app/models/forms/bulk_upload_lettings/needstype.rb
  15. 1
      app/models/organisation.rb
  16. 2
      app/models/scheme.rb
  17. 2
      app/models/validations/property_validations.rb
  18. 2
      app/models/validations/sales/property_validations.rb
  19. 14
      app/services/bulk_upload/lettings/validator.rb
  20. 40
      app/services/bulk_upload/processor.rb
  21. 12
      app/services/bulk_upload/sales/validator.rb
  22. 23
      app/views/bulk_upload_lettings_logs/forms/needstype.erb
  23. 2
      app/views/bulk_upload_shared/uploads.html.erb
  24. 4
      app/views/form/page.html.erb
  25. 2
      app/views/locations/index.html.erb
  26. 2
      app/views/logs/_log_list.html.erb
  27. 2
      app/views/logs/index.html.erb
  28. 2
      app/views/logs/update_logs.html.erb
  29. 4
      app/views/organisation_relationships/managing_agents.html.erb
  30. 4
      app/views/organisation_relationships/stock_owners.html.erb
  31. 2
      app/views/organisations/_organisation_list.html.erb
  32. 2
      app/views/organisations/index.html.erb
  33. 2
      app/views/organisations/logs.html.erb
  34. 2
      app/views/schemes/_scheme_list.html.erb
  35. 2
      app/views/users/_user_list.html.erb
  36. 1
      config/locales/en.yml
  37. 5
      db/migrate/20241125153349_add_unique_index_to_org_name.rb
  38. 1
      db/schema.rb
  39. 26
      spec/components/search_result_caption_component_spec.rb
  40. 7
      spec/db/seeds_spec.rb
  41. 2
      spec/factories/organisation.rb
  42. 16
      spec/features/form/page_routing_spec.rb
  43. 4
      spec/fixtures/exports/general_needs_log.xml
  44. 4
      spec/fixtures/exports/general_needs_log_23_24.xml
  45. 4
      spec/fixtures/exports/general_needs_log_24_25.xml
  46. 2
      spec/fixtures/exports/organisation.xml
  47. 4
      spec/fixtures/exports/supported_housing_logs.xml
  48. 2
      spec/fixtures/exports/user.xml
  49. 25
      spec/mailers/bulk_upload_mailer_spec.rb
  50. 11
      spec/mailers/devise_notify_mailer_spec.rb
  51. 2
      spec/models/form/lettings/questions/managing_organisation_spec.rb
  52. 28
      spec/models/form/sales/questions/buyer2_working_situation_spec.rb
  53. 6
      spec/models/organisation_spec.rb
  54. 7
      spec/models/scheme_spec.rb
  55. 1
      spec/models/user_spec.rb
  56. 111
      spec/requests/bulk_upload_lettings_logs_controller_spec.rb
  57. 111
      spec/requests/bulk_upload_sales_logs_controller_spec.rb
  58. 2
      spec/requests/check_errors_controller_spec.rb
  59. 4
      spec/requests/lettings_logs_controller_spec.rb
  60. 20
      spec/requests/organisation_relationships_controller_spec.rb
  61. 18
      spec/requests/organisations_controller_spec.rb
  62. 4
      spec/requests/sales_logs_controller_spec.rb
  63. 10
      spec/services/bulk_upload/lettings/validator_spec.rb
  64. 32
      spec/services/bulk_upload/processor_spec.rb
  65. 16
      spec/services/bulk_upload/sales/validator_spec.rb
  66. 2
      spec/services/exports/lettings_log_export_service_spec.rb
  67. 1
      spec/services/exports/organisation_export_service_spec.rb
  68. 1
      spec/services/exports/user_export_service_spec.rb
  69. 6
      yarn.lock

411
.github/workflows/run_tests.yml

@ -0,0 +1,411 @@
name: Run Tests
on:
workflow_call:
pull_request:
types:
- opened
- synchronize
merge_group:
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@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
cache: yarn
node-version: 20
- 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|models|requests|services)']
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@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
cache: yarn
node-version: 20
- 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 --exclude-pattern "spec/features/accessibility_spec.rb"
model_test:
name: Model 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@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
cache: yarn
node-version: 20
- 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/models --fail-fast
requests_test:
name: Requests 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@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
cache: yarn
node-version: 20
- 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/requests']
services_test:
name: Services 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@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
cache: yarn
node-version: 20
- 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\/services']
accessibility_test:
name: Accessibility 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@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
cache: yarn
node-version: 20
- name: Create database
run: |
bundle exec rake parallel:setup
- name: Compile assets
run: |
bundle exec rake assets:precompile
- name: Run tests
run: |
bundle exec rspec spec/features/accessibility_spec.rb --fail-fast
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
cache: yarn
node-version: 20
- 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@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Audit
run: |
bundle exec bundler-audit

347
.github/workflows/staging_pipeline.yml

@ -4,11 +4,6 @@ on:
push:
branches:
- main
pull_request:
types:
- opened
- synchronize
merge_group:
workflow_dispatch:
defaults:
@ -21,347 +16,13 @@ env:
repository: core
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@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
cache: yarn
node-version: 20
- 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|models|requests)']
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@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
cache: yarn
node-version: 20
- 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 --exclude-pattern "spec/features/accessibility_spec.rb"
model_test:
name: Model 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@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
cache: yarn
node-version: 20
- 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/models --fail-fast
requests_test:
name: Requests 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@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
cache: yarn
node-version: 20
- 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/requests']
accessibility_test:
name: Accessibility 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@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
cache: yarn
node-version: 20
- name: Create database
run: |
bundle exec rake parallel:setup
- name: Compile assets
run: |
bundle exec rake assets:precompile
- name: Run tests
run: |
bundle exec rspec spec/features/accessibility_spec.rb --fail-fast
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
cache: yarn
node-version: 20
- 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@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Audit
run: |
bundle exec bundler-audit
tests:
name: Run Tests
uses: ./.github/workflows/run_tests.yml
aws_deploy:
name: AWS Deploy
if: github.ref == 'refs/heads/main'
needs: [lint, test, feature_test, requests_test, model_test, audit]
needs: [tests]
uses: ./.github/workflows/aws_deploy.yml
with:
aws_account_id: 107155005276

2
Gemfile

@ -6,7 +6,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby "3.1.4"
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main'
gem "rails", "~> 7.0.8.5"
gem "rails", "~> 7.0.8.7"
# Use postgresql as the database for Active Record
gem "pg", "~> 1.1"
# Use Puma as the app server

124
Gemfile.lock

@ -1,71 +1,71 @@
GEM
remote: https://rubygems.org/
specs:
actioncable (7.0.8.5)
actionpack (= 7.0.8.5)
activesupport (= 7.0.8.5)
actioncable (7.0.8.7)
actionpack (= 7.0.8.7)
activesupport (= 7.0.8.7)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.8.5)
actionpack (= 7.0.8.5)
activejob (= 7.0.8.5)
activerecord (= 7.0.8.5)
activestorage (= 7.0.8.5)
activesupport (= 7.0.8.5)
actionmailbox (7.0.8.7)
actionpack (= 7.0.8.7)
activejob (= 7.0.8.7)
activerecord (= 7.0.8.7)
activestorage (= 7.0.8.7)
activesupport (= 7.0.8.7)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.8.5)
actionpack (= 7.0.8.5)
actionview (= 7.0.8.5)
activejob (= 7.0.8.5)
activesupport (= 7.0.8.5)
actionmailer (7.0.8.7)
actionpack (= 7.0.8.7)
actionview (= 7.0.8.7)
activejob (= 7.0.8.7)
activesupport (= 7.0.8.7)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.8.5)
actionview (= 7.0.8.5)
activesupport (= 7.0.8.5)
actionpack (7.0.8.7)
actionview (= 7.0.8.7)
activesupport (= 7.0.8.7)
rack (~> 2.0, >= 2.2.4)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (7.0.8.5)
actionpack (= 7.0.8.5)
activerecord (= 7.0.8.5)
activestorage (= 7.0.8.5)
activesupport (= 7.0.8.5)
actiontext (7.0.8.7)
actionpack (= 7.0.8.7)
activerecord (= 7.0.8.7)
activestorage (= 7.0.8.7)
activesupport (= 7.0.8.7)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.8.5)
activesupport (= 7.0.8.5)
actionview (7.0.8.7)
activesupport (= 7.0.8.7)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (7.0.8.5)
activesupport (= 7.0.8.5)
activejob (7.0.8.7)
activesupport (= 7.0.8.7)
globalid (>= 0.3.6)
activemodel (7.0.8.5)
activesupport (= 7.0.8.5)
activemodel (7.0.8.7)
activesupport (= 7.0.8.7)
activemodel-serializers-xml (1.0.2)
activemodel (> 5.x)
activesupport (> 5.x)
builder (~> 3.1)
activerecord (7.0.8.5)
activemodel (= 7.0.8.5)
activesupport (= 7.0.8.5)
activestorage (7.0.8.5)
actionpack (= 7.0.8.5)
activejob (= 7.0.8.5)
activerecord (= 7.0.8.5)
activesupport (= 7.0.8.5)
activerecord (7.0.8.7)
activemodel (= 7.0.8.7)
activesupport (= 7.0.8.7)
activestorage (7.0.8.7)
actionpack (= 7.0.8.7)
activejob (= 7.0.8.7)
activerecord (= 7.0.8.7)
activesupport (= 7.0.8.7)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.8.5)
activesupport (7.0.8.7)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -149,7 +149,7 @@ GEM
crass (1.0.6)
cssbundling-rails (1.4.0)
railties (>= 6.0.0)
date (3.3.4)
date (3.4.1)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
devise (4.9.3)
@ -258,13 +258,13 @@ GEM
matrix (0.4.2)
method_source (1.1.0)
mini_mime (1.1.5)
minitest (5.25.1)
minitest (5.25.4)
msgpack (1.7.2)
multipart-post (2.4.1)
nested_form (0.3.2)
net-http (0.4.1)
uri
net-imap (0.4.17)
net-imap (0.5.1)
date
net-protocol
net-pop (0.1.2)
@ -273,12 +273,12 @@ GEM
timeout
net-smtp (0.5.0)
net-protocol
nio4r (2.7.3)
nokogiri (1.16.8-arm64-darwin)
nio4r (2.7.4)
nokogiri (1.17.1-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.8-x86_64-darwin)
nokogiri (1.17.1-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.16.8-x86_64-linux)
nokogiri (1.17.1-x86_64-linux)
racc (~> 1.4)
notifications-ruby-client (6.0.0)
jwt (>= 1.5, < 3)
@ -327,20 +327,20 @@ GEM
rack (>= 1.2.0)
rack-test (2.1.0)
rack (>= 1.3)
rails (7.0.8.5)
actioncable (= 7.0.8.5)
actionmailbox (= 7.0.8.5)
actionmailer (= 7.0.8.5)
actionpack (= 7.0.8.5)
actiontext (= 7.0.8.5)
actionview (= 7.0.8.5)
activejob (= 7.0.8.5)
activemodel (= 7.0.8.5)
activerecord (= 7.0.8.5)
activestorage (= 7.0.8.5)
activesupport (= 7.0.8.5)
rails (7.0.8.7)
actioncable (= 7.0.8.7)
actionmailbox (= 7.0.8.7)
actionmailer (= 7.0.8.7)
actionpack (= 7.0.8.7)
actiontext (= 7.0.8.7)
actionview (= 7.0.8.7)
activejob (= 7.0.8.7)
activemodel (= 7.0.8.7)
activerecord (= 7.0.8.7)
activestorage (= 7.0.8.7)
activesupport (= 7.0.8.7)
bundler (>= 1.15.0)
railties (= 7.0.8.5)
railties (= 7.0.8.7)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
@ -354,9 +354,9 @@ GEM
nested_form (~> 0.3)
rails (>= 6.0, < 8)
turbo-rails (~> 1.0)
railties (7.0.8.5)
actionpack (= 7.0.8.5)
activesupport (= 7.0.8.5)
railties (7.0.8.7)
actionpack (= 7.0.8.7)
activesupport (= 7.0.8.7)
method_source
rake (>= 12.2)
thor (~> 1.0)
@ -465,7 +465,7 @@ GEM
thor (1.3.2)
thread_safe (0.3.6)
timecop (0.9.8)
timeout (0.4.1)
timeout (0.4.2)
turbo-rails (1.5.0)
actionpack (>= 6.0.0)
activejob (>= 6.0.0)
@ -553,7 +553,7 @@ DEPENDENCIES
rack (>= 2.2.6.3)
rack-attack
rack-mini-profiler (~> 2.0)
rails (~> 7.0.8.5)
rails (~> 7.0.8.7)
rails_admin (~> 3.1)
redcarpet (~> 3.6)
redis (~> 4.8)

2
app/components/search_result_caption_component.html.erb

@ -7,7 +7,7 @@
<strong><%= count %></strong> <%= item_label.pluralize(count) %> matching filters<br>
<% else %>
<span class="govuk-!-margin-right-4">
<strong><%= count %></strong> total <%= item %>
<strong><%= count %></strong> total <%= item.pluralize(count) %>
</span>
<% end %>
</span>

23
app/controllers/bulk_upload_lettings_logs_controller.rb

@ -1,6 +1,7 @@
class BulkUploadLettingsLogsController < ApplicationController
before_action :authenticate_user!
before_action :validate_data_protection_agrement_signed!
before_action :validate_data_protection_agreement_signed!
before_action :validate_year!, except: %w[start]
def start
if have_choice_of_year?
@ -24,12 +25,26 @@ class BulkUploadLettingsLogsController < ApplicationController
private
def validate_data_protection_agrement_signed!
def validate_data_protection_agreement_signed!
return if @current_user.organisation.data_protection_confirmed?
redirect_to lettings_logs_path
end
def validate_year!
return if params[:id] == "year"
return if params[:id] == "guidance" && params.dig(:form, :year).nil?
allowed_years = [current_year]
allowed_years.push(current_year - 1) if FormHandler.instance.lettings_in_crossover_period?
allowed_years.push(current_year + 1) if FeatureToggle.allow_future_form_use?
provided_year = params.dig(:form, :year)&.to_i
return if allowed_years.include?(provided_year)
render_not_found
end
def current_year
FormHandler.instance.current_collection_start_year
end
@ -48,8 +63,6 @@ private
Forms::BulkUploadLettings::PrepareYourFile.new(form_params)
when "guidance"
Forms::BulkUploadLettings::Guidance.new(form_params.merge(referrer: params[:referrer]))
when "needstype"
Forms::BulkUploadLettings::Needstype.new(form_params)
when "upload-your-file"
Forms::BulkUploadLettings::UploadYourFile.new(form_params.merge(current_user:))
when "checking-file"
@ -60,6 +73,6 @@ private
end
def form_params
params.fetch(:form, {}).permit(:year, :needstype, :file, :organisation_id)
params.fetch(:form, {}).permit(:year, :file, :organisation_id)
end
end

19
app/controllers/bulk_upload_sales_logs_controller.rb

@ -1,6 +1,7 @@
class BulkUploadSalesLogsController < ApplicationController
before_action :authenticate_user!
before_action :validate_data_protection_agrement_signed!
before_action :validate_data_protection_agreement_signed!
before_action :validate_year!, except: %w[start]
def start
if have_choice_of_year?
@ -24,12 +25,26 @@ class BulkUploadSalesLogsController < ApplicationController
private
def validate_data_protection_agrement_signed!
def validate_data_protection_agreement_signed!
return if @current_user.organisation.data_protection_confirmed?
redirect_to sales_logs_path
end
def validate_year!
return if params[:id] == "year"
return if params[:id] == "guidance" && params.dig(:form, :year).nil?
allowed_years = [current_year]
allowed_years.push(current_year - 1) if FormHandler.instance.sales_in_crossover_period?
allowed_years.push(current_year + 1) if FeatureToggle.allow_future_form_use?
provided_year = params.dig(:form, :year)&.to_i
return if allowed_years.include?(provided_year)
render_not_found
end
def current_year
FormHandler.instance.current_collection_start_year
end

11
app/controllers/form_controller.rb

@ -105,8 +105,13 @@ private
def restore_error_field_values(previous_responses)
return unless previous_responses
previous_responses_to_reset = previous_responses.reject do |key, _|
@log.form.get_question(key, @log)&.type == "date"
previous_responses_to_reset = previous_responses.reject do |key, value|
if @log.form.get_question(key, @log)&.type == "date" && value.present?
year = value.split("-").first.to_i
year&.zero?
else
false
end
end
@log.assign_attributes(previous_responses_to_reset)
@ -433,7 +438,7 @@ private
@log.valid?
@log.reload
error_attributes = @log.errors.map(&:attribute)
@questions = @log.form.questions.select { |q| error_attributes.include?(q.id.to_sym) }
@questions = @log.form.questions.select { |q| error_attributes.include?(q.id.to_sym) && q.page.routed_to?(@log, current_user) }
end
render "form/check_errors"
end

1
app/controllers/organisations_controller.rb

@ -155,6 +155,7 @@ class OrganisationsController < ApplicationController
end
redirect_to details_organisation_path(@organisation)
else
@used_rent_periods = @organisation.lettings_logs.pluck(:period).uniq.compact.map(&:to_s)
@rent_periods = helpers.rent_periods_with_checked_attr(checked_periods: selected_rent_periods)
render :edit, status: :unprocessable_entity
end

5
app/helpers/form_page_error_helper.rb

@ -13,7 +13,8 @@ module FormPageErrorHelper
end
end
def all_questions_affected_by_errors(log)
(log.errors.map(&:attribute) - [:base]).uniq
def all_pages_affected_by_errors(log)
question_ids = (log.errors.map(&:attribute) - [:base]).uniq
question_ids.map { |id| log.form.get_question(id, log)&.page&.id }.compact.uniq
end
end

21
app/mailers/bulk_upload_mailer.rb

@ -3,6 +3,7 @@ class BulkUploadMailer < NotifyMailer
COMPLETE_TEMPLATE_ID = "83279578-c890-4168-838b-33c9cf0dc9f0".freeze
FAILED_CSV_ERRORS_TEMPLATE_ID = "e27abcd4-5295-48c2-b127-e9ee4b781b75".freeze
FAILED_CSV_DUPLICATE_ERRORS_TEMPLATE_ID = "931d5bda-a08f-4de6-a455-38a63bff1956".freeze
FAILED_FILE_SETUP_ERROR_TEMPLATE_ID = "24c9f4c7-96ad-470a-ba31-eb51b7cbafd9".freeze
FAILED_SERVICE_ERROR_TEMPLATE_ID = "c3f6288c-7a74-4e77-99ee-6c4a0f6e125a".freeze
HOW_TO_FIX_UPLOAD_TEMPLATE_ID = "21a07b26-f625-4846-9f4d-39e30937aa24".freeze
@ -95,6 +96,26 @@ class BulkUploadMailer < NotifyMailer
)
end
def send_correct_duplicates_and_upload_again_mail(bulk_upload:)
summary_report_link = if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors?
bulk_upload.sales? ? summary_bulk_upload_sales_result_url(bulk_upload) : summary_bulk_upload_lettings_result_url(bulk_upload)
else
bulk_upload.sales? ? bulk_upload_sales_result_url(bulk_upload) : bulk_upload_lettings_result_url(bulk_upload)
end
send_email(
bulk_upload.user.email,
FAILED_CSV_DUPLICATE_ERRORS_TEMPLATE_ID,
{
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
year_combo: bulk_upload.year_combo,
lettings_or_sales: bulk_upload.log_type,
summary_report_link:,
},
)
end
def send_bulk_upload_failed_file_setup_error_mail(bulk_upload:)
bulk_upload_link = if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors?
bulk_upload.sales? ? summary_bulk_upload_sales_result_url(bulk_upload) : summary_bulk_upload_lettings_result_url(bulk_upload)

2
app/models/form/sales/pages/owning_organisation.rb

@ -20,11 +20,13 @@ class Form::Sales::Pages::OwningOrganisation < ::Form::Page
if current_user.organisation.holds_own_stock?
return true if current_user.organisation.absorbed_organisations.any?(&:holds_own_stock?)
return true if stock_owners.count >= 1
return false if log.owning_organisation == current_user.organisation
log.update!(owning_organisation: current_user.organisation)
else
return false if stock_owners.count.zero?
return true if stock_owners.count > 1
return false if log.owning_organisation == stock_owners.first
log.update!(owning_organisation: stock_owners.first)
end

6
app/models/form/sales/questions/buyer2_working_situation.rb

@ -15,6 +15,10 @@ class Form::Sales::Questions::Buyer2WorkingSituation < ::Form::Question
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
def displayed_answer_options(_log, _user = nil)
answer_options.reject { |key, _| key == "9" }
end
def answer_options
if form.start_year_2025_or_later?
{
@ -26,6 +30,7 @@ class Form::Sales::Questions::Buyer2WorkingSituation < ::Form::Question
"6" => { "value" => "Not seeking work" },
"7" => { "value" => "Full-time student" },
"8" => { "value" => "Unable to work due to long term sick or disability" },
"9" => { "value" => "Child under 16" },
"0" => { "value" => "Other" },
"10" => { "value" => "Buyer prefers not to say" },
}.freeze
@ -41,6 +46,7 @@ class Form::Sales::Questions::Buyer2WorkingSituation < ::Form::Question
"0" => { "value" => "Other" },
"10" => { "value" => "Buyer prefers not to say" },
"7" => { "value" => "Full-time student" },
"9" => { "value" => "Child under 16" },
}.freeze
end
end

39
app/models/forms/bulk_upload_lettings/needstype.rb

@ -1,39 +0,0 @@
module Forms
module BulkUploadLettings
class Needstype
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :needstype, :integer
attribute :year, :integer
attribute :organisation_id, :integer
validates :needstype, presence: true
def view_path
"bulk_upload_lettings_logs/forms/needstype"
end
def options
[OpenStruct.new(id: 1, name: "General needs"), OpenStruct.new(id: 2, name: "Supported housing")]
end
def back_path
bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year:, needstype:, organisation_id: }.compact)
end
def next_path
bulk_upload_lettings_log_path(id: "upload-your-file", form: { year:, needstype:, organisation_id: }.compact)
end
def year_combo
"#{year} to #{year + 1}"
end
def save!
true
end
end
end
end

1
app/models/organisation.rb

@ -58,6 +58,7 @@ class Organisation < ApplicationRecord
alias_method :la?, :LA?
validates :name, presence: { message: I18n.t("validations.organisation.name_missing") }
validates :name, uniqueness: { case_sensitive: false, message: I18n.t("validations.organisation.name_not_unique") }
validates :provider_type, presence: { message: I18n.t("validations.organisation.provider_type_missing") }
def self.find_by_id_on_multiple_fields(id)

2
app/models/scheme.rb

@ -329,9 +329,9 @@ class Scheme < ApplicationRecord
def status_at(date)
return :deleted if discarded_at.present?
return :incomplete unless confirmed && locations.confirmed.any?
return :deactivated if owning_organisation.status_at(date) == :deactivated || owning_organisation.status_at(date) == :merged ||
(open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date)
return :incomplete unless confirmed && locations.confirmed.any?
return :deactivating_soon if open_deactivation&.deactivation_date.present? && date < open_deactivation.deactivation_date
return :reactivating_soon if last_deactivation_before(date)&.reactivation_date.present? && date < last_deactivation_before(date).reactivation_date
return :activating_soon if startdate.present? && date < startdate

2
app/models/validations/property_validations.rb

@ -41,6 +41,8 @@ module Validations::PropertyValidations
def validate_property_postcode(record)
postcode = record.postcode_full
return unless postcode
if record.postcode_known? && (postcode.blank? || !postcode.match(POSTCODE_REGEXP))
error_message = I18n.t("validations.lettings.property.postcode_full.invalid")
record.errors.add :postcode_full, :wrong_format, message: error_message

2
app/models/validations/sales/property_validations.rb

@ -31,6 +31,8 @@ module Validations::Sales::PropertyValidations
def validate_property_postcode(record)
postcode = record.postcode_full
return unless postcode
if record.postcode_known? && (postcode.blank? || !postcode.match(POSTCODE_REGEXP))
error_message = I18n.t("validations.sales.property_information.postcode_full.invalid")
record.errors.add :postcode_full, :wrong_format, message: error_message

14
app/services/bulk_upload/lettings/validator.rb

@ -40,29 +40,25 @@ class BulkUpload::Lettings::Validator
end
end
def create_logs?
return false if any_setup_errors?
def block_log_creation_reason
return "setup_errors" if any_setup_errors?
if row_parsers.any?(&:block_log_creation?)
Sentry.capture_message("Bulk upload log creation blocked: #{bulk_upload.id}.")
return false
return "row_parser_block_log_creation"
end
if any_logs_already_exist? && FeatureToggle.bulk_upload_duplicate_log_check_enabled?
Sentry.capture_message("Bulk upload log creation blocked due to duplicate logs: #{bulk_upload.id}.")
return false
return "duplicate_logs"
end
row_parsers.each do |row_parser|
row_parser.log.blank_invalid_non_setup_fields!
end
if any_logs_invalid?
Sentry.capture_message("Bulk upload log creation blocked due to invalid logs after blanking non setup fields: #{bulk_upload.id}.")
return false
"logs_invalid"
end
true
end
def self.question_for_field(field)

40
app/services/bulk_upload/processor.rb

@ -36,20 +36,28 @@ class BulkUpload::Processor
if validator.any_setup_errors?
send_setup_errors_mail
elsif validator.create_logs?
create_logs
if validator.soft_validation_errors_only?
send_check_soft_validations_mail
elsif created_logs_but_incompleted?
send_how_to_fix_upload_mail
elsif created_logs_and_all_completed?
bulk_upload.unpend
send_success_mail
end
else
send_correct_and_upload_again_mail # summary/full report
block_creation_reason = validator.block_log_creation_reason
if block_creation_reason.present?
case block_creation_reason
when "duplicate_logs"
send_correct_duplicates_and_upload_again_mail
else
send_correct_and_upload_again_mail # summary/full report
end
else
create_logs
if validator.soft_validation_errors_only?
send_check_soft_validations_mail
elsif created_logs_but_incompleted?
send_how_to_fix_upload_mail
elsif created_logs_and_all_completed?
bulk_upload.unpend
send_success_mail
end
end
end
rescue StandardError => e
Sentry.capture_exception(e)
@ -97,6 +105,12 @@ private
.deliver_later
end
def send_correct_duplicates_and_upload_again_mail
BulkUploadMailer
.send_correct_duplicates_and_upload_again_mail(bulk_upload:)
.deliver_later
end
def send_success_mail
BulkUploadMailer
.send_bulk_upload_complete_mail(user:, bulk_upload:)

12
app/services/bulk_upload/sales/validator.rb

@ -39,17 +39,17 @@ class BulkUpload::Sales::Validator
end
end
def create_logs?
return false if any_setup_errors?
def block_log_creation_reason
return "setup_errors" if any_setup_errors?
if row_parsers.any?(&:block_log_creation?)
Sentry.capture_message("Bulk upload log creation blocked: #{bulk_upload.id}.")
return false
return "row_parser_block_log_creation"
end
if any_logs_already_exist? && FeatureToggle.bulk_upload_duplicate_log_check_enabled?
Sentry.capture_message("Bulk upload log creation blocked due to duplicate logs: #{bulk_upload.id}.")
return false
return "duplicate_logs"
end
row_parsers.each do |row_parser|
@ -58,10 +58,8 @@ class BulkUpload::Sales::Validator
if any_logs_invalid?
Sentry.capture_message("Bulk upload log creation blocked due to invalid logs after blanking non setup fields: #{bulk_upload.id}.")
return false
"logs_invalid"
end
true
end
def any_setup_errors?

23
app/views/bulk_upload_lettings_logs/forms/needstype.erb

@ -1,23 +0,0 @@
<% content_for :before_content do %>
<%= govuk_back_link href: @form.back_path %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "needstype"), method: :patch do |f| %>
<%= f.govuk_error_summary %>
<%= f.hidden_field :year %>
<%= f.hidden_field :organisation_id %>
<%= f.govuk_collection_radio_buttons :needstype,
@form.options,
:id,
:name,
hint: { text: I18n.t("hints.bulk_upload.needstype") },
legend: { text: "What is the needs type?", size: "l" },
caption: { text: "Upload lettings logs in bulk (#{@form.year_combo})", size: "l" } %>
<%= f.govuk_submit %>
<% end %>
</div>
</div>

2
app/views/bulk_upload_shared/uploads.html.erb

@ -1,4 +1,4 @@
<% item_label = format_label(@pagy.count, "uploads") %>
<% item_label = format_label(@pagy.count, "upload") %>
<% title = format_title(@searched, bulk_upload_title(controller.controller_name), current_user, item_label, @pagy.count, nil) %>
<% content_for :title, title %>

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

@ -16,7 +16,7 @@
<%= form_with model: @log, url: request.original_url, method: "post", local: true do |f| %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<% all_questions_with_errors = all_questions_affected_by_errors(@log) %>
<% all_pages_with_errors = all_pages_affected_by_errors(@log) %>
<% remove_other_page_errors(@log, @page) %>
<%= f.govuk_error_summary %>
@ -76,7 +76,7 @@
<%= f.hidden_field :check_errors, value: @check_errors %>
<% end %>
<% if all_questions_with_errors.count > 1 %>
<% if all_pages_with_errors.count > 1 %>
<div class="govuk-button-group">
<%= f.submit "See all related answers", name: "check_errors", class: "govuk-body govuk-link submit-button-link" %>
</div>

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

@ -32,7 +32,7 @@
count: @pagy.count,
item_label:,
total_count: @total_count,
item: "locations",
item: "location",
filters_count: applied_filters_count(@filter_type),
)) %>
<% end %>

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

@ -1,7 +1,7 @@
<h2 class="govuk-body">
<div class="govuk-grid-row app-search__caption">
<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))) %>
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "log", 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", style: "white-space: nowrap" %>
<% if @current_user.support? %>

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

@ -1,4 +1,4 @@
<% item_label = format_label(@pagy.count, "logs") %>
<% item_label = format_label(@pagy.count, "log") %>
<% title = format_title(@searched, "#{log_type_for_controller(controller).capitalize} logs", current_user, item_label, @pagy.count, nil) %>
<% content_for :title, title %>

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

@ -1,4 +1,4 @@
<% item_label = format_label(@pagy.count, "logs") %>
<% item_label = format_label(@pagy.count, "log") %>
<% title = "Update logs" %>
<% content_for :title, title %>

4
app/views/organisation_relationships/managing_agents.html.erb

@ -1,4 +1,4 @@
<% item_label = format_label(@pagy.count, "managing agents") %>
<% item_label = format_label(@pagy.count, "managing agent") %>
<% if current_user.support? %>
<%= render partial: "organisations/headings", locals: { main: @organisation.name, sub: nil } %>
@ -44,7 +44,7 @@
pagy: @pagy,
searched: @searched,
item_label:,
search_item: "managing agents",
search_item: "managing agent",
total_count: @total_count,
remove_path: ->(org_id) { managing_agents_remove_organisation_path(target_organisation_id: org_id) },
} %>

4
app/views/organisation_relationships/stock_owners.html.erb

@ -1,4 +1,4 @@
<% item_label = format_label(@pagy.count, "stock owners") %>
<% item_label = format_label(@pagy.count, "stock owner") %>
<% if current_user.support? %>
<%= render partial: "organisations/headings", locals: { main: @organisation.name, sub: nil } %>
<%= render SubNavigationComponent.new(items: secondary_items(request.path, @organisation.id)) %>
@ -41,7 +41,7 @@
pagy: @pagy,
searched: @searched,
item_label:,
search_item: "stock owners",
search_item: "stock owner",
total_count: @total_count,
remove_path: ->(org_id) { stock_owners_remove_organisation_path(target_organisation_id: org_id) },
} %>

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.with_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", filters_count: applied_filters_count(@filter_type))) %>
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "organisation", filters_count: applied_filters_count(@filter_type))) %>
<% end %>
<%= table.with_head do |head| %>
<%= head.with_row do |row| %>

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

@ -1,4 +1,4 @@
<% item_label = format_label(@pagy.count, "organisations") %>
<% item_label = format_label(@pagy.count, "organisation") %>
<% title = format_title(@searched, "Organisations", current_user, item_label, @pagy.count, nil) %>
<% content_for :title, title %>

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

@ -1,4 +1,4 @@
<% item_label = format_label(@pagy.count, "logs") %>
<% item_label = format_label(@pagy.count, "log") %>
<% title = format_title(@searched, action_name.humanize, current_user, item_label, @pagy.count, @organisation.name) %>
<% content_for :title, title %>

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

@ -2,7 +2,7 @@
<%= govuk_table do |table| %>
<%= table.with_caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %>
<span class="app-search__caption">
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "schemes", filters_count: applied_filters_count(@filter_type))) %>
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "scheme", filters_count: applied_filters_count(@filter_type))) %>
<% if @schemes&.any? %>
<%= govuk_link_to "Download schemes (CSV)", schemes_csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4", style: "white-space: nowrap" %>
<%= govuk_link_to "Download locations (CSV)", locations_csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4", style: "white-space: nowrap" %>

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.with_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", filters_count: applied_filters_count(@filter_type))) %>
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "user", 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", style: "white-space: nowrap" %>

1
config/locales/en.yml

@ -235,6 +235,7 @@ en:
organisation:
data_sharing_agreement_not_signed: "Your organisation must accept the Data Sharing Agreement before you can create any logs."
name_missing: "Enter the name of the organisation."
name_not_unique: "An organisation with this name already exists. Use the organisation that already exists or add a location or other identifier to the name. For example: Organisation name (City)."
provider_type_missing: "Select the organisation type."
stock_owner:
blank: "You must choose a stock owner."

5
db/migrate/20241125153349_add_unique_index_to_org_name.rb

@ -0,0 +1,5 @@
class AddUniqueIndexToOrgName < ActiveRecord::Migration[7.0]
def change
add_index :organisations, :name, unique: true
end
end

1
db/schema.rb

@ -546,6 +546,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_12_04_100518) do
t.datetime "discarded_at"
t.datetime "schemes_deduplicated_at"
t.index ["absorbing_organisation_id"], name: "index_organisations_on_absorbing_organisation_id"
t.index ["name"], name: "index_organisations_on_name", unique: true
t.index ["old_visible_id"], name: "index_organisations_on_old_visible_id", unique: true
end

26
spec/components/search_result_caption_component_spec.rb

@ -5,7 +5,7 @@ RSpec.describe SearchResultCaptionComponent, type: :component do
let(:count) { 2 }
let(:item_label) { "user" }
let(:total_count) { 3 }
let(:item) { "schemes" }
let(:item) { "scheme" }
let(:filters_count) { 1 }
let(:result) { render_inline(described_class.new(searched:, count:, item_label:, total_count:, item:, filters_count:)) }
@ -21,6 +21,14 @@ RSpec.describe SearchResultCaptionComponent, type: :component do
it "renders table caption including the search results and total" do
expect(result.to_html).to eq("<span>\n <strong>2</strong> users matching search<br>\n</span>\n")
end
context "with 1 result" do
let(:count) { 1 }
it "renders table caption including the search results and total" do
expect(result.to_html).to eq("<span>\n <strong>1</strong> user matching search<br>\n</span>\n")
end
end
end
context "when filter results are found" do
@ -29,6 +37,14 @@ RSpec.describe SearchResultCaptionComponent, type: :component do
it "renders table caption including the search results and total" do
expect(result.to_html).to eq("<span>\n <strong>2</strong> users matching filters<br>\n</span>\n")
end
context "with 1 result" do
let(:count) { 1 }
it "renders table caption including the search results and total" do
expect(result.to_html).to eq("<span>\n <strong>1</strong> user matching filters<br>\n</span>\n")
end
end
end
context "when no search/filter is applied" do
@ -38,6 +54,14 @@ RSpec.describe SearchResultCaptionComponent, type: :component do
it "renders table caption with total count only" do
expect(result.to_html).to eq("<span>\n <span class=\"govuk-!-margin-right-4\">\n <strong>2</strong> total schemes\n </span>\n</span>\n")
end
context "with 1 result" do
let(:count) { 1 }
it "renders table caption with total count only" do
expect(result.to_html).to eq("<span>\n <span class=\"govuk-!-margin-right-4\">\n <strong>1</strong> total scheme\n </span>\n</span>\n")
end
end
end
context "when nothing is found" do

7
spec/db/seeds_spec.rb

@ -21,7 +21,8 @@ RSpec.describe "seeding process", type: task do
allow(Rails.env).to receive(:review?).and_return(true)
end
it "sets up correct data" do
# Doing this in one test should save ~2 minutes
it "sets up correct data idempotently" do
expect {
Rails.application.load_seed
}.to change(User, :count)
@ -30,10 +31,6 @@ RSpec.describe "seeding process", type: task do
.and change(Scheme, :count)
.and change(Location, :count)
.and change(LaRentRange, :count)
end
it "is idempotent" do
Rails.application.load_seed
expect {
Rails.application.load_seed

2
spec/factories/organisation.rb

@ -1,6 +1,6 @@
FactoryBot.define do
factory :organisation do
name { Faker::Company.name }
sequence(:name) { |n| "#{Faker::Company.name} #{n}" }
address_line1 { Faker::Address.street_address }
address_line2 { Faker::Address.city }
provider_type { "LA" }

16
spec/features/form/page_routing_spec.rb

@ -92,7 +92,7 @@ RSpec.describe "Form Page Routing" do
expect(find("#lettings-log-postcode-full-field-error").value).to eq("FAKE_POSTCODE")
end
it "does not reset the displayed date" do
it "does not reset the displayed date if it's an invalid date" do
lettings_log.update!(startdate: "2021/10/13")
visit("/lettings-logs/#{id}/tenancy-start-date")
fill_in("lettings_log[startdate(1i)]", with: "202")
@ -106,6 +106,20 @@ RSpec.describe "Form Page Routing" do
expect(find_field("lettings_log[startdate(1i)]").value).to eq("2021")
end
it "displays the entered date if it's in a valid format" do
lettings_log.update!(startdate: "2021/10/13")
visit("/lettings-logs/#{id}/tenancy-start-date")
fill_in("lettings_log[startdate(1i)]", with: "202")
fill_in("lettings_log[startdate(2i)]", with: "12")
fill_in("lettings_log[startdate(3i)]", with: "1")
click_button("Save and continue")
expect(page).to have_current_path("/lettings-logs/#{id}/tenancy-start-date")
expect(find_field("lettings_log[startdate(3i)]").value).to eq("1")
expect(find_field("lettings_log[startdate(2i)]").value).to eq("12")
expect(find_field("lettings_log[startdate(1i)]").value).to eq("202")
end
it "does not reset the displayed date if it's empty" do
lettings_log.update!(startdate: nil)
visit("/lettings-logs/#{id}/tenancy-start-date")

4
spec/fixtures/exports/general_needs_log.xml vendored

@ -147,10 +147,10 @@
<duplicate_set_id/>
<formid>{id}</formid>
<owningorgid>{owning_org_id}</owningorgid>
<owningorgname>MHCLG</owningorgname>
<owningorgname>{owning_org_name}</owningorgname>
<hcnum>1234</hcnum>
<maningorgid>{managing_org_id}</maningorgid>
<maningorgname>MHCLG</maningorgname>
<maningorgname>{managing_org_name}</maningorgname>
<manhcnum>1234</manhcnum>
<createddate>2022-05-01T00:00:00+01:00</createddate>
<uploaddate>2022-05-01T00:00:00+01:00</uploaddate>

4
spec/fixtures/exports/general_needs_log_23_24.xml vendored

@ -148,10 +148,10 @@
<duplicate_set_id/>
<formid>{id}</formid>
<owningorgid>{owning_org_id}</owningorgid>
<owningorgname>MHCLG</owningorgname>
<owningorgname>{owning_org_name}</owningorgname>
<hcnum>1234</hcnum>
<maningorgid>{managing_org_id}</maningorgid>
<maningorgname>MHCLG</maningorgname>
<maningorgname>{managing_org_name}</maningorgname>
<manhcnum>1234</manhcnum>
<createddate>2023-04-03T00:00:00+01:00</createddate>
<uploaddate>2023-04-03T00:00:00+01:00</uploaddate>

4
spec/fixtures/exports/general_needs_log_24_25.xml vendored

@ -161,10 +161,10 @@
<la_as_entered>la as entered</la_as_entered>
<formid>{id}</formid>
<owningorgid>{owning_org_id}</owningorgid>
<owningorgname>MHCLG</owningorgname>
<owningorgname>{owning_org_name}</owningorgname>
<hcnum>1234</hcnum>
<maningorgid>{managing_org_id}</maningorgid>
<maningorgname>MHCLG</maningorgname>
<maningorgname>{managing_org_name}</maningorgname>
<manhcnum>1234</manhcnum>
<createddate>2024-04-03T00:00:00+01:00</createddate>
<uploaddate>2024-04-03T00:00:00+01:00</uploaddate>

2
spec/fixtures/exports/organisation.xml vendored

@ -2,7 +2,7 @@
<forms>
<form>
<id>{id}</id>
<name>MHCLG</name>
<name>{name}</name>
<phone/>
<provider_type>1</provider_type>
<address_line1>2 Marsham Street</address_line1>

4
spec/fixtures/exports/supported_housing_logs.xml vendored

@ -146,10 +146,10 @@
<duplicate_set_id/>
<formid>{id}</formid>
<owningorgid>{owning_org_id}</owningorgid>
<owningorgname>MHCLG</owningorgname>
<owningorgname>{owning_org_name}</owningorgname>
<hcnum>1234</hcnum>
<maningorgid>{managing_org_id}</maningorgid>
<maningorgname>MHCLG</maningorgname>
<maningorgname>{managing_org_name}</maningorgname>
<manhcnum>1234</manhcnum>
<createddate>2022-05-01T00:00:00+01:00</createddate>
<uploaddate>2022-05-01T00:00:00+01:00</uploaddate>

2
spec/fixtures/exports/user.xml vendored

@ -12,6 +12,6 @@
<is_dpo>false</is_dpo>
<is_key_contact>false</is_key_contact>
<active>true</active>
<organisation_name>MHCLG</organisation_name>
<organisation_name>{organisation_name}</organisation_name>
</form>
</forms>

25
spec/mailers/bulk_upload_mailer_spec.rb

@ -121,4 +121,29 @@ RSpec.describe BulkUploadMailer do
mailer.send_check_soft_validations_mail(bulk_upload:)
end
end
describe "#send_correct_duplicates_and_upload_again_mail" do
context "when 2 columns with errors" do
before do
create(:bulk_upload_error, bulk_upload:, col: "A")
create(:bulk_upload_error, bulk_upload:, col: "B")
end
it "sends correctly formed email" do
expect(notify_client).to receive(:send_email).with(
email_address: user.email,
template_id: described_class::FAILED_CSV_DUPLICATE_ERRORS_TEMPLATE_ID,
personalisation: {
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
year_combo: bulk_upload.year_combo,
lettings_or_sales: bulk_upload.log_type,
summary_report_link: "http://localhost:3000/lettings-logs/bulk-upload-results/#{bulk_upload.id}",
},
)
mailer.send_correct_duplicates_and_upload_again_mail(bulk_upload:)
end
end
end
end

11
spec/mailers/devise_notify_mailer_spec.rb

@ -36,8 +36,11 @@ RSpec.describe DeviseNotifyMailer do
end
context "when the email domain is in the allowlist" do
let(:domain) { Rails.application.credentials[:email_allowlist].first }
let(:email) { "test@#{domain}" }
before do
allow(Rails.application.credentials).to receive(:[]).with(:email_allowlist).and_return(["example.com"])
end
let(:email) { "test@example.com" }
it "does send emails" do
expect(notify_client).to receive(:send_email).once
@ -48,10 +51,10 @@ RSpec.describe DeviseNotifyMailer do
context "when notify mailer raises BadRequestError" do
before do
allow(notify_client).to receive(:send_email).and_raise(bad_request_error)
allow(Rails.application.credentials).to receive(:[]).with(:email_allowlist).and_return(["example.com"])
end
let(:domain) { Rails.application.credentials[:email_allowlist].first }
let(:email) { "test@#{domain}" }
let(:email) { "test@example.com" }
it "does not raise an error" do
expect {

2
spec/models/form/lettings/questions/managing_organisation_spec.rb

@ -185,7 +185,7 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do
context "when organisation has merged" do
let(:absorbing_org) { create(:organisation, name: "Absorbing org", holds_own_stock: true) }
let!(:merged_org) { create(:organisation, name: "Merged org", holds_own_stock: false) }
let!(:merged_deleted_org) { create(:organisation, name: "Merged org", holds_own_stock: false, discarded_at: Time.zone.yesterday) }
let!(:merged_deleted_org) { create(:organisation, name: "Merged org 2", holds_own_stock: false, discarded_at: Time.zone.yesterday) }
let(:user) { create(:user, :data_coordinator, organisation: absorbing_org) }
let(:log) do

28
spec/models/form/sales/questions/buyer2_working_situation_spec.rb

@ -36,6 +36,22 @@ RSpec.describe Form::Sales::Questions::Buyer2WorkingSituation, type: :model do
"0" => { "value" => "Other" },
"10" => { "value" => "Buyer prefers not to say" },
"7" => { "value" => "Full-time student" },
"9" => { "value" => "Child under 16" },
})
end
it "has the correct displayed_answer_options" do
expect(question.displayed_answer_options(nil)).to eq({
"1" => { "value" => "Full-time - 30 hours or more" },
"2" => { "value" => "Part-time - Less than 30 hours" },
"3" => { "value" => "In government training into work" },
"4" => { "value" => "Jobseeker" },
"6" => { "value" => "Not seeking work" },
"8" => { "value" => "Unable to work due to long term sick or disability" },
"5" => { "value" => "Retired" },
"0" => { "value" => "Other" },
"10" => { "value" => "Buyer prefers not to say" },
"7" => { "value" => "Full-time student" },
})
end
@ -43,7 +59,11 @@ RSpec.describe Form::Sales::Questions::Buyer2WorkingSituation, type: :model do
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1), start_year_2025_or_later?: false) }
it "uses the old ordering for answer options" do
expect(question.answer_options.keys).to eq(%w[1 2 3 4 6 8 5 0 10 7])
expect(question.answer_options.keys).to eq(%w[1 2 3 4 6 8 5 0 10 7 9])
end
it "uses the old ordering for displayed answer options" do
expect(question.displayed_answer_options(nil).keys).to eq(%w[1 2 3 4 6 8 5 0 10 7])
end
end
@ -51,7 +71,11 @@ RSpec.describe Form::Sales::Questions::Buyer2WorkingSituation, type: :model do
let(:form) { instance_double(Form, start_date: Time.zone.local(2025, 4, 1), start_year_2025_or_later?: true) }
it "uses the new ordering for answer options" do
expect(question.answer_options.keys).to eq(%w[1 2 3 4 5 6 7 8 0 10])
expect(question.answer_options.keys).to eq(%w[1 2 3 4 5 6 7 8 9 0 10])
end
it "uses the new ordering for displayed answer options" do
expect(question.displayed_answer_options(nil).keys).to eq(%w[1 2 3 4 5 6 7 8 0 10])
end
end

6
spec/models/organisation_spec.rb

@ -19,6 +19,12 @@ RSpec.describe Organisation, type: :model do
.to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Provider type #{I18n.t('validations.organisation.provider_type_missing')}")
end
it "validates uniqueness of name" do
org = build(:organisation, name: organisation.name.downcase)
org.valid?
expect(org.errors[:name]).to include(I18n.t("validations.organisation.name_not_unique"))
end
context "with parent/child associations", :aggregate_failures do
let!(:child_organisation) { create(:organisation, name: "MHCLG Child") }
let!(:grandchild_organisation) { create(:organisation, name: "MHCLG Grandchild") }

7
spec/models/scheme_spec.rb

@ -390,6 +390,13 @@ RSpec.describe Scheme, type: :model do
scheme.startdate = Time.zone.today + 2.weeks
expect(scheme.status).to eq(:activating_soon)
end
it "returns deactivated if scheme is deactivated and incomplete" do
scheme.update!(support_type: nil, confirmed: nil)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.yesterday, scheme:)
scheme.reload
expect(scheme.status).to eq(:deactivated)
end
end
context "when there have been previous deactivations" do

1
spec/models/user_spec.rb

@ -300,6 +300,7 @@ RSpec.describe User, type: :model do
context "when the user is in staging environment" do
before do
allow(Rails.env).to receive(:staging?).and_return(true)
allow(Rails.application.credentials).to receive(:[]).with(:staging_role_update_email_allowlist).and_return(["not_one_of_the_examples.com"])
end
context "and the user is not in the staging role update email allowlist" do

111
spec/requests/bulk_upload_lettings_logs_controller_spec.rb

@ -73,5 +73,116 @@ RSpec.describe BulkUploadLettingsLogsController, type: :request do
expect(response.body).to include("How to upload logs in bulk")
end
end
context "when no year is specified" do
it "shows guidance page with links defaulting to the current year" do
get "/lettings-logs/bulk-upload-logs/guidance"
expect(response.body).to include("Download the lettings bulk upload template (#{current_collection_start_year} to #{current_collection_start_year + 1})")
end
end
context "when an invalid year is specified" do
it "shows not found" do
get "/lettings-logs/bulk-upload-logs/guidance?form%5Byear%5D=10000"
expect(response).to be_not_found
end
end
end
describe "GET /lettings-logs/bulk-upload-logs/year" do
it "does not require a year to be specified" do
get "/lettings-logs/bulk-upload-logs/year"
expect(response).to be_ok
end
end
pages_requiring_year_specification = %w[prepare-your-file upload-your-file checking-file]
pages_requiring_year_specification.each do |page_id|
describe "GET /lettings-logs/bulk-upload-logs/#{page_id}" do
context "when no year is provided" do
it "returns not found" do
get "/lettings-logs/bulk-upload-logs/#{page_id}"
expect(response).to be_not_found
end
end
context "when requesting the previous year in a crossover period" do
before do
allow(FormHandler.instance).to receive(:lettings_in_crossover_period?).and_return(true)
end
it "succeeds" do
get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year - 1}"
expect(response).to be_ok
end
end
context "when requesting the previous year outside a crossover period" do
before do
allow(FormHandler.instance).to receive(:lettings_in_crossover_period?).and_return(false)
end
it "returns not found" do
get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year - 1}"
expect(response).to be_not_found
end
end
context "when requesting the current year" do
it "succeeds" do
get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year}"
expect(response).to be_ok
end
end
if page_id != "prepare-your-file"
context "when requesting the next year with future form use toggled on" do
before do
allow(FeatureToggle).to receive(:allow_future_form_use?).and_return(true)
end
it "succeeds" do
get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year + 1}"
expect(response).to be_ok
end
end
end
context "when requesting the next year with future form use toggled off" do
before do
allow(FeatureToggle).to receive(:allow_future_form_use?).and_return(false)
end
it "returns not found" do
get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year + 1}"
expect(response).to be_not_found
end
end
context "when requesting a far future year" do
it "returns not found" do
get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=9990"
expect(response).to be_not_found
end
end
context "when requesting a nonsense value for year" do
it "returns not found" do
get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=thisisnotayear"
expect(response).to be_not_found
end
end
end
end
end

111
spec/requests/bulk_upload_sales_logs_controller_spec.rb

@ -73,5 +73,116 @@ RSpec.describe BulkUploadSalesLogsController, type: :request do
expect(response.body).to include("How to upload logs in bulk")
end
end
context "when no year is specified" do
it "shows guidance page with links defaulting to the current year" do
get "/sales-logs/bulk-upload-logs/guidance"
expect(response.body).to include("Download the sales bulk upload template (#{current_collection_start_year} to #{current_collection_start_year + 1})")
end
end
context "when an invalid year is specified" do
it "shows not found" do
get "/sales-logs/bulk-upload-logs/guidance?form%5Byear%5D=10000"
expect(response).to be_not_found
end
end
end
describe "GET /sales-logs/bulk-upload-logs/year" do
it "does not require a year to be specified" do
get "/sales-logs/bulk-upload-logs/year"
expect(response).to be_ok
end
end
pages_requiring_year_specification = %w[prepare-your-file upload-your-file checking-file]
pages_requiring_year_specification.each do |page_id|
describe "GET /sales-logs/bulk-upload-logs/#{page_id}" do
context "when no year is provided" do
it "returns not found" do
get "/sales-logs/bulk-upload-logs/#{page_id}"
expect(response).to be_not_found
end
end
context "when requesting the previous year in a crossover period" do
before do
allow(FormHandler.instance).to receive(:sales_in_crossover_period?).and_return(true)
end
it "succeeds" do
get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year - 1}"
expect(response).to be_ok
end
end
context "when requesting the previous year outside a crossover period" do
before do
allow(FormHandler.instance).to receive(:sales_in_crossover_period?).and_return(false)
end
it "returns not found" do
get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year - 1}"
expect(response).to be_not_found
end
end
context "when requesting the current year" do
it "succeeds" do
get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year}"
expect(response).to be_ok
end
end
if page_id != "prepare-your-file"
context "when requesting the next year with future form use toggled on" do
before do
allow(FeatureToggle).to receive(:allow_future_form_use?).and_return(true)
end
it "succeeds" do
get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year + 1}"
expect(response).to be_ok
end
end
end
context "when requesting the next year with future form use toggled off" do
before do
allow(FeatureToggle).to receive(:allow_future_form_use?).and_return(false)
end
it "returns not found" do
get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year + 1}"
expect(response).to be_not_found
end
end
context "when requesting a far future year" do
it "returns not found" do
get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=9990"
expect(response).to be_not_found
end
end
context "when requesting a nonsense value for year" do
it "returns not found" do
get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=thisisnotayear"
expect(response).to be_not_found
end
end
end
end
end

2
spec/requests/check_errors_controller_spec.rb

@ -84,7 +84,7 @@ RSpec.describe CheckErrorsController, type: :request do
end
it "displays correct clear and change links" do
expect(page.all(:button, value: "Clear").count).to eq(2)
expect(page.all(:button, value: "Clear").count).to eq(1)
expect(page).to have_link("Change", count: 1)
expect(page).to have_button("Clear all")
end

4
spec/requests/lettings_logs_controller_spec.rb

@ -759,7 +759,7 @@ RSpec.describe LettingsLogsController, type: :request do
it "has search results in the title" do
get "/lettings-logs?search=#{log_to_search.id}", headers:, params: {}
expect(page).to have_title("Lettings logs (1 logs matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK")
expect(page).to have_title("Lettings logs (1 log matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
it "shows lettings logs matching the id" do
@ -895,7 +895,7 @@ RSpec.describe LettingsLogsController, type: :request do
end
it "shows the total log count" do
expect(CGI.unescape_html(response.body)).to match("<strong>1</strong> total logs")
expect(CGI.unescape_html(response.body)).to match("<strong>1</strong> total log")
end
it "does not show the pagination links" do

20
spec/requests/organisation_relationships_controller_spec.rb

@ -53,12 +53,12 @@ RSpec.describe OrganisationRelationshipsController, type: :request do
end
it "shows the pagination count" do
expect(page).to have_content("1 total stock owners")
expect(page).to have_content("1 total stock owner")
end
context "when adding a stock owner" do
let!(:active_organisation) { FactoryBot.create(:organisation, name: "Active Org", active: true) }
let!(:inactive_organisation) { FactoryBot.create(:organisation, name: "Inactive LTD", active: false) }
let!(:inactive_organisation) { FactoryBot.create(:organisation, name: "Inactive LTD 2", active: false) }
before do
get "/organisations/#{organisation.id}/stock-owners/add", headers:, params: {}
@ -115,7 +115,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do
let!(:managing_agent) { FactoryBot.create(:organisation) }
let!(:other_org_managing_agent) { FactoryBot.create(:organisation, name: "Foobar LTD") }
let!(:inactive_managing_agent) { FactoryBot.create(:organisation, name: "Inactive LTD", active: false) }
let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD") }
let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD 3") }
before do
FactoryBot.create(:organisation_relationship, parent_organisation: organisation, child_organisation: managing_agent)
@ -149,7 +149,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do
end
it "shows the pagination count" do
expect(page).to have_content("1 total managing agents")
expect(page).to have_content("1 total managing agent")
end
context "and current organisation is deactivated" do
@ -316,7 +316,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do
context "with an organisation that the user belongs to" do
let!(:stock_owner) { FactoryBot.create(:organisation) }
let!(:other_org_stock_owner) { FactoryBot.create(:organisation, name: "Foobar LTD") }
let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD") }
let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD 2") }
before do
FactoryBot.create(:organisation_relationship, child_organisation: organisation, parent_organisation: stock_owner)
@ -345,7 +345,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do
end
it "shows the pagination count" do
expect(page).to have_content("1 total stock owners")
expect(page).to have_content("1 total stock owner")
end
end
@ -452,7 +452,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do
context "with an organisation that the user belongs to" do
let!(:managing_agent) { FactoryBot.create(:organisation) }
let!(:other_org_managing_agent) { FactoryBot.create(:organisation, name: "Foobar LTD") }
let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD") }
let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD 5") }
before do
FactoryBot.create(:organisation_relationship, parent_organisation: organisation, child_organisation: managing_agent)
@ -481,7 +481,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do
end
it "shows the pagination count" do
expect(page).to have_content("1 total managing agents")
expect(page).to have_content("1 total managing agent")
end
end
@ -647,7 +647,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do
end
it "shows the pagination count" do
expect(page).to have_content("1 total stock owners")
expect(page).to have_content("1 total stock owner")
end
context "when adding a stock owner" do
@ -697,7 +697,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do
end
it "shows the pagination count" do
expect(page).to have_content("1 total managing agents")
expect(page).to have_content("1 total managing agent")
end
it "shows remove link(s)" do

18
spec/requests/organisations_controller_spec.rb

@ -1203,7 +1203,7 @@ RSpec.describe OrganisationsController, type: :request do
it "has search results in the title" do
get "/organisations/#{organisation.id}/lettings-logs?search=#{log_to_search.id}", headers: headers, params: {}
expect(page).to have_title("#{organisation.name} (1 logs matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK")
expect(page).to have_title("#{organisation.name} (1 log matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
it "has search term in the search box" do
@ -1352,7 +1352,7 @@ RSpec.describe OrganisationsController, type: :request do
it "has search results in the title" do
get "/organisations/#{organisation.id}/sales-logs?search=#{log_to_search.id}", headers: headers, params: {}
expect(page).to have_title("#{organisation.name} (1 logs matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK")
expect(page).to have_title("#{organisation.name} (1 log matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
it "shows sales logs matching the id" do
@ -1555,7 +1555,10 @@ RSpec.describe OrganisationsController, type: :request do
let(:total_organisations_count) { Organisation.all.count }
before do
create_list(:organisation, 25)
build_list(:organisation, 25) do |organisation, index|
organisation.name = "Organisation #{index}"
organisation.save!
end
get "/organisations"
end
@ -1616,11 +1619,11 @@ RSpec.describe OrganisationsController, type: :request do
end
it "updates the table caption" do
expect(page).to have_content("1 organisations matching search")
expect(page).to have_content("1 organisation matching search")
end
it "has search in the title" do
expect(page).to have_title("Organisations (1 organisations matching ‘#{search_param}’) - Submit social housing lettings and sales data (CORE) - GOV.UK")
expect(page).to have_title("Organisations (1 organisation matching ‘#{search_param}’) - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
context "when the search term matches more than 1 result" do
@ -1644,7 +1647,10 @@ RSpec.describe OrganisationsController, type: :request do
let(:search_param) { "MHCLG" }
before do
create_list(:organisation, 27, name: "MHCLG")
build_list(:organisation, 27) do |organisation, index|
organisation.name = "MHCLG #{index}"
organisation.save!
end
get "/organisations?search=#{search_param}"
end

4
spec/requests/sales_logs_controller_spec.rb

@ -610,7 +610,7 @@ RSpec.describe SalesLogsController, type: :request do
it "has search results in the title" do
get "/sales-logs?search=#{log_to_search.id}", headers: headers, params: {}
expect(page).to have_title("Sales logs (1 logs matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK")
expect(page).to have_title("Sales logs (1 log matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
it "shows sales logs matching the id" do
@ -692,7 +692,7 @@ RSpec.describe SalesLogsController, type: :request do
end
it "shows the total log count" do
expect(CGI.unescape_html(response.body)).to match("<strong>1</strong> total logs")
expect(CGI.unescape_html(response.body)).to match("<strong>1</strong> total log")
end
it "does not show the pagination links" do

10
spec/services/bulk_upload/lettings/validator_spec.rb

@ -232,7 +232,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
end
end
describe "#create_logs?" do
describe "#block_log_creation_reason" do
context "when a log has a clearable, non-setup error" do
let(:log_1) { build(:lettings_log, :completed, period: 2, assigned_to: user) }
let(:log_2) { build(:lettings_log, :completed, period: 2, assigned_to: user, age1: 5) }
@ -245,7 +245,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
it "returns false" do
validator.call
expect(validator).to be_create_logs
expect(validator.block_log_creation_reason).to be_nil
end
end
@ -261,7 +261,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
it "returns true" do
validator.call
expect(validator).to be_create_logs
expect(validator.block_log_creation_reason).to be_nil
end
end
@ -277,7 +277,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
it "will not create logs" do
validator.call
expect(validator).not_to be_create_logs
expect(validator.block_log_creation_reason).to eq("setup_errors")
end
end
@ -291,7 +291,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
it "returns false" do
validator.call
expect(validator).not_to be_create_logs
expect(validator.block_log_creation_reason).to eq("setup_errors")
end
end
end

32
spec/services/bulk_upload/processor_spec.rb

@ -14,7 +14,7 @@ RSpec.describe BulkUpload::Processor do
call: nil,
total_logs_count: 1,
any_setup_errors?: false,
create_logs?: true,
block_log_creation_reason: nil,
soft_validation_errors_only?: false,
)
end
@ -165,7 +165,7 @@ RSpec.describe BulkUpload::Processor do
let(:log) { build(:lettings_log, :setup_completed, assigned_to: user) }
before do
allow(mock_validator).to receive(:create_logs?).and_return(true)
allow(mock_validator).to receive(:block_log_creation_reason).and_return(nil)
allow(mock_validator).to receive(:soft_validation_errors_only?).and_return(false)
allow(FeatureToggle).to receive(:bulk_upload_duplicate_log_check_enabled?).and_return(true)
end
@ -198,7 +198,7 @@ RSpec.describe BulkUpload::Processor do
context "when a bulk upload has logs with only soft validations triggered" do
before do
allow(mock_validator).to receive(:create_logs?).and_return(true)
allow(mock_validator).to receive(:block_log_creation_reason).and_return(nil)
allow(mock_validator).to receive(:soft_validation_errors_only?).and_return(true)
allow(FeatureToggle).to receive(:bulk_upload_duplicate_log_check_enabled?).and_return(true)
end
@ -239,7 +239,7 @@ RSpec.describe BulkUpload::Processor do
call: nil,
total_logs_count: 1,
any_setup_errors?: false,
create_logs?: false,
block_log_creation_reason: "row_parser_block_log_creation",
)
end
@ -254,6 +254,30 @@ RSpec.describe BulkUpload::Processor do
expect(mail_double).to have_received(:deliver_later)
end
end
context "when upload has duplicate logs blocking log creation" do
let(:mock_validator) do
instance_double(
BulkUpload::Lettings::Validator,
invalid?: false,
call: nil,
total_logs_count: 1,
any_setup_errors?: false,
block_log_creation_reason: "duplicate_logs",
)
end
it "sends correct_and_upload_again_mail" do
mail_double = instance_double("ActionMailer::MessageDelivery", deliver_later: nil)
allow(BulkUploadMailer).to receive(:send_correct_duplicates_and_upload_again_mail).and_return(mail_double)
processor.call
expect(BulkUploadMailer).to have_received(:send_correct_duplicates_and_upload_again_mail)
expect(mail_double).to have_received(:deliver_later)
end
end
end
describe "#approve" do

16
spec/services/bulk_upload/sales/validator_spec.rb

@ -204,7 +204,7 @@ RSpec.describe BulkUpload::Sales::Validator do
end
end
describe "#create_logs?" do
describe "#block_log_creation_reason" do
context "when all logs are valid" do
let(:log_1) { build(:sales_log, :completed, assigned_to: user) }
let(:log_2) { build(:sales_log, :completed, assigned_to: user) }
@ -214,9 +214,9 @@ RSpec.describe BulkUpload::Sales::Validator do
file.write(BulkUpload::SalesLogToCsv.new(log: log_2).to_csv_row)
end
it "returns truthy" do
it "returns nil" do
validator.call
expect(validator).to be_create_logs
expect(validator.block_log_creation_reason).to be_nil
end
end
@ -229,9 +229,9 @@ RSpec.describe BulkUpload::Sales::Validator do
file.write(BulkUpload::SalesLogToCsv.new(log: log_2).to_csv_row)
end
it "returns truthy" do
it "returns nil" do
validator.call
expect(validator).to be_create_logs
expect(validator.block_log_creation_reason).to be_nil
end
end
@ -245,9 +245,9 @@ RSpec.describe BulkUpload::Sales::Validator do
file.close
end
it "returns false" do
it "returns the reason" do
validator.call
expect(validator).not_to be_create_logs
expect(validator.block_log_creation_reason).to eq("setup_errors")
end
end
@ -262,7 +262,7 @@ RSpec.describe BulkUpload::Sales::Validator do
it "will not create logs" do
validator.call
expect(validator).not_to be_create_logs
expect(validator.block_log_creation_reason).to eq("setup_errors")
end
end
end

2
spec/services/exports/lettings_log_export_service_spec.rb

@ -21,7 +21,9 @@ RSpec.describe Exports::LettingsLogExportService do
def replace_entity_ids(lettings_log, export_template)
export_template.sub!(/\{id\}/, (lettings_log["id"] + Exports::LettingsLogExportService::LOG_ID_OFFSET).to_s)
export_template.sub!(/\{owning_org_id\}/, (lettings_log["owning_organisation_id"] + Exports::LettingsLogExportService::LOG_ID_OFFSET).to_s)
export_template.sub!(/\{owning_org_name\}/, lettings_log.owning_organisation.name)
export_template.sub!(/\{managing_org_id\}/, (lettings_log["managing_organisation_id"] + Exports::LettingsLogExportService::LOG_ID_OFFSET).to_s)
export_template.sub!(/\{managing_org_name\}/, lettings_log.managing_organisation.name)
export_template.sub!(/\{location_id\}/, (lettings_log["location_id"]).to_s) if lettings_log.needstype == 2
export_template.sub!(/\{scheme_id\}/, (lettings_log["scheme_id"]).to_s) if lettings_log.needstype == 2
export_template.sub!(/\{log_id\}/, lettings_log["id"].to_s)

1
spec/services/exports/organisation_export_service_spec.rb

@ -16,6 +16,7 @@ RSpec.describe Exports::OrganisationExportService do
def replace_entity_ids(organisation, export_template)
export_template.sub!(/\{id\}/, organisation["id"].to_s)
export_template.sub!(/\{name\}/, organisation["name"])
export_template.sub!(/\{dsa_signed_at\}/, organisation.data_protection_confirmation&.signed_at.to_s)
export_template.sub!(/\{dpo_email\}/, organisation.data_protection_confirmation&.data_protection_officer_email)
end

1
spec/services/exports/user_export_service_spec.rb

@ -17,6 +17,7 @@ RSpec.describe Exports::UserExportService do
def replace_entity_ids(user, export_template)
export_template.sub!(/\{id\}/, user["id"].to_s)
export_template.sub!(/\{organisation_id\}/, user["organisation_id"].to_s)
export_template.sub!(/\{organisation_name\}/, user.organisation.name)
export_template.sub!(/\{email\}/, user["email"].to_s)
end

6
yarn.lock

@ -4335,9 +4335,9 @@ mute-stream@0.0.8:
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
nanoid@^3.3.7:
version "3.3.7"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
version "3.3.8"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
natural-compare@^1.4.0:
version "1.4.0"

Loading…
Cancel
Save