Browse Source

Merge branch 'main' into CLDC-3788-export-sales-logs

CLDC-3788-export-sales-logs
kosiakkatrina 7 days ago committed by GitHub
parent
commit
9e93065afb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 22
      .github/workflows/aws_deploy.yml
  2. 6
      .github/workflows/review_teardown_pipeline.yml
  3. 411
      .github/workflows/run_tests.yml
  4. 351
      .github/workflows/staging_pipeline.yml
  5. 12
      Gemfile
  6. 251
      Gemfile.lock
  7. 2
      app/components/bulk_upload_error_row_component.html.erb
  8. 22
      app/components/primary_navigation_component.html.erb
  9. 2
      app/components/search_component.html.erb
  10. 2
      app/components/search_result_caption_component.html.erb
  11. 23
      app/controllers/bulk_upload_lettings_logs_controller.rb
  12. 19
      app/controllers/bulk_upload_sales_logs_controller.rb
  13. 23
      app/controllers/form_controller.rb
  14. 2
      app/controllers/lettings_logs_controller.rb
  15. 2
      app/controllers/logs_controller.rb
  16. 5
      app/controllers/organisations_controller.rb
  17. 2
      app/controllers/sales_logs_controller.rb
  18. 4
      app/controllers/schemes_controller.rb
  19. 12
      app/controllers/sessions_controller.rb
  20. 69
      app/frontend/styles/_primary-navigation.scss
  21. 42
      app/frontend/styles/application.scss
  22. 4
      app/helpers/filters_helper.rb
  23. 4
      app/helpers/form_page_error_helper.rb
  24. 2
      app/helpers/logs_helper.rb
  25. 18
      app/helpers/schemes_helper.rb
  26. 21
      app/mailers/bulk_upload_mailer.rb
  27. 6
      app/models/bulk_upload.rb
  28. 2
      app/models/csv_download.rb
  29. 1
      app/models/form/lettings/questions/offered.rb
  30. 2
      app/models/form/sales/pages/owning_organisation.rb
  31. 2
      app/models/form/sales/questions/buyer2_income_known.rb
  32. 6
      app/models/form/sales/questions/buyer2_working_situation.rb
  33. 2
      app/models/form/sales/questions/equity.rb
  34. 2
      app/models/form/sales/questions/value.rb
  35. 39
      app/models/forms/bulk_upload_lettings/needstype.rb
  36. 2
      app/models/forms/bulk_upload_lettings_resume/fix_choice.rb
  37. 2
      app/models/forms/bulk_upload_sales_resume/fix_choice.rb
  38. 3
      app/models/lettings_log.rb
  39. 7
      app/models/location.rb
  40. 18
      app/models/log.rb
  41. 4
      app/models/merge_request.rb
  42. 3
      app/models/organisation.rb
  43. 3
      app/models/sales_log.rb
  44. 20
      app/models/scheme.rb
  45. 2
      app/models/user.rb
  46. 2
      app/models/validations/property_validations.rb
  47. 2
      app/models/validations/sales/property_validations.rb
  48. 8
      app/models/validations/sales/sale_information_validations.rb
  49. 4
      app/models/validations/soft_validations.rb
  50. 2
      app/services/bulk_upload/lettings/log_creator.rb
  51. 14
      app/services/bulk_upload/lettings/validator.rb
  52. 20
      app/services/bulk_upload/processor.rb
  53. 2
      app/services/bulk_upload/sales/log_creator.rb
  54. 12
      app/services/bulk_upload/sales/validator.rb
  55. 6
      app/services/exports/organisation_export_service.rb
  56. 1
      app/services/exports/user_export_service.rb
  57. 16
      app/services/feature_toggle.rb
  58. 23
      app/views/bulk_upload_lettings_logs/forms/needstype.erb
  59. 21
      app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2024.html.erb
  60. 2
      app/views/bulk_upload_lettings_resume/deletion_report.html.erb
  61. 12
      app/views/bulk_upload_lettings_resume/fix_choice.html.erb
  62. 2
      app/views/bulk_upload_lettings_soft_validations_check/confirm.html.erb
  63. 19
      app/views/bulk_upload_sales_logs/forms/prepare_your_file_2024.html.erb
  64. 2
      app/views/bulk_upload_sales_resume/deletion_report.html.erb
  65. 12
      app/views/bulk_upload_sales_resume/fix_choice.html.erb
  66. 2
      app/views/bulk_upload_sales_soft_validations_check/confirm.html.erb
  67. 4
      app/views/bulk_upload_shared/_moved_user_banner.html.erb
  68. 9
      app/views/bulk_upload_shared/guidance.html.erb
  69. 2
      app/views/bulk_upload_shared/uploads.html.erb
  70. 6
      app/views/cookies/show.html.erb
  71. 2
      app/views/devise/sessions/new.html.erb
  72. 3
      app/views/form/page.html.erb
  73. 2
      app/views/layouts/application.html.erb
  74. 2
      app/views/locations/check_answers.html.erb
  75. 2
      app/views/locations/index.html.erb
  76. 2
      app/views/locations/show.html.erb
  77. 2
      app/views/logs/_log_list.html.erb
  78. 10
      app/views/logs/delete_duplicates.html.erb
  79. 2
      app/views/logs/index.html.erb
  80. 2
      app/views/logs/update_logs.html.erb
  81. 30
      app/views/merge_requests/merge_request.html.erb
  82. 12
      app/views/organisation_relationships/add_managing_agent.html.erb
  83. 12
      app/views/organisation_relationships/add_stock_owner.html.erb
  84. 4
      app/views/organisation_relationships/managing_agents.html.erb
  85. 4
      app/views/organisation_relationships/stock_owners.html.erb
  86. 2
      app/views/organisations/_organisation_list.html.erb
  87. 65
      app/views/organisations/data_sharing_agreement.html.erb
  88. 53
      app/views/organisations/duplicate_schemes.html.erb
  89. 2
      app/views/organisations/index.html.erb
  90. 2
      app/views/organisations/logs.html.erb
  91. 2
      app/views/schemes/_scheme_list.html.erb
  92. 10
      app/views/schemes/changes.html.erb
  93. 2
      app/views/schemes/check_answers.html.erb
  94. 2
      app/views/schemes/confirm_secondary.html.erb
  95. 2
      app/views/schemes/details.html.erb
  96. 8
      app/views/schemes/primary_client_group.html.erb
  97. 2
      app/views/schemes/secondary_client_group.html.erb
  98. 2
      app/views/schemes/show.html.erb
  99. 2
      app/views/schemes/support.html.erb
  100. 68
      app/views/start/guidance.html.erb
  101. Some files were not shown because too many files have changed in this diff Show More

22
.github/workflows/aws_deploy.yml

@ -41,19 +41,17 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Configure AWS credentials - name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v3 uses: aws-actions/configure-aws-credentials@v4
with: with:
aws-region: ${{ env.aws_region }} aws-region: ${{ env.aws_region }}
role-to-assume: ${{ env.app_repo_role }} role-to-assume: ${{ env.app_repo_role }}
- name: Login to Amazon ECR - name: Login to Amazon ECR
id: ecr-login id: ecr-login
uses: aws-actions/amazon-ecr-login@v1 uses: aws-actions/amazon-ecr-login@v2
with:
mask-password: "true"
- name: Check if image with tag already exists - name: Check if image with tag already exists
run: | run: |
@ -81,16 +79,14 @@ jobs:
steps: steps:
- name: Configure AWS credentials - name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v3 uses: aws-actions/configure-aws-credentials@v4
with: with:
aws-region: ${{ env.aws_region }} aws-region: ${{ env.aws_region }}
role-to-assume: ${{ env.app_repo_role }} role-to-assume: ${{ env.app_repo_role }}
- name: Login to Amazon ECR - name: Login to Amazon ECR
id: ecr-login id: ecr-login
uses: aws-actions/amazon-ecr-login@v1 uses: aws-actions/amazon-ecr-login@v2
with:
mask-password: "true"
- name: Get timestamp - name: Get timestamp
id: timestamp id: timestamp
@ -112,7 +108,7 @@ jobs:
echo "image=$registry/$repository:$readable_tag" >> $GITHUB_ENV echo "image=$registry/$repository:$readable_tag" >> $GITHUB_ENV
- name: Configure AWS credentials for environment - name: Configure AWS credentials for environment
uses: aws-actions/configure-aws-credentials@v3 uses: aws-actions/configure-aws-credentials@v4
with: with:
aws-region: ${{ env.aws_region }} aws-region: ${{ env.aws_region }}
role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/${{ inputs.aws_role_prefix }}-deployment role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/${{ inputs.aws_role_prefix }}-deployment
@ -133,7 +129,7 @@ jobs:
image: ${{ env.image }} image: ${{ env.image }}
- name: Update ad hoc task definition - name: Update ad hoc task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1 uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with: with:
task-definition: ${{ steps.ad-hoc-task-def.outputs.task-definition }} task-definition: ${{ steps.ad-hoc-task-def.outputs.task-definition }}
@ -185,7 +181,7 @@ jobs:
image: ${{ env.image }} image: ${{ env.image }}
- name: Deploy updated application - name: Deploy updated application
uses: aws-actions/amazon-ecs-deploy-task-definition@v1 uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with: with:
cluster: ${{ inputs.aws_task_prefix }}-app cluster: ${{ inputs.aws_task_prefix }}-app
service: ${{ inputs.aws_task_prefix }}-app service: ${{ inputs.aws_task_prefix }}-app
@ -207,7 +203,7 @@ jobs:
image: ${{ env.image }} image: ${{ env.image }}
- name: Deploy updated sidekiq - name: Deploy updated sidekiq
uses: aws-actions/amazon-ecs-deploy-task-definition@v1 uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with: with:
cluster: ${{ inputs.aws_task_prefix }}-app cluster: ${{ inputs.aws_task_prefix }}-app
service: ${{ inputs.aws_task_prefix }}-sidekiq service: ${{ inputs.aws_task_prefix }}-sidekiq

6
.github/workflows/review_teardown_pipeline.yml

