diff --git a/.github/workflows/aws_deploy.yml b/.github/workflows/aws_deploy.yml index c72c9d874..5af3c2d08 100644 --- a/.github/workflows/aws_deploy.yml +++ b/.github/workflows/aws_deploy.yml @@ -41,19 +41,17 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v3 + uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ env.aws_region }} role-to-assume: ${{ env.app_repo_role }} - name: Login to Amazon ECR id: ecr-login - uses: aws-actions/amazon-ecr-login@v1 - with: - mask-password: "true" + uses: aws-actions/amazon-ecr-login@v2 - name: Check if image with tag already exists run: | @@ -81,16 +79,14 @@ jobs: steps: - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v3 + uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ env.aws_region }} role-to-assume: ${{ env.app_repo_role }} - name: Login to Amazon ECR id: ecr-login - uses: aws-actions/amazon-ecr-login@v1 - with: - mask-password: "true" + uses: aws-actions/amazon-ecr-login@v2 - name: Get timestamp id: timestamp @@ -112,7 +108,7 @@ jobs: echo "image=$registry/$repository:$readable_tag" >> $GITHUB_ENV - name: Configure AWS credentials for environment - uses: aws-actions/configure-aws-credentials@v3 + uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ env.aws_region }} role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/${{ inputs.aws_role_prefix }}-deployment @@ -133,7 +129,7 @@ jobs: image: ${{ env.image }} - 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: task-definition: ${{ steps.ad-hoc-task-def.outputs.task-definition }} @@ -185,7 +181,7 @@ jobs: image: ${{ env.image }} - name: Deploy updated application - uses: aws-actions/amazon-ecs-deploy-task-definition@v1 + uses: aws-actions/amazon-ecs-deploy-task-definition@v2 with: cluster: ${{ inputs.aws_task_prefix }}-app service: ${{ inputs.aws_task_prefix }}-app @@ -207,7 +203,7 @@ jobs: image: ${{ env.image }} - name: Deploy updated sidekiq - uses: aws-actions/amazon-ecs-deploy-task-definition@v1 + uses: aws-actions/amazon-ecs-deploy-task-definition@v2 with: cluster: ${{ inputs.aws_task_prefix }}-app service: ${{ inputs.aws_task_prefix }}-sidekiq diff --git a/.github/workflows/review_teardown_pipeline.yml b/.github/workflows/review_teardown_pipeline.yml index 4303e4063..8925b3340 100644 --- a/.github/workflows/review_teardown_pipeline.yml +++ b/.github/workflows/review_teardown_pipeline.yml @@ -25,13 +25,13 @@ jobs: steps: - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v3 + uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ env.aws_region }} role-to-assume: ${{ env.app_repo_role }} - name: Configure AWS credentials for review environment - uses: aws-actions/configure-aws-credentials@v3 + uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ env.aws_region }} 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) 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) - echo "Waiting for db prepare task to complete" + echo "Waiting for db drop task to complete" temp=${arn##*/} id=${temp%*\"} aws ecs wait tasks-stopped --cluster $cluster --tasks $id diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml new file mode 100644 index 000000000..1965a4034 --- /dev/null +++ b/.github/workflows/run_tests.yml @@ -0,0 +1,411 @@ +name: Run Tests + +on: + workflow_call: + pull_request: + types: + - opened + - synchronize + merge_group: + workflow_dispatch: + +defaults: + run: + shell: bash + +jobs: + test: + name: Tests + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13.5 + env: + POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + POSTGRES_DB: data_collector + ports: + - 5432:5432 + # Needed because the Postgres container does not provide a health check + # tmpfs makes database faster by using RAM + options: >- + --mount type=tmpfs,destination=/var/lib/postgresql/data + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + RAILS_ENV: test + GEMFILE_RUBY_VERSION: 3.1.1 + DB_HOST: localhost + DB_DATABASE: data_collector + DB_USERNAME: postgres + DB_PASSWORD: password + RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} + PARALLEL_TEST_PROCESSORS: 4 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + cache: yarn + node-version: 20 + + - name: Create database + run: | + bundle exec rake parallel:setup + + - name: Compile assets + run: | + bundle exec rake assets:precompile + + - name: Run tests + run: | + bundle exec rake parallel:spec['spec\/(?!features|models|requests|services)'] + + feature_test: + name: Feature Tests + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13.5 + env: + POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + POSTGRES_DB: data_collector + ports: + - 5432:5432 + # Needed because the Postgres container does not provide a health check + # tmpfs makes database faster by using RAM + options: >- + --mount type=tmpfs,destination=/var/lib/postgresql/data + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + RAILS_ENV: test + GEMFILE_RUBY_VERSION: 3.1.1 + DB_HOST: localhost + DB_DATABASE: data_collector + DB_USERNAME: postgres + DB_PASSWORD: password + RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + cache: yarn + node-version: 20 + + - name: Create database + run: | + bundle exec rake db:prepare + + - name: Compile assets + run: | + bundle exec rake assets:precompile + + - name: Run tests + run: | + bundle exec rspec spec/features --fail-fast --exclude-pattern "spec/features/accessibility_spec.rb" + + model_test: + name: Model tests + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13.5 + env: + POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + POSTGRES_DB: data_collector + ports: + - 5432:5432 + # Needed because the Postgres container does not provide a health check + # tmpfs makes database faster by using RAM + options: >- + --mount type=tmpfs,destination=/var/lib/postgresql/data + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + RAILS_ENV: test + GEMFILE_RUBY_VERSION: 3.1.1 + DB_HOST: localhost + DB_DATABASE: data_collector + DB_USERNAME: postgres + DB_PASSWORD: password + RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + cache: yarn + node-version: 20 + + - name: Create database + run: | + bundle exec rake db:prepare + + - name: Compile assets + run: | + bundle exec rake assets:precompile + + - name: Run tests + run: | + bundle exec rspec spec/models --fail-fast + + requests_test: + name: Requests tests + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13.5 + env: + POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + POSTGRES_DB: data_collector + ports: + - 5432:5432 + # Needed because the Postgres container does not provide a health check + # tmpfs makes database faster by using RAM + options: >- + --mount type=tmpfs,destination=/var/lib/postgresql/data + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + RAILS_ENV: test + GEMFILE_RUBY_VERSION: 3.1.1 + DB_HOST: localhost + DB_DATABASE: data_collector + DB_USERNAME: postgres + DB_PASSWORD: password + RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} + PARALLEL_TEST_PROCESSORS: 4 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + cache: yarn + node-version: 20 + + - name: Create database + run: | + bundle exec rake parallel:setup + + - name: Compile assets + run: | + bundle exec rake assets:precompile + + - name: Run tests + run: | + bundle exec rake parallel:spec['spec/requests'] + + services_test: + name: Services Tests + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13.5 + env: + POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + POSTGRES_DB: data_collector + ports: + - 5432:5432 + # Needed because the Postgres container does not provide a health check + # tmpfs makes database faster by using RAM + options: >- + --mount type=tmpfs,destination=/var/lib/postgresql/data + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + RAILS_ENV: test + GEMFILE_RUBY_VERSION: 3.1.1 + DB_HOST: localhost + DB_DATABASE: data_collector + DB_USERNAME: postgres + DB_PASSWORD: password + RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} + PARALLEL_TEST_PROCESSORS: 4 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + cache: yarn + node-version: 20 + + - name: Create database + run: | + bundle exec rake parallel:setup + + - name: Compile assets + run: | + bundle exec rake assets:precompile + + - name: Run tests + run: | + bundle exec rake parallel:spec['spec\/services'] + + accessibility_test: + name: Accessibility tests + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13.5 + env: + POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + POSTGRES_DB: data_collector + ports: + - 5432:5432 + # Needed because the Postgres container does not provide a health check + # tmpfs makes database faster by using RAM + options: >- + --mount type=tmpfs,destination=/var/lib/postgresql/data + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + RAILS_ENV: test + GEMFILE_RUBY_VERSION: 3.1.1 + DB_HOST: localhost + DB_DATABASE: data_collector + DB_USERNAME: postgres + DB_PASSWORD: password + RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} + PARALLEL_TEST_PROCESSORS: 4 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + cache: yarn + node-version: 20 + + - name: Create database + run: | + bundle exec rake parallel:setup + + - name: Compile assets + run: | + bundle exec rake assets:precompile + + - name: Run tests + run: | + bundle exec rspec spec/features/accessibility_spec.rb --fail-fast + + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + cache: yarn + node-version: 20 + + - name: Install packages and symlink local dependencies + run: | + yarn install --immutable --immutable-cache --check-cache + + - name: Lint + run: | + bundle exec rake lint + + audit: + name: Audit dependencies + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Audit + run: | + bundle exec bundler-audit diff --git a/.github/workflows/staging_pipeline.yml b/.github/workflows/staging_pipeline.yml index e5447f4ef..a2e777db0 100644 --- a/.github/workflows/staging_pipeline.yml +++ b/.github/workflows/staging_pipeline.yml @@ -4,11 +4,6 @@ on: push: branches: - main - pull_request: - types: - - opened - - synchronize - merge_group: workflow_dispatch: defaults: @@ -21,347 +16,13 @@ env: repository: core jobs: - test: - name: Tests - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:13.5 - env: - POSTGRES_PASSWORD: password - POSTGRES_USER: postgres - POSTGRES_DB: data_collector - ports: - - 5432:5432 - # Needed because the Postgres container does not provide a health check - # tmpfs makes database faster by using RAM - options: >- - --mount type=tmpfs,destination=/var/lib/postgresql/data - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - env: - RAILS_ENV: test - GEMFILE_RUBY_VERSION: 3.1.1 - DB_HOST: localhost - DB_DATABASE: data_collector - DB_USERNAME: postgres - DB_PASSWORD: password - RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} - PARALLEL_TEST_PROCESSORS: 4 - - steps: - - name: Checkout - uses: actions/checkout@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 + tests: + name: Run Tests + uses: ./.github/workflows/run_tests.yml aws_deploy: name: AWS Deploy - if: github.ref == 'refs/heads/main' - needs: [lint, test, feature_test, requests_test, model_test, audit] + needs: [tests] uses: ./.github/workflows/aws_deploy.yml with: aws_account_id: 107155005276 @@ -379,13 +40,13 @@ jobs: steps: - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v3 + uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ env.aws_region }} role-to-assume: ${{ env.app_repo_role }} - name: Configure AWS credentials for the environment - uses: aws-actions/configure-aws-credentials@v3 + uses: aws-actions/configure-aws-credentials@v4 with: aws-region: eu-west-2 role-to-assume: arn:aws:iam::107155005276:role/core-staging-deployment diff --git a/Gemfile b/Gemfile index e9af29d55..027ed10d0 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby "3.1.4" # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' -gem "rails", "~> 7.0.8.5" +gem "rails", "~> 7.0.8.7" # Use postgresql as the database for Active Record gem "pg", "~> 1.1" # Use Puma as the app server diff --git a/Gemfile.lock b/Gemfile.lock index 1a98463b5..329c92051 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,71 +1,71 @@ GEM remote: https://rubygems.org/ specs: - actioncable (7.0.8.5) - actionpack (= 7.0.8.5) - activesupport (= 7.0.8.5) + actioncable (7.0.8.7) + actionpack (= 7.0.8.7) + activesupport (= 7.0.8.7) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.8.5) - actionpack (= 7.0.8.5) - activejob (= 7.0.8.5) - activerecord (= 7.0.8.5) - activestorage (= 7.0.8.5) - activesupport (= 7.0.8.5) + actionmailbox (7.0.8.7) + actionpack (= 7.0.8.7) + activejob (= 7.0.8.7) + activerecord (= 7.0.8.7) + activestorage (= 7.0.8.7) + activesupport (= 7.0.8.7) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.8.5) - actionpack (= 7.0.8.5) - actionview (= 7.0.8.5) - activejob (= 7.0.8.5) - activesupport (= 7.0.8.5) + actionmailer (7.0.8.7) + actionpack (= 7.0.8.7) + actionview (= 7.0.8.7) + activejob (= 7.0.8.7) + activesupport (= 7.0.8.7) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.8.5) - actionview (= 7.0.8.5) - activesupport (= 7.0.8.5) + actionpack (7.0.8.7) + actionview (= 7.0.8.7) + activesupport (= 7.0.8.7) rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.8.5) - actionpack (= 7.0.8.5) - activerecord (= 7.0.8.5) - activestorage (= 7.0.8.5) - activesupport (= 7.0.8.5) + actiontext (7.0.8.7) + actionpack (= 7.0.8.7) + activerecord (= 7.0.8.7) + activestorage (= 7.0.8.7) + activesupport (= 7.0.8.7) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.8.5) - activesupport (= 7.0.8.5) + actionview (7.0.8.7) + activesupport (= 7.0.8.7) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.8.5) - activesupport (= 7.0.8.5) + activejob (7.0.8.7) + activesupport (= 7.0.8.7) globalid (>= 0.3.6) - activemodel (7.0.8.5) - activesupport (= 7.0.8.5) + activemodel (7.0.8.7) + activesupport (= 7.0.8.7) activemodel-serializers-xml (1.0.2) activemodel (> 5.x) activesupport (> 5.x) builder (~> 3.1) - activerecord (7.0.8.5) - activemodel (= 7.0.8.5) - activesupport (= 7.0.8.5) - activestorage (7.0.8.5) - actionpack (= 7.0.8.5) - activejob (= 7.0.8.5) - activerecord (= 7.0.8.5) - activesupport (= 7.0.8.5) + activerecord (7.0.8.7) + activemodel (= 7.0.8.7) + activesupport (= 7.0.8.7) + activestorage (7.0.8.7) + actionpack (= 7.0.8.7) + activejob (= 7.0.8.7) + activerecord (= 7.0.8.7) + activesupport (= 7.0.8.7) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.8.5) + activesupport (7.0.8.7) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -149,7 +149,7 @@ GEM crass (1.0.6) cssbundling-rails (1.4.0) railties (>= 6.0.0) - date (3.3.4) + date (3.4.1) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) devise (4.9.3) @@ -246,7 +246,7 @@ GEM listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.22.0) + loofah (2.23.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -258,13 +258,13 @@ GEM matrix (0.4.2) method_source (1.1.0) mini_mime (1.1.5) - minitest (5.25.1) + minitest (5.25.4) msgpack (1.7.2) multipart-post (2.4.1) nested_form (0.3.2) net-http (0.4.1) uri - net-imap (0.4.17) + net-imap (0.5.1) date net-protocol net-pop (0.1.2) @@ -273,12 +273,12 @@ GEM timeout net-smtp (0.5.0) net-protocol - nio4r (2.7.3) - nokogiri (1.16.7-arm64-darwin) + nio4r (2.7.4) + nokogiri (1.17.1-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.7-x86_64-darwin) + nokogiri (1.17.1-x86_64-darwin) racc (~> 1.4) - nokogiri (1.16.7-x86_64-linux) + nokogiri (1.17.1-x86_64-linux) racc (~> 1.4) notifications-ruby-client (6.0.0) jwt (>= 1.5, < 3) @@ -327,36 +327,36 @@ GEM rack (>= 1.2.0) rack-test (2.1.0) rack (>= 1.3) - rails (7.0.8.5) - actioncable (= 7.0.8.5) - actionmailbox (= 7.0.8.5) - actionmailer (= 7.0.8.5) - actionpack (= 7.0.8.5) - actiontext (= 7.0.8.5) - actionview (= 7.0.8.5) - activejob (= 7.0.8.5) - activemodel (= 7.0.8.5) - activerecord (= 7.0.8.5) - activestorage (= 7.0.8.5) - activesupport (= 7.0.8.5) + rails (7.0.8.7) + actioncable (= 7.0.8.7) + actionmailbox (= 7.0.8.7) + actionmailer (= 7.0.8.7) + actionpack (= 7.0.8.7) + actiontext (= 7.0.8.7) + actionview (= 7.0.8.7) + activejob (= 7.0.8.7) + activemodel (= 7.0.8.7) + activerecord (= 7.0.8.7) + activestorage (= 7.0.8.7) + activesupport (= 7.0.8.7) bundler (>= 1.15.0) - railties (= 7.0.8.5) + railties (= 7.0.8.7) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.0) + rails-html-sanitizer (1.6.1) loofah (~> 2.21) - nokogiri (~> 1.14) + 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) activemodel-serializers-xml (>= 1.0) kaminari (>= 0.14, < 2.0) nested_form (~> 0.3) rails (>= 6.0, < 8) turbo-rails (~> 1.0) - railties (7.0.8.5) - actionpack (= 7.0.8.5) - activesupport (= 7.0.8.5) + railties (7.0.8.7) + actionpack (= 7.0.8.7) + activesupport (= 7.0.8.7) method_source rake (>= 12.2) thor (~> 1.0) @@ -465,7 +465,7 @@ GEM thor (1.3.2) thread_safe (0.3.6) timecop (0.9.8) - timeout (0.4.1) + timeout (0.4.2) turbo-rails (1.5.0) actionpack (>= 6.0.0) activejob (>= 6.0.0) @@ -553,7 +553,7 @@ DEPENDENCIES rack (>= 2.2.6.3) rack-attack rack-mini-profiler (~> 2.0) - rails (~> 7.0.8.5) + rails (~> 7.0.8.7) rails_admin (~> 3.1) redcarpet (~> 3.6) redis (~> 4.8) diff --git a/app/components/search_result_caption_component.html.erb b/app/components/search_result_caption_component.html.erb index b8a9382b7..b2a28a505 100644 --- a/app/components/search_result_caption_component.html.erb +++ b/app/components/search_result_caption_component.html.erb @@ -7,7 +7,7 @@ <%= count %> <%= item_label.pluralize(count) %> matching filters
<% else %> - <%= count %> total <%= item %> + <%= count %> total <%= item.pluralize(count) %> <% end %> diff --git a/app/controllers/bulk_upload_lettings_logs_controller.rb b/app/controllers/bulk_upload_lettings_logs_controller.rb index a8ad14f9e..39bc05f7e 100644 --- a/app/controllers/bulk_upload_lettings_logs_controller.rb +++ b/app/controllers/bulk_upload_lettings_logs_controller.rb @@ -1,6 +1,7 @@ class BulkUploadLettingsLogsController < ApplicationController before_action :authenticate_user! - before_action :validate_data_protection_agrement_signed! + before_action :validate_data_protection_agreement_signed! + before_action :validate_year!, except: %w[start] def start if have_choice_of_year? @@ -24,12 +25,26 @@ class BulkUploadLettingsLogsController < ApplicationController private - def validate_data_protection_agrement_signed! + def validate_data_protection_agreement_signed! return if @current_user.organisation.data_protection_confirmed? redirect_to lettings_logs_path end + def validate_year! + return if params[:id] == "year" + return if params[:id] == "guidance" && params.dig(:form, :year).nil? + + allowed_years = [current_year] + allowed_years.push(current_year - 1) if FormHandler.instance.lettings_in_crossover_period? + allowed_years.push(current_year + 1) if FeatureToggle.allow_future_form_use? + + provided_year = params.dig(:form, :year)&.to_i + return if allowed_years.include?(provided_year) + + render_not_found + end + def current_year FormHandler.instance.current_collection_start_year end @@ -48,8 +63,6 @@ private Forms::BulkUploadLettings::PrepareYourFile.new(form_params) when "guidance" Forms::BulkUploadLettings::Guidance.new(form_params.merge(referrer: params[:referrer])) - when "needstype" - Forms::BulkUploadLettings::Needstype.new(form_params) when "upload-your-file" Forms::BulkUploadLettings::UploadYourFile.new(form_params.merge(current_user:)) when "checking-file" @@ -60,6 +73,6 @@ private end def form_params - params.fetch(:form, {}).permit(:year, :needstype, :file, :organisation_id) + params.fetch(:form, {}).permit(:year, :file, :organisation_id) end end diff --git a/app/controllers/bulk_upload_sales_logs_controller.rb b/app/controllers/bulk_upload_sales_logs_controller.rb index 2fd312d10..cb04cea95 100644 --- a/app/controllers/bulk_upload_sales_logs_controller.rb +++ b/app/controllers/bulk_upload_sales_logs_controller.rb @@ -1,6 +1,7 @@ class BulkUploadSalesLogsController < ApplicationController before_action :authenticate_user! - before_action :validate_data_protection_agrement_signed! + before_action :validate_data_protection_agreement_signed! + before_action :validate_year!, except: %w[start] def start if have_choice_of_year? @@ -24,12 +25,26 @@ class BulkUploadSalesLogsController < ApplicationController private - def validate_data_protection_agrement_signed! + def validate_data_protection_agreement_signed! return if @current_user.organisation.data_protection_confirmed? redirect_to sales_logs_path end + def validate_year! + return if params[:id] == "year" + return if params[:id] == "guidance" && params.dig(:form, :year).nil? + + allowed_years = [current_year] + allowed_years.push(current_year - 1) if FormHandler.instance.sales_in_crossover_period? + allowed_years.push(current_year + 1) if FeatureToggle.allow_future_form_use? + + provided_year = params.dig(:form, :year)&.to_i + return if allowed_years.include?(provided_year) + + render_not_found + end + def current_year FormHandler.instance.current_collection_start_year end diff --git a/app/controllers/organisations_controller.rb b/app/controllers/organisations_controller.rb index 8ffe426d7..93b667a99 100644 --- a/app/controllers/organisations_controller.rb +++ b/app/controllers/organisations_controller.rb @@ -155,6 +155,7 @@ class OrganisationsController < ApplicationController end redirect_to details_organisation_path(@organisation) else + @used_rent_periods = @organisation.lettings_logs.pluck(:period).uniq.compact.map(&:to_s) @rent_periods = helpers.rent_periods_with_checked_attr(checked_periods: selected_rent_periods) render :edit, status: :unprocessable_entity end diff --git a/app/controllers/schemes_controller.rb b/app/controllers/schemes_controller.rb index 3dc642345..a76c9db52 100644 --- a/app/controllers/schemes_controller.rb +++ b/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" if scheme_params[:confirmed] == "true" || @scheme.confirmed? 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 @scheme.locations.update!(confirmed: true) flash[:notice] = if scheme_previously_confirmed @@ -162,7 +162,7 @@ class SchemesController < ApplicationController end elsif check_answers 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 redirect_to scheme_check_answers_path(@scheme) end diff --git a/app/helpers/schemes_helper.rb b/app/helpers/schemes_helper.rb index a3d6325cc..8078bd603 100644 --- a/app/helpers/schemes_helper.rb +++ b/app/helpers/schemes_helper.rb @@ -131,6 +131,24 @@ module SchemesHelper messages[attribute[:id]] || "Enter #{text}" 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 ActivePeriod = Struct.new(:from, :to) diff --git a/app/mailers/bulk_upload_mailer.rb b/app/mailers/bulk_upload_mailer.rb index e0115abb0..7c62f71a1 100644 --- a/app/mailers/bulk_upload_mailer.rb +++ b/app/mailers/bulk_upload_mailer.rb @@ -3,6 +3,7 @@ class BulkUploadMailer < NotifyMailer COMPLETE_TEMPLATE_ID = "83279578-c890-4168-838b-33c9cf0dc9f0".freeze FAILED_CSV_ERRORS_TEMPLATE_ID = "e27abcd4-5295-48c2-b127-e9ee4b781b75".freeze + FAILED_CSV_DUPLICATE_ERRORS_TEMPLATE_ID = "931d5bda-a08f-4de6-a455-38a63bff1956".freeze FAILED_FILE_SETUP_ERROR_TEMPLATE_ID = "24c9f4c7-96ad-470a-ba31-eb51b7cbafd9".freeze FAILED_SERVICE_ERROR_TEMPLATE_ID = "c3f6288c-7a74-4e77-99ee-6c4a0f6e125a".freeze HOW_TO_FIX_UPLOAD_TEMPLATE_ID = "21a07b26-f625-4846-9f4d-39e30937aa24".freeze @@ -95,6 +96,26 @@ class BulkUploadMailer < NotifyMailer ) end + def send_correct_duplicates_and_upload_again_mail(bulk_upload:) + summary_report_link = if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors? + bulk_upload.sales? ? summary_bulk_upload_sales_result_url(bulk_upload) : summary_bulk_upload_lettings_result_url(bulk_upload) + else + bulk_upload.sales? ? bulk_upload_sales_result_url(bulk_upload) : bulk_upload_lettings_result_url(bulk_upload) + end + + send_email( + bulk_upload.user.email, + FAILED_CSV_DUPLICATE_ERRORS_TEMPLATE_ID, + { + filename: bulk_upload.filename, + upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), + year_combo: bulk_upload.year_combo, + lettings_or_sales: bulk_upload.log_type, + summary_report_link:, + }, + ) + end + def send_bulk_upload_failed_file_setup_error_mail(bulk_upload:) bulk_upload_link = if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors? bulk_upload.sales? ? summary_bulk_upload_sales_result_url(bulk_upload) : summary_bulk_upload_lettings_result_url(bulk_upload) diff --git a/app/models/derived_variables/lettings_log_variables.rb b/app/models/derived_variables/lettings_log_variables.rb index 4692a0e6b..ced530b17 100644 --- a/app/models/derived_variables/lettings_log_variables.rb +++ b/app/models/derived_variables/lettings_log_variables.rb @@ -84,6 +84,15 @@ module DerivedVariables::LettingsLogVariables if uprn_known&.zero? self.uprn = nil + if uprn_known_was == 1 + self.address_line1 = nil + self.address_line2 = nil + self.town_or_city = nil + self.county = nil + self.postcode_known = nil + self.postcode_full = nil + self.la = nil + end end if uprn_known == 1 && uprn_confirmed&.zero? diff --git a/app/models/derived_variables/sales_log_variables.rb b/app/models/derived_variables/sales_log_variables.rb index 4f4c3105d..6e12ec488 100644 --- a/app/models/derived_variables/sales_log_variables.rb +++ b/app/models/derived_variables/sales_log_variables.rb @@ -53,6 +53,15 @@ module DerivedVariables::SalesLogVariables if uprn_known&.zero? self.uprn = nil + if uprn_known_was == 1 + self.address_line1 = nil + self.address_line2 = nil + self.town_or_city = nil + self.county = nil + self.pcodenk = nil + self.postcode_full = nil + self.la = nil + end end if uprn_known == 1 && uprn_confirmed&.zero? diff --git a/app/models/export.rb b/app/models/export.rb index 2ee04fe3d..27e574c72 100644 --- a/app/models/export.rb +++ b/app/models/export.rb @@ -1,2 +1,5 @@ class Export < ApplicationRecord + scope :lettings, -> { where(collection: "lettings") } + scope :organisations, -> { where(collection: "organisations") } + scope :users, -> { where(collection: "users") } end diff --git a/app/models/form/sales/questions/buyer2_working_situation.rb b/app/models/form/sales/questions/buyer2_working_situation.rb index 38ab320b3..9105b8597 100644 --- a/app/models/form/sales/questions/buyer2_working_situation.rb +++ b/app/models/form/sales/questions/buyer2_working_situation.rb @@ -15,6 +15,10 @@ class Form::Sales::Questions::Buyer2WorkingSituation < ::Form::Question @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] end + def displayed_answer_options(_log, _user = nil) + answer_options.reject { |key, _| key == "9" } + end + def answer_options if form.start_year_2025_or_later? { @@ -26,6 +30,7 @@ class Form::Sales::Questions::Buyer2WorkingSituation < ::Form::Question "6" => { "value" => "Not seeking work" }, "7" => { "value" => "Full-time student" }, "8" => { "value" => "Unable to work due to long term sick or disability" }, + "9" => { "value" => "Child under 16" }, "0" => { "value" => "Other" }, "10" => { "value" => "Buyer prefers not to say" }, }.freeze @@ -41,6 +46,7 @@ class Form::Sales::Questions::Buyer2WorkingSituation < ::Form::Question "0" => { "value" => "Other" }, "10" => { "value" => "Buyer prefers not to say" }, "7" => { "value" => "Full-time student" }, + "9" => { "value" => "Child under 16" }, }.freeze end end diff --git a/app/models/form/sales/questions/equity.rb b/app/models/form/sales/questions/equity.rb index e39e77ebb..74f5e9970 100644 --- a/app/models/form/sales/questions/equity.rb +++ b/app/models/form/sales/questions/equity.rb @@ -2,7 +2,7 @@ class Form::Sales::Questions::Equity < ::Form::Question def initialize(id, hsh, page) super @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" @min = 0 @max = 100 diff --git a/app/models/form/sales/questions/value.rb b/app/models/form/sales/questions/value.rb index ad021e920..ce8d07e28 100644 --- a/app/models/form/sales/questions/value.rb +++ b/app/models/form/sales/questions/value.rb @@ -2,7 +2,7 @@ class Form::Sales::Questions::Value < ::Form::Question def initialize(id, hsh, page) super @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" @min = 0 @step = 1 diff --git a/app/models/forms/bulk_upload_lettings/needstype.rb b/app/models/forms/bulk_upload_lettings/needstype.rb deleted file mode 100644 index b15c05b52..000000000 --- a/app/models/forms/bulk_upload_lettings/needstype.rb +++ /dev/null @@ -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 diff --git a/app/models/log.rb b/app/models/log.rb index dd4301550..bcbea9c92 100644 --- a/app/models/log.rb +++ b/app/models/log.rb @@ -126,9 +126,11 @@ class Log < ApplicationRecord end def address_options - return @address_options if @address_options + return @address_options if @address_options && @last_searched_address_string == address_string if [address_line1_input, postcode_full_input].all?(&:present?) + @last_searched_address_string = address_string + service = AddressClient.new(address_string) service.call if service.result.blank? || service.error.present? diff --git a/app/models/organisation.rb b/app/models/organisation.rb index 23f91f1ad..69c80d198 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -58,6 +58,7 @@ class Organisation < ApplicationRecord alias_method :la?, :LA? validates :name, presence: { message: I18n.t("validations.organisation.name_missing") } + validates :name, uniqueness: { case_sensitive: false, message: I18n.t("validations.organisation.name_not_unique") } validates :provider_type, presence: { message: I18n.t("validations.organisation.provider_type_missing") } def self.find_by_id_on_multiple_fields(id) diff --git a/app/models/scheme.rb b/app/models/scheme.rb index 1cd56ac7d..a9b479342 100644 --- a/app/models/scheme.rb +++ b/app/models/scheme.rb @@ -329,9 +329,9 @@ class Scheme < ApplicationRecord def status_at(date) return :deleted if discarded_at.present? - return :incomplete unless confirmed && locations.confirmed.any? return :deactivated if owning_organisation.status_at(date) == :deactivated || owning_organisation.status_at(date) == :merged || (open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date) + return :incomplete unless confirmed && locations.confirmed.any? return :deactivating_soon if open_deactivation&.deactivation_date.present? && date < open_deactivation.deactivation_date return :reactivating_soon if last_deactivation_before(date)&.reactivation_date.present? && date < last_deactivation_before(date).reactivation_date return :activating_soon if startdate.present? && date < startdate diff --git a/app/models/validations/sales/sale_information_validations.rb b/app/models/validations/sales/sale_information_validations.rb index 3825271c5..4b8948053 100644 --- a/app/models/validations/sales/sale_information_validations.rb +++ b/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? 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}%)" : "" - %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", mortgage: record.mortgage&.positive? ? " (#{record.field_formatted_as_currency('mortgage')})" : "", deposit_and_grant_sentence:, @@ -72,6 +72,12 @@ module Validations::Sales::SaleInformationValidations discount_sentence:, value_with_discount: record.field_formatted_as_currency("value_with_discount")).html_safe 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 diff --git a/app/services/bulk_upload/lettings/validator.rb b/app/services/bulk_upload/lettings/validator.rb index 116c3b745..291bf45e7 100644 --- a/app/services/bulk_upload/lettings/validator.rb +++ b/app/services/bulk_upload/lettings/validator.rb @@ -40,29 +40,25 @@ class BulkUpload::Lettings::Validator end end - def create_logs? - return false if any_setup_errors? + def block_log_creation_reason + return "setup_errors" if any_setup_errors? if row_parsers.any?(&:block_log_creation?) Sentry.capture_message("Bulk upload log creation blocked: #{bulk_upload.id}.") - return false + return "row_parser_block_log_creation" end if any_logs_already_exist? && FeatureToggle.bulk_upload_duplicate_log_check_enabled? - Sentry.capture_message("Bulk upload log creation blocked due to duplicate logs: #{bulk_upload.id}.") - return false + return "duplicate_logs" end row_parsers.each do |row_parser| row_parser.log.blank_invalid_non_setup_fields! end - if any_logs_invalid? Sentry.capture_message("Bulk upload log creation blocked due to invalid logs after blanking non setup fields: #{bulk_upload.id}.") - return false + "logs_invalid" end - - true end def self.question_for_field(field) diff --git a/app/services/bulk_upload/lettings/year2024/row_parser.rb b/app/services/bulk_upload/lettings/year2024/row_parser.rb index ef7433614..6315274ce 100644 --- a/app/services/bulk_upload/lettings/year2024/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2024/row_parser.rb @@ -607,14 +607,22 @@ private def validate_uprn_exists_if_any_key_address_fields_are_blank if field_16.blank? && !key_address_fields_provided? - errors.add(:field_16, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "UPRN.")) + %i[field_17 field_19 field_21 field_22].each do |field| + errors.add(field, I18n.t("#{ERROR_BASE_KEY}.address.not_answered")) if send(field).blank? + end + errors.add(:field_16, I18n.t("#{ERROR_BASE_KEY}.address.not_answered", question: "UPRN.")) end end def validate_address_option_found if log.uprn.nil? && field_16.blank? && key_address_fields_provided? + error_message = if log.address_options_present? + I18n.t("#{ERROR_BASE_KEY}.address.not_determined") + else + I18n.t("#{ERROR_BASE_KEY}.address.not_found") + end %i[field_17 field_18 field_19 field_20 field_21 field_22].each do |field| - errors.add(field, I18n.t("#{ERROR_BASE_KEY}.address.not_found")) + errors.add(field, error_message) if errors[field].blank? end end end @@ -625,19 +633,19 @@ private def validate_address_fields if field_16.blank? || log.errors.attribute_names.include?(:uprn) - if field_17.blank? + if field_17.blank? && errors[:field_17].blank? errors.add(:field_17, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "address line 1.")) end - if field_19.blank? + if field_19.blank? && errors[:field_19].blank? errors.add(:field_19, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "town or city.")) end - if field_21.blank? + if field_21.blank? && errors[:field_21].blank? errors.add(:field_21, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "part 1 of postcode.")) end - if field_22.blank? + if field_22.blank? && errors[:field_22].blank? errors.add(:field_22, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "part 2 of postcode.")) end end diff --git a/app/services/bulk_upload/processor.rb b/app/services/bulk_upload/processor.rb index 38f67ede4..c54032fda 100644 --- a/app/services/bulk_upload/processor.rb +++ b/app/services/bulk_upload/processor.rb @@ -36,20 +36,28 @@ class BulkUpload::Processor if validator.any_setup_errors? send_setup_errors_mail - - elsif validator.create_logs? - create_logs - - if validator.soft_validation_errors_only? - send_check_soft_validations_mail - elsif created_logs_but_incompleted? - send_how_to_fix_upload_mail - elsif created_logs_and_all_completed? - bulk_upload.unpend - send_success_mail - end else - send_correct_and_upload_again_mail # summary/full report + block_creation_reason = validator.block_log_creation_reason + + if block_creation_reason.present? + case block_creation_reason + when "duplicate_logs" + send_correct_duplicates_and_upload_again_mail + else + send_correct_and_upload_again_mail # summary/full report + end + else + create_logs + + if validator.soft_validation_errors_only? + send_check_soft_validations_mail + elsif created_logs_but_incompleted? + send_how_to_fix_upload_mail + elsif created_logs_and_all_completed? + bulk_upload.unpend + send_success_mail + end + end end rescue StandardError => e Sentry.capture_exception(e) @@ -97,6 +105,12 @@ private .deliver_later end + def send_correct_duplicates_and_upload_again_mail + BulkUploadMailer + .send_correct_duplicates_and_upload_again_mail(bulk_upload:) + .deliver_later + end + def send_success_mail BulkUploadMailer .send_bulk_upload_complete_mail(user:, bulk_upload:) diff --git a/app/services/bulk_upload/sales/validator.rb b/app/services/bulk_upload/sales/validator.rb index 76fb6f1ae..7ad9638d7 100644 --- a/app/services/bulk_upload/sales/validator.rb +++ b/app/services/bulk_upload/sales/validator.rb @@ -39,17 +39,17 @@ class BulkUpload::Sales::Validator end end - def create_logs? - return false if any_setup_errors? + def block_log_creation_reason + return "setup_errors" if any_setup_errors? if row_parsers.any?(&:block_log_creation?) Sentry.capture_message("Bulk upload log creation blocked: #{bulk_upload.id}.") - return false + return "row_parser_block_log_creation" end if any_logs_already_exist? && FeatureToggle.bulk_upload_duplicate_log_check_enabled? Sentry.capture_message("Bulk upload log creation blocked due to duplicate logs: #{bulk_upload.id}.") - return false + return "duplicate_logs" end row_parsers.each do |row_parser| @@ -58,10 +58,8 @@ class BulkUpload::Sales::Validator if any_logs_invalid? Sentry.capture_message("Bulk upload log creation blocked due to invalid logs after blanking non setup fields: #{bulk_upload.id}.") - return false + "logs_invalid" end - - true end def any_setup_errors? diff --git a/app/services/bulk_upload/sales/year2024/row_parser.rb b/app/services/bulk_upload/sales/year2024/row_parser.rb index fdef65aec..0e7b72d6f 100644 --- a/app/services/bulk_upload/sales/year2024/row_parser.rb +++ b/app/services/bulk_upload/sales/year2024/row_parser.rb @@ -605,14 +605,22 @@ private def validate_uprn_exists_if_any_key_address_fields_are_blank if field_22.blank? && !key_address_fields_provided? - errors.add(:field_22, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "UPRN.")) + %i[field_23 field_25 field_27 field_28].each do |field| + errors.add(field, I18n.t("#{ERROR_BASE_KEY}.address.not_answered")) if send(field).blank? + end + errors.add(:field_22, I18n.t("#{ERROR_BASE_KEY}.address.not_answered", question: "UPRN.")) end end def validate_address_option_found if log.uprn.nil? && field_22.blank? && key_address_fields_provided? + error_message = if log.address_options_present? + I18n.t("#{ERROR_BASE_KEY}.address.not_determined") + else + I18n.t("#{ERROR_BASE_KEY}.address.not_found") + end %i[field_23 field_24 field_25 field_26 field_27 field_28].each do |field| - errors.add(field, I18n.t("#{ERROR_BASE_KEY}.address.not_found")) + errors.add(field, error_message) if errors[field].blank? end end end @@ -623,19 +631,19 @@ private def validate_address_fields if field_22.blank? || log.errors.attribute_names.include?(:uprn) - if field_23.blank? + if field_23.blank? && errors[:field_23].blank? errors.add(:field_23, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "address line 1.")) end - if field_25.blank? + if field_25.blank? && errors[:field_25].blank? errors.add(:field_25, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "town or city.")) end - if field_27.blank? + if field_27.blank? && errors[:field_27].blank? errors.add(:field_27, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "part 1 of postcode.")) end - if field_28.blank? + if field_28.blank? && errors[:field_28].blank? errors.add(:field_28, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "part 2 of postcode.")) end end diff --git a/app/services/exports/export_service.rb b/app/services/exports/export_service.rb index bf98a4edf..40c10055b 100644 --- a/app/services/exports/export_service.rb +++ b/app/services/exports/export_service.rb @@ -7,7 +7,7 @@ module Exports @logger = logger end - def export_xml(full_update: false, collection: nil) + def export_xml(full_update: false, collection: nil, year: nil) start_time = Time.zone.now daily_run_number = get_daily_run_number lettings_archives_for_manifest = {} @@ -21,12 +21,12 @@ module Exports when "organisations" organisations_archives_for_manifest = get_organisation_archives(start_time, full_update) else - lettings_archives_for_manifest = get_lettings_archives(start_time, full_update, collection) + lettings_archives_for_manifest = get_lettings_archives(start_time, full_update, year) end else users_archives_for_manifest = get_user_archives(start_time, full_update) organisations_archives_for_manifest = get_organisation_archives(start_time, full_update) - lettings_archives_for_manifest = get_lettings_archives(start_time, full_update, collection) + lettings_archives_for_manifest = get_lettings_archives(start_time, full_update, year) end write_master_manifest(daily_run_number, lettings_archives_for_manifest.merge(users_archives_for_manifest).merge(organisations_archives_for_manifest)) @@ -70,9 +70,9 @@ module Exports organisations_export_service.export_xml_organisations(full_update:) end - def get_lettings_archives(start_time, full_update, collection) + def get_lettings_archives(start_time, full_update, collection_year) lettings_export_service = Exports::LettingsLogExportService.new(@storage_service, start_time) - lettings_export_service.export_xml_lettings_logs(full_update:, collection_year: collection) + lettings_export_service.export_xml_lettings_logs(full_update:, collection_year:) end end end diff --git a/app/services/exports/lettings_log_export_service.rb b/app/services/exports/lettings_log_export_service.rb index 97f495e0c..a8877eac3 100644 --- a/app/services/exports/lettings_log_export_service.rb +++ b/app/services/exports/lettings_log_export_service.rb @@ -5,11 +5,11 @@ module Exports def export_xml_lettings_logs(full_update: false, collection_year: nil) archives_for_manifest = {} - collection_years_to_export(collection_year).each do |collection| - recent_export = Export.where(collection:).order("started_at").last - base_number = Export.where(empty_export: false, collection:).maximum(:base_number) || 1 - export = build_export_run(collection, base_number, full_update) - archives = write_export_archive(export, collection, recent_export, full_update) + collection_years_to_export(collection_year).each do |year| + recent_export = Export.lettings.where(year:).order("started_at").last + base_number = Export.lettings.where(empty_export: false, year:).maximum(:base_number) || 1 + export = build_export_run("lettings", base_number, full_update, year) + archives = write_export_archive(export, year, recent_export, full_update) archives_for_manifest.merge!(archives) @@ -22,21 +22,21 @@ module Exports private - def get_archive_name(collection, base_number, increment) - return unless collection + def get_archive_name(year, base_number, increment) + return unless year base_number_str = "f#{base_number.to_s.rjust(4, '0')}" increment_str = "inc#{increment.to_s.rjust(4, '0')}" - "core_#{collection}_#{collection + 1}_apr_mar_#{base_number_str}_#{increment_str}".downcase + "core_#{year}_#{year + 1}_apr_mar_#{base_number_str}_#{increment_str}".downcase end - def retrieve_resources(recent_export, full_update, collection) + def retrieve_resources(recent_export, full_update, year) if !full_update && recent_export params = { from: recent_export.started_at, to: @start_time } - LettingsLog.exportable.where("(updated_at >= :from AND updated_at <= :to) OR (values_updated_at IS NOT NULL AND values_updated_at >= :from AND values_updated_at <= :to)", params).filter_by_year(collection) + LettingsLog.exportable.where("(updated_at >= :from AND updated_at <= :to) OR (values_updated_at IS NOT NULL AND values_updated_at >= :from AND values_updated_at <= :to)", params).filter_by_year(year) else params = { to: @start_time } - LettingsLog.exportable.where("updated_at <= :to", params).filter_by_year(collection) + LettingsLog.exportable.where("updated_at <= :to", params).filter_by_year(year) end end diff --git a/app/services/exports/organisation_export_service.rb b/app/services/exports/organisation_export_service.rb index 71eccf60a..2b2da71c8 100644 --- a/app/services/exports/organisation_export_service.rb +++ b/app/services/exports/organisation_export_service.rb @@ -5,9 +5,9 @@ module Exports def export_xml_organisations(full_update: false) collection = "organisations" - recent_export = Export.where(collection:).order("started_at").last + recent_export = Export.organisations.order("started_at").last - base_number = Export.where(empty_export: false, collection:).maximum(:base_number) || 1 + base_number = Export.organisations.where(empty_export: false).maximum(:base_number) || 1 export = build_export_run(collection, base_number, full_update) archives_for_manifest = write_export_archive(export, collection, recent_export, full_update) @@ -19,15 +19,13 @@ module Exports private - def get_archive_name(collection, base_number, increment) - return unless collection - + def get_archive_name(_year, base_number, increment) base_number_str = "f#{base_number.to_s.rjust(4, '0')}" increment_str = "inc#{increment.to_s.rjust(4, '0')}" - "#{collection}_2024_2025_apr_mar_#{base_number_str}_#{increment_str}".downcase + "organisations_2024_2025_apr_mar_#{base_number_str}_#{increment_str}".downcase end - def retrieve_resources(recent_export, full_update, _collection) + def retrieve_resources(recent_export, full_update, _year) if !full_update && recent_export params = { from: recent_export.started_at, to: @start_time } Organisation.where("(updated_at >= :from AND updated_at <= :to)", params) diff --git a/app/services/exports/user_export_service.rb b/app/services/exports/user_export_service.rb index 907a1cc86..aaa30c424 100644 --- a/app/services/exports/user_export_service.rb +++ b/app/services/exports/user_export_service.rb @@ -5,9 +5,9 @@ module Exports def export_xml_users(full_update: false) collection = "users" - recent_export = Export.where(collection:).order("started_at").last + recent_export = Export.users.order("started_at").last - base_number = Export.where(empty_export: false, collection:).maximum(:base_number) || 1 + base_number = Export.users.where(empty_export: false).maximum(:base_number) || 1 export = build_export_run(collection, base_number, full_update) archives_for_manifest = write_export_archive(export, collection, recent_export, full_update) @@ -19,15 +19,13 @@ module Exports private - def get_archive_name(collection, base_number, increment) - return unless collection - + def get_archive_name(_year, base_number, increment) base_number_str = "f#{base_number.to_s.rjust(4, '0')}" increment_str = "inc#{increment.to_s.rjust(4, '0')}" - "#{collection}_2024_2025_apr_mar_#{base_number_str}_#{increment_str}".downcase + "users_2024_2025_apr_mar_#{base_number_str}_#{increment_str}".downcase end - def retrieve_resources(recent_export, full_update, _collection) + def retrieve_resources(recent_export, full_update, _year) if !full_update && recent_export params = { from: recent_export.started_at, to: @start_time } User.where("(updated_at >= :from AND updated_at <= :to)", params) diff --git a/app/services/exports/xml_export_service.rb b/app/services/exports/xml_export_service.rb index 009a1b306..d499dd518 100644 --- a/app/services/exports/xml_export_service.rb +++ b/app/services/exports/xml_export_service.rb @@ -11,9 +11,9 @@ module Exports private - def build_export_run(collection, base_number, full_update) - @logger.info("Building export run for #{collection}") - previous_exports_with_data = Export.where(collection:, empty_export: false) + def build_export_run(collection, base_number, full_update, year = nil) + @logger.info("Building export run for #{[collection, year].join(' ')}") + previous_exports_with_data = Export.where(collection:, year:, empty_export: false) increment_number = previous_exports_with_data.where(base_number:).maximum(:increment_number) || 1 @@ -25,16 +25,16 @@ module Exports end if previous_exports_with_data.empty? - return Export.new(collection:, base_number:, started_at: @start_time) + return Export.new(collection:, year:, base_number:, started_at: @start_time) end - Export.new(collection:, started_at: @start_time, base_number:, increment_number:) + Export.new(collection:, year:, started_at: @start_time, base_number:, increment_number:) end - def write_export_archive(export, collection, recent_export, full_update) - archive = get_archive_name(collection, export.base_number, export.increment_number) # archive name would be the same for all logs because they're already filtered by year (?) + def write_export_archive(export, year, recent_export, full_update) + archive = get_archive_name(year, export.base_number, export.increment_number) - initial_count = retrieve_resources(recent_export, full_update, collection).count + initial_count = retrieve_resources(recent_export, full_update, year).count @logger.info("Creating #{archive} - #{initial_count} resources") return {} if initial_count.zero? @@ -46,12 +46,12 @@ module Exports loop do slice = if last_processed_marker.present? - retrieve_resources(recent_export, full_update, collection) + retrieve_resources(recent_export, full_update, year) .where("created_at > ?", last_processed_marker) .order(:created_at) .limit(MAX_XML_RECORDS).to_a else - retrieve_resources(recent_export, full_update, collection) + retrieve_resources(recent_export, full_update, year) .order(:created_at) .limit(MAX_XML_RECORDS).to_a end diff --git a/app/views/bulk_upload_lettings_logs/forms/needstype.erb b/app/views/bulk_upload_lettings_logs/forms/needstype.erb deleted file mode 100644 index 644dd9f5f..000000000 --- a/app/views/bulk_upload_lettings_logs/forms/needstype.erb +++ /dev/null @@ -1,23 +0,0 @@ -<% content_for :before_content do %> - <%= govuk_back_link href: @form.back_path %> -<% end %> - -
-
- <%= 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 %> -
-
diff --git a/app/views/bulk_upload_shared/uploads.html.erb b/app/views/bulk_upload_shared/uploads.html.erb index 958887453..a9d134c60 100644 --- a/app/views/bulk_upload_shared/uploads.html.erb +++ b/app/views/bulk_upload_shared/uploads.html.erb @@ -1,4 +1,4 @@ -<% item_label = format_label(@pagy.count, "uploads") %> +<% item_label = format_label(@pagy.count, "upload") %> <% title = format_title(@searched, bulk_upload_title(controller.controller_name), current_user, item_label, @pagy.count, nil) %> <% content_for :title, title %> diff --git a/app/views/locations/index.html.erb b/app/views/locations/index.html.erb index 23550f894..8ef5bcb56 100644 --- a/app/views/locations/index.html.erb +++ b/app/views/locations/index.html.erb @@ -32,7 +32,7 @@ count: @pagy.count, item_label:, total_count: @total_count, - item: "locations", + item: "location", filters_count: applied_filters_count(@filter_type), )) %> <% end %> diff --git a/app/views/logs/_log_list.html.erb b/app/views/logs/_log_list.html.erb index b5290c117..24714f247 100644 --- a/app/views/logs/_log_list.html.erb +++ b/app/views/logs/_log_list.html.erb @@ -1,7 +1,7 @@