@ -25,13 +25,13 @@ jobs:
steps: steps:
- name: Configure AWS credentials - name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v3 uses: aws-actions/configure-aws-credentials@v4
with: with:
aws-region: ${{ env.aws_region }} aws-region: ${{ env.aws_region }}
role-to-assume: ${{ env.app_repo_role }} role-to-assume: ${{ env.app_repo_role }}
- name: Configure AWS credentials for review environment - name: Configure AWS credentials for review environment
uses: aws-actions/configure-aws-credentials@v3 uses: aws-actions/configure-aws-credentials@v4
with: with:
aws-region: ${{ env.aws_region }} aws-region: ${{ env.aws_region }}
role-to-assume: arn:aws:iam::${{ env.aws_account_id }}:role/${{ env.aws_role_prefix }}-deployment role-to-assume: arn:aws:iam::${{ env.aws_account_id }}:role/${{ env.aws_role_prefix }}-deployment
@ -46,7 +46,7 @@ jobs:
network=$(aws ecs describe-services --cluster $cluster --services $service --query services[0].networkConfiguration) network=$(aws ecs describe-services --cluster $cluster --services $service --query services[0].networkConfiguration)
overrides='{ "containerOverrides" : [{ "name" : "app", "command" : ["bundle", "exec", "rake", "db:drop"]}]}' overrides='{ "containerOverrides" : [{ "name" : "app", "command" : ["bundle", "exec", "rake", "db:drop"]}]}'
arn=$(aws ecs run-task --cluster $cluster --task-definition $ad_hoc_task_definition --network-configuration "$network" --overrides "$overrides" --group migrations --launch-type FARGATE --query tasks[0].taskArn) arn=$(aws ecs run-task --cluster $cluster --task-definition $ad_hoc_task_definition --network-configuration "$network" --overrides "$overrides" --group migrations --launch-type FARGATE --query tasks[0].taskArn)
echo "Waiting for db prepare task to complete" echo "Waiting for db drop task to complete"
temp=${arn##*/} temp=${arn##*/}
id=${temp%*\"} id=${temp%*\"}
aws ecs wait tasks-stopped --cluster $cluster --tasks $id aws ecs wait tasks-stopped --cluster $cluster --tasks $id

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.18
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.18
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.18
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.18
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.18
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.18
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

351
.github/workflows/staging_pipeline.yml

@ -4,11 +4,6 @@ on:
push: push:
branches: branches:
- main - main
pull_request:
types:
- opened
- synchronize
merge_group:
workflow_dispatch: workflow_dispatch:
defaults: defaults:
@ -21,347 +16,13 @@ env:
repository: core repository: core
jobs: jobs:
test: tests:
name: Tests name: Run Tests
runs-on: ubuntu-latest uses: ./.github/workflows/run_tests.yml
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: 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@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: 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@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: 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@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: 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@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: 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@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: 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@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Audit
run: |
bundle exec bundler-audit
aws_deploy: aws_deploy:
name: AWS Deploy name: AWS Deploy
if: github.ref == 'refs/heads/main' needs: [tests]
needs: [lint, test, feature_test, requests_test, model_test, audit]
uses: ./.github/workflows/aws_deploy.yml uses: ./.github/workflows/aws_deploy.yml
with: with:
aws_account_id: 107155005276 aws_account_id: 107155005276
@ -379,13 +40,13 @@ jobs:
steps: steps:
- name: Configure AWS credentials - name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v3 uses: aws-actions/configure-aws-credentials@v4
with: with:
aws-region: ${{ env.aws_region }} aws-region: ${{ env.aws_region }}
role-to-assume: ${{ env.app_repo_role }} role-to-assume: ${{ env.app_repo_role }}
- name: Configure AWS credentials for the environment - name: Configure AWS credentials for the environment
uses: aws-actions/configure-aws-credentials@v3 uses: aws-actions/configure-aws-credentials@v4
with: with:
aws-region: eu-west-2 aws-region: eu-west-2
role-to-assume: arn:aws:iam::107155005276:role/core-staging-deployment role-to-assume: arn:aws:iam::107155005276:role/core-staging-deployment

12
Gemfile

@ -6,11 +6,11 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby "3.1.4" ruby "3.1.4"
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main'
gem "rails", "~> 7.0.8.5" gem "rails", "~> 7.2.2"
# Use postgresql as the database for Active Record # Use postgresql as the database for Active Record
gem "pg", "~> 1.1" gem "pg", "~> 1.1"
# Use Puma as the app server # Use Puma as the app server
gem "puma", "~> 5.6" gem "puma", "~> 6.4"
# The modern asset pipeline for Rails [https://github.com/rails/propshaft] # The modern asset pipeline for Rails [https://github.com/rails/propshaft]
gem "propshaft" gem "propshaft"
# Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails] # Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails]
@ -18,9 +18,9 @@ gem "jsbundling-rails"
# Reduces boot times through caching; required in config/boot.rb # Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", ">= 1.4.4", require: false gem "bootsnap", ">= 1.4.4", require: false
# GOV UK frontend components # GOV UK frontend components
gem "govuk-components", "~> 5.1" gem "govuk-components", "~> 5.7"
# GOV UK component form builder DSL # GOV UK component form builder DSL
gem "govuk_design_system_formbuilder", "~> 5.0" gem "govuk_design_system_formbuilder", "~> 5.7"
# Convert Markdown into GOV.UK frontend-styled HTML # Convert Markdown into GOV.UK frontend-styled HTML
gem "govuk_markdown" gem "govuk_markdown"
gem "redcarpet", "~> 3.6" gem "redcarpet", "~> 3.6"
@ -44,7 +44,7 @@ gem "view_component", "~> 3.9"
# Use the AWS S3 SDK as storage mechanism # Use the AWS S3 SDK as storage mechanism
gem "aws-sdk-s3" gem "aws-sdk-s3"
# Track changes to models for auditing or versioning. # Track changes to models for auditing or versioning.
gem "paper_trail" gem "paper_trail", "~> 15.2"
# Store active record objects in version whodunnits # Store active record objects in version whodunnits
gem "paper_trail-globalid" gem "paper_trail-globalid"
@ -90,7 +90,7 @@ group :development do
# Display performance information such as SQL time and flame graphs for each request in your browser. # Display performance information such as SQL time and flame graphs for each request in your browser.
# Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md
gem "erb_lint", require: false gem "erb_lint", require: false
gem "rack-mini-profiler", "~> 2.0" gem "rack-mini-profiler", "~> 3.3.0"
gem "rubocop-govuk", "4.3.0", require: false gem "rubocop-govuk", "4.3.0", require: false
gem "rubocop-performance", require: false gem "rubocop-performance", require: false
gem "rubocop-rails", require: false gem "rubocop-rails", require: false

251
Gemfile.lock

@ -1,75 +1,81 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.0.8.5) actioncable (7.2.2.1)
actionpack (= 7.0.8.5) actionpack (= 7.2.2.1)
activesupport (= 7.0.8.5) activesupport (= 7.2.2.1)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (7.0.8.5) zeitwerk (~> 2.6)
actionpack (= 7.0.8.5) actionmailbox (7.2.2.1)
activejob (= 7.0.8.5) actionpack (= 7.2.2.1)
activerecord (= 7.0.8.5) activejob (= 7.2.2.1)
activestorage (= 7.0.8.5) activerecord (= 7.2.2.1)
activesupport (= 7.0.8.5) activestorage (= 7.2.2.1)
mail (>= 2.7.1) activesupport (= 7.2.2.1)
net-imap mail (>= 2.8.0)
net-pop actionmailer (7.2.2.1)
net-smtp actionpack (= 7.2.2.1)
actionmailer (7.0.8.5) actionview (= 7.2.2.1)
actionpack (= 7.0.8.5) activejob (= 7.2.2.1)
actionview (= 7.0.8.5) activesupport (= 7.2.2.1)
activejob (= 7.0.8.5) mail (>= 2.8.0)
activesupport (= 7.0.8.5) rails-dom-testing (~> 2.2)
mail (~> 2.5, >= 2.5.4) actionpack (7.2.2.1)
net-imap actionview (= 7.2.2.1)
net-pop activesupport (= 7.2.2.1)
net-smtp nokogiri (>= 1.8.5)
rails-dom-testing (~> 2.0) racc
actionpack (7.0.8.5) rack (>= 2.2.4, < 3.2)
actionview (= 7.0.8.5) rack-session (>= 1.0.1)
activesupport (= 7.0.8.5)
rack (~> 2.0, >= 2.2.4)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.6)
actiontext (7.0.8.5) useragent (~> 0.16)
actionpack (= 7.0.8.5) actiontext (7.2.2.1)
activerecord (= 7.0.8.5) actionpack (= 7.2.2.1)
activestorage (= 7.0.8.5) activerecord (= 7.2.2.1)
activesupport (= 7.0.8.5) activestorage (= 7.2.2.1)
activesupport (= 7.2.2.1)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.0.8.5) actionview (7.2.2.1)
activesupport (= 7.0.8.5) activesupport (= 7.2.2.1)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.11)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.1, >= 1.2.0) rails-html-sanitizer (~> 1.6)
activejob (7.0.8.5) activejob (7.2.2.1)
activesupport (= 7.0.8.5) activesupport (= 7.2.2.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.0.8.5) activemodel (7.2.2.1)
activesupport (= 7.0.8.5) activesupport (= 7.2.2.1)
activemodel-serializers-xml (1.0.2) activemodel-serializers-xml (1.0.3)
activemodel (> 5.x) activemodel (>= 5.0.0.a)
activesupport (> 5.x) activesupport (>= 5.0.0.a)
builder (~> 3.1) builder (~> 3.1)
activerecord (7.0.8.5) activerecord (7.2.2.1)
activemodel (= 7.0.8.5) activemodel (= 7.2.2.1)
activesupport (= 7.0.8.5) activesupport (= 7.2.2.1)
activestorage (7.0.8.5) timeout (>= 0.4.0)
actionpack (= 7.0.8.5) activestorage (7.2.2.1)
activejob (= 7.0.8.5) actionpack (= 7.2.2.1)
activerecord (= 7.0.8.5) activejob (= 7.2.2.1)
activesupport (= 7.0.8.5) activerecord (= 7.2.2.1)
activesupport (= 7.2.2.1)
marcel (~> 1.0) marcel (~> 1.0)
mini_mime (>= 1.1.0) activesupport (7.2.2.1)
activesupport (7.0.8.5) base64
concurrent-ruby (~> 1.0, >= 1.0.2) benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1) minitest (>= 5.1)
tzinfo (~> 2.0) securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
addressable (2.8.6) addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0) public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2) ast (2.4.2)
@ -104,6 +110,7 @@ GEM
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
base64 (0.2.0) base64 (0.2.0)
bcrypt (3.1.20) bcrypt (3.1.20)
benchmark (0.4.0)
better_html (2.0.2) better_html (2.0.2)
actionview (>= 6.0) actionview (>= 6.0)
activesupport (>= 6.0) activesupport (>= 6.0)
@ -111,7 +118,7 @@ GEM
erubi (~> 1.4) erubi (~> 1.4)
parser (>= 2.4) parser (>= 2.4)
smart_properties smart_properties
bigdecimal (3.1.6) bigdecimal (3.1.8)
bindex (0.8.1) bindex (0.8.1)
bootsnap (1.18.3) bootsnap (1.18.3)
msgpack (~> 1.2) msgpack (~> 1.2)
@ -149,7 +156,8 @@ GEM
crass (1.0.6) crass (1.0.6)
cssbundling-rails (1.4.0) cssbundling-rails (1.4.0)
railties (>= 6.0.0) railties (>= 6.0.0)
date (3.3.4) csv (3.3.0)
date (3.4.1)
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
devise (4.9.3) devise (4.9.3)
@ -170,6 +178,7 @@ GEM
dotenv-rails (3.0.2) dotenv-rails (3.0.2)
dotenv (= 3.0.2) dotenv (= 3.0.2)
railties (>= 6.1) railties (>= 6.1)
drb (2.2.1)
dumb_delegator (1.0.0) dumb_delegator (1.0.0)
encryptor (3.0.0) encryptor (3.0.0)
erb_lint (0.5.0) erb_lint (0.5.0)
@ -184,10 +193,10 @@ GEM
tzinfo tzinfo
event_stream_parser (1.0.0) event_stream_parser (1.0.0)
excon (0.111.0) excon (0.111.0)
factory_bot (6.4.6) factory_bot (6.5.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
factory_bot_rails (6.4.3) factory_bot_rails (6.4.4)
factory_bot (~> 6.4) factory_bot (~> 6.5)
railties (>= 5.0.0) railties (>= 5.0.0)
faker (3.2.3) faker (3.2.3)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
@ -203,11 +212,11 @@ GEM
raabro (~> 1.4) raabro (~> 1.4)
globalid (1.2.1) globalid (1.2.1)
activesupport (>= 6.1) activesupport (>= 6.1)
govuk-components (5.2.1) govuk-components (5.7.0)
html-attributes-utils (~> 1.0.0, >= 1.0.0) html-attributes-utils (~> 1.0.0, >= 1.0.0)
pagy (~> 6.0) pagy (>= 6, < 10)
view_component (>= 3.9, < 3.11) view_component (>= 3.9, < 3.17)
govuk_design_system_formbuilder (5.2.0) govuk_design_system_formbuilder (5.7.1)
actionview (>= 6.1) actionview (>= 6.1)
activemodel (>= 6.1) activemodel (>= 6.1)
activesupport (>= 6.1) activesupport (>= 6.1)
@ -222,6 +231,10 @@ GEM
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
ice_nine (0.11.2) ice_nine (0.11.2)
iniparse (1.5.0) iniparse (1.5.0)
io-console (0.8.0)
irb (1.14.1)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jmespath (1.6.2) jmespath (1.6.2)
jsbundling-rails (1.3.0) jsbundling-rails (1.3.0)
railties (>= 6.0.0) railties (>= 6.0.0)
@ -246,6 +259,7 @@ GEM
listen (3.9.0) listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
logger (1.6.2)
loofah (2.23.1) loofah (2.23.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
@ -258,13 +272,13 @@ GEM
matrix (0.4.2) matrix (0.4.2)
method_source (1.1.0) method_source (1.1.0)
mini_mime (1.1.5) mini_mime (1.1.5)
minitest (5.25.1) minitest (5.25.4)
msgpack (1.7.2) msgpack (1.7.2)
multipart-post (2.4.1) multipart-post (2.4.1)
nested_form (0.3.2) nested_form (0.3.2)
net-http (0.4.1) net-http (0.4.1)
uri uri
net-imap (0.4.17) net-imap (0.5.1)
date date
net-protocol net-protocol
net-pop (0.1.2) net-pop (0.1.2)
@ -273,12 +287,12 @@ GEM
timeout timeout
net-smtp (0.5.0) net-smtp (0.5.0)
net-protocol net-protocol
nio4r (2.7.3) nio4r (2.7.4)
nokogiri (1.16.8-arm64-darwin) nokogiri (1.17.1-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.8-x86_64-darwin) nokogiri (1.17.1-x86_64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.8-x86_64-linux) nokogiri (1.17.1-x86_64-linux)
racc (~> 1.4) racc (~> 1.4)
notifications-ruby-client (6.0.0) notifications-ruby-client (6.0.0)
jwt (>= 1.5, < 3) jwt (>= 1.5, < 3)
@ -287,8 +301,8 @@ GEM
childprocess (>= 0.6.3, < 6) childprocess (>= 0.6.3, < 6)
iniparse (~> 1.4) iniparse (~> 1.4)
rexml (~> 3.2) rexml (~> 3.2)
pagy (6.5.0) pagy (9.3.2)
paper_trail (15.1.0) paper_trail (15.2.0)
activerecord (>= 6.1) activerecord (>= 6.1)
request_store (~> 1.4) request_store (~> 1.4)
paper_trail-globalid (0.2.0) paper_trail-globalid (0.2.0)
@ -313,34 +327,41 @@ GEM
pry-byebug (3.10.1) pry-byebug (3.10.1)
byebug (~> 11.0) byebug (~> 11.0)
pry (>= 0.13, < 0.15) pry (>= 0.13, < 0.15)
psych (5.2.1)
date
stringio
public_suffix (5.0.4) public_suffix (5.0.4)
puma (5.6.9) puma (6.5.0)
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.3.1) pundit (2.3.1)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
raabro (1.4.0) raabro (1.4.0)
racc (1.8.1) racc (1.8.1)
rack (2.2.10) rack (3.1.8)
rack-attack (6.7.0) rack-attack (6.7.0)
rack (>= 1.0, < 4) rack (>= 1.0, < 4)
rack-mini-profiler (2.3.4) rack-mini-profiler (3.3.1)
rack (>= 1.2.0) rack (>= 1.2.0)
rack-session (2.0.0)
rack (>= 3.0.0)
rack-test (2.1.0) rack-test (2.1.0)
rack (>= 1.3) rack (>= 1.3)
rails (7.0.8.5) rackup (2.2.1)
actioncable (= 7.0.8.5) rack (>= 3)
actionmailbox (= 7.0.8.5) rails (7.2.2.1)
actionmailer (= 7.0.8.5) actioncable (= 7.2.2.1)
actionpack (= 7.0.8.5) actionmailbox (= 7.2.2.1)
actiontext (= 7.0.8.5) actionmailer (= 7.2.2.1)
actionview (= 7.0.8.5) actionpack (= 7.2.2.1)
activejob (= 7.0.8.5) actiontext (= 7.2.2.1)
activemodel (= 7.0.8.5) actionview (= 7.2.2.1)
activerecord (= 7.0.8.5) activejob (= 7.2.2.1)
activestorage (= 7.0.8.5) activemodel (= 7.2.2.1)
activesupport (= 7.0.8.5) activerecord (= 7.2.2.1)
activestorage (= 7.2.2.1)
activesupport (= 7.2.2.1)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.0.8.5) railties (= 7.2.2.1)
rails-dom-testing (2.2.0) rails-dom-testing (2.2.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
minitest minitest
@ -348,31 +369,37 @@ GEM
rails-html-sanitizer (1.6.1) rails-html-sanitizer (1.6.1)
loofah (~> 2.21) loofah (~> 2.21)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
rails_admin (3.1.3) rails_admin (3.3.0)
activemodel-serializers-xml (>= 1.0) activemodel-serializers-xml (>= 1.0)
csv
kaminari (>= 0.14, < 2.0) kaminari (>= 0.14, < 2.0)
nested_form (~> 0.3) nested_form (~> 0.3)
rails (>= 6.0, < 8) rails (>= 6.0, < 9)
turbo-rails (~> 1.0) turbo-rails (>= 1.0, < 3)
railties (7.0.8.5) railties (7.2.2.1)
actionpack (= 7.0.8.5) actionpack (= 7.2.2.1)
activesupport (= 7.0.8.5) activesupport (= 7.2.2.1)
method_source irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0) thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.5) zeitwerk (~> 2.6)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.2.1) rake (13.2.1)
randexp (0.1.7) randexp (0.1.7)
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
rdoc (6.8.1)
psych (>= 4.0.0)
redcarpet (3.6.0) redcarpet (3.6.0)
redis (4.8.1) redis (4.8.1)
redis-client (0.22.1) redis-client (0.22.1)
connection_pool connection_pool
regexp_parser (2.9.0) regexp_parser (2.9.0)
request_store (1.6.0) reline (0.5.12)
io-console (~> 0.5)
request_store (1.7.0)
rack (>= 1.4) rack (>= 1.4)
responders (3.1.1) responders (3.1.1)
actionpack (>= 5.2) actionpack (>= 5.2)
@ -434,6 +461,7 @@ GEM
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
rubyzip (2.3.2) rubyzip (2.3.2)
securerandom (0.4.0)
selenium-webdriver (4.18.1) selenium-webdriver (4.18.1)
base64 (~> 0.2) base64 (~> 0.2)
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
@ -462,21 +490,22 @@ GEM
smart_properties (1.17.0) smart_properties (1.17.0)
stimulus-rails (1.3.3) stimulus-rails (1.3.3)
railties (>= 6.0.0) railties (>= 6.0.0)
stringio (3.1.2)
thor (1.3.2) thor (1.3.2)
thread_safe (0.3.6) thread_safe (0.3.6)
timecop (0.9.8) timecop (0.9.8)
timeout (0.4.1) timeout (0.4.2)
turbo-rails (1.5.0) turbo-rails (2.0.11)
actionpack (>= 6.0.0) actionpack (>= 6.0.0)
activejob (>= 6.0.0)
railties (>= 6.0.0) railties (>= 6.0.0)
tzinfo (2.0.6) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
uk_postcode (2.1.8) uk_postcode (2.1.8)
unicode-display_width (2.5.0) unicode-display_width (2.5.0)
unread (0.13.1) unread (0.14.0)
activerecord (>= 6.1) activerecord (>= 6.1)
uri (0.13.0) uri (0.13.0)
useragent (0.16.11)
view_component (3.10.0) view_component (3.10.0)
activesupport (>= 5.2.0, < 8.0) activesupport (>= 5.2.0, < 8.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
@ -532,8 +561,8 @@ DEPENDENCIES
excon (~> 0.111.0) excon (~> 0.111.0)
factory_bot_rails factory_bot_rails
faker faker
govuk-components (~> 5.1) govuk-components (~> 5.7)
govuk_design_system_formbuilder (~> 5.0) govuk_design_system_formbuilder (~> 5.7)
govuk_markdown govuk_markdown
jsbundling-rails jsbundling-rails
json-schema json-schema
@ -541,19 +570,19 @@ DEPENDENCIES
method_source (~> 1.1) method_source (~> 1.1)
notifications-ruby-client notifications-ruby-client
overcommit (>= 0.37.0) overcommit (>= 0.37.0)
paper_trail paper_trail (~> 15.2)
paper_trail-globalid paper_trail-globalid
parallel_tests parallel_tests
pg (~> 1.1) pg (~> 1.1)
possessive possessive
propshaft propshaft
pry-byebug pry-byebug
puma (~> 5.6) puma (~> 6.4)
pundit pundit
rack (>= 2.2.6.3) rack (>= 2.2.6.3)
rack-attack rack-attack
rack-mini-profiler (~> 2.0) rack-mini-profiler (~> 3.3.0)
rails (~> 7.0.8.5) rails (~> 7.2.2)
rails_admin (~> 3.1) rails_admin (~> 3.1)
redcarpet (~> 3.6) redcarpet (~> 3.6)
redis (~> 4.8) redis (~> 4.8)

2
app/components/bulk_upload_error_row_component.html.erb

@ -38,7 +38,7 @@
<% if potential_errors.any? %> <% if potential_errors.any? %>
<h2 class="govuk-heading-m">Potential errors</h2> <h2 class="govuk-heading-m">Potential errors</h2>
<p class="govuk-body">The following groups of cells might have conflicting data. Check the answers and fix any incorrect data.<br><br>If the answers are correct, fix the critical errors and reupload the file. You'll need to confirm that the following data is correct when the file only contains potential errors.</p> <p class="govuk-body">The following groups of cells might have conflicting data. Check the answers and fix any incorrect data.<br><br>If the answers are correct, fix the critical errors and upload the file again. You'll need to confirm that the following data is correct when the file only contains potential errors.</p>
<%= govuk_table(html_attributes: { class: "no-bottom-border" }) do |table| %> <%= govuk_table(html_attributes: { class: "no-bottom-border" }) do |table| %>
<%= table.with_head do |head| %> <%= table.with_head do |head| %>
<% head.with_row do |row| %> <% head.with_row do |row| %>

22
app/components/primary_navigation_component.html.erb

@ -1,17 +1,5 @@
<nav class="app-primary-navigation" aria-label="primary"> <%= govuk_service_navigation(navigation_id: "primary-navigation", classes: "app-service-navigation") do |sn|
<div class="govuk-width-container"> items.each do |item|
<ul class="app-primary-navigation__list"> sn.with_navigation_item(text: item[:text], href: item[:href], classes: "", current: item[:current])
<% items.each do |item| %> end
<% if item.current %> end %>
<li class="app-primary-navigation__item app-primary-navigation__item--current">
<%= govuk_link_to item[:text], item[:href], class: "app-primary-navigation__link", aria: { current: "page" } %>
</li>
<% else %>
<li class="app-primary-navigation__item">
<%= govuk_link_to item[:text], item[:href], class: "app-primary-navigation__link" %>
</li>
<% end %>
<% end %>
</ul>
</div>
</nav>

2
app/components/search_component.html.erb

@ -1,4 +1,4 @@
<%= form_with model: @user, url: path(current_user), method: "get", local: true do |f| %> <%= form_with url: path(current_user), method: "get", local: true do |f| %>
<div class="app-search govuk-!-margin-bottom-4"> <div class="app-search govuk-!-margin-bottom-4">
<%= f.govuk_text_field :search, <%= f.govuk_text_field :search,
form_group: { form_group: {

2
app/components/search_result_caption_component.html.erb

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

23
app/controllers/bulk_upload_lettings_logs_controller.rb

@ -1,6 +1,7 @@
class BulkUploadLettingsLogsController < ApplicationController class BulkUploadLettingsLogsController < ApplicationController
before_action :authenticate_user! 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 def start
if have_choice_of_year? if have_choice_of_year?
@ -24,12 +25,26 @@ class BulkUploadLettingsLogsController < ApplicationController
private private
def validate_data_protection_agrement_signed! def validate_data_protection_agreement_signed!
return if @current_user.organisation.data_protection_confirmed? return if @current_user.organisation.data_protection_confirmed?
redirect_to lettings_logs_path redirect_to lettings_logs_path
end 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 def current_year
FormHandler.instance.current_collection_start_year FormHandler.instance.current_collection_start_year
end end
@ -48,8 +63,6 @@ private
Forms::BulkUploadLettings::PrepareYourFile.new(form_params) Forms::BulkUploadLettings::PrepareYourFile.new(form_params)
when "guidance" when "guidance"
Forms::BulkUploadLettings::Guidance.new(form_params.merge(referrer: params[:referrer])) Forms::BulkUploadLettings::Guidance.new(form_params.merge(referrer: params[:referrer]))
when "needstype"
Forms::BulkUploadLettings::Needstype.new(form_params)
when "upload-your-file" when "upload-your-file"
Forms::BulkUploadLettings::UploadYourFile.new(form_params.merge(current_user:)) Forms::BulkUploadLettings::UploadYourFile.new(form_params.merge(current_user:))
when "checking-file" when "checking-file"
@ -60,6 +73,6 @@ private
end end
def form_params def form_params
params.fetch(:form, {}).permit(:year, :needstype, :file, :organisation_id) params.fetch(:form, {}).permit(:year, :file, :organisation_id)
end end
end end

19
app/controllers/bulk_upload_sales_logs_controller.rb

@ -1,6 +1,7 @@
class BulkUploadSalesLogsController < ApplicationController class BulkUploadSalesLogsController < ApplicationController
before_action :authenticate_user! 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 def start
if have_choice_of_year? if have_choice_of_year?
@ -24,12 +25,26 @@ class BulkUploadSalesLogsController < ApplicationController
private private
def validate_data_protection_agrement_signed! def validate_data_protection_agreement_signed!
return if @current_user.organisation.data_protection_confirmed? return if @current_user.organisation.data_protection_confirmed?
redirect_to sales_logs_path redirect_to sales_logs_path
end 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 def current_year
FormHandler.instance.current_collection_start_year FormHandler.instance.current_collection_start_year
end end

23
app/controllers/form_controller.rb

@ -38,8 +38,14 @@ class FormController < ApplicationController
error_attributes = @log.errors.map(&:attribute) error_attributes = @log.errors.map(&:attribute)
Rails.logger.info "User triggered validation(s) on: #{error_attributes.join(', ')}" Rails.logger.info "User triggered validation(s) on: #{error_attributes.join(', ')}"
@subsection = form.subsection_for_page(@page) @subsection = form.subsection_for_page(@page)
flash[:errors] = @log.errors flash[:errors] = @log.errors.each_with_object({}) do |error, result|
if @page.questions.map(&:id).include?(error.attribute.to_s)
result[error.attribute.to_s] = error.message
end
end
flash[:log_data] = responses_for_page flash[:log_data] = responses_for_page
question_ids = (@log.errors.map(&:attribute) - [:base]).uniq
flash[:pages_with_errors_count] = question_ids.map { |id| @log.form.get_question(id, @log)&.page&.id }.compact.uniq.count
redirect_to send("#{@log.class.name.underscore}_#{@page.id}_path", @log, { referrer: request.params["referrer"], original_page_id: request.params["original_page_id"], related_question_ids: request.params["related_question_ids"] }) redirect_to send("#{@log.class.name.underscore}_#{@page.id}_path", @log, { referrer: request.params["referrer"], original_page_id: request.params["original_page_id"], related_question_ids: request.params["related_question_ids"] })
end end
else else
@ -81,6 +87,7 @@ class FormController < ApplicationController
page_id = request.path.split("/")[-1].underscore page_id = request.path.split("/")[-1].underscore
@page = form.get_page(page_id) @page = form.get_page(page_id)
@subsection = form.subsection_for_page(@page) @subsection = form.subsection_for_page(@page)
@pages_with_errors_count = 0
if @page.routed_to?(@log, current_user) || is_referrer_type?("interruption_screen") || adding_answer_from_check_errors_page? if @page.routed_to?(@log, current_user) || is_referrer_type?("interruption_screen") || adding_answer_from_check_errors_page?
if updated_answer_from_check_errors_page? if updated_answer_from_check_errors_page?
@questions = request.params["related_question_ids"].map { |id| @log.form.get_question(id, @log) } @questions = request.params["related_question_ids"].map { |id| @log.form.get_question(id, @log) }
@ -89,6 +96,7 @@ class FormController < ApplicationController
if flash[:errors].present? if flash[:errors].present?
restore_previous_errors(flash[:errors]) restore_previous_errors(flash[:errors])
restore_error_field_values(flash[:log_data]) restore_error_field_values(flash[:log_data])
@pages_with_errors_count = flash[:pages_with_errors_count]
end end
render "form/page" render "form/page"
end end
@ -105,8 +113,13 @@ private
def restore_error_field_values(previous_responses) def restore_error_field_values(previous_responses)
return unless previous_responses return unless previous_responses
previous_responses_to_reset = previous_responses.reject do |key, _| previous_responses_to_reset = previous_responses.reject do |key, value|
@log.form.get_question(key, @log)&.type == "date" if @log.form.get_question(key, @log)&.type == "date" && value.present?
year = value.split("-").first.to_i
year&.zero?
else
false
end
end end
@log.assign_attributes(previous_responses_to_reset) @log.assign_attributes(previous_responses_to_reset)
@ -116,7 +129,7 @@ private
return unless previous_errors return unless previous_errors
previous_errors.each do |attribute, message| previous_errors.each do |attribute, message|
@log.errors.add attribute, message.first @log.errors.add attribute, message.html_safe
end end
end end
@ -433,7 +446,7 @@ private
@log.valid? @log.valid?
@log.reload @log.reload
error_attributes = @log.errors.map(&:attribute) 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 end
render "form/check_errors" render "form/check_errors"
end end

2
app/controllers/lettings_logs_controller.rb

@ -22,7 +22,7 @@ class LettingsLogsController < LogsController
@total_count = all_logs.size @total_count = all_logs.size
@unresolved_count = all_logs.unresolved.assigned_to(current_user).count @unresolved_count = all_logs.unresolved.assigned_to(current_user).count
@filter_type = "lettings_logs" @filter_type = "lettings_logs"
@duplicate_sets_count = FeatureToggle.duplicate_summary_enabled? && !current_user.support? ? duplicate_sets_count(current_user, current_user.organisation) : 0 @duplicate_sets_count = !current_user.support? ? duplicate_sets_count(current_user, current_user.organisation) : 0
render "logs/index" render "logs/index"
end end

2
app/controllers/logs_controller.rb

@ -38,7 +38,7 @@ private
API_ACTIONS = %w[create show update].freeze API_ACTIONS = %w[create show update].freeze
def json_api_request? def json_api_request?
API_ACTIONS.include?(request["action"]) && request.format.json? API_ACTIONS.include?(params["action"]) && request.format.json?
end end
def authenticate def authenticate

5
app/controllers/organisations_controller.rb

@ -155,6 +155,7 @@ class OrganisationsController < ApplicationController
end end
redirect_to details_organisation_path(@organisation) redirect_to details_organisation_path(@organisation)
else 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) @rent_periods = helpers.rent_periods_with_checked_attr(checked_periods: selected_rent_periods)
render :edit, status: :unprocessable_entity render :edit, status: :unprocessable_entity
end end
@ -185,7 +186,7 @@ class OrganisationsController < ApplicationController
@total_count = organisation_logs.size @total_count = organisation_logs.size
@log_type = :lettings @log_type = :lettings
@filter_type = "lettings_logs" @filter_type = "lettings_logs"
@duplicate_sets_count = FeatureToggle.duplicate_summary_enabled? ? duplicate_sets_count(current_user, @organisation) : 0 @duplicate_sets_count = duplicate_sets_count(current_user, @organisation)
render "logs", layout: "application" render "logs", layout: "application"
end end
@ -217,7 +218,7 @@ class OrganisationsController < ApplicationController
@total_count = organisation_logs.size @total_count = organisation_logs.size
@log_type = :sales @log_type = :sales
@filter_type = "sales_logs" @filter_type = "sales_logs"
@duplicate_sets_count = FeatureToggle.duplicate_summary_enabled? ? duplicate_sets_count(current_user, @organisation) : 0 @duplicate_sets_count = duplicate_sets_count(current_user, @organisation)
render "logs", layout: "application" render "logs", layout: "application"
end end

2
app/controllers/sales_logs_controller.rb

@ -24,7 +24,7 @@ class SalesLogsController < LogsController
@searched = search_term.presence @searched = search_term.presence
@total_count = all_logs.size @total_count = all_logs.size
@filter_type = "sales_logs" @filter_type = "sales_logs"
@duplicate_sets_count = FeatureToggle.duplicate_summary_enabled? && !current_user.support? ? duplicate_sets_count(current_user, current_user.organisation) : 0 @duplicate_sets_count = !current_user.support? ? duplicate_sets_count(current_user, current_user.organisation) : 0
render "logs/index" render "logs/index"
end end

4
app/controllers/schemes_controller.rb

@ -150,7 +150,7 @@ class SchemesController < ApplicationController
@scheme.update!(secondary_client_group: nil) if @scheme.has_other_client_group == "No" @scheme.update!(secondary_client_group: nil) if @scheme.has_other_client_group == "No"
if scheme_params[:confirmed] == "true" || @scheme.confirmed? if scheme_params[:confirmed] == "true" || @scheme.confirmed?
if check_answers && should_direct_via_secondary_client_group_page?(page) if check_answers && should_direct_via_secondary_client_group_page?(page)
redirect_to scheme_secondary_client_group_path(@scheme, referrer: "check-answers") redirect_to scheme_secondary_client_group_path(@scheme, referrer: "has-other-client-group")
else else
@scheme.locations.update!(confirmed: true) @scheme.locations.update!(confirmed: true)
flash[:notice] = if scheme_previously_confirmed flash[:notice] = if scheme_previously_confirmed
@ -162,7 +162,7 @@ class SchemesController < ApplicationController
end end
elsif check_answers elsif check_answers
if should_direct_via_secondary_client_group_page?(page) if should_direct_via_secondary_client_group_page?(page)
redirect_to scheme_secondary_client_group_path(@scheme, referrer: "check-answers") redirect_to scheme_secondary_client_group_path(@scheme, referrer: "has-other-client-group")
else else
redirect_to scheme_check_answers_path(@scheme) redirect_to scheme_check_answers_path(@scheme)
end end

12
app/controllers/sessions_controller.rb

@ -1,20 +1,20 @@
class SessionsController < ApplicationController class SessionsController < ApplicationController
def clear_filters def clear_filters
session[session_name_for(params[:filter_type])] = "{}" session[session_name_for(params[:filter_type])] = "{}"
path_params = params[:path_params].presence || {} filter_path_params = params[:filter_path_params].presence || {}
if path_params[:organisation_id].present? if filter_path_params[:organisation_id].present?
redirect_to send("#{params[:filter_type]}_organisation_path", id: path_params[:organisation_id], scheme_id: path_params[:scheme_id], search: path_params[:search]) redirect_to send("#{params[:filter_type]}_organisation_path", id: filter_path_params[:organisation_id], scheme_id: filter_path_params[:scheme_id], search: filter_path_params[:search])
elsif params[:filter_type].include?("bulk_uploads") elsif params[:filter_type].include?("bulk_uploads")
bulk_upload_type = params[:filter_type].split("_").first bulk_upload_type = params[:filter_type].split("_").first
uploading_organisation = params[:organisation_id].presence uploading_organisation = params[:organisation_id].presence
if uploading_organisation.present? if uploading_organisation.present?
redirect_to send("bulk_uploads_#{bulk_upload_type}_logs_path", search: path_params[:search], uploading_organisation:) redirect_to send("bulk_uploads_#{bulk_upload_type}_logs_path", search: filter_path_params[:search], uploading_organisation:)
else else
redirect_to send("bulk_uploads_#{bulk_upload_type}_logs_path", search: path_params[:search]) redirect_to send("bulk_uploads_#{bulk_upload_type}_logs_path", search: filter_path_params[:search])
end end
else else
redirect_to send("#{params[:filter_type]}_path", scheme_id: path_params[:scheme_id], search: path_params[:search]) redirect_to send("#{params[:filter_type]}_path", scheme_id: filter_path_params[:scheme_id], search: filter_path_params[:search])
end end
end end

69
app/frontend/styles/_primary-navigation.scss

@ -1,69 +0,0 @@
.app-primary-navigation {
@include govuk-font(19, $weight: bold);
background-color: govuk-colour("light-grey");
border-bottom: 1px solid $govuk-border-colour;
}
.govuk-phase-banner + .app-primary-navigation {
margin-top: -1px;
}
.app-primary-navigation__list {
@include govuk-clearfix;
left: govuk-spacing(-3);
list-style: none;
margin: 0;
padding: 0;
position: relative;
right: govuk-spacing(-3);
width: calc(100% + #{govuk-spacing(6)});
}
.app-primary-navigation__item {
box-sizing: border-box;
display: block;
float: left;
line-height: 50px;
height: 50px;
padding: 0 govuk-spacing(3);
position: relative;
}
.app-primary-navigation__item--current {
border-bottom: $govuk-border-width-narrow solid $govuk-link-colour;
&:hover {
border-bottom-color: $govuk-link-hover-colour;
}
&:active {
border-bottom-color: $govuk-link-active-colour;
}
}
.app-primary-navigation__item--align-right {
@include govuk-media-query($from: tablet) {
float: right;
}
}
.app-primary-navigation__link {
@include govuk-link-common;
@include govuk-link-style-no-visited-state;
@include govuk-link-style-no-underline;
@include govuk-typography-weight-bold;
// Extend the touch area of the link to the list
&::after {
bottom: 0;
content: "";
left: 0;
position: absolute;
right: 0;
top: 0;
}
}
.app-primary-navigation__item--current .app-primary-navigation__link:hover {
text-decoration: none;
}

42
app/frontend/styles/application.scss

@ -45,7 +45,6 @@ $govuk-breakpoints: (
@import "task-list"; @import "task-list";
@import "template"; @import "template";
@import "panel"; @import "panel";
@import "primary-navigation";
@import "search"; @import "search";
@import "sub-navigation"; @import "sub-navigation";
@import "unread-notification"; @import "unread-notification";
@ -86,3 +85,44 @@ $govuk-breakpoints: (
.govuk-notification-banner__content > * { .govuk-notification-banner__content > * {
max-width: fit-content; max-width: fit-content;
} }
.govuk-service-navigation__active-fallback,
.govuk-service-navigation__list {
font-weight: bold;
}
.govuk-service-navigation__link {
@include govuk-link-common;
@include govuk-link-style-no-visited-state;
@include govuk-link-style-no-underline;
@include govuk-typography-weight-bold;
// Extend the touch area of the link to the list
&::after {
bottom: 0;
content: "";
left: 0;
position: absolute;
right: 0;
top: 0;
}
}
.govuk-service-navigation__item--active {
border-bottom-width: 4px;
}
.govuk-service-navigation__item {
padding-right: 15px;
padding-left: 15px;
margin: 0;
}
.govuk-service-navigation__item:not(:last-child) {
margin-right: 0;
}
.govuk-service-navigation__container {
left: -15px;
position: relative;
}

4
app/helpers/filters_helper.rb

@ -165,9 +165,9 @@ module FiltersHelper
applied_filters_count(filter_type).zero? ? "No filters applied" : "#{pluralize(applied_filters_count(filter_type), 'filter')} applied" applied_filters_count(filter_type).zero? ? "No filters applied" : "#{pluralize(applied_filters_count(filter_type), 'filter')} applied"
end end
def reset_filters_link(filter_type, path_params = {}) def reset_filters_link(filter_type, filter_path_params = {})
if applied_filters_count(filter_type).positive? if applied_filters_count(filter_type).positive?
govuk_link_to "Clear", clear_filters_path(filter_type:, path_params:) govuk_link_to "Clear", clear_filters_path(filter_type:, filter_path_params:)
end end
end end

4
app/helpers/form_page_error_helper.rb

@ -12,8 +12,4 @@ module FormPageErrorHelper
errors.each { |error| lettings_log.errors.delete(error.attribute) } errors.each { |error| lettings_log.errors.delete(error.attribute) }
end end
end end
def all_questions_affected_by_errors(log)
(log.errors.map(&:attribute) - [:base]).uniq
end
end end

2
app/helpers/logs_helper.rb

@ -10,7 +10,7 @@ module LogsHelper
def bulk_upload_options(bulk_upload) def bulk_upload_options(bulk_upload)
array = bulk_upload ? [bulk_upload.id] : [] array = bulk_upload ? [bulk_upload.id] : []
array.index_with { |_bulk_upload_id| "With logs from bulk upload" } array.index_with { |_bulk_upload_id| "Only logs from this bulk upload" }
end end
def search_label_for_controller(controller) def search_label_for_controller(controller)

18
app/helpers/schemes_helper.rb

@ -101,6 +101,24 @@ module SchemesHelper
organisation.owned_schemes.duplicate_sets.any? || organisation.owned_schemes.any? { |scheme| scheme.locations.duplicate_sets.any? } organisation.owned_schemes.duplicate_sets.any? || organisation.owned_schemes.any? { |scheme| scheme.locations.duplicate_sets.any? }
end end
def scheme_back_button_path(scheme, current_page)
return scheme_check_answers_path(scheme) if request.params[:referrer] == "check-answers"
return scheme_confirm_secondary_client_group_path(scheme, referrer: "check-answers") if request.params[:referrer] == "has-other-client-group"
case current_page
when "details"
schemes_path
when "primary_client_group"
scheme_details_path(scheme)
when "confirm_secondary_client_group"
scheme_primary_client_group_path(scheme)
when "secondary_client_group"
scheme_confirm_secondary_client_group_path(scheme)
when "support"
scheme.has_other_client_group == "Yes" ? scheme_secondary_client_group_path(scheme) : scheme_confirm_secondary_client_group_path(scheme)
end
end
private private
ActivePeriod = Struct.new(:from, :to) ActivePeriod = Struct.new(:from, :to)

21
app/mailers/bulk_upload_mailer.rb

@ -3,6 +3,7 @@ class BulkUploadMailer < NotifyMailer
COMPLETE_TEMPLATE_ID = "83279578-c890-4168-838b-33c9cf0dc9f0".freeze COMPLETE_TEMPLATE_ID = "83279578-c890-4168-838b-33c9cf0dc9f0".freeze
FAILED_CSV_ERRORS_TEMPLATE_ID = "e27abcd4-5295-48c2-b127-e9ee4b781b75".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_FILE_SETUP_ERROR_TEMPLATE_ID = "24c9f4c7-96ad-470a-ba31-eb51b7cbafd9".freeze
FAILED_SERVICE_ERROR_TEMPLATE_ID = "c3f6288c-7a74-4e77-99ee-6c4a0f6e125a".freeze FAILED_SERVICE_ERROR_TEMPLATE_ID = "c3f6288c-7a74-4e77-99ee-6c4a0f6e125a".freeze
HOW_TO_FIX_UPLOAD_TEMPLATE_ID = "21a07b26-f625-4846-9f4d-39e30937aa24".freeze HOW_TO_FIX_UPLOAD_TEMPLATE_ID = "21a07b26-f625-4846-9f4d-39e30937aa24".freeze
@ -95,6 +96,26 @@ class BulkUploadMailer < NotifyMailer
) )
end 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:) def send_bulk_upload_failed_file_setup_error_mail(bulk_upload:)
bulk_upload_link = if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors? 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) bulk_upload.sales? ? summary_bulk_upload_sales_result_url(bulk_upload) : summary_bulk_upload_lettings_result_url(bulk_upload)

6
app/models/bulk_upload.rb

@ -1,7 +1,7 @@
class BulkUpload < ApplicationRecord class BulkUpload < ApplicationRecord
enum log_type: { lettings: "lettings", sales: "sales" } enum :log_type, { lettings: "lettings", sales: "sales" }
enum rent_type_fix_status: { not_applied: "not_applied", applied: "applied", not_needed: "not_needed" } enum :rent_type_fix_status, { not_applied: "not_applied", applied: "applied", not_needed: "not_needed" }
enum failure_reason: { blank_template: "blank_template", wrong_template: "wrong_template" } enum :failure_reason, { blank_template: "blank_template", wrong_template: "wrong_template" }
belongs_to :user belongs_to :user

2
app/models/csv_download.rb

@ -1,5 +1,5 @@
class CsvDownload < ApplicationRecord class CsvDownload < ApplicationRecord
enum download_type: { lettings: "lettings", sales: "sales", schemes: "schemes", locations: "locations", combined: "combined" } enum :download_type, { lettings: "lettings", sales: "sales", schemes: "schemes", locations: "locations", combined: "combined" }
belongs_to :user belongs_to :user
belongs_to :organisation belongs_to :organisation

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

@ -7,7 +7,6 @@ class Form::Lettings::Questions::Offered < ::Form::Question
@check_answers_card_number = 0 @check_answers_card_number = 0
@max = 150 @max = 150
@min = 0 @min = 0
@hint_text = I18n.t("hints.offered")
@step = 1 @step = 1
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end end

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? if current_user.organisation.holds_own_stock?
return true if current_user.organisation.absorbed_organisations.any?(&:holds_own_stock?) return true if current_user.organisation.absorbed_organisations.any?(&:holds_own_stock?)
return true if stock_owners.count >= 1 return true if stock_owners.count >= 1
return false if log.owning_organisation == current_user.organisation
log.update!(owning_organisation: current_user.organisation) log.update!(owning_organisation: current_user.organisation)
else else
return false if stock_owners.count.zero? return false if stock_owners.count.zero?
return true if stock_owners.count > 1 return true if stock_owners.count > 1
return false if log.owning_organisation == stock_owners.first
log.update!(owning_organisation: stock_owners.first) log.update!(owning_organisation: stock_owners.first)
end end

2
app/models/form/sales/questions/buyer2_income_known.rb

@ -2,7 +2,7 @@ class Form::Sales::Questions::Buyer2IncomeKnown < ::Form::Question
def initialize(id, hsh, page) def initialize(id, hsh, page)
super super
@id = "income2nk" @id = "income2nk"
@copy_key = "sales.income_benefits_and_savings.buyer_2_income.income2" @copy_key = "sales.income_benefits_and_savings.buyer_2_income.income2nk"
@type = "radio" @type = "radio"
@answer_options = ANSWER_OPTIONS @answer_options = ANSWER_OPTIONS
@conditional_for = { @conditional_for = {

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

2
app/models/form/sales/questions/equity.rb

@ -2,7 +2,7 @@ class Form::Sales::Questions::Equity < ::Form::Question
def initialize(id, hsh, page) def initialize(id, hsh, page)
super super
@id = "equity" @id = "equity"
@copy_key = "sales.sale_information.equity.#{page.id}" @copy_key = form.start_year_2025_or_later? ? "sales.sale_information.equity.#{page.id}" : "sales.sale_information.equity"
@type = "numeric" @type = "numeric"
@min = 0 @min = 0
@max = 100 @max = 100

2
app/models/form/sales/questions/value.rb

@ -2,7 +2,7 @@ class Form::Sales::Questions::Value < ::Form::Question
def initialize(id, hsh, page) def initialize(id, hsh, page)
super super
@id = "value" @id = "value"
@copy_key = "sales.sale_information.value.#{page.id}" @copy_key = form.start_year_2025_or_later? ? "sales.sale_information.value.#{page.id}" : "sales.sale_information.value"
@type = "numeric" @type = "numeric"
@min = 0 @min = 0
@step = 1 @step = 1

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

2
app/models/forms/bulk_upload_lettings_resume/fix_choice.rb

@ -14,7 +14,7 @@ module Forms
def options def options
[ [
OpenStruct.new(id: "create-fix-inline", name: "Upload these logs and fix errors on CORE site"), OpenStruct.new(id: "create-fix-inline", name: "Upload these logs and fix errors on CORE site"),
OpenStruct.new(id: "upload-again", name: "Fix errors in the CSV and re-upload"), OpenStruct.new(id: "upload-again", name: "Fix errors in the CSV and upload the file again"),
] ]
end end

2
app/models/forms/bulk_upload_sales_resume/fix_choice.rb

@ -14,7 +14,7 @@ module Forms
def options def options
[ [
OpenStruct.new(id: "create-fix-inline", name: "Upload these logs and fix errors on CORE site"), OpenStruct.new(id: "create-fix-inline", name: "Upload these logs and fix errors on CORE site"),
OpenStruct.new(id: "upload-again", name: "Fix errors in the CSV and re-upload"), OpenStruct.new(id: "upload-again", name: "Fix errors in the CSV and upload the file again"),
] ]
end end

3
app/models/lettings_log.rb

@ -779,8 +779,7 @@ private
not_required << "previous_la_known" if postcode_known? not_required << "previous_la_known" if postcode_known?
not_required << "tshortfall" if tshortfall_unknown? not_required << "tshortfall" if tshortfall_unknown?
not_required << "tenancylength" if tenancylength_optional? not_required << "tenancylength" if tenancylength_optional?
not_required += %w[address_line2 county]
not_required |= %w[address_line2 county postcode_full] if startdate && collection_start_year_for_date(startdate) >= 2023
not_required not_required
end end

7
app/models/location.rb

@ -171,7 +171,8 @@ class Location < ApplicationRecord
DUPLICATE_LOCATION_ATTRIBUTES = %w[scheme_id postcode mobility_type].freeze DUPLICATE_LOCATION_ATTRIBUTES = %w[scheme_id postcode mobility_type].freeze
LOCAL_AUTHORITIES = LocalAuthority.all.map { |la| [la.name, la.code] }.to_h LOCAL_AUTHORITIES = LocalAuthority.all.map { |la| [la.name, la.code] }.to_h
enum local_authorities: LOCAL_AUTHORITIES attribute :local_authorities, :string
enum :local_authorities, LOCAL_AUTHORITIES
def self.local_authorities_for_current_year def self.local_authorities_for_current_year
LocalAuthority.all.active(Time.zone.today).england.map { |la| [la.code, la.name] }.to_h LocalAuthority.all.active(Time.zone.today).england.map { |la| [la.code, la.name] }.to_h
end end
@ -184,7 +185,7 @@ class Location < ApplicationRecord
"Missing": "X", "Missing": "X",
}.freeze }.freeze
enum mobility_type: MOBILITY_TYPE enum :mobility_type, MOBILITY_TYPE
TYPE_OF_UNIT = { TYPE_OF_UNIT = {
"Bungalow": 6, "Bungalow": 6,
@ -195,7 +196,7 @@ class Location < ApplicationRecord
"Shared house or hostel": 4, "Shared house or hostel": 4,
}.freeze }.freeze
enum type_of_unit: TYPE_OF_UNIT enum :type_of_unit, TYPE_OF_UNIT
def self.find_by_id_on_multiple_fields(id) def self.find_by_id_on_multiple_fields(id)
return if id.nil? return if id.nil?

18
app/models/log.rb

@ -18,14 +18,14 @@ class Log < ApplicationRecord
"pending" => 3, "pending" => 3,
"deleted" => 4, "deleted" => 4,
}.freeze }.freeze
enum status: STATUS enum :status, STATUS
enum status_cache: STATUS, _prefix: true enum :status_cache, STATUS, prefix: true
CREATION_METHOD = { CREATION_METHOD = {
"single log" => 1, "single log" => 1,
"bulk upload" => 2, "bulk upload" => 2,
}.freeze }.freeze
enum creation_method: CREATION_METHOD, _prefix: true enum :creation_method, CREATION_METHOD, prefix: true
scope :visible, -> { where(status: %w[not_started in_progress completed]) } scope :visible, -> { where(status: %w[not_started in_progress completed]) }
scope :exportable, -> { where(status: %w[not_started in_progress completed deleted]) } scope :exportable, -> { where(status: %w[not_started in_progress completed deleted]) }
@ -110,7 +110,7 @@ class Log < ApplicationRecord
self.address_line2 = nil self.address_line2 = nil
self.town_or_city = nil self.town_or_city = nil
self.county = nil self.county = nil
self.postcode_full = postcode_full_input self.postcode_full = postcode_full_input if postcode_full_input.match(POSTCODE_REGEXP)
process_postcode_changes! process_postcode_changes!
else else
self.uprn = uprn_selection self.uprn = uprn_selection
@ -317,8 +317,12 @@ private
def update_status! def update_status!
return if skip_update_status return if skip_update_status
if status == "pending"
self.status_cache = calculate_status
else
self.status = calculate_status self.status = calculate_status
end end
end
def all_subsections_completed? def all_subsections_completed?
form.subsections.all? { |subsection| subsection.complete?(self) || subsection.not_displayed_in_tasklist?(self) } form.subsections.all? { |subsection| subsection.complete?(self) || subsection.not_displayed_in_tasklist?(self) }
@ -373,14 +377,14 @@ private
end end
def reset_location_fields! def reset_location_fields!
reset_location(is_la_inferred, "la", "is_la_inferred", "postcode_full", 1) reset_log_location(is_la_inferred, "la", "is_la_inferred", "postcode_full", 1)
end end
def reset_previous_location_fields! def reset_previous_location_fields!
reset_location(is_previous_la_inferred, "prevloc", "is_previous_la_inferred", "ppostcode_full", previous_la_known) reset_log_location(is_previous_la_inferred, "prevloc", "is_previous_la_inferred", "ppostcode_full", previous_la_known)
end end
def reset_location(is_inferred, la_key, is_inferred_key, postcode_key, is_la_known) def reset_log_location(is_inferred, la_key, is_inferred_key, postcode_key, is_la_known)
if is_inferred || is_la_known != 1 if is_inferred || is_la_known != 1
self[la_key] = nil self[la_key] = nil
end end

4
app/models/merge_request.rb

@ -13,7 +13,9 @@ class MergeRequest < ApplicationRecord
request_merged: "request_merged", request_merged: "request_merged",
deleted: "deleted", deleted: "deleted",
}.freeze }.freeze
enum status: STATUS
attribute :status, :string
enum :status, STATUS
scope :not_merged, -> { where(request_merged: [false, nil]) } scope :not_merged, -> { where(request_merged: [false, nil]) }
scope :merged, -> { where(request_merged: true) } scope :merged, -> { where(request_merged: true) }

3
app/models/organisation.rb

@ -53,11 +53,12 @@ class Organisation < ApplicationRecord
PRP: 2, PRP: 2,
}.freeze }.freeze
enum provider_type: PROVIDER_TYPE enum :provider_type, PROVIDER_TYPE
alias_method :la?, :LA? alias_method :la?, :LA?
validates :name, presence: { message: I18n.t("validations.organisation.name_missing") } 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") } validates :provider_type, presence: { message: I18n.t("validations.organisation.provider_type_missing") }
def self.find_by_id_on_multiple_fields(id) def self.find_by_id_on_multiple_fields(id)

3
app/models/sales_log.rb

@ -132,8 +132,7 @@ class SalesLog < Log
not_required << "mortlen" if mortlen_optional? not_required << "mortlen" if mortlen_optional?
not_required << "frombeds" if frombeds_optional? not_required << "frombeds" if frombeds_optional?
not_required << "deposit" if form.start_year_2024_or_later? && stairowned_100? not_required << "deposit" if form.start_year_2024_or_later? && stairowned_100?
not_required += %w[address_line2 county]
not_required |= %w[address_line2 county postcode_full] if saledate && collection_start_year_for_date(saledate) >= 2023
not_required not_required
end end