- <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "logs", filters_count: applied_filters_count(@filter_type))) %> + <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "log", filters_count: applied_filters_count(@filter_type))) %> <% if logs&.any? %> <%= govuk_link_to "Download (CSV)", csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4", style: "white-space: nowrap" %> <% if @current_user.support? %> diff --git a/app/views/logs/index.html.erb b/app/views/logs/index.html.erb index f142a2580..c51466097 100644 --- a/app/views/logs/index.html.erb +++ b/app/views/logs/index.html.erb @@ -1,4 +1,4 @@ -<% item_label = format_label(@pagy.count, "logs") %> +<% item_label = format_label(@pagy.count, "log") %> <% title = format_title(@searched, "#{log_type_for_controller(controller).capitalize} logs", current_user, item_label, @pagy.count, nil) %> <% content_for :title, title %> diff --git a/app/views/logs/update_logs.html.erb b/app/views/logs/update_logs.html.erb index 1ab1fa31c..985adecf3 100644 --- a/app/views/logs/update_logs.html.erb +++ b/app/views/logs/update_logs.html.erb @@ -1,4 +1,4 @@ -<% item_label = format_label(@pagy.count, "logs") %> +<% item_label = format_label(@pagy.count, "log") %> <% title = "Update logs" %> <% content_for :title, title %> diff --git a/app/views/organisation_relationships/managing_agents.html.erb b/app/views/organisation_relationships/managing_agents.html.erb index 726533e53..d09056d86 100644 --- a/app/views/organisation_relationships/managing_agents.html.erb +++ b/app/views/organisation_relationships/managing_agents.html.erb @@ -1,4 +1,4 @@ -<% item_label = format_label(@pagy.count, "managing agents") %> +<% item_label = format_label(@pagy.count, "managing agent") %> <% if current_user.support? %> <%= render partial: "organisations/headings", locals: { main: @organisation.name, sub: nil } %> @@ -44,7 +44,7 @@ pagy: @pagy, searched: @searched, item_label:, - search_item: "managing agents", + search_item: "managing agent", total_count: @total_count, remove_path: ->(org_id) { managing_agents_remove_organisation_path(target_organisation_id: org_id) }, } %> diff --git a/app/views/organisation_relationships/stock_owners.html.erb b/app/views/organisation_relationships/stock_owners.html.erb index 41b7af06d..3bbba6bf8 100644 --- a/app/views/organisation_relationships/stock_owners.html.erb +++ b/app/views/organisation_relationships/stock_owners.html.erb @@ -1,4 +1,4 @@ -<% item_label = format_label(@pagy.count, "stock owners") %> +<% item_label = format_label(@pagy.count, "stock owner") %> <% if current_user.support? %> <%= render partial: "organisations/headings", locals: { main: @organisation.name, sub: nil } %> <%= render SubNavigationComponent.new(items: secondary_items(request.path, @organisation.id)) %> @@ -41,7 +41,7 @@ pagy: @pagy, searched: @searched, item_label:, - search_item: "stock owners", + search_item: "stock owner", total_count: @total_count, remove_path: ->(org_id) { stock_owners_remove_organisation_path(target_organisation_id: org_id) }, } %> diff --git a/app/views/organisations/_organisation_list.html.erb b/app/views/organisations/_organisation_list.html.erb index 67cc9c7a3..16309a5eb 100644 --- a/app/views/organisations/_organisation_list.html.erb +++ b/app/views/organisations/_organisation_list.html.erb @@ -1,7 +1,7 @@
<%= govuk_table do |table| %> <%= table.with_caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %> - <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "organisations", filters_count: applied_filters_count(@filter_type))) %> + <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "organisation", filters_count: applied_filters_count(@filter_type))) %> <% end %> <%= table.with_head do |head| %> <%= head.with_row do |row| %> diff --git a/app/views/organisations/index.html.erb b/app/views/organisations/index.html.erb index 411d792c1..1de12ab77 100644 --- a/app/views/organisations/index.html.erb +++ b/app/views/organisations/index.html.erb @@ -1,4 +1,4 @@ -<% item_label = format_label(@pagy.count, "organisations") %> +<% item_label = format_label(@pagy.count, "organisation") %> <% title = format_title(@searched, "Organisations", current_user, item_label, @pagy.count, nil) %> <% content_for :title, title %> diff --git a/app/views/organisations/logs.html.erb b/app/views/organisations/logs.html.erb index e172a76a9..e318ff6ee 100644 --- a/app/views/organisations/logs.html.erb +++ b/app/views/organisations/logs.html.erb @@ -1,4 +1,4 @@ -<% item_label = format_label(@pagy.count, "logs") %> +<% item_label = format_label(@pagy.count, "log") %> <% title = format_title(@searched, action_name.humanize, current_user, item_label, @pagy.count, @organisation.name) %> <% content_for :title, title %> diff --git a/app/views/schemes/_scheme_list.html.erb b/app/views/schemes/_scheme_list.html.erb index 967295236..1c11e86d1 100644 --- a/app/views/schemes/_scheme_list.html.erb +++ b/app/views/schemes/_scheme_list.html.erb @@ -2,7 +2,7 @@ <%= govuk_table do |table| %> <%= table.with_caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %> - <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "schemes", filters_count: applied_filters_count(@filter_type))) %> + <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "scheme", filters_count: applied_filters_count(@filter_type))) %> <% if @schemes&.any? %> <%= govuk_link_to "Download schemes (CSV)", schemes_csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4", style: "white-space: nowrap" %> <%= govuk_link_to "Download locations (CSV)", locations_csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4", style: "white-space: nowrap" %> diff --git a/app/views/schemes/confirm_secondary.html.erb b/app/views/schemes/confirm_secondary.html.erb index 9b715c264..9feca87bc 100644 --- a/app/views/schemes/confirm_secondary.html.erb +++ b/app/views/schemes/confirm_secondary.html.erb @@ -1,7 +1,7 @@ <% content_for :title, "Does this scheme provide for another client group?" %> <% content_for :before_content do %> - <%= govuk_back_link(href: :back) %> + <%= govuk_back_link(href: scheme_back_button_path(@scheme, "confirm_secondary_client_group")) %> <% end %> <%= render partial: "organisations/headings", locals: { main: "Does this scheme provide for another client group?", sub: @scheme.service_name } %> diff --git a/app/views/schemes/details.html.erb b/app/views/schemes/details.html.erb index 4b23ab016..d1943cabe 100644 --- a/app/views/schemes/details.html.erb +++ b/app/views/schemes/details.html.erb @@ -1,7 +1,7 @@ <% content_for :title, "Create a new supported housing scheme" %> <% content_for :before_content do %> - <%= govuk_back_link(href: :back) %> + <%= govuk_back_link(href: scheme_back_button_path(@scheme, "details")) %> <% end %> <%= form_for(@scheme, method: :patch) do |f| %> diff --git a/app/views/schemes/primary_client_group.html.erb b/app/views/schemes/primary_client_group.html.erb index 55dc504b4..68da2f35c 100644 --- a/app/views/schemes/primary_client_group.html.erb +++ b/app/views/schemes/primary_client_group.html.erb @@ -1,13 +1,7 @@ <% 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 %> - <%= govuk_back_link(href: back_button_path) %> + <%= govuk_back_link(href: scheme_back_button_path(@scheme, "primary_client_group")) %> <% end %> <%= form_for(@scheme, method: :patch) do |f| %> diff --git a/app/views/schemes/secondary_client_group.html.erb b/app/views/schemes/secondary_client_group.html.erb index cd50283e3..8200702d2 100644 --- a/app/views/schemes/secondary_client_group.html.erb +++ b/app/views/schemes/secondary_client_group.html.erb @@ -1,7 +1,7 @@ <% content_for :title, "What is the other client group?" %> <% content_for :before_content do %> - <%= govuk_back_link(href: :back) %> + <%= govuk_back_link(href: scheme_back_button_path(@scheme, "secondary_client_group")) %> <% end %> <%= form_for(@scheme, method: :patch) do |f| %> diff --git a/app/views/schemes/support.html.erb b/app/views/schemes/support.html.erb index a5d30ed7f..eca72014f 100644 --- a/app/views/schemes/support.html.erb +++ b/app/views/schemes/support.html.erb @@ -1,7 +1,7 @@ <% content_for :title, "What support does this scheme provide?" %> <% content_for :before_content do %> - <%= govuk_back_link(href: :back) %> + <%= govuk_back_link(href: scheme_back_button_path(@scheme, "support")) %> <% end %> <%= form_for(@scheme, method: :patch) do |f| %> diff --git a/app/views/users/_user_list.html.erb b/app/views/users/_user_list.html.erb index 436c0def2..82a82b33b 100644 --- a/app/views/users/_user_list.html.erb +++ b/app/views/users/_user_list.html.erb @@ -1,7 +1,7 @@
<%= govuk_table do |table| %> <%= table.with_caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %> - <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "users", filters_count: applied_filters_count(@filter_type))) %> + <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "user", filters_count: applied_filters_count(@filter_type))) %> <% if current_user.support? %> <% query = searched.present? ? "?search=#{searched}" : nil %> <%= govuk_link_to "Download (CSV)", "#{request.path}.csv#{query}", type: "text/csv", style: "white-space: nowrap" %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 851a9ea2c..965c1f7a6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -235,6 +235,7 @@ en: organisation: data_sharing_agreement_not_signed: "Your organisation must accept the Data Sharing Agreement before you can create any logs." name_missing: "Enter the name of the organisation." + name_not_unique: "An organisation with this name already exists. Use the organisation that already exists or add a location or other identifier to the name. For example: Organisation name (City)." provider_type_missing: "Select the organisation type." stock_owner: blank: "You must choose a stock owner." diff --git a/config/locales/forms/2023/lettings/household_characteristics.en.yml b/config/locales/forms/2023/lettings/household_characteristics.en.yml index f96c6b19f..9e162dae1 100644 --- a/config/locales/forms/2023/lettings/household_characteristics.en.yml +++ b/config/locales/forms/2023/lettings/household_characteristics.en.yml @@ -22,7 +22,7 @@ en: age1_known: check_answer_label: "" check_answer_prompt: "Answer if you know the lead tenant's age" - hint_text: "The ’lead’ or ’main’ tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest." + hint_text: "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest." question_text: "Do you know the lead tenant’s age?" age1: check_answer_label: "Lead tenant’s age" diff --git a/config/locales/forms/2023/sales/household_characteristics.en.yml b/config/locales/forms/2023/sales/household_characteristics.en.yml index a933316e9..4fc1e9183 100644 --- a/config/locales/forms/2023/sales/household_characteristics.en.yml +++ b/config/locales/forms/2023/sales/household_characteristics.en.yml @@ -120,10 +120,10 @@ en: question_text: "What is buyer 2's relationship to buyer 1?" person: page_header: "" - check_answer_label: "Person 2’s relationship to Buyer 1" + check_answer_label: "Person 2’s relationship to buyer 1" check_answer_prompt: "" hint_text: "" - question_text: "What is Person 2’s relationship to Buyer 1?" + question_text: "What is person 2’s relationship to buyer 1?" age2: buyer: @@ -163,7 +163,7 @@ en: check_answer_label: "Person 2’s gender identity" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 2’s gender identity?" + question_text: "Which of these best describes person 2’s gender identity?" ethnic_group2: page_header: "" @@ -223,7 +223,7 @@ en: check_answer_label: "Person 2’s working situation" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 2’s working situation?" + question_text: "Which of these best describes person 2’s working situation?" buy2livein: page_header: "" @@ -262,10 +262,10 @@ en: relat3: page_header: "" - check_answer_label: "Person 3’s relationship to Buyer 1" + check_answer_label: "Person 3’s relationship to buyer 1" check_answer_prompt: "" hint_text: "" - question_text: "What is Person 3’s relationship to Buyer 1?" + question_text: "What is person 3’s relationship to buyer 1?" age3: page_header: "" @@ -285,14 +285,14 @@ en: check_answer_label: "Person 3’s gender identity" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 3’s gender identity?" + question_text: "Which of these best describes person 3’s gender identity?" ecstat3: page_header: "" check_answer_label: "Person 3’s working situation" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 3’s working situation?" + question_text: "Which of these best describes person 3’s working situation?" details_known_4: page_header: "" @@ -303,10 +303,10 @@ en: relat4: page_header: "" - check_answer_label: "Person 4’s relationship to Buyer 1" + check_answer_label: "Person 4’s relationship to buyer 1" check_answer_prompt: "" hint_text: "" - question_text: "What is Person 4’s relationship to Buyer 1?" + question_text: "What is person 4’s relationship to buyer 1?" age4: page_header: "" @@ -326,14 +326,14 @@ en: check_answer_label: "Person 4’s gender identity" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 4’s gender identity?" + question_text: "Which of these best describes person 4’s gender identity?" ecstat4: page_header: "" check_answer_label: "Person 4’s working situation" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 4’s working situation?" + question_text: "Which of these best describes person 4’s working situation?" details_known_5: page_header: "" @@ -344,10 +344,10 @@ en: relat5: page_header: "" - check_answer_label: "Person 5’s relationship to Buyer 1" + check_answer_label: "Person 5’s relationship to buyer 1" check_answer_prompt: "" hint_text: "" - question_text: "What is Person 5’s relationship to Buyer 1?" + question_text: "What is person 5’s relationship to buyer 1?" age5: page_header: "" @@ -367,14 +367,14 @@ en: check_answer_label: "Person 5’s gender identity" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 5’s gender identity?" + question_text: "Which of these best describes person 5’s gender identity?" ecstat5: page_header: "" check_answer_label: "Person 5’s working situation" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 5’s working situation?" + question_text: "Which of these best describes person 5’s working situation?" details_known_6: page_header: "" @@ -385,10 +385,10 @@ en: relat6: page_header: "" - check_answer_label: "Person 6’s relationship to Buyer 1" + check_answer_label: "Person 6’s relationship to buyer 1" check_answer_prompt: "" hint_text: "" - question_text: "What is Person 6’s relationship to Buyer 1?" + question_text: "What is person 6’s relationship to buyer 1?" age6: page_header: "" @@ -408,11 +408,11 @@ en: check_answer_label: "Person 6’s gender identity" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 6’s gender identity?" + question_text: "Which of these best describes person 6’s gender identity?" ecstat6: page_header: "" check_answer_label: "Person 6’s working situation" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 6’s working situation?" + question_text: "Which of these best describes person 6’s working situation?" diff --git a/config/locales/forms/2023/sales/income_benefits_and_savings.en.yml b/config/locales/forms/2023/sales/income_benefits_and_savings.en.yml index 1eea1d0a9..45e57ea33 100644 --- a/config/locales/forms/2023/sales/income_benefits_and_savings.en.yml +++ b/config/locales/forms/2023/sales/income_benefits_and_savings.en.yml @@ -47,13 +47,11 @@ en: joint_purchase: page_header: "" check_answer_label: "Housing-related benefits buyers received before buying this property" - check_answer_prompt: "" hint_text: "" question_text: "Were the buyers receiving any of these housing-related benefits immediately before buying this property?" not_joint_purchase: page_header: "" check_answer_label: "Housing-related benefits buyer received before buying this property" - check_answer_prompt: "" hint_text: "" question_text: "Was the buyer receiving any of these housing-related benefits immediately before buying this property?" @@ -61,10 +59,10 @@ en: joint_purchase: page_header: "" savingsnk: - check_answer_label: "Buyers’ total savings known?" + check_answer_label: "Buyers’ total savings known" check_answer_prompt: "" hint_text: "" - question_text: "Do you know how much the 'buyers' had in savings before they paid any deposit for the property?" + question_text: "Do you know how much the buyers had in savings before they paid any deposit for the property?" savings: check_answer_label: "Buyers’ total savings before any deposit paid" check_answer_prompt: "" @@ -73,7 +71,7 @@ en: not_joint_purchase: page_header: "" savingsnk: - check_answer_label: "Buyer’s total savings known?" + check_answer_label: "Buyer’s total savings known" check_answer_prompt: "" hint_text: "" question_text: "Do you know how much the buyer had in savings before they paid any deposit for the property?" diff --git a/config/locales/forms/2023/sales/sale_information.en.yml b/config/locales/forms/2023/sales/sale_information.en.yml index 23768de7e..b6747f81e 100644 --- a/config/locales/forms/2023/sales/sale_information.en.yml +++ b/config/locales/forms/2023/sales/sale_information.en.yml @@ -33,7 +33,7 @@ en: page_header: "" check_answer_label: "Staircasing transaction" check_answer_prompt: "" - hint_text: "A staircasing transaction is when the household purchases more shares in their property, increasing the proportion they own and decreasing the proportion the housing association owns. Once the household purchases 100% of the shares, they own the property" + hint_text: "A staircasing transaction is when the household purchases more shares in their property, increasing the proportion they own and decreasing the proportion the housing association owns. Once the household purchases 100% of the shares, they own the property." question_text: "Is this a staircasing transaction?" about_staircasing: page_header: "About the staircasing transaction" @@ -86,7 +86,6 @@ en: joint_purchase: page_header: "" check_answer_label: "Any buyers were registered providers, housing association or local authority tenants immediately before this sale?" - check_answer_prompt: "" hint_text: "" question_text: "Were any of the buyers private registered providers, housing association or local authority tenants immediately before this sale?" not_joint_purchase: @@ -97,10 +96,10 @@ en: question_text: "Was the buyer a private registered provider, housing association or local authority tenant immediately before this sale?" frombeds: - page_header: "About the buyers’ previous property" + page_header: "" check_answer_label: "Number of bedrooms in previous property" check_answer_prompt: "" - hint_text: "For bedsits enter 1" + hint_text: "A bedsit has 1 bedroom." question_text: "How many bedrooms did the property have?" fromprop: @@ -132,14 +131,14 @@ en: question_text: "What was the initial percentage equity stake purchased?" mortgageused: - page_header: "Mortgage Amount" + page_header: "" check_answer_label: "Mortgage used" check_answer_prompt: "" hint_text: "" question_text: "Was a mortgage used for the purchase of this property?" mortgage: - page_header: "Mortgage Amount" + page_header: "" check_answer_label: "Mortgage amount" check_answer_prompt: "" hint_text: "Enter the amount of mortgage agreed with the mortgage lender. Exclude any deposits or cash payments. Numeric in pounds. Rounded to the nearest pound." diff --git a/config/locales/forms/2024/lettings/household_characteristics.en.yml b/config/locales/forms/2024/lettings/household_characteristics.en.yml index 821cdd649..fa5927942 100644 --- a/config/locales/forms/2024/lettings/household_characteristics.en.yml +++ b/config/locales/forms/2024/lettings/household_characteristics.en.yml @@ -15,7 +15,7 @@ en: age1_known: check_answer_label: "" check_answer_prompt: "Answer if you know the lead tenant's age" - hint_text: "The ’lead’ or ’main’ tenant is the person in the household who does the most paid work. If several people do the same amount of paid work, the lead tenant is whoever is the oldest." + hint_text: "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest." question_text: "Do you know the lead tenant’s age?" age1: check_answer_label: "Lead tenant’s age" diff --git a/config/locales/forms/2024/sales/household_characteristics.en.yml b/config/locales/forms/2024/sales/household_characteristics.en.yml index 92db39dbd..1926f920c 100644 --- a/config/locales/forms/2024/sales/household_characteristics.en.yml +++ b/config/locales/forms/2024/sales/household_characteristics.en.yml @@ -99,10 +99,10 @@ en: question_text: "What is buyer 2's relationship to buyer 1?" person: page_header: "" - check_answer_label: "Person 2’s relationship to Buyer 1" + check_answer_label: "Person 2’s relationship to buyer 1" check_answer_prompt: "" hint_text: "" - question_text: "What is Person 2’s relationship to Buyer 1?" + question_text: "What is person 2’s relationship to buyer 1?" age2: buyer: @@ -142,7 +142,7 @@ en: check_answer_label: "Person 2’s gender identity" check_answer_prompt: "" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 2’s gender identity?" + question_text: "Which of these best describes person 2’s gender identity?" ethnic_group2: page_header: "" @@ -209,7 +209,7 @@ en: check_answer_label: "Person 2’s working situation" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 2’s working situation?" + question_text: "Which of these best describes person 2’s working situation?" buy2livein: page_header: "" @@ -248,10 +248,10 @@ en: relat3: page_header: "" - check_answer_label: "Person 3’s relationship to Buyer 1" + check_answer_label: "Person 3’s relationship to buyer 1" check_answer_prompt: "" hint_text: "" - question_text: "What is Person 3’s relationship to Buyer 1?" + question_text: "What is person 3’s relationship to buyer 1?" age3: page_header: "" @@ -271,14 +271,14 @@ en: check_answer_label: "Person 3’s gender identity" check_answer_prompt: "" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 3’s gender identity?" + question_text: "Which of these best describes person 3’s gender identity?" ecstat3: page_header: "" check_answer_label: "Person 3’s working situation" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 3’s working situation?" + question_text: "Which of these best describes person 3’s working situation?" details_known_4: page_header: "" @@ -289,10 +289,10 @@ en: relat4: page_header: "" - check_answer_label: "Person 4’s relationship to Buyer 1" + check_answer_label: "Person 4’s relationship to buyer 1" check_answer_prompt: "" hint_text: "" - question_text: "What is Person 4’s relationship to Buyer 1?" + question_text: "What is person 4’s relationship to buyer 1?" age4: page_header: "" @@ -312,14 +312,14 @@ en: check_answer_label: "Person 4’s gender identity" check_answer_prompt: "" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 4’s gender identity?" + question_text: "Which of these best describes person 4’s gender identity?" ecstat4: page_header: "" check_answer_label: "Person 4’s working situation" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 4’s working situation?" + question_text: "Which of these best describes person 4’s working situation?" details_known_5: page_header: "" @@ -330,10 +330,10 @@ en: relat5: page_header: "" - check_answer_label: "Person 5’s relationship to Buyer 1" + check_answer_label: "Person 5’s relationship to buyer 1" check_answer_prompt: "" hint_text: "" - question_text: "What is Person 5’s relationship to Buyer 1?" + question_text: "What is person 5’s relationship to buyer 1?" age5: page_header: "" @@ -353,14 +353,14 @@ en: check_answer_label: "Person 5’s gender identity" check_answer_prompt: "" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 5’s gender identity?" + question_text: "Which of these best describes person 5’s gender identity?" ecstat5: page_header: "" check_answer_label: "Person 5’s working situation" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 5’s working situation?" + question_text: "Which of these best describes person 5’s working situation?" details_known_6: page_header: "" @@ -371,10 +371,10 @@ en: relat6: page_header: "" - check_answer_label: "Person 6’s relationship to Buyer 1" + check_answer_label: "Person 6’s relationship to buyer 1" check_answer_prompt: "" hint_text: "" - question_text: "What is Person 6’s relationship to Buyer 1?" + question_text: "What is person 6’s relationship to buyer 1?" age6: page_header: "" @@ -394,11 +394,11 @@ en: check_answer_label: "Person 6’s gender identity" check_answer_prompt: "" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 6’s gender identity?" + question_text: "Which of these best describes person 6’s gender identity?" ecstat6: page_header: "" check_answer_label: "Person 6’s working situation" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 6’s working situation?" + question_text: "Which of these best describes person 6’s working situation?" diff --git a/config/locales/forms/2024/sales/income_benefits_and_savings.en.yml b/config/locales/forms/2024/sales/income_benefits_and_savings.en.yml index c680e3167..31a7dd631 100644 --- a/config/locales/forms/2024/sales/income_benefits_and_savings.en.yml +++ b/config/locales/forms/2024/sales/income_benefits_and_savings.en.yml @@ -64,7 +64,7 @@ en: check_answer_label: "Buyers’ total savings known?" check_answer_prompt: "" hint_text: "" - question_text: "Do you know how much the 'buyers' had in savings before they paid any deposit for the property?" + question_text: "Do you know how much the buyers had in savings before they paid any deposit for the property?" savings: check_answer_label: "Buyers’ total savings before any deposit paid" check_answer_prompt: "" diff --git a/config/locales/forms/2024/sales/sale_information.en.yml b/config/locales/forms/2024/sales/sale_information.en.yml index cfde05d84..7d1a672d4 100644 --- a/config/locales/forms/2024/sales/sale_information.en.yml +++ b/config/locales/forms/2024/sales/sale_information.en.yml @@ -33,7 +33,7 @@ en: page_header: "" check_answer_label: "Staircasing transaction" check_answer_prompt: "" - hint_text: "A staircasing transaction is when the household purchases more shares in their property, increasing the proportion they own and decreasing the proportion the housing association owns. Once the household purchases 100% of the shares, they own the property" + hint_text: "A staircasing transaction is when the household purchases more shares in their property, increasing the proportion they own and decreasing the proportion the housing association owns. Once the household purchases 100% of the shares, they own the property." question_text: "Is this a staircasing transaction?" about_staircasing: page_header: "About the staircasing transaction" @@ -102,10 +102,10 @@ en: question_text: "Was the buyer a private registered provider, housing association or local authority tenant immediately before this sale?" frombeds: - page_header: "About the buyers’ previous property" + page_header: "" check_answer_label: "Number of bedrooms in previous property" check_answer_prompt: "" - hint_text: "For bedsits enter 1" + hint_text: "A bedsit has 1 bedroom." question_text: "How many bedrooms did the property have?" fromprop: @@ -137,14 +137,14 @@ en: question_text: "What was the initial percentage equity stake purchased?" mortgageused: - page_header: "Mortgage Amount" + page_header: "" check_answer_label: "Mortgage used" check_answer_prompt: "" hint_text: "" question_text: "Was a mortgage used for the purchase of this property?" mortgage: - page_header: "Mortgage Amount" + page_header: "" check_answer_label: "Mortgage amount" check_answer_prompt: "" hint_text: "Enter the amount of mortgage agreed with the mortgage lender. Exclude any deposits or cash payments. Numeric in pounds. Rounded to the nearest pound." diff --git a/config/locales/forms/2025/lettings/household_characteristics.en.yml b/config/locales/forms/2025/lettings/household_characteristics.en.yml index 39e7c2782..eeb91cbb9 100644 --- a/config/locales/forms/2025/lettings/household_characteristics.en.yml +++ b/config/locales/forms/2025/lettings/household_characteristics.en.yml @@ -15,7 +15,7 @@ en: age1_known: check_answer_label: "" check_answer_prompt: "Answer if you know the lead tenant's age" - hint_text: "The ’lead’ or ’main’ tenant is the person in the household who does the most paid work. If several people do the same amount of paid work, the lead tenant is whoever is the oldest." + hint_text: "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest." question_text: "Do you know the lead tenant’s age?" age1: check_answer_label: "Lead tenant’s age" diff --git a/config/locales/forms/2025/sales/household_characteristics.en.yml b/config/locales/forms/2025/sales/household_characteristics.en.yml index e85e90eab..63ec5a23b 100644 --- a/config/locales/forms/2025/sales/household_characteristics.en.yml +++ b/config/locales/forms/2025/sales/household_characteristics.en.yml @@ -142,7 +142,7 @@ en: check_answer_label: "Person 2’s gender identity" check_answer_prompt: "" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 2’s gender identity?" + question_text: "Which of these best describes person 2’s gender identity?" ethnic_group2: page_header: "" @@ -209,7 +209,7 @@ en: check_answer_label: "Person 2’s working situation" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 2’s working situation?" + question_text: "Which of these best describes person 2’s working situation?" buy2livein: page_header: "" @@ -271,14 +271,14 @@ en: check_answer_label: "Person 3’s gender identity" check_answer_prompt: "" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 3’s gender identity?" + question_text: "Which of these best describes person 3’s gender identity?" ecstat3: page_header: "" check_answer_label: "Person 3’s working situation" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 3’s working situation?" + question_text: "Which of these best describes person 3’s working situation?" details_known_4: page_header: "" @@ -312,14 +312,14 @@ en: check_answer_label: "Person 4’s gender identity" check_answer_prompt: "" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 4’s gender identity?" + question_text: "Which of these best describes person 4’s gender identity?" ecstat4: page_header: "" check_answer_label: "Person 4’s working situation" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 4’s working situation?" + question_text: "Which of these best describes person 4’s working situation?" details_known_5: page_header: "" @@ -353,14 +353,14 @@ en: check_answer_label: "Person 5’s gender identity" check_answer_prompt: "" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 5’s gender identity?" + question_text: "Which of these best describes person 5’s gender identity?" ecstat5: page_header: "" check_answer_label: "Person 5’s working situation" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 5’s working situation?" + question_text: "Which of these best describes person 5’s working situation?" details_known_6: page_header: "" @@ -394,11 +394,11 @@ en: check_answer_label: "Person 6’s gender identity" check_answer_prompt: "" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 6’s gender identity?" + question_text: "Which of these best describes person 6’s gender identity?" ecstat6: page_header: "" check_answer_label: "Person 6’s working situation" check_answer_prompt: "" hint_text: "" - question_text: "Which of these best describes Person 6’s working situation?" + question_text: "Which of these best describes person 6’s working situation?" diff --git a/config/locales/forms/2025/sales/income_benefits_and_savings.en.yml b/config/locales/forms/2025/sales/income_benefits_and_savings.en.yml index f9d19bd6e..fba1fbb18 100644 --- a/config/locales/forms/2025/sales/income_benefits_and_savings.en.yml +++ b/config/locales/forms/2025/sales/income_benefits_and_savings.en.yml @@ -64,7 +64,7 @@ en: check_answer_label: "Buyers’ total savings known?" check_answer_prompt: "" hint_text: "" - question_text: "Do you know how much the 'buyers' had in savings before they paid any deposit for the property?" + question_text: "Do you know how much the buyers had in savings before they paid any deposit for the property?" savings: check_answer_label: "Buyers’ total savings before any deposit paid" check_answer_prompt: "" diff --git a/config/locales/forms/2025/sales/sale_information.en.yml b/config/locales/forms/2025/sales/sale_information.en.yml index 2ba563bd3..9626532f4 100644 --- a/config/locales/forms/2025/sales/sale_information.en.yml +++ b/config/locales/forms/2025/sales/sale_information.en.yml @@ -123,10 +123,10 @@ en: question_text: "Was the buyer a private registered provider, housing association or local authority tenant immediately before this sale?" frombeds: - page_header: "About the buyers’ previous property" + page_header: "" check_answer_label: "Number of bedrooms in previous property" check_answer_prompt: "" - hint_text: "For bedsits enter 1" + hint_text: "A bedsit has 1 bedroom." question_text: "How many bedrooms did the property have?" fromprop: @@ -170,14 +170,14 @@ en: question_text: "What was the percentage shared purchased in the initial transaction?" mortgageused: - page_header: "Mortgage Amount" + page_header: "" check_answer_label: "Mortgage used" - check_answer_prompt: "" + check_answer_prompt: "Answer if a mortgage was used" hint_text: "" question_text: "Was a mortgage used for the purchase of this property?" mortgage: - page_header: "Mortgage Amount" + page_header: "" check_answer_label: "Mortgage amount" check_answer_prompt: "" hint_text: "Enter the amount of mortgage agreed with the mortgage lender. Exclude any deposits or cash payments. Numeric in pounds. Rounded to the nearest pound." diff --git a/config/locales/forms/2025/sales/setup.en.yml b/config/locales/forms/2025/sales/setup.en.yml index 17f77ba61..2c295c5b3 100644 --- a/config/locales/forms/2025/sales/setup.en.yml +++ b/config/locales/forms/2025/sales/setup.en.yml @@ -49,7 +49,7 @@ en: page_header: "" check_answer_label: "Staircasing transaction" check_answer_prompt: "" - hint_text: "A staircasing transaction is when the household purchases more shares in their property, increasing the proportion they own and decreasing the proportion the housing association owns. Once the household purchases 100% of the shares, they own the property" + hint_text: "A staircasing transaction is when the household purchases more shares in their property, increasing the proportion they own and decreasing the proportion the housing association owns. Once the household purchases 100% of the shares, they own the property." question_text: "Is this a staircasing transaction?" type: diff --git a/config/locales/validations/lettings/2024/bulk_upload.en.yml b/config/locales/validations/lettings/2024/bulk_upload.en.yml index 76985ee32..a16090543 100644 --- a/config/locales/validations/lettings/2024/bulk_upload.en.yml +++ b/config/locales/validations/lettings/2024/bulk_upload.en.yml @@ -50,6 +50,8 @@ en: invalid: "Age of person %{person_num} must be a number or the letter R" address: not_found: "We could not find this address. Check the address data in your CSV file is correct and complete, or select the correct address using the CORE site." + not_determined: "There are multiple matches for this address. Either select the correct address manually or correct the UPRN in the CSV file." + not_answered: "Enter either the UPRN or the full address." nationality: invalid: "Select a valid nationality." charges: diff --git a/config/locales/validations/sales/2024/bulk_upload.en.yml b/config/locales/validations/sales/2024/bulk_upload.en.yml index fc68662d1..2621386c1 100644 --- a/config/locales/validations/sales/2024/bulk_upload.en.yml +++ b/config/locales/validations/sales/2024/bulk_upload.en.yml @@ -40,5 +40,7 @@ en: buyer_cannot_be_over_16_and_child: "Buyer 2's age cannot be 16 or over if their working situation is child under 16." address: not_found: "We could not find this address. Check the address data in your CSV file is correct and complete, or select the correct address using the CORE site." + not_determined: "There are multiple matches for this address. Either select the correct address manually or correct the UPRN in the CSV file." + not_answered: "Enter either the UPRN or the full address." nationality: invalid: "Select a valid nationality." diff --git a/db/migrate/20241125153349_add_unique_index_to_org_name.rb b/db/migrate/20241125153349_add_unique_index_to_org_name.rb new file mode 100644 index 000000000..a7a124183 --- /dev/null +++ b/db/migrate/20241125153349_add_unique_index_to_org_name.rb @@ -0,0 +1,5 @@ +class AddUniqueIndexToOrgName < ActiveRecord::Migration[7.0] + def change + add_index :organisations, :name, unique: true + end +end diff --git a/db/migrate/20241204100518_add_year_to_export.rb b/db/migrate/20241204100518_add_year_to_export.rb new file mode 100644 index 000000000..784754888 --- /dev/null +++ b/db/migrate/20241204100518_add_year_to_export.rb @@ -0,0 +1,5 @@ +class AddYearToExport < ActiveRecord::Migration[7.0] + def change + add_column :exports, :year, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 0cdc15e9f..1a1c127c6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_11_22_154743) do +ActiveRecord::Schema[7.0].define(version: 2024_12_04_100518) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -113,6 +113,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_11_22_154743) do t.integer "increment_number", default: 1, null: false t.boolean "empty_export", default: false, null: false t.string "collection" + t.integer "year" end create_table "la_rent_ranges", force: :cascade do |t| @@ -545,6 +546,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_11_22_154743) do t.datetime "discarded_at" t.datetime "schemes_deduplicated_at" t.index ["absorbing_organisation_id"], name: "index_organisations_on_absorbing_organisation_id" + t.index ["name"], name: "index_organisations_on_name", unique: true t.index ["old_visible_id"], name: "index_organisations_on_old_visible_id", unique: true end diff --git a/lib/tasks/data_export.rake b/lib/tasks/data_export.rake index 7a9a90bc8..43ef06374 100644 --- a/lib/tasks/data_export.rake +++ b/lib/tasks/data_export.rake @@ -7,13 +7,13 @@ namespace :core do end desc "Export all data XMLs for import into Central Data System (CDS)" - task :full_data_export_xml, %i[collection] => :environment do |_task, args| + task :full_data_export_xml, %i[collection year] => :environment do |_task, args| collection = args[:collection].presence - collection = collection.to_i if collection.present? && collection.scan(/\D/).empty? + year = args[:year]&.to_i storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["EXPORT_BUCKET"]) export_service = Exports::ExportService.new(storage_service) - export_service.export_xml(full_update: true, collection:) + export_service.export_xml(full_update: true, collection:, year:) end end diff --git a/lib/tasks/set_export_collection_years.rake b/lib/tasks/set_export_collection_years.rake new file mode 100644 index 000000000..badaab0eb --- /dev/null +++ b/lib/tasks/set_export_collection_years.rake @@ -0,0 +1,8 @@ +desc "Set export collection years for lettings exports" +task set_export_collection_years: :environment do + Export.where(collection: %w[2022 2023 2024 2025]).find_each do |export| + export.year = export.collection.to_i + export.collection = "lettings" + export.save! + end +end diff --git a/spec/components/search_result_caption_component_spec.rb b/spec/components/search_result_caption_component_spec.rb index 25cbc1bdd..05ac09630 100644 --- a/spec/components/search_result_caption_component_spec.rb +++ b/spec/components/search_result_caption_component_spec.rb @@ -5,7 +5,7 @@ RSpec.describe SearchResultCaptionComponent, type: :component do let(:count) { 2 } let(:item_label) { "user" } let(:total_count) { 3 } - let(:item) { "schemes" } + let(:item) { "scheme" } let(:filters_count) { 1 } let(:result) { render_inline(described_class.new(searched:, count:, item_label:, total_count:, item:, filters_count:)) } @@ -21,6 +21,14 @@ RSpec.describe SearchResultCaptionComponent, type: :component do it "renders table caption including the search results and total" do expect(result.to_html).to eq("\n 2 users matching search
\n
\n") end + + context "with 1 result" do + let(:count) { 1 } + + it "renders table caption including the search results and total" do + expect(result.to_html).to eq("\n 1 user matching search
\n
\n") + end + end end context "when filter results are found" do @@ -29,6 +37,14 @@ RSpec.describe SearchResultCaptionComponent, type: :component do it "renders table caption including the search results and total" do expect(result.to_html).to eq("\n 2 users matching filters
\n
\n") end + + context "with 1 result" do + let(:count) { 1 } + + it "renders table caption including the search results and total" do + expect(result.to_html).to eq("\n 1 user matching filters
\n
\n") + end + end end context "when no search/filter is applied" do @@ -38,6 +54,14 @@ RSpec.describe SearchResultCaptionComponent, type: :component do it "renders table caption with total count only" do expect(result.to_html).to eq("\n \n 2 total schemes\n \n\n") end + + context "with 1 result" do + let(:count) { 1 } + + it "renders table caption with total count only" do + expect(result.to_html).to eq("\n \n 1 total scheme\n \n\n") + end + end end context "when nothing is found" do diff --git a/spec/db/seeds_spec.rb b/spec/db/seeds_spec.rb index 316f04ba6..6ae07ddb0 100644 --- a/spec/db/seeds_spec.rb +++ b/spec/db/seeds_spec.rb @@ -21,7 +21,8 @@ RSpec.describe "seeding process", type: task do allow(Rails.env).to receive(:review?).and_return(true) end - it "sets up correct data" do + # Doing this in one test should save ~2 minutes + it "sets up correct data idempotently" do expect { Rails.application.load_seed }.to change(User, :count) @@ -30,10 +31,6 @@ RSpec.describe "seeding process", type: task do .and change(Scheme, :count) .and change(Location, :count) .and change(LaRentRange, :count) - end - - it "is idempotent" do - Rails.application.load_seed expect { Rails.application.load_seed diff --git a/spec/features/lettings_log_spec.rb b/spec/features/lettings_log_spec.rb index 15d24e5ed..1c278b8d8 100644 --- a/spec/features/lettings_log_spec.rb +++ b/spec/features/lettings_log_spec.rb @@ -729,5 +729,353 @@ RSpec.describe "Lettings Log Features" do expect(duplicate_log.duplicate_set_id).to be_nil end end + + context "when filling out address fields" do + let(:lettings_log) { create(:lettings_log, :setup_completed, assigned_to: user) } + + before do + body = { + results: [ + { + DPA: { + "POSTCODE": "AA1 1AA", + "POST_TOWN": "Bristol", + "ORGANISATION_NAME": "Some place", + }, + }, + ], + }.to_json + + WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/uprn?dataset=DPA,LPI&key=OS_DATA_KEY&uprn=111") + .to_return(status: 200, body:, headers: {}) + + body = { results: [{ DPA: { UPRN: "111" } }] }.to_json + WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/find?query=Address+line+1%2C+AA1+1AA&key=OS_DATA_KEY&maxresults=10&minmatch=0.4") + .to_return(status: 200, body:, headers: {}) + + WebMock.stub_request(:get, "https://api.postcodes.io/postcodes/AA11AA") + .to_return(status: 200, body: "{\"status\":200,\"result\":{\"postcode\":\"AA1 1AA\",\"admin_district\":\"Westminster\",\"codes\":{\"admin_district\":\"E09000033\"}}}", headers: {}) + + WebMock.stub_request(:get, "https://api.postcodes.io/postcodes/AA12AA") + .to_return(status: 200, body: "{\"status\":200,\"result\":{\"postcode\":\"AA1 2AA\",\"admin_district\":\"Wigan\",\"codes\":{\"admin_district\":\"E08000010\"}}}", headers: {}) + + body = { results: [] }.to_json + WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/find?query=Address+line+1%2C+AA1+1AB&key=OS_DATA_KEY&maxresults=10&minmatch=0.4") + .to_return(status: 200, body:, headers: {}) + + visit("/lettings-logs/#{lettings_log.id}/uprn") + end + + context "and uprn is known and answered" do + before do + choose "Yes" + fill_in("lettings_log[uprn]", with: "111") + click_button("Save and continue") + end + + context "and uprn is confirmed" do + it "sets correct address fields" do + lettings_log.reload + expect(lettings_log.uprn_known).to eq(1) # yes + expect(lettings_log.uprn).to eq("111") + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(1) + expect(lettings_log.postcode_full).to eq("AA1 1AA") + expect(lettings_log.address_line1).to eq("Some Place") + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq("Bristol") + expect(lettings_log.address_line1_input).to eq(nil) + expect(lettings_log.postcode_full_input).to eq(nil) + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq("E09000033") + + choose "Yes" + click_button("Save and continue") + + lettings_log.reload + expect(lettings_log.uprn_known).to eq(1) # yes + expect(lettings_log.uprn).to eq("111") + expect(lettings_log.uprn_confirmed).to eq(1) # yes + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(1) + expect(lettings_log.postcode_full).to eq("AA1 1AA") + expect(lettings_log.address_line1).to eq("Some Place") + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq("Bristol") + expect(lettings_log.address_line1_input).to eq(nil) + expect(lettings_log.postcode_full_input).to eq(nil) + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq("E09000033") + end + + context "and changes to uprn not known" do + it "sets correct address fields" do + visit("/lettings-logs/#{lettings_log.id}/uprn") + + choose "No" + click_button("Save and continue") + + lettings_log.reload + expect(lettings_log.uprn_known).to eq(0) # no + expect(lettings_log.uprn).to eq(nil) + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(nil) + expect(lettings_log.postcode_full).to eq(nil) + expect(lettings_log.address_line1).to eq(nil) + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq(nil) + expect(lettings_log.address_line1_input).to eq(nil) + expect(lettings_log.postcode_full_input).to eq(nil) + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq(nil) + end + end + end + + context "and uprn is not confirmed" do + before do + choose "No, I want to search for the address instead" + click_button("Save and continue") + end + + it "sets correct address fields" do + lettings_log.reload + + expect(lettings_log.uprn_known).to eq(0) # no + expect(lettings_log.uprn).to eq(nil) + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(nil) + expect(lettings_log.postcode_full).to eq(nil) + expect(lettings_log.address_line1).to eq(nil) + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq(nil) + expect(lettings_log.address_line1_input).to eq(nil) + expect(lettings_log.postcode_full_input).to eq(nil) + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq(nil) + end + end + end + + context "and uprn is not known" do + before do + choose "No" + click_button("Save and continue") + end + + it "sets correct address fields" do + lettings_log.reload + expect(lettings_log.uprn_known).to eq(0) # no + expect(lettings_log.uprn).to eq(nil) + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(nil) + expect(lettings_log.postcode_full).to eq(nil) + expect(lettings_log.address_line1).to eq(nil) + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq(nil) + expect(lettings_log.address_line1_input).to eq(nil) + expect(lettings_log.postcode_full_input).to eq(nil) + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq(nil) + end + + context "and the address is not found" do + it "sets correct address fields" do + fill_in("lettings_log[address_line1_input]", with: "Address line 1") + fill_in("lettings_log[postcode_full_input]", with: "AA1 1AB") + click_button("Search") + + lettings_log.reload + expect(lettings_log.uprn_known).to eq(0) # no + expect(lettings_log.uprn).to eq(nil) + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(nil) + expect(lettings_log.postcode_full).to eq(nil) + expect(lettings_log.address_line1).to eq(nil) + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq(nil) + expect(lettings_log.address_line1_input).to eq("Address line 1") + expect(lettings_log.postcode_full_input).to eq("AA1 1AB") + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq(nil) + + click_button("Confirm and continue") + + lettings_log.reload + expect(lettings_log.uprn_known).to eq(0) # no + expect(lettings_log.uprn).to eq(nil) + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(nil) + expect(lettings_log.postcode_full).to eq(nil) + expect(lettings_log.address_line1).to eq(nil) + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq(nil) + expect(lettings_log.address_line1_input).to eq("Address line 1") + expect(lettings_log.postcode_full_input).to eq("AA1 1AB") + expect(lettings_log.address_search_value_check).to eq(0) + expect(lettings_log.la).to eq(nil) + end + end + + context "and address is found, re-searched and not found" do + before do + fill_in("lettings_log[address_line1_input]", with: "Address line 1") + fill_in("lettings_log[postcode_full_input]", with: "AA1 1AA") + click_button("Search") + visit("/lettings-logs/#{lettings_log.id}/address-matcher") + + fill_in("lettings_log[address_line1_input]", with: "Address line 1") + fill_in("lettings_log[postcode_full_input]", with: "AA1 1AB") + click_button("Search") + end + + it "routes to the correct page" do + expect(page).to have_current_path("/lettings-logs/#{lettings_log.id}/no-address-found") + end + end + + context "and the user selects 'address_not_listed'" do + before do + fill_in("lettings_log[address_line1_input]", with: "Address line 1") + fill_in("lettings_log[postcode_full_input]", with: "AA1 1AA") + click_button("Search") + choose "The address is not listed, I want to enter the address manually" + click_button("Save and continue") + end + + it "sets correct address fields" do + lettings_log.reload + expect(lettings_log.uprn_known).to eq(0) # no + expect(lettings_log.uprn).to eq(nil) + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq("uprn_not_listed") + expect(lettings_log.postcode_known).to eq(1) + expect(lettings_log.postcode_full).to eq("AA1 1AA") + expect(lettings_log.address_line1).to eq("Address line 1") + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq(nil) + expect(lettings_log.address_line1_input).to eq("Address line 1") + expect(lettings_log.postcode_full_input).to eq("AA1 1AA") + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq("E09000033") + end + + context "and the user enters a new address manually" do + context "without changing a valid postcode" do + before do + fill_in("lettings_log[town_or_city]", with: "Town") + click_button("Save and continue") + end + + it "sets correct address fields" do + lettings_log.reload + expect(lettings_log.uprn_known).to eq(0) # no + expect(lettings_log.uprn).to eq(nil) + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq("uprn_not_listed") + expect(lettings_log.postcode_known).to eq(1) + expect(lettings_log.postcode_full).to eq("AA1 1AA") + expect(lettings_log.address_line1).to eq("Address line 1") + expect(lettings_log.address_line2).to eq("") + expect(lettings_log.town_or_city).to eq("Town") + expect(lettings_log.address_line1_input).to eq("Address line 1") + expect(lettings_log.postcode_full_input).to eq("AA1 1AA") + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq("E09000033") + end + end + + context "with changing the postcode" do + before do + fill_in("lettings_log[town_or_city]", with: "Town") + fill_in("lettings_log[postcode_full]", with: "AA12AA") + click_button("Save and continue") + end + + it "sets correct address fields" do + lettings_log.reload + expect(lettings_log.uprn_known).to eq(0) # no + expect(lettings_log.uprn).to eq(nil) + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq("uprn_not_listed") + expect(lettings_log.postcode_known).to eq(1) + expect(lettings_log.postcode_full).to eq("AA1 2AA") + expect(lettings_log.address_line1).to eq("Address line 1") + expect(lettings_log.address_line2).to eq("") + expect(lettings_log.town_or_city).to eq("Town") + expect(lettings_log.address_line1_input).to eq("Address line 1") + expect(lettings_log.postcode_full_input).to eq("AA1 1AA") + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq("E08000010") + end + end + end + end + + context "and the user selects 'address_not_listed' and then changes their mind and selects an address" do + before do + fill_in("lettings_log[address_line1_input]", with: "Address line 1") + fill_in("lettings_log[postcode_full_input]", with: "AA1 1AA") + click_button("Search") + choose "The address is not listed, I want to enter the address manually" + click_button("Save and continue") + + visit("/lettings-logs/#{lettings_log.id}/uprn-selection") + choose("lettings-log-uprn-selection-111-field", allow_label_click: true) + click_button("Save and continue") + end + + it "sets correct address fields" do + lettings_log.reload + expect(lettings_log.uprn_known).to eq(1) + expect(lettings_log.uprn).to eq("111") + expect(lettings_log.uprn_confirmed).to eq(1) + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(1) + expect(lettings_log.postcode_full).to eq("AA1 1AA") + expect(lettings_log.address_line1).to eq("Some Place") + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq("Bristol") + expect(lettings_log.address_line1_input).to eq("Address line 1") + expect(lettings_log.postcode_full_input).to eq("AA1 1AA") + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq("E09000033") + end + end + + context "and possible addresses found and selected" do + before do + fill_in("lettings_log[address_line1_input]", with: "Address line 1") + fill_in("lettings_log[postcode_full_input]", with: "AA1 1AA") + click_button("Search") + choose("lettings-log-uprn-selection-111-field", allow_label_click: true) + click_button("Save and continue") + end + + it "sets correct address fields" do + lettings_log.reload + expect(lettings_log.uprn_known).to eq(1) + expect(lettings_log.uprn).to eq("111") + expect(lettings_log.uprn_confirmed).to eq(1) + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(1) + expect(lettings_log.postcode_full).to eq("AA1 1AA") + expect(lettings_log.address_line1).to eq("Some Place") + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq("Bristol") + expect(lettings_log.address_line1_input).to eq("Address line 1") + expect(lettings_log.postcode_full_input).to eq("AA1 1AA") + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq("E09000033") + end + end + end + end end end diff --git a/spec/features/sales_log_spec.rb b/spec/features/sales_log_spec.rb index 879f2b5c8..d418bcb37 100644 --- a/spec/features/sales_log_spec.rb +++ b/spec/features/sales_log_spec.rb @@ -310,6 +310,354 @@ RSpec.describe "Sales Log Features" do expect(page).to have_current_path("/sales-logs/bulk-uploads") end end + + context "when filling out address fields" do + let(:sales_log) { create(:sales_log, :shared_ownership_setup_complete, assigned_to: user) } + + before do + body = { + results: [ + { + DPA: { + "POSTCODE": "AA1 1AA", + "POST_TOWN": "Bristol", + "ORGANISATION_NAME": "Some place", + }, + }, + ], + }.to_json + + WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/uprn?dataset=DPA,LPI&key=OS_DATA_KEY&uprn=111") + .to_return(status: 200, body:, headers: {}) + + body = { results: [{ DPA: { UPRN: "111" } }] }.to_json + WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/find?query=Address+line+1%2C+AA1+1AA&key=OS_DATA_KEY&maxresults=10&minmatch=0.4") + .to_return(status: 200, body:, headers: {}) + + WebMock.stub_request(:get, "https://api.postcodes.io/postcodes/AA11AA") + .to_return(status: 200, body: "{\"status\":200,\"result\":{\"postcode\":\"AA1 1AA\",\"admin_district\":\"Westminster\",\"codes\":{\"admin_district\":\"E09000033\"}}}", headers: {}) + + WebMock.stub_request(:get, "https://api.postcodes.io/postcodes/AA12AA") + .to_return(status: 200, body: "{\"status\":200,\"result\":{\"postcode\":\"AA1 2AA\",\"admin_district\":\"Wigan\",\"codes\":{\"admin_district\":\"E08000010\"}}}", headers: {}) + + body = { results: [] }.to_json + WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/find?query=Address+line+1%2C+AA1+1AB&key=OS_DATA_KEY&maxresults=10&minmatch=0.4") + .to_return(status: 200, body:, headers: {}) + + visit("/sales-logs/#{sales_log.id}/uprn") + end + + context "and uprn is known and answered" do + before do + choose "Yes" + fill_in("sales_log[uprn]", with: "111") + click_button("Save and continue") + end + + context "and uprn is confirmed" do + it "sets correct address fields" do + sales_log.reload + expect(sales_log.uprn_known).to eq(1) # yes + expect(sales_log.uprn).to eq("111") + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(0) + expect(sales_log.postcode_full).to eq("AA1 1AA") + expect(sales_log.address_line1).to eq("Some Place") + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq("Bristol") + expect(sales_log.address_line1_input).to eq(nil) + expect(sales_log.postcode_full_input).to eq(nil) + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq("E09000033") + + choose "Yes" + click_button("Save and continue") + + sales_log.reload + expect(sales_log.uprn_known).to eq(1) # yes + expect(sales_log.uprn).to eq("111") + expect(sales_log.uprn_confirmed).to eq(1) # yes + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(0) + expect(sales_log.postcode_full).to eq("AA1 1AA") + expect(sales_log.address_line1).to eq("Some Place") + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq("Bristol") + expect(sales_log.address_line1_input).to eq(nil) + expect(sales_log.postcode_full_input).to eq(nil) + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq("E09000033") + end + + context "and changes to uprn not known" do + it "sets correct address fields" do + visit("/sales-logs/#{sales_log.id}/uprn") + + choose "No" + click_button("Save and continue") + + sales_log.reload + expect(sales_log.uprn_known).to eq(0) # no + expect(sales_log.uprn).to eq(nil) + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(nil) + expect(sales_log.postcode_full).to eq(nil) + expect(sales_log.address_line1).to eq(nil) + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq(nil) + expect(sales_log.address_line1_input).to eq(nil) + expect(sales_log.postcode_full_input).to eq(nil) + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq(nil) + end + end + end + + context "and uprn is not confirmed" do + before do + choose "No, I want to search for the address instead" + click_button("Save and continue") + end + + it "sets correct address fields" do + sales_log.reload + + expect(sales_log.uprn_known).to eq(0) # no + expect(sales_log.uprn).to eq(nil) + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(nil) + expect(sales_log.postcode_full).to eq(nil) + expect(sales_log.address_line1).to eq(nil) + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq(nil) + expect(sales_log.address_line1_input).to eq(nil) + expect(sales_log.postcode_full_input).to eq(nil) + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq(nil) + end + end + end + + context "and uprn is not known" do + before do + choose "No" + click_button("Save and continue") + end + + it "sets correct address fields" do + sales_log.reload + expect(sales_log.uprn_known).to eq(0) # no + expect(sales_log.uprn).to eq(nil) + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(nil) + expect(sales_log.postcode_full).to eq(nil) + expect(sales_log.address_line1).to eq(nil) + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq(nil) + expect(sales_log.address_line1_input).to eq(nil) + expect(sales_log.postcode_full_input).to eq(nil) + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq(nil) + end + + context "and the address is not found" do + it "sets correct address fields" do + fill_in("sales_log[address_line1_input]", with: "Address line 1") + fill_in("sales_log[postcode_full_input]", with: "AA1 1AB") + click_button("Search") + + sales_log.reload + expect(sales_log.uprn_known).to eq(0) # no + expect(sales_log.uprn).to eq(nil) + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(nil) + expect(sales_log.postcode_full).to eq(nil) + expect(sales_log.address_line1).to eq(nil) + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq(nil) + expect(sales_log.address_line1_input).to eq("Address line 1") + expect(sales_log.postcode_full_input).to eq("AA1 1AB") + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq(nil) + + click_button("Confirm and continue") + + sales_log.reload + expect(sales_log.uprn_known).to eq(0) # no + expect(sales_log.uprn).to eq(nil) + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(nil) + expect(sales_log.postcode_full).to eq(nil) + expect(sales_log.address_line1).to eq(nil) + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq(nil) + expect(sales_log.address_line1_input).to eq("Address line 1") + expect(sales_log.postcode_full_input).to eq("AA1 1AB") + expect(sales_log.address_search_value_check).to eq(0) + expect(sales_log.la).to eq(nil) + end + end + + context "and address is found, re-searched and not found" do + before do + fill_in("sales_log[address_line1_input]", with: "Address line 1") + fill_in("sales_log[postcode_full_input]", with: "AA1 1AA") + click_button("Search") + visit("/sales-logs/#{sales_log.id}/address-matcher") + + fill_in("sales_log[address_line1_input]", with: "Address line 1") + fill_in("sales_log[postcode_full_input]", with: "AA1 1AB") + click_button("Search") + end + + it "routes to the correct page" do + expect(page).to have_current_path("/sales-logs/#{sales_log.id}/no-address-found") + end + end + + context "and the user selects 'address_not_listed'" do + before do + fill_in("sales_log[address_line1_input]", with: "Address line 1") + fill_in("sales_log[postcode_full_input]", with: "AA1 1AA") + click_button("Search") + choose "The address is not listed, I want to enter the address manually" + click_button("Save and continue") + end + + it "sets correct address fields" do + sales_log.reload + expect(sales_log.uprn_known).to eq(0) # no + expect(sales_log.uprn).to eq(nil) + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq("uprn_not_listed") + expect(sales_log.pcodenk).to eq(0) + expect(sales_log.postcode_full).to eq("AA1 1AA") + expect(sales_log.address_line1).to eq("Address line 1") + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq(nil) + expect(sales_log.address_line1_input).to eq("Address line 1") + expect(sales_log.postcode_full_input).to eq("AA1 1AA") + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq("E09000033") + end + + context "and the user enters a new address manually" do + context "without changing a valid postcode" do + before do + fill_in("sales_log[town_or_city]", with: "Town") + click_button("Save and continue") + end + + it "sets correct address fields" do + sales_log.reload + expect(sales_log.uprn_known).to eq(0) # no + expect(sales_log.uprn).to eq(nil) + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq("uprn_not_listed") + expect(sales_log.pcodenk).to eq(0) + expect(sales_log.postcode_full).to eq("AA1 1AA") + expect(sales_log.address_line1).to eq("Address line 1") + expect(sales_log.address_line2).to eq("") + expect(sales_log.town_or_city).to eq("Town") + expect(sales_log.address_line1_input).to eq("Address line 1") + expect(sales_log.postcode_full_input).to eq("AA1 1AA") + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq("E09000033") + end + end + + context "with changing the postcode" do + before do + fill_in("sales_log[town_or_city]", with: "Town") + fill_in("sales_log[postcode_full]", with: "AA12AA") + click_button("Save and continue") + end + + it "sets correct address fields" do + sales_log.reload + expect(sales_log.uprn_known).to eq(0) # no + expect(sales_log.uprn).to eq(nil) + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq("uprn_not_listed") + expect(sales_log.pcodenk).to eq(0) + expect(sales_log.postcode_full).to eq("AA1 2AA") + expect(sales_log.address_line1).to eq("Address line 1") + expect(sales_log.address_line2).to eq("") + expect(sales_log.town_or_city).to eq("Town") + expect(sales_log.address_line1_input).to eq("Address line 1") + expect(sales_log.postcode_full_input).to eq("AA1 1AA") + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq("E08000010") + end + end + end + end + + context "and the user selects 'address_not_listed' and then changes their mind and selects an address" do + before do + fill_in("sales_log[address_line1_input]", with: "Address line 1") + fill_in("sales_log[postcode_full_input]", with: "AA1 1AA") + click_button("Search") + choose "The address is not listed, I want to enter the address manually" + click_button("Save and continue") + + visit("/sales-logs/#{sales_log.id}/uprn-selection") + choose("sales-log-uprn-selection-111-field", allow_label_click: true) + click_button("Save and continue") + end + + it "sets correct address fields" do + sales_log.reload + expect(sales_log.uprn_known).to eq(1) + expect(sales_log.uprn).to eq("111") + expect(sales_log.uprn_confirmed).to eq(1) + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(0) + expect(sales_log.postcode_full).to eq("AA1 1AA") + expect(sales_log.address_line1).to eq("Some Place") + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq("Bristol") + expect(sales_log.address_line1_input).to eq("Address line 1") + expect(sales_log.postcode_full_input).to eq("AA1 1AA") + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq("E09000033") + end + end + + context "and possible addresses found and selected" do + before do + fill_in("sales_log[address_line1_input]", with: "Address line 1") + fill_in("sales_log[postcode_full_input]", with: "AA1 1AA") + click_button("Search") + choose("sales-log-uprn-selection-111-field", allow_label_click: true) + click_button("Save and continue") + end + + it "sets correct address fields" do + sales_log.reload + expect(sales_log.uprn_known).to eq(1) + expect(sales_log.uprn).to eq("111") + expect(sales_log.uprn_confirmed).to eq(1) + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(0) + expect(sales_log.postcode_full).to eq("AA1 1AA") + expect(sales_log.address_line1).to eq("Some Place") + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq("Bristol") + expect(sales_log.address_line1_input).to eq("Address line 1") + expect(sales_log.postcode_full_input).to eq("AA1 1AA") + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq("E09000033") + end + end + end + end end context "when a log becomes a duplicate" do diff --git a/spec/fixtures/exports/general_needs_log.xml b/spec/fixtures/exports/general_needs_log.xml index bacc7e9f0..0341dd2d4 100644 --- a/spec/fixtures/exports/general_needs_log.xml +++ b/spec/fixtures/exports/general_needs_log.xml @@ -147,10 +147,10 @@ {id} {owning_org_id} - MHCLG + {owning_org_name} 1234 {managing_org_id} - MHCLG + {managing_org_name} 1234 2022-05-01T00:00:00+01:00 2022-05-01T00:00:00+01:00 diff --git a/spec/fixtures/exports/general_needs_log_23_24.xml b/spec/fixtures/exports/general_needs_log_23_24.xml index 9635cd0e4..ef0c4066c 100644 --- a/spec/fixtures/exports/general_needs_log_23_24.xml +++ b/spec/fixtures/exports/general_needs_log_23_24.xml @@ -148,10 +148,10 @@ {id} {owning_org_id} - MHCLG + {owning_org_name} 1234 {managing_org_id} - MHCLG + {managing_org_name} 1234 2023-04-03T00:00:00+01:00 2023-04-03T00:00:00+01:00 diff --git a/spec/fixtures/exports/general_needs_log_24_25.xml b/spec/fixtures/exports/general_needs_log_24_25.xml index a665a284e..00d8bb1a5 100644 --- a/spec/fixtures/exports/general_needs_log_24_25.xml +++ b/spec/fixtures/exports/general_needs_log_24_25.xml @@ -161,10 +161,10 @@ la as entered {id} {owning_org_id} - MHCLG + {owning_org_name} 1234 {managing_org_id} - MHCLG + {managing_org_name} 1234 2024-04-03T00:00:00+01:00 2024-04-03T00:00:00+01:00 diff --git a/spec/fixtures/exports/organisation.xml b/spec/fixtures/exports/organisation.xml index 8d87da16c..70c699915 100644 --- a/spec/fixtures/exports/organisation.xml +++ b/spec/fixtures/exports/organisation.xml @@ -2,7 +2,7 @@
{id} - MHCLG + {name} 1 2 Marsham Street diff --git a/spec/fixtures/exports/supported_housing_logs.xml b/spec/fixtures/exports/supported_housing_logs.xml index 50649241b..310d5ba2b 100644 --- a/spec/fixtures/exports/supported_housing_logs.xml +++ b/spec/fixtures/exports/supported_housing_logs.xml @@ -146,10 +146,10 @@ {id} {owning_org_id} - MHCLG + {owning_org_name} 1234 {managing_org_id} - MHCLG + {managing_org_name} 1234 2022-05-01T00:00:00+01:00 2022-05-01T00:00:00+01:00 diff --git a/spec/fixtures/exports/user.xml b/spec/fixtures/exports/user.xml index 5652ac9c6..d29a33225 100644 --- a/spec/fixtures/exports/user.xml +++ b/spec/fixtures/exports/user.xml @@ -12,6 +12,6 @@ false false true - MHCLG + {organisation_name}
diff --git a/spec/helpers/tab_nav_helper_spec.rb b/spec/helpers/tab_nav_helper_spec.rb index 89f867775..bd29f4c46 100644 --- a/spec/helpers/tab_nav_helper_spec.rb +++ b/spec/helpers/tab_nav_helper_spec.rb @@ -9,7 +9,7 @@ RSpec.describe TabNavHelper do describe "#user_cell" do it "returns user link and email separated by a newline character" do expected_html = "#{current_user.name}\nUser #{current_user.email}" - expect(user_cell(current_user)).to match(expected_html) + expect(CGI.unescapeHTML(user_cell(current_user))).to match(expected_html) end end diff --git a/spec/lib/tasks/data_export_spec.rb b/spec/lib/tasks/data_export_spec.rb index 8b5dd5fbe..2f6127512 100644 --- a/spec/lib/tasks/data_export_spec.rb +++ b/spec/lib/tasks/data_export_spec.rb @@ -30,7 +30,7 @@ describe "rake core:data_export", type: task do context "with all available years" do it "calls the export service" do - expect(export_service).to receive(:export_xml).with(full_update: true, collection: nil) + expect(export_service).to receive(:export_xml).with(full_update: true, collection: nil, year: nil) task.invoke end @@ -38,9 +38,9 @@ describe "rake core:data_export", type: task do context "with a specific collection" do it "calls the export service" do - expect(export_service).to receive(:export_xml).with(full_update: true, collection: 2022) + expect(export_service).to receive(:export_xml).with(full_update: true, collection: "lettings", year: 2022) - task.invoke("2022") + task.invoke("lettings", "2022") end end end diff --git a/spec/lib/tasks/set_export_collection_years_spec.rb b/spec/lib/tasks/set_export_collection_years_spec.rb new file mode 100644 index 000000000..76ff779b0 --- /dev/null +++ b/spec/lib/tasks/set_export_collection_years_spec.rb @@ -0,0 +1,41 @@ +require "rails_helper" +require "rake" + +RSpec.describe "set_export_collection_years" do + describe ":set_export_collection_years", type: :task do + subject(:task) { Rake::Task["set_export_collection_years"] } + + before do + Rake.application.rake_require("tasks/set_export_collection_years") + Rake::Task.define_task(:environment) + task.reenable + end + + context "when the rake task is run" do + let!(:lettings_export_2023) { Export.create(collection: "2023", year: nil, started_at: Time.zone.now) } + let!(:lettings_export_2024) { Export.create(collection: "2024", year: nil, started_at: Time.zone.now) } + let!(:updated_lettings_export) { Export.create(collection: "lettings", year: 2023, started_at: Time.zone.now) } + let!(:organisations_export) { Export.create(collection: "organisations", year: nil, started_at: Time.zone.now) } + let!(:users_export) { Export.create(collection: "users", year: nil, started_at: Time.zone.now) } + + it "correctly updates collection years" do + task.invoke + + expect(lettings_export_2023.reload.collection).to eq("lettings") + expect(lettings_export_2023.year).to eq(2023) + + expect(lettings_export_2024.reload.collection).to eq("lettings") + expect(lettings_export_2024.year).to eq(2024) + + expect(updated_lettings_export.reload.collection).to eq("lettings") + expect(updated_lettings_export.year).to eq(2023) + + expect(organisations_export.reload.collection).to eq("organisations") + expect(organisations_export.year).to eq(nil) + + expect(users_export.reload.collection).to eq("users") + expect(users_export.year).to eq(nil) + end + end + end +end diff --git a/spec/mailers/bulk_upload_mailer_spec.rb b/spec/mailers/bulk_upload_mailer_spec.rb index c3225c937..910bca4a9 100644 --- a/spec/mailers/bulk_upload_mailer_spec.rb +++ b/spec/mailers/bulk_upload_mailer_spec.rb @@ -121,4 +121,29 @@ RSpec.describe BulkUploadMailer do mailer.send_check_soft_validations_mail(bulk_upload:) end end + + describe "#send_correct_duplicates_and_upload_again_mail" do + context "when 2 columns with errors" do + before do + create(:bulk_upload_error, bulk_upload:, col: "A") + create(:bulk_upload_error, bulk_upload:, col: "B") + end + + it "sends correctly formed email" do + expect(notify_client).to receive(:send_email).with( + email_address: user.email, + template_id: described_class::FAILED_CSV_DUPLICATE_ERRORS_TEMPLATE_ID, + personalisation: { + filename: bulk_upload.filename, + upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), + year_combo: bulk_upload.year_combo, + lettings_or_sales: bulk_upload.log_type, + summary_report_link: "http://localhost:3000/lettings-logs/bulk-upload-results/#{bulk_upload.id}", + }, + ) + + mailer.send_correct_duplicates_and_upload_again_mail(bulk_upload:) + end + end + end end diff --git a/spec/mailers/devise_notify_mailer_spec.rb b/spec/mailers/devise_notify_mailer_spec.rb index 4ed209b24..7a7123be0 100644 --- a/spec/mailers/devise_notify_mailer_spec.rb +++ b/spec/mailers/devise_notify_mailer_spec.rb @@ -36,8 +36,11 @@ RSpec.describe DeviseNotifyMailer do end context "when the email domain is in the allowlist" do - let(:domain) { Rails.application.credentials[:email_allowlist].first } - let(:email) { "test@#{domain}" } + before do + allow(Rails.application.credentials).to receive(:[]).with(:email_allowlist).and_return(["example.com"]) + end + + let(:email) { "test@example.com" } it "does send emails" do expect(notify_client).to receive(:send_email).once @@ -48,10 +51,10 @@ RSpec.describe DeviseNotifyMailer do context "when notify mailer raises BadRequestError" do before do allow(notify_client).to receive(:send_email).and_raise(bad_request_error) + allow(Rails.application.credentials).to receive(:[]).with(:email_allowlist).and_return(["example.com"]) end - let(:domain) { Rails.application.credentials[:email_allowlist].first } - let(:email) { "test@#{domain}" } + let(:email) { "test@example.com" } it "does not raise an error" do expect { diff --git a/spec/models/form/lettings/questions/managing_organisation_spec.rb b/spec/models/form/lettings/questions/managing_organisation_spec.rb index 776a873a6..00485e80f 100644 --- a/spec/models/form/lettings/questions/managing_organisation_spec.rb +++ b/spec/models/form/lettings/questions/managing_organisation_spec.rb @@ -185,7 +185,7 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do context "when organisation has merged" do let(:absorbing_org) { create(:organisation, name: "Absorbing org", holds_own_stock: true) } let!(:merged_org) { create(:organisation, name: "Merged org", holds_own_stock: false) } - let!(:merged_deleted_org) { create(:organisation, name: "Merged org", holds_own_stock: false, discarded_at: Time.zone.yesterday) } + let!(:merged_deleted_org) { create(:organisation, name: "Merged org 2", holds_own_stock: false, discarded_at: Time.zone.yesterday) } let(:user) { create(:user, :data_coordinator, organisation: absorbing_org) } let(:log) do diff --git a/spec/models/form/sales/pages/equity_spec.rb b/spec/models/form/sales/pages/equity_spec.rb index 83a5dfaa3..e27538263 100644 --- a/spec/models/form/sales/pages/equity_spec.rb +++ b/spec/models/form/sales/pages/equity_spec.rb @@ -7,6 +7,10 @@ RSpec.describe Form::Sales::Pages::Equity, type: :model do let(:page_definition) { nil } let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1))) } + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(false) + end + it "has correct subsection" do expect(page.subsection).to eq(subsection) end diff --git a/spec/models/form/sales/pages/value_shared_ownership_spec.rb b/spec/models/form/sales/pages/value_shared_ownership_spec.rb index eb1b1099f..82e6c1055 100644 --- a/spec/models/form/sales/pages/value_shared_ownership_spec.rb +++ b/spec/models/form/sales/pages/value_shared_ownership_spec.rb @@ -7,6 +7,10 @@ RSpec.describe Form::Sales::Pages::ValueSharedOwnership, type: :model do let(:page_definition) { nil } let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1))) } + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(false) + end + it "has correct subsection" do expect(page.subsection).to eq(subsection) end diff --git a/spec/models/form/sales/questions/buyer2_working_situation_spec.rb b/spec/models/form/sales/questions/buyer2_working_situation_spec.rb index 7b825185c..c8ee349b6 100644 --- a/spec/models/form/sales/questions/buyer2_working_situation_spec.rb +++ b/spec/models/form/sales/questions/buyer2_working_situation_spec.rb @@ -36,6 +36,22 @@ RSpec.describe Form::Sales::Questions::Buyer2WorkingSituation, type: :model do "0" => { "value" => "Other" }, "10" => { "value" => "Buyer prefers not to say" }, "7" => { "value" => "Full-time student" }, + "9" => { "value" => "Child under 16" }, + }) + end + + it "has the correct displayed_answer_options" do + expect(question.displayed_answer_options(nil)).to eq({ + "1" => { "value" => "Full-time - 30 hours or more" }, + "2" => { "value" => "Part-time - Less than 30 hours" }, + "3" => { "value" => "In government training into work" }, + "4" => { "value" => "Jobseeker" }, + "6" => { "value" => "Not seeking work" }, + "8" => { "value" => "Unable to work due to long term sick or disability" }, + "5" => { "value" => "Retired" }, + "0" => { "value" => "Other" }, + "10" => { "value" => "Buyer prefers not to say" }, + "7" => { "value" => "Full-time student" }, }) end @@ -43,7 +59,11 @@ RSpec.describe Form::Sales::Questions::Buyer2WorkingSituation, type: :model do let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1), start_year_2025_or_later?: false) } it "uses the old ordering for answer options" do - expect(question.answer_options.keys).to eq(%w[1 2 3 4 6 8 5 0 10 7]) + expect(question.answer_options.keys).to eq(%w[1 2 3 4 6 8 5 0 10 7 9]) + end + + it "uses the old ordering for displayed answer options" do + expect(question.displayed_answer_options(nil).keys).to eq(%w[1 2 3 4 6 8 5 0 10 7]) end end @@ -51,7 +71,11 @@ RSpec.describe Form::Sales::Questions::Buyer2WorkingSituation, type: :model do let(:form) { instance_double(Form, start_date: Time.zone.local(2025, 4, 1), start_year_2025_or_later?: true) } it "uses the new ordering for answer options" do - expect(question.answer_options.keys).to eq(%w[1 2 3 4 5 6 7 8 0 10]) + expect(question.answer_options.keys).to eq(%w[1 2 3 4 5 6 7 8 9 0 10]) + end + + it "uses the new ordering for displayed answer options" do + expect(question.displayed_answer_options(nil).keys).to eq(%w[1 2 3 4 5 6 7 8 0 10]) end end diff --git a/spec/models/form/sales/questions/equity_spec.rb b/spec/models/form/sales/questions/equity_spec.rb index 5083af9e8..3e6b9d85a 100644 --- a/spec/models/form/sales/questions/equity_spec.rb +++ b/spec/models/form/sales/questions/equity_spec.rb @@ -7,6 +7,10 @@ RSpec.describe Form::Sales::Questions::Equity, type: :model do let(:question_definition) { nil } let(:page) { instance_double(Form::Page, id: "initial_equity", subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(false) + end + it "has correct page" do expect(question.page).to eq(page) end diff --git a/spec/models/organisation_spec.rb b/spec/models/organisation_spec.rb index 9b01845ae..b117feef7 100644 --- a/spec/models/organisation_spec.rb +++ b/spec/models/organisation_spec.rb @@ -19,6 +19,12 @@ RSpec.describe Organisation, type: :model do .to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Provider type #{I18n.t('validations.organisation.provider_type_missing')}") end + it "validates uniqueness of name" do + org = build(:organisation, name: organisation.name.downcase) + org.valid? + expect(org.errors[:name]).to include(I18n.t("validations.organisation.name_not_unique")) + end + context "with parent/child associations", :aggregate_failures do let!(:child_organisation) { create(:organisation, name: "MHCLG Child") } let!(:grandchild_organisation) { create(:organisation, name: "MHCLG Grandchild") } diff --git a/spec/models/scheme_spec.rb b/spec/models/scheme_spec.rb index 65174388d..21b60cd52 100644 --- a/spec/models/scheme_spec.rb +++ b/spec/models/scheme_spec.rb @@ -390,6 +390,13 @@ RSpec.describe Scheme, type: :model do scheme.startdate = Time.zone.today + 2.weeks expect(scheme.status).to eq(:activating_soon) end + + it "returns deactivated if scheme is deactivated and incomplete" do + scheme.update!(support_type: nil, confirmed: nil) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.yesterday, scheme:) + scheme.reload + expect(scheme.status).to eq(:deactivated) + end end context "when there have been previous deactivations" do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 51cfc00bd..53561f3e9 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -300,6 +300,7 @@ RSpec.describe User, type: :model do context "when the user is in staging environment" do before do allow(Rails.env).to receive(:staging?).and_return(true) + allow(Rails.application.credentials).to receive(:[]).with(:staging_role_update_email_allowlist).and_return(["not_one_of_the_examples.com"]) end context "and the user is not in the staging role update email allowlist" do diff --git a/spec/requests/bulk_upload_lettings_logs_controller_spec.rb b/spec/requests/bulk_upload_lettings_logs_controller_spec.rb index 18b208e74..c9a22768d 100644 --- a/spec/requests/bulk_upload_lettings_logs_controller_spec.rb +++ b/spec/requests/bulk_upload_lettings_logs_controller_spec.rb @@ -73,5 +73,116 @@ RSpec.describe BulkUploadLettingsLogsController, type: :request do expect(response.body).to include("How to upload logs in bulk") end end + + context "when no year is specified" do + it "shows guidance page with links defaulting to the current year" do + get "/lettings-logs/bulk-upload-logs/guidance" + + expect(response.body).to include("Download the lettings bulk upload template (#{current_collection_start_year} to #{current_collection_start_year + 1})") + end + end + + context "when an invalid year is specified" do + it "shows not found" do + get "/lettings-logs/bulk-upload-logs/guidance?form%5Byear%5D=10000" + + expect(response).to be_not_found + end + end + end + + describe "GET /lettings-logs/bulk-upload-logs/year" do + it "does not require a year to be specified" do + get "/lettings-logs/bulk-upload-logs/year" + + expect(response).to be_ok + end + end + + pages_requiring_year_specification = %w[prepare-your-file upload-your-file checking-file] + pages_requiring_year_specification.each do |page_id| + describe "GET /lettings-logs/bulk-upload-logs/#{page_id}" do + context "when no year is provided" do + it "returns not found" do + get "/lettings-logs/bulk-upload-logs/#{page_id}" + + expect(response).to be_not_found + end + end + + context "when requesting the previous year in a crossover period" do + before do + allow(FormHandler.instance).to receive(:lettings_in_crossover_period?).and_return(true) + end + + it "succeeds" do + get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year - 1}" + + expect(response).to be_ok + end + end + + context "when requesting the previous year outside a crossover period" do + before do + allow(FormHandler.instance).to receive(:lettings_in_crossover_period?).and_return(false) + end + + it "returns not found" do + get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year - 1}" + + expect(response).to be_not_found + end + end + + context "when requesting the current year" do + it "succeeds" do + get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year}" + + expect(response).to be_ok + end + end + + if page_id != "prepare-your-file" + context "when requesting the next year with future form use toggled on" do + before do + allow(FeatureToggle).to receive(:allow_future_form_use?).and_return(true) + end + + it "succeeds" do + get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year + 1}" + + expect(response).to be_ok + end + end + end + + context "when requesting the next year with future form use toggled off" do + before do + allow(FeatureToggle).to receive(:allow_future_form_use?).and_return(false) + end + + it "returns not found" do + get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year + 1}" + + expect(response).to be_not_found + end + end + + context "when requesting a far future year" do + it "returns not found" do + get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=9990" + + expect(response).to be_not_found + end + end + + context "when requesting a nonsense value for year" do + it "returns not found" do + get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=thisisnotayear" + + expect(response).to be_not_found + end + end + end end end diff --git a/spec/requests/bulk_upload_sales_logs_controller_spec.rb b/spec/requests/bulk_upload_sales_logs_controller_spec.rb index 7cd6d4be8..4c20482be 100644 --- a/spec/requests/bulk_upload_sales_logs_controller_spec.rb +++ b/spec/requests/bulk_upload_sales_logs_controller_spec.rb @@ -73,5 +73,116 @@ RSpec.describe BulkUploadSalesLogsController, type: :request do expect(response.body).to include("How to upload logs in bulk") end end + + context "when no year is specified" do + it "shows guidance page with links defaulting to the current year" do + get "/sales-logs/bulk-upload-logs/guidance" + + expect(response.body).to include("Download the sales bulk upload template (#{current_collection_start_year} to #{current_collection_start_year + 1})") + end + end + + context "when an invalid year is specified" do + it "shows not found" do + get "/sales-logs/bulk-upload-logs/guidance?form%5Byear%5D=10000" + + expect(response).to be_not_found + end + end + end + + describe "GET /sales-logs/bulk-upload-logs/year" do + it "does not require a year to be specified" do + get "/sales-logs/bulk-upload-logs/year" + + expect(response).to be_ok + end + end + + pages_requiring_year_specification = %w[prepare-your-file upload-your-file checking-file] + pages_requiring_year_specification.each do |page_id| + describe "GET /sales-logs/bulk-upload-logs/#{page_id}" do + context "when no year is provided" do + it "returns not found" do + get "/sales-logs/bulk-upload-logs/#{page_id}" + + expect(response).to be_not_found + end + end + + context "when requesting the previous year in a crossover period" do + before do + allow(FormHandler.instance).to receive(:sales_in_crossover_period?).and_return(true) + end + + it "succeeds" do + get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year - 1}" + + expect(response).to be_ok + end + end + + context "when requesting the previous year outside a crossover period" do + before do + allow(FormHandler.instance).to receive(:sales_in_crossover_period?).and_return(false) + end + + it "returns not found" do + get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year - 1}" + + expect(response).to be_not_found + end + end + + context "when requesting the current year" do + it "succeeds" do + get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year}" + + expect(response).to be_ok + end + end + + if page_id != "prepare-your-file" + context "when requesting the next year with future form use toggled on" do + before do + allow(FeatureToggle).to receive(:allow_future_form_use?).and_return(true) + end + + it "succeeds" do + get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year + 1}" + + expect(response).to be_ok + end + end + end + + context "when requesting the next year with future form use toggled off" do + before do + allow(FeatureToggle).to receive(:allow_future_form_use?).and_return(false) + end + + it "returns not found" do + get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year + 1}" + + expect(response).to be_not_found + end + end + + context "when requesting a far future year" do + it "returns not found" do + get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=9990" + + expect(response).to be_not_found + end + end + + context "when requesting a nonsense value for year" do + it "returns not found" do + get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=thisisnotayear" + + expect(response).to be_not_found + end + end + end end end diff --git a/spec/requests/lettings_logs_controller_spec.rb b/spec/requests/lettings_logs_controller_spec.rb index 0e95829f5..6742de91e 100644 --- a/spec/requests/lettings_logs_controller_spec.rb +++ b/spec/requests/lettings_logs_controller_spec.rb @@ -759,7 +759,7 @@ RSpec.describe LettingsLogsController, type: :request do it "has search results in the title" do get "/lettings-logs?search=#{log_to_search.id}", headers:, params: {} - expect(page).to have_title("Lettings logs (1 logs matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") + expect(page).to have_title("Lettings logs (1 log matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") end it "shows lettings logs matching the id" do @@ -895,7 +895,7 @@ RSpec.describe LettingsLogsController, type: :request do end it "shows the total log count" do - expect(CGI.unescape_html(response.body)).to match("1 total logs") + expect(CGI.unescape_html(response.body)).to match("1 total log") end it "does not show the pagination links" do @@ -1483,7 +1483,7 @@ RSpec.describe LettingsLogsController, type: :request do end context "when viewing a collection of logs affected by deactivated location" do - let!(:affected_lettings_logs) { FactoryBot.create_list(:lettings_log, 3, unresolved: true, assigned_to: user) } + let!(:affected_lettings_logs) { FactoryBot.create_list(:lettings_log, 3, unresolved: true, assigned_to: user, tenancycode: "affected tenancycode", propcode: "affected propcode") } let!(:other_user_affected_lettings_log) { FactoryBot.create(:lettings_log, unresolved: true) } let!(:non_affected_lettings_logs) { FactoryBot.create_list(:lettings_log, 4, assigned_to: user) } let(:other_user) { FactoryBot.create(:user, organisation: user.organisation) } diff --git a/spec/requests/organisation_relationships_controller_spec.rb b/spec/requests/organisation_relationships_controller_spec.rb index feb687e0c..8733fba4b 100644 --- a/spec/requests/organisation_relationships_controller_spec.rb +++ b/spec/requests/organisation_relationships_controller_spec.rb @@ -40,7 +40,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do it "shows a table of stock owners" do expected_html = "1 total logs") + expect(CGI.unescape_html(response.body)).to match("1 total log") end it "does not show the pagination links" do diff --git a/spec/requests/schemes_controller_spec.rb b/spec/requests/schemes_controller_spec.rb index 83ba11fd9..2eb2330c8 100644 --- a/spec/requests/schemes_controller_spec.rb +++ b/spec/requests/schemes_controller_spec.rb @@ -2035,6 +2035,17 @@ RSpec.describe SchemesController, type: :request do expect(page).to have_content("What client group is this scheme intended for?") end + it "has correct back link" do + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/details") + end + + context "and accessed from check answers" do + it "has correct back link" do + get "/schemes/#{scheme.id}/primary-client-group?referrer=check-answers" + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/check-answers") + end + end + context "when attempting to access primary-client-group scheme page for another organisation" do before do get "/schemes/#{another_scheme.id}/primary-client-group" @@ -2112,6 +2123,17 @@ RSpec.describe SchemesController, type: :request do expect(page).to have_content("Does this scheme provide for another client group?") end + it "has correct back link" do + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/primary-client-group") + end + + context "and accessed from check answers" do + it "has correct back link" do + get "/schemes/#{scheme.id}/confirm-secondary-client-group?referrer=check-answers" + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/check-answers") + end + end + context "when attempting to access confirm-secondary-client-group scheme page for another organisation" do before do get "/schemes/#{another_scheme.id}/confirm-secondary-client-group" @@ -2189,6 +2211,24 @@ RSpec.describe SchemesController, type: :request do expect(page).to have_content("What is the other client group?") end + it "has correct back link" do + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/confirm-secondary-client-group") + end + + context "and accessed from check answers" do + it "has correct back link" do + get "/schemes/#{scheme.id}/secondary-client-group?referrer=check-answers" + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/check-answers") + end + end + + context "and accessed from has other client group" do + it "has correct back link" do + get "/schemes/#{scheme.id}/secondary-client-group?referrer=has-other-client-group" + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/confirm-secondary-client-group?referrer=check-answers") + end + end + context "when attempting to access secondary-client-group scheme page for another organisation" do before do get "/schemes/#{another_scheme.id}/secondary-client-group" @@ -2258,7 +2298,7 @@ RSpec.describe SchemesController, type: :request do context "when signed in as a data coordinator" do let(:user) { create(:user, :data_coordinator) } - let(:scheme) { create(:scheme, owning_organisation: user.organisation, confirmed: nil) } + let(:scheme) { create(:scheme, owning_organisation: user.organisation, confirmed: nil, has_other_client_group: "Yes") } let(:another_scheme) { create(:scheme, confirmed: nil) } before do @@ -2271,6 +2311,27 @@ RSpec.describe SchemesController, type: :request do expect(page).to have_content("What support does this scheme provide?") end + context "when scheme has secondary client group" do + it "has correct back link" do + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/secondary-client-group") + end + end + + context "when scheme has no secondary client group" do + let(:scheme) { create(:scheme, owning_organisation: user.organisation, confirmed: nil, has_other_client_group: "No") } + + it "has correct back link" do + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/confirm-secondary-client-group") + end + end + + context "and accessed from check answers" do + it "has correct back link" do + get "/schemes/#{scheme.id}/support?referrer=check-answers" + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/check-answers") + end + end + context "when attempting to access secondary-client-group scheme page for another organisation" do before do get "/schemes/#{another_scheme.id}/support" @@ -2433,6 +2494,17 @@ RSpec.describe SchemesController, type: :request do expect(page).to have_content("Create a new supported housing scheme") end + it "has correct back link" do + expect(page).to have_link("Back", href: "/schemes") + end + + context "and accessed from check answers" do + it "has correct back link" do + get "/schemes/#{scheme.id}/details?referrer=check-answers" + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/check-answers") + end + end + context "when attempting to access check-answers scheme page for another organisation" do before do get "/schemes/#{another_scheme.id}/details" diff --git a/spec/services/bulk_upload/lettings/validator_spec.rb b/spec/services/bulk_upload/lettings/validator_spec.rb index 897010de6..60eb8a955 100644 --- a/spec/services/bulk_upload/lettings/validator_spec.rb +++ b/spec/services/bulk_upload/lettings/validator_spec.rb @@ -232,7 +232,7 @@ RSpec.describe BulkUpload::Lettings::Validator do end end - describe "#create_logs?" do + describe "#block_log_creation_reason" do context "when a log has a clearable, non-setup error" do let(:log_1) { build(:lettings_log, :completed, period: 2, assigned_to: user) } let(:log_2) { build(:lettings_log, :completed, period: 2, assigned_to: user, age1: 5) } @@ -245,7 +245,7 @@ RSpec.describe BulkUpload::Lettings::Validator do it "returns false" do validator.call - expect(validator).to be_create_logs + expect(validator.block_log_creation_reason).to be_nil end end @@ -261,7 +261,7 @@ RSpec.describe BulkUpload::Lettings::Validator do it "returns true" do validator.call - expect(validator).to be_create_logs + expect(validator.block_log_creation_reason).to be_nil end end @@ -277,7 +277,7 @@ RSpec.describe BulkUpload::Lettings::Validator do it "will not create logs" do validator.call - expect(validator).not_to be_create_logs + expect(validator.block_log_creation_reason).to eq("setup_errors") end end @@ -291,7 +291,7 @@ RSpec.describe BulkUpload::Lettings::Validator do it "returns false" do validator.call - expect(validator).not_to be_create_logs + expect(validator.block_log_creation_reason).to eq("setup_errors") end end end diff --git a/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb index e9047b2ae..13ab91370 100644 --- a/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb @@ -1658,11 +1658,11 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "adds appropriate errors to UPRN and key address fields" do parser.valid? - expect(parser.errors[:field_16]).to eql([I18n.t("validations.lettings.2024.bulk_upload.not_answered", question: "UPRN.")]) - expect(parser.errors[:field_17]).to eql([I18n.t("validations.lettings.2024.bulk_upload.not_answered", question: "address line 1.")]) - expect(parser.errors[:field_19]).to eql([I18n.t("validations.lettings.2024.bulk_upload.not_answered", question: "town or city.")]) - expect(parser.errors[:field_21]).to eql([I18n.t("validations.lettings.2024.bulk_upload.not_answered", question: "part 1 of postcode.")]) - expect(parser.errors[:field_22]).to eql([I18n.t("validations.lettings.2024.bulk_upload.not_answered", question: "part 2 of postcode.")]) + expect(parser.errors[:field_16]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_17]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_19]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_21]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_22]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_answered")]) end end @@ -1671,8 +1671,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "adds errors to UPRN and the missing key address field" do parser.valid? - expect(parser.errors[:field_16]).to eql([I18n.t("validations.lettings.2024.bulk_upload.not_answered", question: "UPRN.")]) - expect(parser.errors[:field_17]).to eql([I18n.t("validations.lettings.2024.bulk_upload.not_answered", question: "address line 1.")]) + expect(parser.errors[:field_16]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_17]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_answered")]) expect(parser.errors[:field_19]).to be_empty expect(parser.errors[:field_21]).to be_empty expect(parser.errors[:field_22]).to be_empty @@ -1721,7 +1721,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do parser.valid? expect(parser.errors[:field_16]).to be_empty %i[field_17 field_18 field_19 field_20 field_21 field_22].each do |field| - expect(parser.errors[field]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_found")]) + expect(parser.errors[field]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_determined")]) end end end diff --git a/spec/services/bulk_upload/processor_spec.rb b/spec/services/bulk_upload/processor_spec.rb index de0ed2dba..0368635e7 100644 --- a/spec/services/bulk_upload/processor_spec.rb +++ b/spec/services/bulk_upload/processor_spec.rb @@ -14,7 +14,7 @@ RSpec.describe BulkUpload::Processor do call: nil, total_logs_count: 1, any_setup_errors?: false, - create_logs?: true, + block_log_creation_reason: nil, soft_validation_errors_only?: false, ) end @@ -165,7 +165,7 @@ RSpec.describe BulkUpload::Processor do let(:log) { build(:lettings_log, :setup_completed, assigned_to: user) } before do - allow(mock_validator).to receive(:create_logs?).and_return(true) + allow(mock_validator).to receive(:block_log_creation_reason).and_return(nil) allow(mock_validator).to receive(:soft_validation_errors_only?).and_return(false) allow(FeatureToggle).to receive(:bulk_upload_duplicate_log_check_enabled?).and_return(true) end @@ -198,7 +198,7 @@ RSpec.describe BulkUpload::Processor do context "when a bulk upload has logs with only soft validations triggered" do before do - allow(mock_validator).to receive(:create_logs?).and_return(true) + allow(mock_validator).to receive(:block_log_creation_reason).and_return(nil) allow(mock_validator).to receive(:soft_validation_errors_only?).and_return(true) allow(FeatureToggle).to receive(:bulk_upload_duplicate_log_check_enabled?).and_return(true) end @@ -239,7 +239,7 @@ RSpec.describe BulkUpload::Processor do call: nil, total_logs_count: 1, any_setup_errors?: false, - create_logs?: false, + block_log_creation_reason: "row_parser_block_log_creation", ) end @@ -254,6 +254,30 @@ RSpec.describe BulkUpload::Processor do expect(mail_double).to have_received(:deliver_later) end end + + context "when upload has duplicate logs blocking log creation" do + let(:mock_validator) do + instance_double( + BulkUpload::Lettings::Validator, + invalid?: false, + call: nil, + total_logs_count: 1, + any_setup_errors?: false, + block_log_creation_reason: "duplicate_logs", + ) + end + + it "sends correct_and_upload_again_mail" do + mail_double = instance_double("ActionMailer::MessageDelivery", deliver_later: nil) + + allow(BulkUploadMailer).to receive(:send_correct_duplicates_and_upload_again_mail).and_return(mail_double) + + processor.call + + expect(BulkUploadMailer).to have_received(:send_correct_duplicates_and_upload_again_mail) + expect(mail_double).to have_received(:deliver_later) + end + end end describe "#approve" do diff --git a/spec/services/bulk_upload/sales/validator_spec.rb b/spec/services/bulk_upload/sales/validator_spec.rb index c275ce681..968014e7c 100644 --- a/spec/services/bulk_upload/sales/validator_spec.rb +++ b/spec/services/bulk_upload/sales/validator_spec.rb @@ -204,7 +204,7 @@ RSpec.describe BulkUpload::Sales::Validator do end end - describe "#create_logs?" do + describe "#block_log_creation_reason" do context "when all logs are valid" do let(:log_1) { build(:sales_log, :completed, assigned_to: user) } let(:log_2) { build(:sales_log, :completed, assigned_to: user) } @@ -214,9 +214,9 @@ RSpec.describe BulkUpload::Sales::Validator do file.write(BulkUpload::SalesLogToCsv.new(log: log_2).to_csv_row) end - it "returns truthy" do + it "returns nil" do validator.call - expect(validator).to be_create_logs + expect(validator.block_log_creation_reason).to be_nil end end @@ -229,9 +229,9 @@ RSpec.describe BulkUpload::Sales::Validator do file.write(BulkUpload::SalesLogToCsv.new(log: log_2).to_csv_row) end - it "returns truthy" do + it "returns nil" do validator.call - expect(validator).to be_create_logs + expect(validator.block_log_creation_reason).to be_nil end end @@ -245,9 +245,9 @@ RSpec.describe BulkUpload::Sales::Validator do file.close end - it "returns false" do + it "returns the reason" do validator.call - expect(validator).not_to be_create_logs + expect(validator.block_log_creation_reason).to eq("setup_errors") end end @@ -262,7 +262,7 @@ RSpec.describe BulkUpload::Sales::Validator do it "will not create logs" do validator.call - expect(validator).not_to be_create_logs + expect(validator.block_log_creation_reason).to eq("setup_errors") end end end diff --git a/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb b/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb index e4e2eb6f5..7e969c10e 100644 --- a/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb +++ b/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb @@ -310,7 +310,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "only has one error added to the field" do parser.valid? - expect(parser.errors[:field_23]).to eql([I18n.t("validations.sales.2024.bulk_upload.not_answered", question: "address line 1.")]) + expect(parser.errors[:field_23]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_answered")]) end end @@ -1042,11 +1042,11 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "adds appropriate errors to UPRN and key address fields" do parser.valid? - expect(parser.errors[:field_22]).to eql([I18n.t("validations.sales.2024.bulk_upload.not_answered", question: "UPRN.")]) - expect(parser.errors[:field_23]).to eql([I18n.t("validations.sales.2024.bulk_upload.not_answered", question: "address line 1.")]) - expect(parser.errors[:field_25]).to eql([I18n.t("validations.sales.2024.bulk_upload.not_answered", question: "town or city.")]) - expect(parser.errors[:field_27]).to eql([I18n.t("validations.sales.2024.bulk_upload.not_answered", question: "part 1 of postcode.")]) - expect(parser.errors[:field_28]).to eql([I18n.t("validations.sales.2024.bulk_upload.not_answered", question: "part 2 of postcode.")]) + expect(parser.errors[:field_22]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_23]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_25]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_27]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_28]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_answered")]) end end @@ -1055,8 +1055,8 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "adds errors to UPRN and the missing key address field" do parser.valid? - expect(parser.errors[:field_22]).to eql([I18n.t("validations.sales.2024.bulk_upload.not_answered", question: "UPRN.")]) - expect(parser.errors[:field_23]).to eql([I18n.t("validations.sales.2024.bulk_upload.not_answered", question: "address line 1.")]) + expect(parser.errors[:field_22]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_23]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_answered")]) expect(parser.errors[:field_25]).to be_empty expect(parser.errors[:field_27]).to be_empty expect(parser.errors[:field_28]).to be_empty @@ -1105,7 +1105,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do parser.valid? expect(parser.errors[:field_22]).to be_empty %i[field_23 field_24 field_25 field_26 field_27 field_28].each do |field| - expect(parser.errors[field]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_found")]) + expect(parser.errors[field]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_determined")]) end end end diff --git a/spec/services/exports/lettings_log_export_service_spec.rb b/spec/services/exports/lettings_log_export_service_spec.rb index 6a07af8dd..c0dde5771 100644 --- a/spec/services/exports/lettings_log_export_service_spec.rb +++ b/spec/services/exports/lettings_log_export_service_spec.rb @@ -21,7 +21,9 @@ RSpec.describe Exports::LettingsLogExportService do def replace_entity_ids(lettings_log, export_template) export_template.sub!(/\{id\}/, (lettings_log["id"] + Exports::LettingsLogExportService::LOG_ID_OFFSET).to_s) export_template.sub!(/\{owning_org_id\}/, (lettings_log["owning_organisation_id"] + Exports::LettingsLogExportService::LOG_ID_OFFSET).to_s) + export_template.sub!(/\{owning_org_name\}/, lettings_log.owning_organisation.name) export_template.sub!(/\{managing_org_id\}/, (lettings_log["managing_organisation_id"] + Exports::LettingsLogExportService::LOG_ID_OFFSET).to_s) + export_template.sub!(/\{managing_org_name\}/, lettings_log.managing_organisation.name) export_template.sub!(/\{location_id\}/, (lettings_log["location_id"]).to_s) if lettings_log.needstype == 2 export_template.sub!(/\{scheme_id\}/, (lettings_log["scheme_id"]).to_s) if lettings_log.needstype == 2 export_template.sub!(/\{log_id\}/, lettings_log["id"].to_s) @@ -207,15 +209,15 @@ RSpec.describe Exports::LettingsLogExportService do it "generates multiple ZIP export files with the expected filenames" do expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) expect(storage_service).to receive(:write_file).with(expected_zip_filename2, any_args) - expect(Rails.logger).to receive(:info).with("Building export run for 2021") + expect(Rails.logger).to receive(:info).with("Building export run for lettings 2021") expect(Rails.logger).to receive(:info).with("Creating core_2021_2022_apr_mar_f0001_inc0001 - 1 resources") expect(Rails.logger).to receive(:info).with("Added core_2021_2022_apr_mar_f0001_inc0001_pt001.xml") expect(Rails.logger).to receive(:info).with("Writing core_2021_2022_apr_mar_f0001_inc0001.zip") - expect(Rails.logger).to receive(:info).with("Building export run for 2022") + expect(Rails.logger).to receive(:info).with("Building export run for lettings 2022") expect(Rails.logger).to receive(:info).with("Creating core_2022_2023_apr_mar_f0001_inc0001 - 1 resources") expect(Rails.logger).to receive(:info).with("Added core_2022_2023_apr_mar_f0001_inc0001_pt001.xml") expect(Rails.logger).to receive(:info).with("Writing core_2022_2023_apr_mar_f0001_inc0001.zip") - expect(Rails.logger).to receive(:info).with("Building export run for 2023") + expect(Rails.logger).to receive(:info).with("Building export run for lettings 2023") expect(Rails.logger).to receive(:info).with("Creating core_2023_2024_apr_mar_f0001_inc0001 - 0 resources") export_service.export_xml_lettings_logs @@ -223,7 +225,7 @@ RSpec.describe Exports::LettingsLogExportService do it "generates zip export files only for specified year" do expect(storage_service).to receive(:write_file).with(expected_zip_filename2, any_args) - expect(Rails.logger).to receive(:info).with("Building export run for 2022") + expect(Rails.logger).to receive(:info).with("Building export run for lettings 2022") expect(Rails.logger).to receive(:info).with("Creating core_2022_2023_apr_mar_f0001_inc0001 - 1 resources") expect(Rails.logger).to receive(:info).with("Added core_2022_2023_apr_mar_f0001_inc0001_pt001.xml") expect(Rails.logger).to receive(:info).with("Writing core_2022_2023_apr_mar_f0001_inc0001.zip") @@ -236,21 +238,21 @@ RSpec.describe Exports::LettingsLogExportService do let(:expected_zip_filename2) { "core_2022_2023_apr_mar_f0001_inc0001.zip" } before do - Export.new(started_at: Time.zone.yesterday, base_number: 7, increment_number: 3, collection: 2021).save! + Export.new(started_at: Time.zone.yesterday, base_number: 7, increment_number: 3, collection: "lettings", year: 2021).save! end it "generates multiple ZIP export files with different base numbers in the filenames" do expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) expect(storage_service).to receive(:write_file).with(expected_zip_filename2, any_args) - expect(Rails.logger).to receive(:info).with("Building export run for 2021") + expect(Rails.logger).to receive(:info).with("Building export run for lettings 2021") expect(Rails.logger).to receive(:info).with("Creating core_2021_2022_apr_mar_f0007_inc0004 - 1 resources") expect(Rails.logger).to receive(:info).with("Added core_2021_2022_apr_mar_f0007_inc0004_pt001.xml") expect(Rails.logger).to receive(:info).with("Writing core_2021_2022_apr_mar_f0007_inc0004.zip") - expect(Rails.logger).to receive(:info).with("Building export run for 2022") + expect(Rails.logger).to receive(:info).with("Building export run for lettings 2022") expect(Rails.logger).to receive(:info).with("Creating core_2022_2023_apr_mar_f0001_inc0001 - 1 resources") expect(Rails.logger).to receive(:info).with("Added core_2022_2023_apr_mar_f0001_inc0001_pt001.xml") expect(Rails.logger).to receive(:info).with("Writing core_2022_2023_apr_mar_f0001_inc0001.zip") - expect(Rails.logger).to receive(:info).with("Building export run for 2023") + expect(Rails.logger).to receive(:info).with("Building export run for lettings 2023") expect(Rails.logger).to receive(:info).with("Creating core_2023_2024_apr_mar_f0001_inc0001 - 0 resources") export_service.export_xml_lettings_logs @@ -329,7 +331,7 @@ RSpec.describe Exports::LettingsLogExportService do context "when this is a second export (partial)" do before do start_time = Time.zone.local(2022, 6, 1) - Export.new(started_at: start_time, collection: 2021).save! + Export.new(started_at: start_time, collection: "lettings", year: 2021).save! end it "does not add any entry for the master manifest (no lettings logs)" do diff --git a/spec/services/exports/organisation_export_service_spec.rb b/spec/services/exports/organisation_export_service_spec.rb index 43ca19095..51c8fe8cf 100644 --- a/spec/services/exports/organisation_export_service_spec.rb +++ b/spec/services/exports/organisation_export_service_spec.rb @@ -16,6 +16,7 @@ RSpec.describe Exports::OrganisationExportService do def replace_entity_ids(organisation, export_template) export_template.sub!(/\{id\}/, organisation["id"].to_s) + export_template.sub!(/\{name\}/, organisation["name"]) export_template.sub!(/\{dsa_signed_at\}/, organisation.data_protection_confirmation&.signed_at.to_s) export_template.sub!(/\{dpo_email\}/, organisation.data_protection_confirmation&.data_protection_officer_email) end diff --git a/spec/services/exports/user_export_service_spec.rb b/spec/services/exports/user_export_service_spec.rb index 8a0e22267..854dd1ce7 100644 --- a/spec/services/exports/user_export_service_spec.rb +++ b/spec/services/exports/user_export_service_spec.rb @@ -17,6 +17,7 @@ RSpec.describe Exports::UserExportService do def replace_entity_ids(user, export_template) export_template.sub!(/\{id\}/, user["id"].to_s) export_template.sub!(/\{organisation_id\}/, user["organisation_id"].to_s) + export_template.sub!(/\{organisation_name\}/, user.organisation.name) export_template.sub!(/\{email\}/, user["email"].to_s) end diff --git a/yarn.lock b/yarn.lock index 3e9afa0d8..94b220fe1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4335,9 +4335,9 @@ mute-stream@0.0.8: integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + version "3.3.8" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" + integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== natural-compare@^1.4.0: version "1.4.0"