20
app/models/scheme.rb

@ -145,7 +145,7 @@ class Scheme < ApplicationRecord
Yes: 1, Yes: 1,
}.freeze }.freeze
enum sensitive: SENSITIVE, _suffix: true enum :sensitive, SENSITIVE, suffix: true
REGISTERED_UNDER_CARE_ACT = { REGISTERED_UNDER_CARE_ACT = {
"Yes – registered care home providing nursing care": 4, "Yes – registered care home providing nursing care": 4,
@ -154,7 +154,7 @@ class Scheme < ApplicationRecord
"No": 1, "No": 1,
}.freeze }.freeze
enum registered_under_care_act: REGISTERED_UNDER_CARE_ACT enum :registered_under_care_act, REGISTERED_UNDER_CARE_ACT
SCHEME_TYPE = { SCHEME_TYPE = {
"Direct Access Hostel": 5, "Direct Access Hostel": 5,
@ -164,7 +164,7 @@ class Scheme < ApplicationRecord
"Missing": 0, "Missing": 0,
}.freeze }.freeze
enum scheme_type: SCHEME_TYPE, _suffix: true enum :scheme_type, SCHEME_TYPE, suffix: true
SUPPORT_TYPE = { SUPPORT_TYPE = {
"Missing": 0, "Missing": 0,
@ -175,7 +175,7 @@ class Scheme < ApplicationRecord
"Floating support": 6, "Floating support": 6,
}.freeze }.freeze
enum support_type: SUPPORT_TYPE, _suffix: true enum :support_type, SUPPORT_TYPE, suffix: true
PRIMARY_CLIENT_GROUP = { PRIMARY_CLIENT_GROUP = {
"Homeless families with support needs": "O", "Homeless families with support needs": "O",
@ -197,8 +197,8 @@ class Scheme < ApplicationRecord
"Missing": "X", "Missing": "X",
}.freeze }.freeze
enum primary_client_group: PRIMARY_CLIENT_GROUP, _suffix: true enum :primary_client_group, PRIMARY_CLIENT_GROUP, suffix: true
enum secondary_client_group: PRIMARY_CLIENT_GROUP, _suffix: true enum :secondary_client_group, PRIMARY_CLIENT_GROUP, suffix: true
INTENDED_STAY = { INTENDED_STAY = {
"Very short stay": "V", "Very short stay": "V",
@ -213,8 +213,8 @@ class Scheme < ApplicationRecord
Yes: 1, Yes: 1,
}.freeze }.freeze
enum intended_stay: INTENDED_STAY, _suffix: true enum :intended_stay, INTENDED_STAY, suffix: true
enum has_other_client_group: HAS_OTHER_CLIENT_GROUP, _suffix: true enum :has_other_client_group, HAS_OTHER_CLIENT_GROUP, suffix: true
ARRANGEMENT_TYPE = { ARRANGEMENT_TYPE = {
"The same organisation that owns the housing stock": "D", "The same organisation that owns the housing stock": "D",
@ -226,7 +226,7 @@ class Scheme < ApplicationRecord
DUPLICATE_SCHEME_ATTRIBUTES = %w[scheme_type registered_under_care_act primary_client_group secondary_client_group has_other_client_group support_type intended_stay].freeze DUPLICATE_SCHEME_ATTRIBUTES = %w[scheme_type registered_under_care_act primary_client_group secondary_client_group has_other_client_group support_type intended_stay].freeze
enum arrangement_type: ARRANGEMENT_TYPE, _suffix: true enum :arrangement_type, ARRANGEMENT_TYPE, suffix: true
def self.find_by_id_on_multiple_fields(scheme_id, location_id) def self.find_by_id_on_multiple_fields(scheme_id, location_id)
return if scheme_id.nil? return if scheme_id.nil?
@ -329,9 +329,9 @@ class Scheme < ApplicationRecord
def status_at(date) def status_at(date)
return :deleted if discarded_at.present? 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 || 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) (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 :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 :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 return :activating_soon if startdate.present? && date < startdate

2
app/models/user.rb

@ -56,7 +56,7 @@ class User < ApplicationRecord
unassign: "No, unassign the logs", unassign: "No, unassign the logs",
}.freeze }.freeze
enum role: ROLES enum :role, ROLES
scope :search_by_name, ->(name) { where("users.name ILIKE ?", "%#{name}%") } scope :search_by_name, ->(name) { where("users.name ILIKE ?", "%#{name}%") }
scope :search_by_email, ->(email) { where("email ILIKE ?", "%#{email}%") } scope :search_by_email, ->(email) { where("email ILIKE ?", "%#{email}%") }

2
app/models/validations/property_validations.rb

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

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

@ -64,7 +64,7 @@ module Validations::Sales::SaleInformationValidations
if over_tolerance?(record.mortgage_deposit_and_grant_total, record.value_with_discount, tolerance, strict: !record.discount.nil?) && record.discounted_ownership_sale? if over_tolerance?(record.mortgage_deposit_and_grant_total, record.value_with_discount, tolerance, strict: !record.discount.nil?) && record.discounted_ownership_sale?
deposit_and_grant_sentence = record.grant.present? ? ", cash deposit (#{record.field_formatted_as_currency('deposit')}), and grant (#{record.field_formatted_as_currency('grant')})" : " and cash deposit (#{record.field_formatted_as_currency('deposit')})" deposit_and_grant_sentence = record.grant.present? ? ", cash deposit (#{record.field_formatted_as_currency('deposit')}), and grant (#{record.field_formatted_as_currency('grant')})" : " and cash deposit (#{record.field_formatted_as_currency('deposit')})"
discount_sentence = record.discount.present? ? " (#{record.field_formatted_as_currency('value')}) subtracted by the sum of the full purchase price (#{record.field_formatted_as_currency('value')}) multiplied by the percentage discount (#{record.discount}%)" : "" discount_sentence = record.discount.present? ? " (#{record.field_formatted_as_currency('value')}) subtracted by the sum of the full purchase price (#{record.field_formatted_as_currency('value')}) multiplied by the percentage discount (#{record.discount}%)" : ""
%i[mortgageused mortgage value deposit ownershipsch discount grant].each do |field| %i[mortgageused mortgage value deposit discount grant].each do |field|
record.errors.add field, I18n.t("validations.sales.sale_information.#{field}.discounted_ownership_value", record.errors.add field, I18n.t("validations.sales.sale_information.#{field}.discounted_ownership_value",
mortgage: record.mortgage&.positive? ? " (#{record.field_formatted_as_currency('mortgage')})" : "", mortgage: record.mortgage&.positive? ? " (#{record.field_formatted_as_currency('mortgage')})" : "",
deposit_and_grant_sentence:, deposit_and_grant_sentence:,
@ -72,6 +72,12 @@ module Validations::Sales::SaleInformationValidations
discount_sentence:, discount_sentence:,
value_with_discount: record.field_formatted_as_currency("value_with_discount")).html_safe value_with_discount: record.field_formatted_as_currency("value_with_discount")).html_safe
end end
record.errors.add :ownershipsch, :skip_bu_error, message: I18n.t("validations.sales.sale_information.ownershipsch.discounted_ownership_value",
mortgage: record.mortgage&.positive? ? " (#{record.field_formatted_as_currency('mortgage')})" : "",
deposit_and_grant_sentence:,
mortgage_deposit_and_grant_total: record.field_formatted_as_currency("mortgage_deposit_and_grant_total"),
discount_sentence:,
value_with_discount: record.field_formatted_as_currency("value_with_discount")).html_safe
end end
end end

4
app/models/validations/soft_validations.rb

@ -84,6 +84,8 @@ module Validations::SoftValidations
end end
def all_tenants_age_and_gender_information_completed? def all_tenants_age_and_gender_information_completed?
return false if hhmemb.present? && hhmemb > 8
person_count = hhmemb || 8 person_count = hhmemb || 8
(1..person_count).all? do |n| (1..person_count).all? do |n|
@ -235,6 +237,8 @@ private
end end
def all_male_tenants_in_the_household? def all_male_tenants_in_the_household?
return false if hhmemb.present? && hhmemb > 8
person_count = hhmemb || 8 person_count = hhmemb || 8
(1..person_count).all? do |n| (1..person_count).all? do |n|

2
app/services/bulk_upload/lettings/log_creator.rb

@ -16,9 +16,7 @@ class BulkUpload::Lettings::LogCreator
row_parser.log.blank_invalid_non_setup_fields! row_parser.log.blank_invalid_non_setup_fields!
row_parser.log.bulk_upload = bulk_upload row_parser.log.bulk_upload = bulk_upload
row_parser.log.creation_method = "bulk upload" row_parser.log.creation_method = "bulk upload"
row_parser.log.skip_update_status = true
row_parser.log.status = "pending" row_parser.log.status = "pending"
row_parser.log.status_cache = row_parser.log.calculate_status
begin begin
row_parser.log.save! row_parser.log.save!

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

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

20
app/services/bulk_upload/processor.rb

@ -36,8 +36,17 @@ class BulkUpload::Processor
if validator.any_setup_errors? if validator.any_setup_errors?
send_setup_errors_mail send_setup_errors_mail
else
block_creation_reason = validator.block_log_creation_reason
elsif validator.create_logs? 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 create_logs
if validator.soft_validation_errors_only? if validator.soft_validation_errors_only?
@ -48,8 +57,7 @@ class BulkUpload::Processor
bulk_upload.unpend bulk_upload.unpend
send_success_mail send_success_mail
end end
else end
send_correct_and_upload_again_mail # summary/full report
end end
rescue StandardError => e rescue StandardError => e
Sentry.capture_exception(e) Sentry.capture_exception(e)
@ -97,6 +105,12 @@ private
.deliver_later .deliver_later
end 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 def send_success_mail
BulkUploadMailer BulkUploadMailer
.send_bulk_upload_complete_mail(user:, bulk_upload:) .send_bulk_upload_complete_mail(user:, bulk_upload:)

2
app/services/bulk_upload/sales/log_creator.rb

@ -15,9 +15,7 @@ class BulkUpload::Sales::LogCreator
row_parser.log.blank_invalid_non_setup_fields! row_parser.log.blank_invalid_non_setup_fields!
row_parser.log.bulk_upload = bulk_upload row_parser.log.bulk_upload = bulk_upload
row_parser.log.creation_method = "bulk upload" row_parser.log.creation_method = "bulk upload"
row_parser.log.skip_update_status = true
row_parser.log.status = "pending" row_parser.log.status = "pending"
row_parser.log.status_cache = row_parser.log.calculate_status
begin begin
row_parser.log.save! row_parser.log.save!

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

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

6
app/services/exports/organisation_export_service.rb

@ -56,11 +56,13 @@ module Exports
def apply_cds_transformation(organisation) def apply_cds_transformation(organisation)
attribute_hash = organisation.attributes attribute_hash = organisation.attributes
attribute_hash["deleted_at"] = organisation.discarded_at attribute_hash["deleted_at"] = organisation.discarded_at&.iso8601
attribute_hash["dsa_signed"] = organisation.data_protection_confirmed? attribute_hash["dsa_signed"] = organisation.data_protection_confirmed?
attribute_hash["dsa_signed_at"] = organisation.data_protection_confirmation&.signed_at attribute_hash["dsa_signed_at"] = organisation.data_protection_confirmation&.signed_at&.iso8601
attribute_hash["dpo_email"] = organisation.data_protection_confirmation&.data_protection_officer_email attribute_hash["dpo_email"] = organisation.data_protection_confirmation&.data_protection_officer_email
attribute_hash["provider_type"] = organisation.provider_type_before_type_cast attribute_hash["provider_type"] = organisation.provider_type_before_type_cast
attribute_hash["merge_date"] = organisation.merge_date&.iso8601
attribute_hash["available_from"] = organisation.available_from&.iso8601
attribute_hash["profit_status"] = nil # will need update when we add the field to the org attribute_hash["profit_status"] = nil # will need update when we add the field to the org
attribute_hash["group"] = nil # will need update when we add the field to the org attribute_hash["group"] = nil # will need update when we add the field to the org

1
app/services/exports/user_export_service.rb

@ -60,6 +60,7 @@ module Exports
attribute_hash["organisation_name"] = user.organisation.name attribute_hash["organisation_name"] = user.organisation.name
attribute_hash["active"] = user.active? attribute_hash["active"] = user.active?
attribute_hash["phone"] = [user.phone, user.phone_extension].compact.join(" ") attribute_hash["phone"] = [user.phone, user.phone_extension].compact.join(" ")
attribute_hash["last_sign_in_at"] = user.last_sign_in_at&.iso8601
attribute_hash attribute_hash
end end
end end

16
app/services/feature_toggle.rb

@ -11,10 +11,6 @@ class FeatureToggle
!Rails.env.development? !Rails.env.development?
end end
def self.duplicate_summary_enabled?
true
end
def self.service_unavailable? def self.service_unavailable?
false false
end end
@ -23,18 +19,6 @@ class FeatureToggle
false false
end end
def self.delete_scheme_enabled?
true
end
def self.delete_location_enabled?
true
end
def self.delete_user_enabled?
true
end
def self.local_storage? def self.local_storage?
Rails.env.development? Rails.env.development?
end end

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>

21
app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2024.html.erb

@ -19,22 +19,17 @@
<h2 class="govuk-heading-s">Create your file</h2> <h2 class="govuk-heading-s">Create your file</h2>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list [
<li>Fill in the template with data from your housing management system. Your data should go below the headers, with one row per log. Leave column A blank - the bulk upload fields start in column B.</li> "Fill in the template with data from your housing management system. Your data should go below the headers, with one row per log. Leave column A blank - the bulk upload fields start in column B.",
<li>Make sure each column of your data aligns with the matching headers above. You may need to reorder your data.</li> "Make sure each column of your data aligns with the matching headers above. You may need to reorder your data.",
<li>Use the <%= govuk_link_to "Lettings bulk upload Specification (2024 to 2025)", @form.specification_path %> to check your data is in the correct format.</li> "Use the #{govuk_link_to 'Lettings bulk upload Specification (2024 to 2025)', @form.specification_path} to check your data is in the correct format.".html_safe,
<li><strong>Username field:</strong> To assign a log to someone else, enter the email address they use to log into CORE.</li> "<strong>Username field:</strong> To assign a log to someone else, enter the email address they use to log into CORE.".html_safe,
<li>If you have reordered the headers, keep the headers in the file.</li> "If you have reordered the headers, keep the headers in the file.",
</ul> ], type: :bullet %>
<%= govuk_inset_text(text: "You can upload both general needs and supported housing logs in the same file for 2024 to 2025 data.") %>
<h2 class="govuk-heading-s">Save your file</h2> <h2 class="govuk-heading-s">Save your file</h2>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list ["Save your file as a CSV.", "Your file should now be ready to upload."], type: :bullet %>
<li>Save your file as a CSV.</li>
<li>Your file should now be ready to upload.</li>
</ul>
<%= f.govuk_submit class: "govuk-!-margin-top-7" %> <%= f.govuk_submit class: "govuk-!-margin-top-7" %>
<% end %> <% end %>

2
app/views/bulk_upload_lettings_resume/deletion_report.html.erb

@ -28,6 +28,6 @@
<div class="govuk-button-group"> <div class="govuk-button-group">
<%= form_with model: @form, scope: :form, url: page_bulk_upload_lettings_resume_path(@bulk_upload, "confirm"), method: :patch do |f| %> <%= form_with model: @form, scope: :form, url: page_bulk_upload_lettings_resume_path(@bulk_upload, "confirm"), method: :patch do |f| %>
<%= f.govuk_submit "Clear this data and upload the logs" %> <%= f.govuk_submit "Clear this data and upload the logs" %>
<%= govuk_button_link_to "I have fixed these errors and I want to reupload the file", start_bulk_upload_lettings_logs_path, secondary: true %> <%= govuk_button_link_to "I have fixed these errors and I want to upload the file again", start_bulk_upload_lettings_logs_path, secondary: true %>
<% end %> <% end %>
</div> </div>

12
app/views/bulk_upload_lettings_resume/fix_choice.html.erb

@ -24,17 +24,9 @@
<%= govuk_details(summary_text: "How to choose between fixing errors on the CORE site or in the CSV") do %> <%= govuk_details(summary_text: "How to choose between fixing errors on the CORE site or in the CSV") do %>
<p class="govuk-body govuk-!-margin-bottom-2">You may find it easier to fix the errors in the CSV file if:</p> <p class="govuk-body govuk-!-margin-bottom-2">You may find it easier to fix the errors in the CSV file if:</p>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list ["you have a lot of errors", "the CSV file is formatted incorrectly and you can see where the errors are", "you need to fix multiple errors at once"], type: :bullet %>
<li>you have a lot of errors</li>
<li>the CSV file is formatted incorrectly and you can see where the errors are</li>
<li>you need to fix multiple errors at once</li>
</ul>
<p class="govuk-body govuk-!-margin-bottom-2">You may find it easier to fix the errors on the CORE site if:</p> <p class="govuk-body govuk-!-margin-bottom-2">You may find it easier to fix the errors on the CORE site if:</p>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list ["you need to see the data in context", "you have a smaller file, with a few errors", "you are not sure where the errors are"], type: :bullet %>
<li>you need to see the data in context</li>
<li>you have a smaller file, with a few errors</li>
<li>you are not sure where the errors are</li>
</ul>
<% end %> <% end %>
<%= f.govuk_collection_radio_buttons :choice, <%= f.govuk_collection_radio_buttons :choice,

2
app/views/bulk_upload_lettings_soft_validations_check/confirm.html.erb

@ -5,7 +5,7 @@
<div class="govuk-grid-row"> <div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds"> <div class="govuk-grid-column-two-thirds">
<span class="govuk-caption-l">Upload lettings logs in bulk (<%= @bulk_upload.year_combo %>)</span> <span class="govuk-caption-l">Upload lettings logs in bulk (<%= @bulk_upload.year_combo %>)</span>
<h1 class="govuk-heading-l">You have chosen to upload all logs from this bulk upload.</h1> <h1 class="govuk-heading-l">Are you sure you want to upload all logs from this bulk upload?</h1>
<p class="govuk-body"><%= logs_and_soft_validations_warning(@bulk_upload) %></p> <p class="govuk-body"><%= logs_and_soft_validations_warning(@bulk_upload) %></p>

19
app/views/bulk_upload_sales_logs/forms/prepare_your_file_2024.html.erb

@ -19,19 +19,16 @@
<p class="govuk-body govuk-!-margin-bottom-2">There are 8 rows of content in the templates. These rows are called the ‘headers’. They contain the CORE form questions and guidance about which questions are required and how to format your answers.</p> <p class="govuk-body govuk-!-margin-bottom-2">There are 8 rows of content in the templates. These rows are called the ‘headers’. They contain the CORE form questions and guidance about which questions are required and how to format your answers.</p>
<h2 class="govuk-heading-s">Create your file</h2> <h2 class="govuk-heading-s">Create your file</h2>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list [
<li>Fill in the template with data from your housing management system. Your data should go below the headers, with one row per log. The bulk upload fields start at column B. Leave column A blank.</li> "Fill in the template with data from your housing management system. Your data should go below the headers, with one row per log. The bulk upload fields start at column B. Leave column A blank.",
<li>Make sure each column of your data aligns with the matching headers above. You may need to reorder your data.</li> "Make sure each column of your data aligns with the matching headers above. You may need to reorder your data.",
<li>Use the <%= govuk_link_to "Sales bulk upload Specification (2024 to 2025)", @form.specification_path %> to check your data is in the correct format.</li> "Use the #{govuk_link_to 'Sales bulk upload Specification (2024 to 2025)', @form.specification_path} to check your data is in the correct format.".html_safe,
<li><strong>Username field:</strong> To assign a log to someone else, enter the email address they use to log into CORE.</li> "<strong>Username field:</strong> To assign a log to someone else, enter the email address they use to log into CORE.".html_safe,
<li>If you have reordered the headers, keep the headers in the file.</li> "If you have reordered the headers, keep the headers in the file.",
</ul> ], type: :bullet %>
<h2 class="govuk-heading-s">Save your file</h2> <h2 class="govuk-heading-s">Save your file</h2>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list ["Save your file as a CSV.", "Your file should now be ready to upload."], type: :bullet %>
<li>Save your file as a CSV.</li>
<li>Your file should now be ready to upload.</li>
</ul>
<%= f.govuk_submit %> <%= f.govuk_submit %>
<% end %> <% end %>

2
app/views/bulk_upload_sales_resume/deletion_report.html.erb

@ -28,6 +28,6 @@
<div class="govuk-button-group"> <div class="govuk-button-group">
<%= form_with model: @form, scope: :form, url: page_bulk_upload_sales_resume_path(@bulk_upload, "confirm"), method: :patch do |f| %> <%= form_with model: @form, scope: :form, url: page_bulk_upload_sales_resume_path(@bulk_upload, "confirm"), method: :patch do |f| %>
<%= f.govuk_submit "Clear this data and upload the logs" %> <%= f.govuk_submit "Clear this data and upload the logs" %>
<%= govuk_button_link_to "I have fixed these errors and I want to reupload the file", start_bulk_upload_sales_logs_path, secondary: true %> <%= govuk_button_link_to "I have fixed these errors and I want to upload the file again", start_bulk_upload_sales_logs_path, secondary: true %>
<% end %> <% end %>
</div> </div>

12
app/views/bulk_upload_sales_resume/fix_choice.html.erb

@ -24,17 +24,9 @@
<%= govuk_details(summary_text: "How to choose between fixing errors on the CORE site or in the CSV") do %> <%= govuk_details(summary_text: "How to choose between fixing errors on the CORE site or in the CSV") do %>
<p class="govuk-body govuk-!-margin-bottom-2">You may find it easier to fix the errors in the CSV file if:</p> <p class="govuk-body govuk-!-margin-bottom-2">You may find it easier to fix the errors in the CSV file if:</p>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list ["you have a lot of errors", "the CSV file is formatted incorrectly and you can see where the errors are", "you need to fix multiple errors at once"], type: :bullet %>
<li>you have a lot of errors</li>
<li>the CSV file is formatted incorrectly and you can see where the errors are</li>
<li>you need to fix multiple errors at once</li>
</ul>
<p class="govuk-body govuk-!-margin-bottom-2">You may find it easier to fix the errors on the CORE site if:</p> <p class="govuk-body govuk-!-margin-bottom-2">You may find it easier to fix the errors on the CORE site if:</p>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list ["you need to see the data in context", "you have a smaller file, with a few errors", "you are not sure where the errors are"], type: :bullet %>
<li>you need to see the data in context</li>
<li>you have a smaller file, with a few errors</li>
<li>you are not sure where the errors are</li>
</ul>
<% end %> <% end %>
<%= f.govuk_collection_radio_buttons :choice, <%= f.govuk_collection_radio_buttons :choice,

2
app/views/bulk_upload_sales_soft_validations_check/confirm.html.erb

@ -5,7 +5,7 @@
<div class="govuk-grid-row"> <div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds"> <div class="govuk-grid-column-two-thirds">
<span class="govuk-caption-l">Upload sales logs in bulk (<%= @bulk_upload.year_combo %>)</span> <span class="govuk-caption-l">Upload sales logs in bulk (<%= @bulk_upload.year_combo %>)</span>
<h1 class="govuk-heading-l">You have chosen to upload all logs from this bulk upload.</h1> <h1 class="govuk-heading-l">Are you sure you want to upload all logs from this bulk upload?</h1>
<p class="govuk-body"><%= logs_and_soft_validations_warning(@bulk_upload) %></p> <p class="govuk-body"><%= logs_and_soft_validations_warning(@bulk_upload) %></p>

4
app/views/bulk_upload_shared/_moved_user_banner.html.erb

@ -4,9 +4,9 @@
This error report is out of date. This error report is out of date.
<p> <p>
<% if current_user.id == @bulk_upload.moved_user_id %> <% if current_user.id == @bulk_upload.moved_user_id %>
You moved to a different organisation since this file was uploaded. Reupload the file to get an accurate error report. You moved to a different organisation since this file was uploaded. Upload the file again to get an accurate error report.
<% else %> <% else %>
Some logs in this upload are assigned to <%= @bulk_upload.moved_user_name %>, who has moved to a different organisation since this file was uploaded. Reupload the file to get an accurate error report. Some logs in this upload are assigned to <%= @bulk_upload.moved_user_name %>, who has moved to a different organisation since this file was uploaded. Upload the file again to get an accurate error report.
<% end %> <% end %>
<% end %> <% end %>
<% end %> <% end %>

9
app/views/bulk_upload_shared/guidance.html.erb

@ -34,12 +34,7 @@
<p class="govuk-body">The bulk upload templates contain 8 rows of ‘headers’ with information about how to fill in the template, including:</p> <p class="govuk-body">The bulk upload templates contain 8 rows of ‘headers’ with information about how to fill in the template, including:</p>
<% end %> <% end %>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list ["the CORE form questions and their field numbers", "each field’s valid responses", "if/when certain fields can be left blank", "which fields are used to check for duplicate logs"], type: :bullet %>
<li>the CORE form questions and their field numbers</li>
<li>each field’s valid responses</li>
<li>if/when certain fields can be left blank</li>
<li>which fields are used to check for duplicate logs</li>
</ul>
<p class="govuk-body">You can paste your data below the headers or copy the headers and insert them above the data in your file. The bulk upload fields start at column B. Leave column A blank.</p> <p class="govuk-body">You can paste your data below the headers or copy the headers and insert them above the data in your file. The bulk upload fields start at column B. Leave column A blank.</p>
<p class="govuk-body">Make sure that each column of data aligns with the corresponding question in the headers. We recommend ordering your data to match the headers, but you can also reorder the headers to match your data. When processing the file, we check what each column of data represents based on the headers above.</p> <p class="govuk-body">Make sure that each column of data aligns with the corresponding question in the headers. We recommend ordering your data to match the headers, but you can also reorder the headers to match your data. When processing the file, we check what each column of data represents based on the headers above.</p>
@ -80,7 +75,7 @@
<p class="govuk-body">Once you've saved your CSV file, you can upload it via a button at the top of the lettings and sales logs pages.</p> <p class="govuk-body">Once you've saved your CSV file, you can upload it via a button at the top of the lettings and sales logs pages.</p>
<p class="govuk-body">When your file is done processing, you will receive an email explaining your next steps. If all your data is valid, your logs will be created. If some data is invalid, you’ll receive an email with instructions about how to resolve the errors.</p> <p class="govuk-body">When your file is done processing, you will receive an email explaining your next steps. If all your data is valid, your logs will be created. If some data is invalid, you’ll receive an email with instructions about how to resolve the errors.</p>
<p class="govuk-body">If your file has errors on fields 1 through 15 for lettings, or 1 through 18 for sales, you must fix these in the CSV. This is because we need to know these answers to validate the rest of the data. Any errors in these fields will be featured in the error report’s summary tab.</p> <p class="govuk-body">If your file has errors on fields 1 through 15 for lettings, or 1 through 18 for sales, you must fix these in the CSV. This is because we need to know these answers to validate the rest of the data. Any errors in these fields will be featured in the error report’s summary tab.</p>
<p class="govuk-body">If none of your errors are in fields 1 through 15 for lettings, or 1 through 18 for sales, you can choose how to fix the errors. You can either fix them in the CSV and reupload, or create partially complete logs and answer the remaining questions on the CORE site. Any errors that affect a significant number of logs will be featured in the error report’s summary tab to help you decide.</p> <p class="govuk-body">If none of your errors are in fields 1 through 15 for lettings, or 1 through 18 for sales, you can choose how to fix the errors. You can either fix them in the CSV and upload the file again, or create partially complete logs and answer the remaining questions on the CORE site. Any errors that affect a significant number of logs will be featured in the error report’s summary tab to help you decide.</p>
<p class="govuk-body"></p> <p class="govuk-body"></p>
<% end %> <% end %>

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) %> <% title = format_title(@searched, bulk_upload_title(controller.controller_name), current_user, item_label, @pagy.count, nil) %>
<% content_for :title, title %> <% content_for :title, title %>

6
app/views/cookies/show.html.erb

@ -37,11 +37,7 @@
<p>Google is not allowed to use or share our analytics data with anyone.</p> <p>Google is not allowed to use or share our analytics data with anyone.</p>
<p>Google Analytics stores anonymised information about:</p> <p>Google Analytics stores anonymised information about:</p>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list ["how you got to the service", "the pages you visit on the service and how long you spend on them", "any errors you see while using the service"], type: :bullet %>
<li>how you got to the service</li>
<li>the pages you visit on the service and how long you spend on them</li>
<li>any errors you see while using the service</li>
</ul>
<table class="govuk-table"> <table class="govuk-table">
<caption class="govuk-visually-hidden">Google Analytics cookies</caption> <caption class="govuk-visually-hidden">Google Analytics cookies</caption>

2
app/views/devise/sessions/new.html.erb

@ -22,7 +22,7 @@
<%= f.govuk_password_field :password, <%= f.govuk_password_field :password,
autocomplete: "current-password" %> autocomplete: "current-password" %>
<%= f.hidden_field :start, value: request["start"] %> <%= f.hidden_field :start, value: request.params["start"] %>
<%= f.govuk_submit "Sign in" %> <%= f.govuk_submit "Sign in" %>
</div> </div>
</div> </div>

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

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

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

@ -103,7 +103,7 @@
<% feedback_link = govuk_link_to "giving us your feedback (opens in a new tab)", t("feedback_form"), rel: "noreferrer noopener", target: "_blank" %> <% feedback_link = govuk_link_to "giving us your feedback (opens in a new tab)", t("feedback_form"), rel: "noreferrer noopener", target: "_blank" %>
<%= govuk_phase_banner( <%= govuk_phase_banner(
classes: "govuk-width-container", classes: "#{current_user.present? ? 'no-bottom-border ' : ''}govuk-width-container",
tag: govuk_phase_banner_tag(current_user), tag: govuk_phase_banner_tag(current_user),
text: "This is a new service – help us improve it by #{feedback_link}".html_safe, text: "This is a new service – help us improve it by #{feedback_link}".html_safe,
) %> ) %>

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

@ -42,7 +42,7 @@
<% if LocationPolicy.new(current_user, @location).create? %> <% if LocationPolicy.new(current_user, @location).create? %>
<div class="govuk-button-group"> <div class="govuk-button-group">
<%= govuk_button_to "Save and return to locations", scheme_location_confirm_path(@scheme, @location, route: params[:route]), method: :patch %> <%= govuk_button_to "Save and return to locations", scheme_location_confirm_path(@scheme, @location, route: params[:route]), method: :patch %>
<% if LocationPolicy.new(current_user, @location).delete? && FeatureToggle.delete_location_enabled? %> <% if LocationPolicy.new(current_user, @location).delete? %>
<%= delete_location_link(@location) %> <%= delete_location_link(@location) %>
<% end %> <% end %>
<%= govuk_button_link_to "Cancel", scheme_locations_path(@scheme), secondary: true %> <%= govuk_button_link_to "Cancel", scheme_locations_path(@scheme), secondary: true %>

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

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

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

@ -51,6 +51,6 @@
<%= toggle_location_link(@location) %> <%= toggle_location_link(@location) %>
<% end %> <% end %>
<% if LocationPolicy.new(current_user, @location).delete? && FeatureToggle.delete_location_enabled? %> <% if LocationPolicy.new(current_user, @location).delete? %>
<%= delete_location_link(@location) %> <%= delete_location_link(@location) %>
<% end %> <% end %>

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

@ -1,7 +1,7 @@
<h2 class="govuk-body"> <h2 class="govuk-body">
<div class="govuk-grid-row app-search__caption"> <div class="govuk-grid-row app-search__caption">
<div class="govuk-grid-column-three-quarters"> <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? %> <% if logs&.any? %>
<%= govuk_link_to "Download (CSV)", csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4", style: "white-space: nowrap" %> <%= govuk_link_to "Download (CSV)", csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4", style: "white-space: nowrap" %>
<% if @current_user.support? %> <% if @current_user.support? %>

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

@ -15,15 +15,7 @@
<p class="govuk-body govuk-!-margin-bottom-2"> <p class="govuk-body govuk-!-margin-bottom-2">
<%= @duplicate_logs.count == 1 ? "This log" : "These logs" %> will be deleted: <%= @duplicate_logs.count == 1 ? "This log" : "These logs" %> will be deleted:
</p> </p>
<ul class="govuk-list govuk-!-margin-bottom-6"> <%= govuk_list(@duplicate_logs.map { |log| "<strong>#{govuk_link_to "Log #{log.id}", url_for(log)}</strong>".html_safe }) %>
<% @duplicate_logs.each do |duplicate_log| %>
<li>
<strong>
<%= govuk_link_to "Log #{duplicate_log.id}", url_for(duplicate_log) %>
</strong>
</li>
<% end %>
</ul>
<div class="govuk-button-group"> <div class="govuk-button-group">
<%= govuk_button_to @duplicate_logs.count == 1 ? "Delete this log" : "Delete these logs", <%= govuk_button_to @duplicate_logs.count == 1 ? "Delete this log" : "Delete these logs",

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) %> <% title = format_title(@searched, "#{log_type_for_controller(controller).capitalize} logs", current_user, item_label, @pagy.count, nil) %>
<% content_for :title, title %> <% 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" %> <% title = "Update logs" %>
<% content_for :title, title %> <% content_for :title, title %>

30
app/views/merge_requests/merge_request.html.erb

@ -14,13 +14,13 @@
<h2 class="govuk-heading-m">Before you start</h2> <h2 class="govuk-heading-m">Before you start</h2>
<p class="govuk-body">Regardless of the merge type, you’ll be asked for:</p> <p class="govuk-body">Regardless of the merge type, you’ll be asked for:</p>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list [
<li>the organisations merging in CORE</li> "the organisations merging in CORE",
<li>the merge type: for example one organisation absorbing the rest, or them all creating a new organisation</li> "the merge type: for example one organisation absorbing the rest, or them all creating a new organisation",
<li>to confirm or update the telephone number</li> "to confirm or update the telephone number",
<li>the merge date</li> "the merge date",
<li>if user email addresses will change with this merge</li> "if user email addresses will change with this merge",
</ul> ], type: :bullet %>
<h2 class="govuk-heading-m">If email addresses are changing</h2> <h2 class="govuk-heading-m">If email addresses are changing</h2>
<%= govuk_inset_text(text: "Update all user email addresses on CORE as soon as they change, so that everyone can still access their CORE account. ") %> <%= govuk_inset_text(text: "Update all user email addresses on CORE as soon as they change, so that everyone can still access their CORE account. ") %>
@ -30,14 +30,14 @@
<h2 class="govuk-heading-m">If merging into a new organisation</h2> <h2 class="govuk-heading-m">If merging into a new organisation</h2>
<p class="govuk-body">You'll also be asked for the new organisation’s:</p> <p class="govuk-body">You'll also be asked for the new organisation’s:</p>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list [
<li>name</li> "name",
<li>address</li> "address",
<li>telephone number</li> "telephone number",
<li>housing provider type</li> "housing provider type",
<li>Regulator of Social Housing registration number</li> "Regulator of Social Housing registration number",
<li>held stock details</li> "held stock details",
</ul> ], type: :bullet %>
<%= govuk_warning_text text: "You will not be able to submit your request without the above information. Do not start the form until you have obtained all of the information. " %> <%= govuk_warning_text text: "You will not be able to submit your request without the above information. Do not start the form until you have obtained all of the information. " %>

12
app/views/organisation_relationships/add_managing_agent.html.erb

@ -28,12 +28,10 @@
<%= govuk_button_link_to("Cancel", managing_agents_organisation_path(@organisation), secondary: true) %> <%= govuk_button_link_to("Cancel", managing_agents_organisation_path(@organisation), secondary: true) %>
</div> </div>
<%= govuk_details(summary_text: "Can't find the managing agent you're looking for?") do %> <%= govuk_details(summary_text: "Can't find the managing agent you're looking for?") do %>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list [
<li>Double check the spelling and try again</li> "Double check the spelling and try again",
<li>Type the first few letters to see the suggestions</li> "Type the first few letters to see the suggestions",
<li>If you still can't find it, "If you still can't find it, #{govuk_link_to('contact the MHCLG service desk', GlobalConstants::HELPDESK_URL, rel: 'noreferrer noopener', target: '_blank')}",
<%= govuk_link_to("contact the MHCLG service desk", GlobalConstants::HELPDESK_URL, rel: "noreferrer noopener", target: "_blank") %> ], type: :bullet %>
</li>
</ul>
<% end %> <% end %>
<% end %> <% end %>

12
app/views/organisation_relationships/add_stock_owner.html.erb

@ -28,12 +28,10 @@
<%= govuk_button_link_to("Cancel", stock_owners_organisation_path(@organisation), secondary: true) %> <%= govuk_button_link_to("Cancel", stock_owners_organisation_path(@organisation), secondary: true) %>
</div> </div>
<%= govuk_details(summary_text: "Can't find the stock owner you're looking for?") do %> <%= govuk_details(summary_text: "Can't find the stock owner you're looking for?") do %>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list [
<li>Double check the spelling and try again</li> "Double check the spelling and try again",
<li>Type the first few letters to see the suggestions</li> "Type the first few letters to see the suggestions",
<li>If you still can't find it, "If you still can't find it, #{govuk_link_to('contact the MHCLG service desk', GlobalConstants::HELPDESK_URL, rel: 'noreferrer noopener', target: '_blank')}",
<%= govuk_link_to("contact the MHCLG service desk", GlobalConstants::HELPDESK_URL, rel: "noreferrer noopener", target: "_blank") %> ], type: :bullet %>
</li>
</ul>
<% end %> <% end %>
<% end %> <% end %>

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? %> <% if current_user.support? %>
<%= render partial: "organisations/headings", locals: { main: @organisation.name, sub: nil } %> <%= render partial: "organisations/headings", locals: { main: @organisation.name, sub: nil } %>
@ -44,7 +44,7 @@
pagy: @pagy, pagy: @pagy,
searched: @searched, searched: @searched,
item_label:, item_label:,
search_item: "managing agents", search_item: "managing agent",
total_count: @total_count, total_count: @total_count,
remove_path: ->(org_id) { managing_agents_remove_organisation_path(target_organisation_id: org_id) }, 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? %> <% if current_user.support? %>
<%= render partial: "organisations/headings", locals: { main: @organisation.name, sub: nil } %> <%= render partial: "organisations/headings", locals: { main: @organisation.name, sub: nil } %>
<%= render SubNavigationComponent.new(items: secondary_items(request.path, @organisation.id)) %> <%= render SubNavigationComponent.new(items: secondary_items(request.path, @organisation.id)) %>
@ -41,7 +41,7 @@
pagy: @pagy, pagy: @pagy,
searched: @searched, searched: @searched,
item_label:, item_label:,
search_item: "stock owners", search_item: "stock owner",
total_count: @total_count, total_count: @total_count,
remove_path: ->(org_id) { stock_owners_remove_organisation_path(target_organisation_id: org_id) }, 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 %>"> <section class="app-table-group" tabindex="0" aria-labelledby="<%= title.dasherize %>">
<%= govuk_table do |table| %> <%= govuk_table do |table| %>
<%= table.with_caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %> <%= 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 %> <% end %>
<%= table.with_head do |head| %> <%= table.with_head do |head| %>
<%= head.with_row do |row| %> <%= head.with_row do |row| %>

65
app/views/organisations/data_sharing_agreement.html.erb

@ -35,30 +35,26 @@
<p class="govuk-body-m"><strong>It is now agreed</strong> as follows:</p> <p class="govuk-body-m"><strong>It is now agreed</strong> as follows:</p>
<h3 id="2-definitions-and-interpretation" class="govuk-heading-m">2. Definitions and interpretation</h3> <h3 id="2-definitions-and-interpretation" class="govuk-heading-m">2. Definitions and interpretation</h3>
<p class="govuk-body-m">2.1. In this Agreement the following words and phrases shall have the following meanings, unless expressly stated to the contrary:</p> <p class="govuk-body-m">2.1. In this Agreement the following words and phrases shall have the following meanings, unless expressly stated to the contrary:</p>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list [
<li>“Act” means the Data Protection Act 2018;</li> "“Act” means the Data Protection Act 2018;",
<li>“Authorised Representatives” means the nominated lead officer representing each of the "“Authorised Representatives” means the nominated lead officer representing each of the parties with delegated authority to handle the day-to-day matters arising from this Agreement;",
parties with delegated authority to handle the day-to-day matters arising from this Agreement;</li> "“Data Subject” means social housing lettings tenants and participants in discounted sales where their data is reported via the CORE system.",
<li>“Data Subject” means social housing lettings tenants and participants in discounted sales where their data is reported via the CORE system.</li> "“Data Controller” has the meaning in Article 4(7) of the GDPR and section 5(2) of the Act.",
<li>“Data Controller” has the meaning in Article 4(7) of the GDPR and section 5(2) of the Act.</li> "“Data Processor” has the meaning in Article 4(8) of the GDPR.",
<li>“Data Processor” has the meaning in Article 4(8) of the GDPR.</li> "“Data Protection Legislation” means the Data Protection Act 2018 and all applicable laws and regulations relating to the processing of personal data and privacy, including where applicable the guidance and codes of practice issued by the Information Commissioner; it includes the General Data Protection Regulation (GDPR).",
<li>“Data Protection Legislation” means the Data Protection Act 2018 and all applicable laws and regulations relating to the processing of personal data and privacy, including where applicable the guidance and codes of practice issued by the Information Commissioner; it includes the General Data Protection Regulation (GDPR).</li> "“Data” means the data supplied by the CORE data providers via the CORE system and the data that is calculated or derived via the CORE system based on that initial data;",
<li>“Data” means the data supplied by the CORE data providers via the CORE system and the data that is calculated or derived via the CORE system based on that initial data;</li> "“GDPR” means the General Data Protection Regulation.",
<li>“GDPR” means the General Data Protection Regulation.</li> "“Parties” means the parties to this Agreement, namely MHCLG and the CORE data providers. CORE data providers include social housing providers and managing organisations that provide data on behalf of the social housing providers.",
<li>“Parties” means the parties to this Agreement, namely MHCLG and the CORE data providers. CORE data providers include social housing providers and managing organisations that provide data on behalf of the social housing providers.</li> "“Personal Data” has the meaning in Article 4(1) of the GDPR. “Processing” has the meaning in Article 4(2) of the GDPR.",
<li>“Personal Data” has the meaning in Article 4(1) of the GDPR. “Processing” has the meaning in Article 4(2) of the GDPR.</li> "“Request for Information” means a request for information or a request under the Freedom of Information Act 2000.",
<li>“Request for Information” means a request for information or a request under the Freedom of Information Act 2000.</li> "“Special category personal data” has the meaning in Article 9(1) of the GDPR. In this Agreement:
<li>“Special category personal data” has the meaning in Article 9(1) of the GDPR. In this Agreement: #{ govuk_list([
<ul class="govuk-list govuk-list--bullet"> 'A. The masculine includes the feminine and neuter;',
<li>A. The masculine includes the feminine and neuter;</li> 'B. Person means a natural person;',
<li>B. Person means a natural person;</li> 'C. The singular includes the plural and vice versa;',
<li>C. The singular includes the plural and vice versa;</li> 'D. A reference to any statute, enactment, order, regulation or other similar instrument shall be construed as a reference to the statute, enactment, order, regulation or instrument as amended by any subsequent statute, enactment, order, regulation or instrument or as contained in any subsequent re-enactment.',
<li>D. A reference to any statute, enactment, order, regulation or other similar instrument ], type: :bullet)}".html_safe,
shall be construed as a reference to the statute, enactment, order, regulation or instrument as amended by any subsequent statute, enactment, order, regulation or instrument or as contained in any subsequent re-enactment.</li> ], type: :bullet %>
</ul></li>
</ul>
<p class="govuk-body-m">2.2. Headings are included in this Agreement for ease of reference only and shall not affect the interpretation or construction of this Agreement.</p> <p class="govuk-body-m">2.2. Headings are included in this Agreement for ease of reference only and shall not affect the interpretation or construction of this Agreement.</p>
<p class="govuk-body-m">2.3. References in this Agreement to Clauses, Paragraphs and Annexes are, unless otherwise provided, references to the Clauses, Paragraphs and Annexes of this Agreement.</p> <p class="govuk-body-m">2.3. References in this Agreement to Clauses, Paragraphs and Annexes are, unless otherwise provided, references to the Clauses, Paragraphs and Annexes of this Agreement.</p>
<p class="govuk-body-m">2.4. In the event and to the extent only of any conflict or inconsistency between the provisions of this Agreement and the provisions of any document referred to or referenced herein, the provisions of this Agreement shall prevail.</p> <p class="govuk-body-m">2.4. In the event and to the extent only of any conflict or inconsistency between the provisions of this Agreement and the provisions of any document referred to or referenced herein, the provisions of this Agreement shall prevail.</p>
@ -95,11 +91,13 @@
<p class="govuk-body-m">8.5. All work carried out by MHCLG will follow appropriate security measures and procedures to ensure the protection of the data.</p> <p class="govuk-body-m">8.5. All work carried out by MHCLG will follow appropriate security measures and procedures to ensure the protection of the data.</p>
<h3 id="9-protection-of-personal-data" class="govuk-heading-m">9. Protection of personal data</h3> <h3 id="9-protection-of-personal-data" class="govuk-heading-m">9. Protection of personal data</h3>
<p class="govuk-body-m">9.1. CORE data providers and MHCLG agree that they shall:</p> <p class="govuk-body-m">9.1. CORE data providers and MHCLG agree that they shall:</p>
<ul class="govuk-list govuk-list--bullet">
<li>A. Implement appropriate technical and organisational measures to protect the Personal Data against unauthorised or unlawful Processing and against accidental loss, destruction, damage, alteration or disclosure. These measures shall ensure a level of security appropriate to the harm which might result from any unauthorised or unlawful Processing, accidental loss, destruction or damage to the Personal Data and having regard to the nature of the Personal Data which is to be protected;</li>
<li>B. Take reasonable steps to ensure the reliability of any personnel who have access to the Personal Data. MHCLG and Data providers will ensure such personnel will be a limited number of analysts assigned to the data collection.</li>
</ul> <%= govuk_list [
"A. Implement appropriate technical and organisational measures to protect the Personal Data against unauthorised or unlawful Processing and against accidental loss, destruction, damage, alteration or disclosure. These measures shall ensure a level of security appropriate to the harm which might result from any unauthorised or unlawful Processing, accidental loss, destruction or damage to the Personal Data and having regard to the nature of the Personal Data which is to be protected;",
"B. Take reasonable steps to ensure the reliability of any personnel who have access to the Personal Data. MHCLG and Data providers will ensure such personnel will be a limited number of analysts assigned to the data collection.",
],
type: :bullet %>
<p class="govuk-body-m">9.2. The data providers and MHCLG shall comply at all times with the Data Protection Legislation and shall ensure that they each perform their obligations under this agreement in full compliance with the Data Protection Legislation and any other applicable law, in particular the Human Rights Act 1998 and the common law duty of confidentiality.</p> <p class="govuk-body-m">9.2. The data providers and MHCLG shall comply at all times with the Data Protection Legislation and shall ensure that they each perform their obligations under this agreement in full compliance with the Data Protection Legislation and any other applicable law, in particular the Human Rights Act 1998 and the common law duty of confidentiality.</p>
<p class="govuk-body-m">9.3. CORE data providers should limit access to CORE to a small number of individuals who can be named on request. CORE access is limited to registered users only via password, but it is the responsibility of the CORE data providers to ensure that all individuals granted access to the datasets should be briefed on the legal requirements around handling and storing the Data from CORE.</p> <p class="govuk-body-m">9.3. CORE data providers should limit access to CORE to a small number of individuals who can be named on request. CORE access is limited to registered users only via password, but it is the responsibility of the CORE data providers to ensure that all individuals granted access to the datasets should be briefed on the legal requirements around handling and storing the Data from CORE.</p>
<h3 id="10-freedom-of-information" class="govuk-heading-m">10. Freedom of information</h3> <h3 id="10-freedom-of-information" class="govuk-heading-m">10. Freedom of information</h3>
@ -128,15 +126,10 @@
<p class="govuk-body-m">16.1. The Parties shall comply with all relevant legislation, regulations, orders, statutory instruments and any amendments or re-enactments thereof from the commencement of this agreement.</p> <p class="govuk-body-m">16.1. The Parties shall comply with all relevant legislation, regulations, orders, statutory instruments and any amendments or re-enactments thereof from the commencement of this agreement.</p>
<p class="govuk-body-m">As witness of which the parties have set their hands on the day and year first above written <p class="govuk-body-m">As witness of which the parties have set their hands on the day and year first above written
signed for and on behalf of the Data Protection Officer for <%= org_name_for_data_sharing_agreement(@data_protection_confirmation, current_user) %>, by:</p> signed for and on behalf of the Data Protection Officer for <%= org_name_for_data_sharing_agreement(@data_protection_confirmation, current_user) %>, by:</p>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list ["Name: #{name_for_data_sharing_agreement(@data_protection_confirmation, current_user)}".html_safe, "Title: Data Protection Officer"], type: :bullet %>
<li>Name: <%= name_for_data_sharing_agreement(@data_protection_confirmation, current_user) %></li>
<li>Title: Data Protection Officer</li>
</ul>
<p class="govuk-body-m">SIGNED for and on behalf of the deputy director of the data, analytics &amp; statistics in the Ministry of Housing, Communities and Local Government, by:</p> <p class="govuk-body-m">SIGNED for and on behalf of the deputy director of the data, analytics &amp; statistics in the Ministry of Housing, Communities and Local Government, by:</p>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list ["Name: Sandra Tudor", "Title: Deputy Director"], type: :bullet %>
<li>Name: Sandra Tudor</li>
<li>Title: Deputy Director</li>
</ul>
<% if current_user.is_dpo? && !(@organisation.data_protection_confirmed?) %> <% if current_user.is_dpo? && !(@organisation.data_protection_confirmed?) %>
<div class="govuk-button-group govuk-!-margin-top-9"> <div class="govuk-button-group govuk-!-margin-top-9">

53
app/views/organisations/duplicate_schemes.html.erb

@ -29,11 +29,11 @@
<p class="govuk-body">Since your organisation recently merged, we’ve reviewed your schemes for possible duplicates.</p> <p class="govuk-body">Since your organisation recently merged, we’ve reviewed your schemes for possible duplicates.</p>
<p class="govuk-body">These sets of schemes and locations might be duplicates because they have the same answers for certain fields.</p> <p class="govuk-body">These sets of schemes and locations might be duplicates because they have the same answers for certain fields.</p>
<h2 class="govuk-heading-m">What you need to do</h2> <h2 class="govuk-heading-m">What you need to do</h2>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list [
<li>Review each set of schemes or locations and decide if they are duplicates.</li> "Review each set of schemes or locations and decide if they are duplicates.",
<li>If they are, choose one to keep and deactivate the others on the date your organisation merged.</li> "If they are, choose one to keep and deactivate the others on the date your organisation merged.",
<li>When you have resolved all duplicates, confirm below.</li> "When you have resolved all duplicates, confirm below.",
</ul> ], type: :bullet %>
<p class="govuk-body">If you need help with this, <%= govuk_link_to "contact the helpdesk (opens in a new tab)", GlobalConstants::HELPDESK_URL, target: "#" %>.</p> <p class="govuk-body">If you need help with this, <%= govuk_link_to "contact the helpdesk (opens in a new tab)", GlobalConstants::HELPDESK_URL, target: "#" %>.</p>
<% if @duplicate_schemes.any? %> <% if @duplicate_schemes.any? %>
@ -43,17 +43,17 @@
<p class="govuk-body"> <p class="govuk-body">
These schemes have the same answers for the following fields: These schemes have the same answers for the following fields:
</p> </p>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list [
<li>Type of scheme</li> "Type of scheme",
<li>Registered under Care Standards Act 2000</li> "Registered under Care Standards Act 2000",
<li>Housing stock owned by</li> "Housing stock owned by",
<li>Support services provided by</li> "Support services provided by",
<li>Primary client group</li> "Primary client group",
<li>Has another client group</li> "Has another client group",
<li>Secondary client group</li> "Secondary client group",
<li>Level of support given</li> "Level of support given",
<li>Intended length of stay</li> "Intended length of stay",
</ul> ], type: :bullet %>
<% end %> <% end %>
<p class="govuk-body">The links below open in a new tab.</p> <p class="govuk-body">The links below open in a new tab.</p>
@ -68,13 +68,7 @@
<% @duplicate_schemes.each do |duplicate_set| %> <% @duplicate_schemes.each do |duplicate_set| %>
<% body.with_row do |row| %> <% body.with_row do |row| %>
<% row.with_cell do %> <% row.with_cell do %>
<ol class="govuk-list govuk-list--number"> <%= govuk_list duplicate_set.map { |scheme| govuk_link_to(scheme.service_name, scheme, target: "#") }, type: :number %>
<% duplicate_set.each do |scheme| %>
<li>
<%= govuk_link_to scheme.service_name, scheme, target: "#" %>
</li>
<% end %>
</ol>
<% end %> <% end %>
<% end %> <% end %>
<% end %> <% end %>
@ -89,10 +83,7 @@
<p class="govuk-body"> <p class="govuk-body">
These locations belong to the same scheme and have the same answers for the following fields: These locations belong to the same scheme and have the same answers for the following fields:
</p> </p>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list ["Postcode", "Mobility standards"], type: :bullet %>
<li>Postcode</li>
<li>Mobility standards</li>
</ul>
<% end %> <% end %>
<p class="govuk-body">The links below open in a new tab.</p> <p class="govuk-body">The links below open in a new tab.</p>
@ -108,13 +99,7 @@
<% @duplicate_locations.each do |duplicate_set| %> <% @duplicate_locations.each do |duplicate_set| %>
<% body.with_row do |row| %> <% body.with_row do |row| %>
<% row.with_cell do %> <% row.with_cell do %>
<ol class="govuk-list govuk-list--number"> <%= govuk_list duplicate_set[:locations].map { |location| govuk_link_to(location.name, scheme_location_path(location), target: "#") }, type: :number %>
<% duplicate_set[:locations].each do |location| %>
<li>
<%= govuk_link_to location.name, scheme_location_path(location), target: "#" %>
</li>
<% end %>
</ol>
<% end %> <% end %>
<% row.with_cell do %> <% row.with_cell do %>
<%= govuk_link_to duplicate_set[:scheme].service_name, duplicate_set[:scheme], target: "#" %> <%= govuk_link_to duplicate_set[:scheme].service_name, duplicate_set[:scheme], target: "#" %>

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) %> <% title = format_title(@searched, "Organisations", current_user, item_label, @pagy.count, nil) %>
<% content_for :title, title %> <% 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) %> <% title = format_title(@searched, action_name.humanize, current_user, item_label, @pagy.count, @organisation.name) %>
<% content_for :title, title %> <% content_for :title, title %>

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

@ -2,7 +2,7 @@
<%= govuk_table do |table| %> <%= govuk_table do |table| %>
<%= table.with_caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %> <%= table.with_caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %>
<span class="app-search__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? %> <% 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 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" %> <%= govuk_link_to "Download locations (CSV)", locations_csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4", style: "white-space: nowrap" %>

10
app/views/schemes/changes.html.erb

@ -15,11 +15,11 @@
<p class="govuk-body">We have restructured the way we group supported housing scheme data.</p> <p class="govuk-body">We have restructured the way we group supported housing scheme data.</p>
<p class="govuk-body">On old CORE, a scheme’s data was split into management group and scheme. On new CORE, the data is split into scheme and location.</p> <p class="govuk-body">On old CORE, a scheme’s data was split into management group and scheme. On new CORE, the data is split into scheme and location.</p>
<p class="govuk-body">These are the main changes:</p> <p class="govuk-body">These are the main changes:</p>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list [
<li>Schemes now store data about the stock owner and support provider. This was previously stored in management group.</li> "Schemes now store data about the stock owner and support provider. This was previously stored in management group.",
<li>Schemes still store data about the type of support, as they did in old CORE.</li> "Schemes still store data about the type of support, as they did in old CORE.",
<li>Schemes no longer store data about postcodes where support is provided. These are now in locations.</li> "Schemes no longer store data about postcodes where support is provided. These are now in locations.",
</ul> ], type: :bullet %>
<p class="govuk-body">This new structure means data coordinators only needs to fill in support details once.</p> <p class="govuk-body">This new structure means data coordinators only needs to fill in support details once.</p>
<h2 class="govuk-heading-m">How schemes are migrated from old CORE</h2> <h2 class="govuk-heading-m">How schemes are migrated from old CORE</h2>
<p class="govuk-body">If your organisation has migrated from old CORE to new CORE, your existing schemes are on the <%= govuk_link_to("schemes page", schemes_path) %>, with the same details.</p> <p class="govuk-body">If your organisation has migrated from old CORE to new CORE, your existing schemes are on the <%= govuk_link_to("schemes page", schemes_path) %>, with the same details.</p>

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

@ -24,7 +24,7 @@
<%= f.govuk_submit button_label %> <%= f.govuk_submit button_label %>
<% end %> <% end %>
<% if SchemePolicy.new(current_user, @scheme).delete? && FeatureToggle.delete_scheme_enabled? %> <% if SchemePolicy.new(current_user, @scheme).delete? %>
<%= delete_scheme_link(@scheme) %> <%= delete_scheme_link(@scheme) %>
<% end %> <% end %>
<% end %> <% end %>

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

@ -1,7 +1,7 @@
<% content_for :title, "Does this scheme provide for another client group?" %> <% content_for :title, "Does this scheme provide for another client group?" %>
<% content_for :before_content do %> <% content_for :before_content do %>
<%= govuk_back_link(href: :back) %> <%= govuk_back_link(href: scheme_back_button_path(@scheme, "confirm_secondary_client_group")) %>
<% end %> <% end %>
<%= render partial: "organisations/headings", locals: { main: "Does this scheme provide for another client group?", sub: @scheme.service_name } %> <%= render partial: "organisations/headings", locals: { main: "Does this scheme provide for another client group?", sub: @scheme.service_name } %>

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

@ -1,7 +1,7 @@
<% content_for :title, "Create a new supported housing scheme" %> <% content_for :title, "Create a new supported housing scheme" %>
<% content_for :before_content do %> <% content_for :before_content do %>
<%= govuk_back_link(href: :back) %> <%= govuk_back_link(href: scheme_back_button_path(@scheme, "details")) %>
<% end %> <% end %>
<%= form_for(@scheme, method: :patch) do |f| %> <%= form_for(@scheme, method: :patch) do |f| %>

8
app/views/schemes/primary_client_group.html.erb

@ -1,13 +1,7 @@
<% content_for :title, "What client group is this scheme intended for?" %> <% content_for :title, "What client group is this scheme intended for?" %>
<% if request.referer&.include?("new") %>
<% back_button_path = scheme_details_path(@scheme) %>
<% else %>
<% back_button_path = :back %>
<% end %>
<% content_for :before_content do %> <% content_for :before_content do %>
<%= govuk_back_link(href: back_button_path) %> <%= govuk_back_link(href: scheme_back_button_path(@scheme, "primary_client_group")) %>
<% end %> <% end %>
<%= form_for(@scheme, method: :patch) do |f| %> <%= form_for(@scheme, method: :patch) do |f| %>

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

@ -1,7 +1,7 @@
<% content_for :title, "What is the other client group?" %> <% content_for :title, "What is the other client group?" %>
<% content_for :before_content do %> <% content_for :before_content do %>
<%= govuk_back_link(href: :back) %> <%= govuk_back_link(href: scheme_back_button_path(@scheme, "secondary_client_group")) %>
<% end %> <% end %>
<%= form_for(@scheme, method: :patch) do |f| %> <%= form_for(@scheme, method: :patch) do |f| %>

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

@ -56,6 +56,6 @@
<%= toggle_scheme_link(@scheme) %> <%= toggle_scheme_link(@scheme) %>
<% end %> <% end %>
<% if SchemePolicy.new(current_user, @scheme).delete? && FeatureToggle.delete_scheme_enabled? %> <% if SchemePolicy.new(current_user, @scheme).delete? %>
<%= delete_scheme_link(@scheme) %> <%= delete_scheme_link(@scheme) %>
<% end %> <% end %>

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

@ -1,7 +1,7 @@
<% content_for :title, "What support does this scheme provide?" %> <% content_for :title, "What support does this scheme provide?" %>
<% content_for :before_content do %> <% content_for :before_content do %>
<%= govuk_back_link(href: :back) %> <%= govuk_back_link(href: scheme_back_button_path(@scheme, "support")) %>
<% end %> <% end %>
<%= form_for(@scheme, method: :patch) do |f| %> <%= form_for(@scheme, method: :patch) do |f| %>

68
app/views/start/guidance.html.erb

@ -21,46 +21,50 @@
<%= accordion.with_section(heading_text: "Types of lettings you should create logs for") do %> <%= accordion.with_section(heading_text: "Types of lettings you should create logs for") do %>
<p class="govuk-body">You’ll need to create a log for:</p> <p class="govuk-body">You’ll need to create a log for:</p>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list [
<li>Tenants in general needs housing allocated a new letting. This includes tenants moving into the social rented sector from outside, existing social tenants moving between properties or landlords, and existing social tenants renewing lettings in the same property. If fixed-term and social or affordable rent, only include tenancies of 2 years or more.</li> "Tenants in general needs housing allocated a new letting. This includes tenants moving into the social rented sector from outside, existing social tenants moving between properties or landlords, and existing social tenants renewing lettings in the same property. If fixed-term and social or affordable rent, only include tenancies of 2 years or more.",
<li>Tenants in supported housing (social housing, sheltered accommodation and care homes) allocated a new letting. This includes tenants moving into the social rented sector from outside, existing social tenants moving between properties or landlords, and existing social tenants renewing lettings in the same property. All supported housing tenancies should be reported regardless of length.</li> "Tenants in supported housing (social housing, sheltered accommodation and care homes) allocated a new letting. This includes tenants moving into the social rented sector from outside, existing social tenants moving between properties or landlords, and existing social tenants renewing lettings in the same property. All supported housing tenancies should be reported regardless of length.",
<li>Starter tenancies provided by local authorities (LAs) and lettings with an introductory period provided by private registered providers (PRPs) should be completed in CORE at the beginning of the starter or introductory period. The tenancy type and length entered should be based on the tenancy the tenant will roll onto once the starter or introductory period has been completed. You do not need to submit another CORE log once the period has been completed.</li> "Starter tenancies provided by local authorities (LAs) and lettings with an introductory period provided by private registered providers (PRPs) should be completed in CORE at the beginning of the starter or introductory period. The tenancy type and length entered should be based on the tenancy the tenant will roll onto once the starter or introductory period has been completed. You do not need to submit another CORE log once the period has been completed.",
<li>Room moves within a shared housing unit that result in a different property type or support needs – this is classed as an internal transfer of an existing social tenant to another property.</li> "Room moves within a shared housing unit that result in a different property type or support needs – this is classed as an internal transfer of an existing social tenant to another property.",
<li>Existing tenants who are issued with a new tenancy agreement when stock is acquired, transferred or permanently decanted.</li> "Existing tenants who are issued with a new tenancy agreement when stock is acquired, transferred or permanently decanted.",
<li>Tenants under the Rough Sleepers Initiative or Rough Sleeping Accommodation Programme, where accommodation is permanent.</li> "Tenants under the Rough Sleepers Initiative or Rough Sleeping Accommodation Programme, where accommodation is permanent.",
<li>Households previously provided with temporary accommodation to meet a duty under the homelessness legislation who are allocated a tenancy as a settled home ending the duty (this may be the same property).</li> "Households previously provided with temporary accommodation to meet a duty under the homelessness legislation who are allocated a tenancy as a settled home ending the duty (this may be the same property).",
<li>Refugees and asylum seekers who have been granted indefinite leave to remain, humanitarian protection or exceptional leave to remain.</li> "Refugees and asylum seekers who have been granted indefinite leave to remain, humanitarian protection or exceptional leave to remain.",
<li>Affordable Rent lettings – where up to 80% of market rent can be charged and a new supply agreement is signed.</li> "Affordable Rent lettings – where up to 80% of market rent can be charged and a new supply agreement is signed.",
<li>London Affordable Rent lettings – a type of Affordable Rent available in London through the Greater London Authority (GLA).</li> "London Affordable Rent lettings – a type of Affordable Rent available in London through the Greater London Authority (GLA).",
<li>Intermediate Rent lettings – where the rent must not exceed 80% of the current market rate (including any service charges).</li> "Intermediate Rent lettings – where the rent must not exceed 80% of the current market rate (including any service charges).",
<li>Rent to Buy lettings – where a discount of up to 20% market rent is charged for a single rental period for a minimum of 5 years. After that period, the tenant is offered the chance to purchase the property (either shared ownership or outright) at full market value.</li> "Rent to Buy lettings – where a discount of up to 20% market rent is charged for a single rental period for a minimum of 5 years. After that period, the tenant is offered the chance to purchase the property (either shared ownership or outright) at full market value.",
<li>London Living Rent lettings – a type of Intermediate Rent available in London through the Greater London Authority (GLA).</li> "London Living Rent lettings – a type of Intermediate Rent available in London through the Greater London Authority (GLA).",
</ul> ],
type: :bullet %>
<% end %> <% end %>
<%= accordion.with_section(heading_text: "Types of lettings you should not create logs for") do %> <%= accordion.with_section(heading_text: "Types of lettings you should not create logs for") do %>
<p class="govuk-body">You don’t need to create a log for:</p> <p class="govuk-body">You don’t need to create a log for:</p>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list [
<li>Temporary general needs housing with a fixed period of less than 2 years if they are social or affordable rent. (Temporary lettings for intermediate rent and supported housing should be recorded).</li> "Temporary general needs housing with a fixed period of less than 2 years if they are social or affordable rent. (Temporary lettings for intermediate rent and supported housing should be recorded).",
<li>Starter tenancies or lettings with an introductory period that roll onto or convert into the main tenancy. The CORE log should be completed at the beginning of this period.</li> "Starter tenancies or lettings with an introductory period that roll onto or convert into the main tenancy. The CORE log should be completed at the beginning of this period.",
<li>Changes from sole to joint or joint to sole tenancies, where the number of people in the household has not changed.</li> "Changes from sole to joint or joint to sole tenancies, where the number of people in the household has not changed.",
<li>Moves within a shared housing unit resulting in the same support needs or property type, even if a new tenancy or licence agreement is issued.</li> "Moves within a shared housing unit resulting in the same support needs or property type, even if a new tenancy or licence agreement is issued.",
<li>Lettings where no new tenancy agreement is signed.</li> "Lettings where no new tenancy agreement is signed.",
<li>Where stock is acquired, transferred or permanently decanted and the existing tenants are not issued with a new tenancy agreement.</li> "Where stock is acquired, transferred or permanently decanted and the existing tenants are not issued with a new tenancy agreement.",
<li>Mutual exchanges including lettings where registered provider tenants have exchanged homes, for example through the national HOMESWAP system.</li> "Mutual exchanges including lettings where registered provider tenants have exchanged homes, for example through the national HOMESWAP system.",
<li>Successions and assignments.</li> "Successions and assignments.",
<li>Demotion of a secure or assured tenancy, and any subsequent conversion of the demoted tenancy to a secure or assured tenancy.</li> "Demotion of a secure or assured tenancy, and any subsequent conversion of the demoted tenancy to a secure or assured tenancy.",
<li>Lettings made to asylum seekers who are awaiting a decision on their applications for asylum under the Immigration and Asylum Act 1999.</li> "Lettings made to asylum seekers who are awaiting a decision on their applications for asylum under the Immigration and Asylum Act 1999.",
<li>Non-social lettings, including market-rented properties, employer-provided housing where the employer provides financial support, homes for staff of social landlords linked to employment, homes social landlords manage for organisations who are not social landlords, homes social landlords own but lease in entirety to organisations who are not social landlords, and freehold housing with variable charges for services and communal facilities.</li> "Non-social lettings, including market-rented properties, employer-provided housing where the employer provides financial support, homes for staff of social landlords linked to employment, homes social landlords manage for organisations who are not social landlords, homes social landlords own but lease in entirety to organisations who are not social landlords, and freehold housing with variable charges for services and communal facilities.",
</ul> ],
type: :bullet %>
<% end %> <% end %>
<%= accordion.with_section(heading_text: "What if someone is reluctant to answer any questions?") do %> <%= accordion.with_section(heading_text: "What if someone is reluctant to answer any questions?") do %>
<p class="govuk-body">If a tenant or buyer is reluctant to answer questions as part of a log, you should explain that:</p> <p class="govuk-body">If a tenant or buyer is reluctant to answer questions as part of a log, you should explain that:</p>
<ul class="govuk-list govuk-list--bullet"> <%= govuk_list [
<li>all information they provide is anonymous and will not affect their housing, benefits or other services they receive.</li> "all information they provide is anonymous and will not affect their housing, benefits or other services they receive.",
<li>the data they provide is vital in helping to build a complete picture of social housing in England and is used to inform social housing policy.</li> "the data they provide is vital in helping to build a complete picture of social housing in England and is used to inform social housing policy.",
</ul> ],
type: :bullet %>
<p class="govuk-body">If a tenant or buyer is still unwilling or unable to answer questions, select the ‘Don’t know’ or ‘Tenant/person prefers not to say’ options.</p> <p class="govuk-body">If a tenant or buyer is still unwilling or unable to answer questions, select the ‘Don’t know’ or ‘Tenant/person prefers not to say’ options.</p>
<% end %> <% end %>
<% end %> <% end %>

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

Loading…
Cancel
Save