Browse Source

Merge branch 'main' into CLDC-2831-page-load

CLDC-2831-page-load
natdeanlewissoftwire 8 months ago
parent
commit
36800aae51
  1. 6
      .github/workflows/aws_deploy.yml
  2. 200
      .github/workflows/production_pipeline.yml
  3. 2
      app/components/check_answers_summary_list_card_component.html.erb
  4. 5
      app/components/check_answers_summary_list_card_component.rb
  5. 40
      app/controllers/form_controller.rb
  6. 10
      app/controllers/organisation_relationships_controller.rb
  7. 31
      app/controllers/organisations_controller.rb
  8. 3
      app/controllers/users_controller.rb
  9. 2
      app/helpers/form_page_helper.rb
  10. 13
      app/helpers/toggle_active_organisation_helper.rb
  11. 2
      app/jobs/create_addresses_csv_job.rb
  12. 2
      app/jobs/data_export_xml_job.rb
  13. 2
      app/jobs/email_csv_job.rb
  14. 2
      app/jobs/email_missing_addresses_csv_job.rb
  15. 2
      app/jobs/scheme_email_csv_job.rb
  16. 11
      app/models/form.rb
  17. 4
      app/models/form/lettings/pages/uprn.rb
  18. 2
      app/models/form/lettings/pages/uprn_selection.rb
  19. 2
      app/models/form/lettings/questions/ethnic_arab.rb
  20. 2
      app/models/form/lettings/questions/ethnic_asian.rb
  21. 2
      app/models/form/lettings/questions/ethnic_black.rb
  22. 2
      app/models/form/lettings/questions/ethnic_group.rb
  23. 2
      app/models/form/lettings/questions/ethnic_mixed.rb
  24. 6
      app/models/form/lettings/questions/managing_organisation.rb
  25. 2
      app/models/form/lettings/questions/nationality_all_group.rb
  26. 1
      app/models/form/lettings/questions/period.rb
  27. 2
      app/models/form/lettings/questions/person_working_situation.rb
  28. 7
      app/models/form/lettings/questions/referral_prp.rb
  29. 10
      app/models/form/lettings/questions/referral_supported_housing.rb
  30. 4
      app/models/form/lettings/questions/stock_owner.rb
  31. 4
      app/models/form/lettings/questions/working_situation1.rb
  32. 4
      app/models/form/page.rb
  33. 10
      app/models/form/question.rb
  34. 4
      app/models/form/sales/pages/uprn.rb
  35. 2
      app/models/form/sales/pages/uprn_selection.rb
  36. 2
      app/models/form/sales/questions/buyer1_ethnic_background_arab.rb
  37. 2
      app/models/form/sales/questions/buyer1_ethnic_background_asian.rb
  38. 2
      app/models/form/sales/questions/buyer1_ethnic_background_black.rb
  39. 2
      app/models/form/sales/questions/buyer1_ethnic_background_mixed.rb
  40. 2
      app/models/form/sales/questions/buyer1_ethnic_group.rb
  41. 2
      app/models/form/sales/questions/buyer1_live_in_property.rb
  42. 4
      app/models/form/sales/questions/buyer1_working_situation.rb
  43. 2
      app/models/form/sales/questions/buyer2_working_situation.rb
  44. 6
      app/models/form/sales/questions/managing_organisation.rb
  45. 10
      app/models/form/sales/questions/nationality_all_group.rb
  46. 4
      app/models/form/sales/questions/owning_organisation_id.rb
  47. 2
      app/models/form/sales/questions/person_working_situation.rb
  48. 2
      app/models/forms/bulk_upload_lettings/upload_your_file.rb
  49. 2
      app/models/forms/bulk_upload_sales/upload_your_file.rb
  50. 20
      app/models/location.rb
  51. 3
      app/models/organisation.rb
  52. 23
      app/models/scheme.rb
  53. 17
      app/models/user.rb
  54. 4
      app/models/validations/household_validations.rb
  55. 16
      app/policies/organisation_policy.rb
  56. 2
      app/services/bulk_update_from_csv/update_locations_from_csv_service.rb
  57. 2
      app/services/bulk_update_from_csv/update_schemes_from_csv_service.rb
  58. 2
      app/services/bulk_upload/downloader.rb
  59. 2
      app/services/bulk_upload/lettings/year2023/row_parser.rb
  60. 6
      app/services/feature_toggle.rb
  61. 6
      app/views/form/_check_answers_summary_list.html.erb
  62. 2
      app/views/form/_interruption_screen_question.html.erb
  63. 1
      app/views/form/check_answers.html.erb
  64. 7
      app/views/form/review.html.erb
  65. 2
      app/views/locations/show.html.erb
  66. 18
      app/views/organisation_relationships/managing_agents.html.erb
  67. 18
      app/views/organisation_relationships/stock_owners.html.erb
  68. 9
      app/views/organisations/show.html.erb
  69. 29
      app/views/organisations/toggle_active.html.erb
  70. 2
      app/views/schemes/confirm_secondary.html.erb
  71. 2
      app/views/schemes/secondary_client_group.html.erb
  72. 2
      app/views/schemes/show.html.erb
  73. 2
      app/views/schemes/support.html.erb
  74. 2
      app/views/users/new.html.erb
  75. 2
      app/views/users/show.html.erb
  76. 7
      config/locales/en.yml
  77. 2
      config/routes.rb
  78. 5
      db/migrate/20240304112411_add_reactivate_with_organisation_to_users.rb
  79. 12
      db/migrate/20240305112507_add_default_value_to_organisation_active_field.rb
  80. 3
      db/schema.rb
  81. 2
      lib/tasks/data_export.rake
  82. 4
      lib/tasks/import_address_from_csv.rake
  83. 4
      spec/features/form/check_answers_page_lettings_logs_spec.rb
  84. 6
      spec/features/form/form_navigation_spec.rb
  85. 10
      spec/features/form/validations_spec.rb
  86. 2
      spec/fixtures/files/sales_logs_csv_export_labels_23.csv
  87. 2
      spec/fixtures/files/sales_logs_csv_export_labels_23_during_24_period.csv
  88. 2
      spec/fixtures/files/sales_logs_csv_export_labels_24.csv
  89. 2
      spec/lib/tasks/correct_address_from_csv_spec.rb
  90. 4
      spec/lib/tasks/data_export_spec.rb
  91. 2
      spec/lib/tasks/update_schemes_and_locations_from_csv_spec.rb
  92. 2
      spec/models/form/lettings/pages/uprn_selection_spec.rb
  93. 4
      spec/models/form/lettings/pages/uprn_spec.rb
  94. 2
      spec/models/form/lettings/questions/gender_identity1_spec.rb
  95. 15
      spec/models/form/lettings/questions/managing_organisation_spec.rb
  96. 2
      spec/models/form/lettings/questions/nationality_all_group_spec.rb
  97. 2
      spec/models/form/lettings/questions/person_working_situation_spec.rb
  98. 104
      spec/models/form/lettings/questions/referral_prp_spec.rb
  99. 105
      spec/models/form/lettings/questions/referral_supported_housing_prp_spec.rb
  100. 101
      spec/models/form/lettings/questions/referral_supported_housing_spec.rb
  101. Some files were not shown because too many files have changed in this diff Show More

6
.github/workflows/aws_deploy.yml

@ -59,13 +59,17 @@ jobs:
run: |
echo "image-exists=$(if aws ecr list-images --repository-name=$repository --query "imageIds[*].imageTag" | grep -q ${{ github.sha }}; then echo true; else echo false; fi)" >> $GITHUB_ENV
- name: Build, tag, and push docker image to ECR
- name: Build, tag, and push docker image to ECR if there is no image, failing for releases
id: build-image
if: ${{ env.image-exists == 'false' }}
env:
registry: ${{ steps.ecr-login.outputs.registry }}
commit_tag: ${{ github.sha }}
run: |
if [[ ${{ inputs.environment }} == 'production' ]]; then
echo "Error: Deployment to production environment is not allowed as there is no docker image (i.e. the AWS deploy on staging was unsuccessful for this commit)."
exit 1
fi
docker build -t $registry/$repository:$commit_tag . --target=production
docker push $registry/$repository:$commit_tag

200
.github/workflows/production_pipeline.yml

@ -5,213 +5,13 @@ on:
types: [released]
workflow_dispatch:
env:
REPO_URL: communitiesuk/submit-social-housing-lettings-and-sales-data
defaults:
run:
shell: bash
jobs:
test:
name: Test
runs-on: ubuntu-latest
outputs:
releasetag: ${{ steps.latestrelease.outputs.releasetag }}
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: Get latest release with tag
id: latestrelease
run: |
echo "releasetag=$(curl -s https://api.github.com/repos/${REPO_URL}/releases/latest | jq '.tag_name' | sed 's/\"//g')" >> $GITHUB_OUTPUT
- name: Confirm release tag
run: |
echo ${{ steps.latestrelease.outputs.releasetag }}
- name: Checkout tag
uses: actions/checkout@v3
with:
ref: ${{ steps.latestrelease.outputs.releasetag }}
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set up node
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)']
feature_test:
name: Feature Tests
if: '!github.event.pull_request.draft'
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
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Get latest release with tag
id: latestrelease
run: |
echo "::set-output name=releasetag::$(curl -s https://api.github.com/repos/${REPO_URL}/releases/latest | jq '.tag_name' | sed 's/\"//g')"
- name: Confirm release tag
run: |
echo ${{ steps.latestrelease.outputs.releasetag }}
- name: Checkout tag
uses: actions/checkout@v3
with:
ref: ${{ steps.latestrelease.outputs.releasetag }}
- 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: Get latest release with tag
id: latestrelease
run: |
echo "::set-output name=releasetag::$(curl -s https://api.github.com/repos/${REPO_URL}/releases/latest | jq '.tag_name' | sed 's/\"//g')"
- name: Confirm release tag
run: |
echo ${{ steps.latestrelease.outputs.releasetag }}
- name: Checkout tag
uses: actions/checkout@v3
with:
ref: ${{ steps.latestrelease.outputs.releasetag }}
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Audit
run: |
bundle exec bundler-audit
aws_deploy:
name: AWS Deploy
needs: [lint, test, feature_test, audit]
uses: ./.github/workflows/aws_deploy.yml
with:
aws_account_id: 977287343304

2
app/components/check_answers_summary_list_card_component.html.erb

@ -36,7 +36,7 @@
<% if @log.collection_period_open_for_editing? %>
<% row.with_action(
text: question.action_text(log),
href: action_href(log, question.page.id),
href: action_href(question, log),
visually_hidden_text: question.check_answer_label.to_s.downcase,
) %>
<% end %>

5
app/components/check_answers_summary_list_card_component.rb

@ -28,8 +28,9 @@ class CheckAnswersSummaryListCardComponent < ViewComponent::Base
"Person #{question.check_answers_card_number}"
end
def action_href(log, page_id, referrer = "check_answers")
send("#{log.model_name.param_key}_#{page_id}_path", log, referrer:)
def action_href(question, log)
referrer = question.displayed_as_answered?(log) ? "check_answers" : "check_answers_new_answer"
send("#{log.model_name.param_key}_#{question.page.id}_path", log, referrer:)
end
private

40
app/controllers/form_controller.rb

@ -9,6 +9,11 @@ class FormController < ApplicationController
def submit_form
if @log
@page = form.get_page(params[@log.model_name.param_key][:page])
shown_page_ids_with_unanswered_questions_before_update = @page.subsection.pages
.select { |page| page.routed_to?(@log, current_user) }
.select { |page| page.has_unanswered_questions?(@log) }
.map(&:id)
responses_for_page = responses_for_page(@page)
mandatory_questions_with_no_response = mandatory_questions_with_no_response(responses_for_page)
@ -18,7 +23,9 @@ class FormController < ApplicationController
updated_question_string = [updated_question&.question_number_string, updated_question&.check_answer_label.to_s.downcase].compact.join(": ")
flash[:notice] = "You have successfully updated #{updated_question_string}"
end
redirect_to(successful_redirect_path)
pages_requiring_update = pages_requiring_update(shown_page_ids_with_unanswered_questions_before_update)
redirect_to(successful_redirect_path(pages_requiring_update))
else
mandatory_questions_with_no_response.map do |question|
@log.errors.add question.id.to_sym, question.unanswered_error_message, category: :not_answered
@ -171,7 +178,7 @@ private
params[@log.model_name.param_key]["interruption_page_referrer_type"].presence
end
def successful_redirect_path
def successful_redirect_path(pages_to_check)
if FeatureToggle.deduplication_flow_enabled?
if is_referrer_type?("duplicate_logs") || is_referrer_type?("duplicate_logs_banner")
return correcting_duplicate_logs_redirect_path
@ -195,7 +202,9 @@ private
previous_page = form.previous_page_id(@page, @log, current_user)
if next_page&.interruption_screen? || next_page_id == previous_page || CONFIRMATION_PAGE_IDS.include?(next_page_id)
return send("#{@log.class.name.underscore}_#{next_page_id}_path", @log, { referrer: "check_answers" })
return redirect_path_to_question(next_page, pages_to_check)
elsif pages_to_check.any?
return redirect_path_to_question(pages_to_check[0], pages_to_check)
else
return send("#{@log.model_name.param_key}_#{form.subsection_for_page(@page).id}_check_answers_path", @log)
end
@ -204,8 +213,29 @@ private
return send("#{@log.class.name.underscore}_#{previous_interruption_screen_page_id}_path", @log, { referrer: previous_interruption_screen_referrer, original_log_id: original_duplicate_log_id_from_query }.compact)
end
redirect_path = form.next_page_redirect_path(@page, @log, current_user)
send(redirect_path, @log)
is_new_answer_from_check_answers = is_referrer_type?("check_answers_new_answer")
redirect_path = form.next_page_redirect_path(@page, @log, current_user, ignore_answered: is_new_answer_from_check_answers)
referrer = is_new_answer_from_check_answers ? "check_answers_new_answer" : nil
send(redirect_path, @log, { referrer: })
end
def redirect_path_to_question(page_to_show, unanswered_pages)
remaining_pages = unanswered_pages.excluding(page_to_show)
remaining_page_ids = remaining_pages.any? ? remaining_pages.map(&:id).join(",") : nil
send("#{@log.class.name.underscore}_#{page_to_show.id}_path", @log, { referrer: "check_answers", unanswered_pages: remaining_page_ids })
end
def pages_requiring_update(previously_visible_empty_page_ids)
return [] unless is_referrer_type?("check_answers")
currently_shown_pages = @page.subsection.pages
.select { |page| page.routed_to?(@log, current_user) }
existing_unanswered_pages = request.params["unanswered_pages"].nil? ? [] : request.params["unanswered_pages"].split(",")
currently_shown_pages
.reject { |page| previously_visible_empty_page_ids.include?(page.id) && !existing_unanswered_pages.include?(page.id) }
.select { |page| page.has_unanswered_questions?(@log) }
end
def form

10
app/controllers/organisation_relationships_controller.rb

@ -14,7 +14,7 @@ class OrganisationRelationshipsController < ApplicationController
]
def stock_owners
stock_owners = organisation.stock_owners
stock_owners = organisation.stock_owners.filter_by_active
unpaginated_filtered_stock_owners = filtered_collection(stock_owners, search_term)
@pagy, @stock_owners = pagy(unpaginated_filtered_stock_owners)
@ -23,7 +23,7 @@ class OrganisationRelationshipsController < ApplicationController
end
def managing_agents
managing_agents = organisation.managing_agents
managing_agents = organisation.managing_agents.filter_by_active
unpaginated_filtered_managing_agents = filtered_collection(managing_agents, search_term)
@pagy, @managing_agents = pagy(unpaginated_filtered_managing_agents)
@ -48,7 +48,7 @@ class OrganisationRelationshipsController < ApplicationController
flash[:notice] = "#{@organisation_relationship.parent_organisation.name} is now one of #{current_user.data_coordinator? ? 'your' : "this organisation's"} stock owners"
redirect_to stock_owners_organisation_path
else
@organisations = Organisation.where.not(id: organisation.id).pluck(:id, :name)
@organisations = Organisation.filter_by_active.where.not(id: organisation.id).pluck(:id, :name)
render "organisation_relationships/add_stock_owner", status: :unprocessable_entity
end
end
@ -60,7 +60,7 @@ class OrganisationRelationshipsController < ApplicationController
flash[:notice] = "#{@organisation_relationship.child_organisation.name} is now one of #{current_user.data_coordinator? ? 'your' : "this organisation's"} managing agents"
redirect_to managing_agents_organisation_path
else
@organisations = Organisation.where.not(id: organisation.id).pluck(:id, :name)
@organisations = Organisation.filter_by_active.where.not(id: organisation.id).pluck(:id, :name)
render "organisation_relationships/add_managing_agent", status: :unprocessable_entity
end
end
@ -110,7 +110,7 @@ private
end
def organisations
@organisations ||= Organisation.where.not(id: organisation.id).pluck(:id, :name)
@organisations ||= Organisation.filter_by_active.where.not(id: organisation.id).pluck(:id, :name)
end
def parent_organisation

31
app/controllers/organisations_controller.rb

@ -94,10 +94,37 @@ class OrganisationsController < ApplicationController
end
end
def deactivate
authorize @organisation
render "toggle_active", locals: { action: "deactivate" }
end
def reactivate
authorize @organisation
render "toggle_active", locals: { action: "reactivate" }
end
def update
if current_user.data_coordinator? || current_user.support?
if (current_user.data_coordinator? && org_params[:active].nil?) || current_user.support?
if @organisation.update(org_params)
case org_params[:active]
when "false"
@organisation.users.filter_by_active.each do |user|
user.deactivate!(reactivate_with_organisation: true)
end
flash[:notice] = I18n.t("organisation.deactivated", organisation: @organisation.name)
when "true"
users_to_reactivate = @organisation.users.where(reactivate_with_organisation: true)
users_to_reactivate.each do |user|
user.reactivate!
user.send_confirmation_instructions
end
flash[:notice] = I18n.t("organisation.reactivated", organisation: @organisation.name)
else
flash[:notice] = I18n.t("organisation.updated")
end
redirect_to details_organisation_path(@organisation)
end
else
@ -239,7 +266,7 @@ private
end
def org_params
params.require(:organisation).permit(:name, :address_line1, :address_line2, :postcode, :phone, :holds_own_stock, :provider_type, :housing_registration_no)
params.require(:organisation).permit(:name, :address_line1, :address_line2, :postcode, :phone, :holds_own_stock, :provider_type, :housing_registration_no, :active)
end
def codes_only_export?

3
app/controllers/users_controller.rb

@ -62,9 +62,10 @@ class UsersController < ApplicationController
else
user_name = @user.name&.possessive || @user.email.possessive
if user_params[:active] == "false"
@user.update!(confirmed_at: nil, sign_in_count: 0, initial_confirmation_sent: false)
@user.deactivate!
flash[:notice] = I18n.t("devise.activation.deactivated", user_name:)
elsif user_params[:active] == "true"
@user.reactivate!
@user.send_confirmation_instructions
flash[:notice] = I18n.t("devise.activation.reactivated", user_name:)
elsif user_params.key?("email")

2
app/helpers/form_page_helper.rb

@ -43,7 +43,7 @@ module FormPageHelper
elsif returning_to_question_page?(page, referrer)
send(log.form.cancel_path(page, log), log)
else
page.skip_href(log) || send(log.form.next_page_redirect_path(page, log, current_user), log)
page.skip_href(log) || send(log.form.next_page_redirect_path(page, log, current_user, ignore_answered: true), log)
end
end
end

13
app/helpers/toggle_active_organisation_helper.rb

@ -0,0 +1,13 @@
module ToggleActiveOrganisationHelper
def toggle_organisation_form_path(action, organisation)
if action == "deactivate"
organisation_new_deactivation_path(organisation)
else
organisation_reactivate_path(organisation)
end
end
def date_type_question(action)
action == "deactivate" ? :deactivation_date_type : :reactivation_date_type
end
end

2
app/jobs/create_addresses_csv_job.rb

@ -14,7 +14,7 @@ class CreateAddressesCsvJob < ApplicationJob
filename = "#{['sales-logs-addresses', organisation.name, Time.zone.now].compact.join('-')}.csv"
end
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["CSV_DOWNLOAD_PAAS_INSTANCE"])
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
storage_service.write_file(filename, BYTE_ORDER_MARK + csv_string)
Rails.logger.info("Created addresses file: #{filename}")

2
app/jobs/data_export_xml_job.rb

@ -2,7 +2,7 @@ class DataExportXmlJob < ApplicationJob
queue_as :default
def perform(full_update: false)
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["EXPORT_PAAS_INSTANCE"])
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["EXPORT_BUCKET"])
export_service = Exports::LettingsLogExportService.new(storage_service)
export_service.export_xml_lettings_logs(full_update:)

2
app/jobs/email_csv_job.rb

@ -20,7 +20,7 @@ class EmailCsvJob < ApplicationJob
filename = "#{[log_type, 'logs', organisation&.name, Time.zone.now].compact.join('-')}.csv"
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["CSV_DOWNLOAD_PAAS_INSTANCE"])
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
storage_service.write_file(filename, BYTE_ORDER_MARK + csv_string)
url = storage_service.get_presigned_url(filename, EXPIRATION_TIME)

2
app/jobs/email_missing_addresses_csv_job.rb

@ -18,7 +18,7 @@ class EmailMissingAddressesCsvJob < ApplicationJob
email_method = :send_missing_sales_addresses_csv_download_mail
end
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["CSV_DOWNLOAD_PAAS_INSTANCE"])
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
storage_service.write_file(filename, BYTE_ORDER_MARK + csv_string)
url = storage_service.get_presigned_url(filename, EXPIRATION_TIME)

2
app/jobs/scheme_email_csv_job.rb

@ -23,7 +23,7 @@ class SchemeEmailCsvJob < ApplicationJob
filename = "#{['schemes-and-locations', organisation&.name, Time.zone.now].compact.join('-')}.csv"
end
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["CSV_DOWNLOAD_PAAS_INSTANCE"])
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
storage_service.write_file(filename, BYTE_ORDER_MARK + csv_string)
url = storage_service.get_presigned_url(filename, EXPIRATION_TIME)

11
app/models/form.rb

@ -80,7 +80,7 @@ class Form
subsections.find { |s| s.pages.find { |p| p.id == page.id } }
end
def next_page_id(page, log, current_user)
def next_page_id(page, log, current_user, ignore_answered: false)
return page.next_unresolved_page_id || :check_answers if log.unresolved
page_ids = subsection_for_page(page).pages.map(&:id)
@ -93,13 +93,14 @@ class Form
next_page = get_page(page_id)
return :check_answers if next_page.nil?
return next_page.id if next_page.routed_to?(log, current_user)
return next_page.id if next_page.routed_to?(log, current_user) &&
(!ignore_answered || next_page.has_unanswered_questions?(log))
next_page_id(next_page, log, current_user)
next_page_id(next_page, log, current_user, ignore_answered:)
end
def next_page_redirect_path(page, log, current_user)
next_page_id = next_page_id(page, log, current_user)
def next_page_redirect_path(page, log, current_user, ignore_answered: false)
next_page_id = next_page_id(page, log, current_user, ignore_answered:)
if next_page_id == :check_answers
"#{type}_log_#{subsection_for_page(page).id}_check_answers_path"
else

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

@ -24,9 +24,9 @@ class Form::Lettings::Pages::Uprn < ::Form::Page
return unless log
if form.start_year_after_2024?
"/#{log.model_name.param_key.dasherize}s/#{log.id}/address-matcher"
"address-matcher"
else
"/#{log.model_name.param_key.dasherize}s/#{log.id}/address"
"address"
end
end
end

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

@ -27,6 +27,6 @@ class Form::Lettings::Pages::UprnSelection < ::Form::Page
def skip_href(log = nil)
return unless log
"/#{log.model_name.param_key.dasherize}s/#{log.id}/address-matcher"
"address-matcher"
end
end

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

@ -6,7 +6,7 @@ class Form::Lettings::Questions::EthnicArab < ::Form::Question
@header = "Which of the following best describes the lead tenant’s Arab background?"
@type = "radio"
@check_answers_card_number = 1
@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."
@hint_text = form.start_year_after_2024? ? "" : "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."
@answer_options = ANSWER_OPTIONS
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

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

@ -6,7 +6,7 @@ class Form::Lettings::Questions::EthnicAsian < ::Form::Question
@header = "Which of the following best describes the lead tenant’s Asian or Asian British background?"
@type = "radio"
@check_answers_card_number = 1
@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."
@hint_text = form.start_year_after_2024? ? "" : "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."
@answer_options = ANSWER_OPTIONS
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

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

@ -6,7 +6,7 @@ class Form::Lettings::Questions::EthnicBlack < ::Form::Question
@header = "Which of the following best describes the lead tenant’s Black, African, Caribbean or Black British background?"
@type = "radio"
@check_answers_card_number = 1
@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."
@hint_text = form.start_year_after_2024? ? "" : "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."
@answer_options = ANSWER_OPTIONS
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

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

@ -6,7 +6,7 @@ class Form::Lettings::Questions::EthnicGroup < ::Form::Question
@header = "What is the lead tenant’s ethnic group?"
@type = "radio"
@check_answers_card_number = 1
@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."
@hint_text = form.start_year_after_2024? ? "" : "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."
@answer_options = ANSWER_OPTIONS
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

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

@ -6,7 +6,7 @@ class Form::Lettings::Questions::EthnicMixed < ::Form::Question
@header = "Which of the following best describes the lead tenant’s Mixed or Multiple ethnic groups background?"
@type = "radio"
@check_answers_card_number = 1
@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."
@hint_text = form.start_year_after_2024? ? "" : "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."
@answer_options = ANSWER_OPTIONS
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

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

@ -31,11 +31,11 @@ class Form::Lettings::Questions::ManagingOrganisation < ::Form::Question
end
orgs = if user.support?
log.owning_organisation.managing_agents
log.owning_organisation.managing_agents.filter_by_active
elsif user.organisation.absorbed_organisations.include?(log.owning_organisation)
user.organisation.managing_agents + log.owning_organisation.managing_agents
user.organisation.managing_agents.filter_by_active + log.owning_organisation.managing_agents.filter_by_active
else
user.organisation.managing_agents
user.organisation.managing_agents.filter_by_active
end
user.organisation.absorbed_organisations.each do |absorbed_org|

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

@ -6,7 +6,7 @@ class Form::Lettings::Questions::NationalityAllGroup < ::Form::Question
@header = "What is the nationality of the lead tenant?"
@type = "radio"
@check_answers_card_number = 1
@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. If the lead tenant is a dual national of the United Kingdom and another country, enter United Kingdom. If they are a dual national of two other countries, the tenant should decide which country to enter."
@hint_text = "If the lead tenant is a dual national of the United Kingdom and another country, enter United Kingdom. If they are a dual national of two other countries, the tenant should decide which country to enter."
@answer_options = ANSWER_OPTIONS
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
@conditional_for = { "nationality_all" => [12] }

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

@ -20,6 +20,7 @@ class Form::Lettings::Questions::Period < ::Form::Question
"7" => { "value" => "Weekly for 48 weeks" },
"6" => { "value" => "Weekly for 49 weeks" },
"5" => { "value" => "Weekly for 50 weeks" },
"11" => { "value" => "Weekly for 51 weeks" },
"1" => { "value" => "Weekly for 52 weeks" },
"10" => { "value" => "Weekly for 53 weeks" },
}.freeze

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

@ -16,7 +16,7 @@ class Form::Lettings::Questions::PersonWorkingSituation < ::Form::Question
{ "1" => { "value" => "Full-time – 30 hours or more" },
"2" => { "value" => "Part-time – Less than 30 hours" },
"7" => { "value" => "Full-time student" },
"3" => { "value" => "In government training into work, such as New Deal" },
"3" => { "value" => "In government training into work" },
"4" => { "value" => "Jobseeker" },
"6" => { "value" => "Not seeking work" },
"8" => { "value" => "Unable to work because of long term sick or disability" },

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

@ -18,14 +18,11 @@ class Form::Lettings::Questions::ReferralPrp < ::Form::Question
"hint" => "Where the tenant has moved to another social property owned by the same landlord.",
},
"2" => {
"value" => "Tenant applied directly (no nomination)",
"value" => "Tenant applied directly (no referral or nomination)",
},
"3" => {
"value" => "Nominated by a local housing authority",
},
"4" => {
"value" => "Referred by local authority housing department",
},
"8" => {
"value" => "Re-located through official housing mobility scheme",
},
@ -64,7 +61,7 @@ class Form::Lettings::Questions::ReferralPrp < ::Form::Question
"hint" => "Where the tenant has moved to another social property owned by the same landlord.",
},
"2" => {
"value" => "Tenant applied directly (no nomination)",
"value" => "Tenant applied directly (no referral or nomination)",
},
"3" => {
"value" => "Nominated by a local housing authority",

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

@ -18,10 +18,7 @@ class Form::Lettings::Questions::ReferralSupportedHousing < ::Form::Question
"hint" => "Where the tenant has moved to another social property owned by the same landlord.",
},
"2" => {
"value" => "Tenant applied directly (no referral)",
},
"3" => {
"value" => "Nominated by a local housing authority",
"value" => "Tenant applied directly (no referral or nomination)",
},
"8" => {
"value" => "Re-located through official housing mobility scheme",
@ -61,10 +58,7 @@ class Form::Lettings::Questions::ReferralSupportedHousing < ::Form::Question
"hint" => "Where the tenant has moved to another social property owned by the same landlord.",
},
"2" => {
"value" => "Tenant applied directly (no referral)",
},
"3" => {
"value" => "Nominated by a local housing authority",
"value" => "Tenant applied directly (no referral or nomination)",
},
"8" => {
"value" => "Re-located through official housing mobility scheme",

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

@ -30,7 +30,7 @@ class Form::Lettings::Questions::StockOwner < ::Form::Question
end
if user.support?
Organisation.where(holds_own_stock: true).find_each do |org|
Organisation.filter_by_active.where(holds_own_stock: true).find_each do |org|
if org.merge_date.present?
answer_opts[org.id] = "#{org.name} (inactive as of #{org.merge_date.to_fs(:govuk_date)})" if org.merge_date >= FormHandler.instance.start_date_of_earliest_open_for_editing_collection_period
elsif org.absorbed_organisations.merged_during_open_collection_period.exists? && org.available_from.present?
@ -40,7 +40,7 @@ class Form::Lettings::Questions::StockOwner < ::Form::Question
end
end
else
user.organisation.stock_owners.each do |stock_owner|
user.organisation.stock_owners.filter_by_active.each do |stock_owner|
answer_opts[stock_owner.id] = stock_owner.name
end
recently_absorbed_organisations.each do |absorbed_org|

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

@ -6,7 +6,7 @@ class Form::Lettings::Questions::WorkingSituation1 < ::Form::Question
@header = "Which of these best describes the lead tenant’s working situation?"
@type = "radio"
@check_answers_card_number = 1
@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."
@hint_text = form.start_year_after_2024? ? "" : "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."
@answer_options = ANSWER_OPTIONS
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
@ -15,7 +15,7 @@ class Form::Lettings::Questions::WorkingSituation1 < ::Form::Question
"1" => { "value" => "Full-time – 30 hours or more" },
"2" => { "value" => "Part-time – Less than 30 hours" },
"7" => { "value" => "Full-time student" },
"3" => { "value" => "In government training into work, such as New Deal" },
"3" => { "value" => "In government training into work" },
"4" => { "value" => "Jobseeker" },
"6" => { "value" => "Not seeking work" },
"8" => { "value" => "Unable to work because of long term sick or disability" },

4
app/models/form/page.rb

@ -36,6 +36,10 @@ class Form::Page
end
end
def has_unanswered_questions?(log)
questions.any? { |question| question.displayed_to_user?(log) && question.unanswered?(log) }
end
def interruption_screen?
questions.all? { |question| question.type == "interruption_screen" }
end

10
app/models/form/question.rb

@ -110,12 +110,16 @@ class Form::Question
end
def action_text(log)
displayed_as_answered?(log) ? "Change" : "Answer"
end
def displayed_as_answered?(log)
if is_derived_or_has_inferred_check_answers_value?(log)
"Change"
true
elsif type == "checkbox"
answer_options.keys.any? { |key| value_is_yes?(log[key]) } ? "Change" : "Answer"
answer_options.keys.any? { |key| value_is_yes?(log[key]) }
else
log[id].blank? ? "Answer" : "Change"
log[id].present?
end
end

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

@ -23,9 +23,9 @@ class Form::Sales::Pages::Uprn < ::Form::Page
return unless log
if form.start_year_after_2024?
"/#{log.model_name.param_key.dasherize}s/#{log.id}/address-matcher"
"address-matcher"
else
"/#{log.model_name.param_key.dasherize}s/#{log.id}/address"
"address"
end
end
end

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

@ -27,6 +27,6 @@ class Form::Sales::Pages::UprnSelection < ::Form::Page
def skip_href(log = nil)
return unless log
"/#{log.model_name.param_key.dasherize}s/#{log.id}/address-matcher"
"address-matcher"
end
end

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

@ -6,7 +6,7 @@ class Form::Sales::Questions::Buyer1EthnicBackgroundArab < ::Form::Question
@header = "Which of the following best describes buyer 1’s Arab background?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@hint_text = "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@hint_text = form.start_year_after_2024? ? "" : "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@check_answers_card_number = 1
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

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

@ -6,7 +6,7 @@ class Form::Sales::Questions::Buyer1EthnicBackgroundAsian < ::Form::Question
@header = "Which of the following best describes buyer 1’s Asian or Asian British background?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@hint_text = "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@hint_text = form.start_year_after_2024? ? "" : "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@check_answers_card_number = 1
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

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

@ -6,7 +6,7 @@ class Form::Sales::Questions::Buyer1EthnicBackgroundBlack < ::Form::Question
@header = "Which of the following best describes buyer 1’s Black, African, Caribbean or Black British background?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@hint_text = "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@hint_text = form.start_year_after_2024? ? "" : "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@check_answers_card_number = 1
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

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

@ -6,7 +6,7 @@ class Form::Sales::Questions::Buyer1EthnicBackgroundMixed < ::Form::Question
@header = "Which of the following best describes buyer 1’s Mixed or Multiple ethnic groups background?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@hint_text = "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@hint_text = form.start_year_after_2024? ? "" : "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@check_answers_card_number = 1
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

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

@ -6,7 +6,7 @@ class Form::Sales::Questions::Buyer1EthnicGroup < ::Form::Question
@header = "What is buyer 1’s ethnic group?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@hint_text = "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@hint_text = form.start_year_after_2024? ? "" : "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@check_answers_card_number = 1
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

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

@ -6,7 +6,7 @@ class Form::Sales::Questions::Buyer1LiveInProperty < ::Form::Question
@header = "Will buyer 1 live in the property?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@hint_text = "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@hint_text = form.start_year_after_2024? ? "" : "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@check_answers_card_number = 1
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

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

@ -6,7 +6,7 @@ class Form::Sales::Questions::Buyer1WorkingSituation < ::Form::Question
@header = "Which of these best describes buyer 1's working situation?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@hint_text = "Buyer 1 is the person in the household who does the most paid work. If it's a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@hint_text = form.start_year_after_2024? ? "" : "Buyer 1 is the person in the household who does the most paid work. If its a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@check_answers_card_number = 1
@inferred_check_answers_value = [{
"condition" => {
@ -20,7 +20,7 @@ class Form::Sales::Questions::Buyer1WorkingSituation < ::Form::Question
ANSWER_OPTIONS = {
"1" => { "value" => "Full-time - 30 hours or more" },
"2" => { "value" => "Part-time - Less than 30 hours" },
"3" => { "value" => "In government training into work, such as New Deal" },
"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" },

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

@ -19,7 +19,7 @@ class Form::Sales::Questions::Buyer2WorkingSituation < ::Form::Question
ANSWER_OPTIONS = {
"1" => { "value" => "Full-time - 30 hours or more" },
"2" => { "value" => "Part-time - Less than 30 hours" },
"3" => { "value" => "In government training into work, such as New Deal" },
"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" },

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

@ -31,11 +31,11 @@ class Form::Sales::Questions::ManagingOrganisation < ::Form::Question
end
orgs = if user.support?
log.owning_organisation.managing_agents
log.owning_organisation.managing_agents.filter_by_active
elsif user.organisation.absorbed_organisations.include?(log.owning_organisation)
user.organisation.managing_agents + log.owning_organisation.managing_agents
user.organisation.managing_agents.filter_by_active + log.owning_organisation.managing_agents.filter_by_active
else
user.organisation.managing_agents
user.organisation.managing_agents.filter_by_active
end.pluck(:id, :name).to_h
user.organisation.absorbed_organisations.each do |absorbed_org|

10
app/models/form/sales/questions/nationality_all_group.rb

@ -4,7 +4,7 @@ class Form::Sales::Questions::NationalityAllGroup < ::Form::Question
@check_answer_label = "Buyer #{buyer_index}’s nationality"
@header = "What is buyer #{buyer_index}’s nationality?"
@type = "radio"
@hint_text = buyer_index == 1 ? "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest." : ""
@hint_text = "If buyer #{buyer_index} is a dual national of the United Kingdom and another country, enter United Kingdom. If they are a dual national of two other countries, the buyer should decide which country to enter."
@answer_options = ANSWER_OPTIONS
@check_answers_card_number = buyer_index
@conditional_for = buyer_index == 1 ? { "nationality_all" => [12] } : { "nationality_all_buyer2" => [12] }
@ -19,14 +19,6 @@ class Form::Sales::Questions::NationalityAllGroup < ::Form::Question
"0" => { "value" => "Buyer prefers not to say" },
}.freeze
def hint_text
if @buyer_index == 1
"Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest. If buyer 1 is a dual national of the United Kingdom and another country, enter United Kingdom. If they are a dual national of two other countries, the buyer should decide which country to enter."
else
"If buyer 2 is a dual national of the United Kingdom and another country, enter United Kingdom. If they are a dual national of two other countries, the buyer should decide which country to enter."
end
end
def question_number
if form.start_date.year == 2023
@buyer_index == 1 ? 24 : 32

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

@ -25,7 +25,7 @@ class Form::Sales::Questions::OwningOrganisationId < ::Form::Question
answer_opts[user.organisation.id] = "#{user.organisation.name} (Your organisation)"
end
user.organisation.stock_owners.where(holds_own_stock: true).find_each do |org|
user.organisation.stock_owners.filter_by_active.where(holds_own_stock: true).find_each do |org|
answer_opts[org.id] = org.name
end
end
@ -44,7 +44,7 @@ class Form::Sales::Questions::OwningOrganisationId < ::Form::Question
end
if user.support?
Organisation.where(holds_own_stock: true).find_each do |org|
Organisation.filter_by_active.where(holds_own_stock: true).find_each do |org|
if org.merge_date.present?
answer_opts[org.id] = "#{org.name} (inactive as of #{org.merge_date.to_fs(:govuk_date)})" if org.merge_date >= FormHandler.instance.start_date_of_earliest_open_for_editing_collection_period
elsif org.absorbed_organisations.merged_during_open_collection_period.exists? && org.available_from.present?

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

@ -20,7 +20,7 @@ class Form::Sales::Questions::PersonWorkingSituation < ::Form::Question
{
"1" => { "value" => "Full-time - 30 hours or more" },
"2" => { "value" => "Part-time - Less than 30 hours" },
"3" => { "value" => "In government training into work, such as New Deal" },
"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" },

2
app/models/forms/bulk_upload_lettings/upload_your_file.rb

@ -56,7 +56,7 @@ module Forms
def storage_service
@storage_service ||= if upload_enabled?
Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["CSV_DOWNLOAD_PAAS_INSTANCE"])
Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
else
Storage::LocalDiskService.new
end

2
app/models/forms/bulk_upload_sales/upload_your_file.rb

@ -49,7 +49,7 @@ module Forms
def storage_service
@storage_service ||= if FeatureToggle.upload_enabled?
Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["CSV_DOWNLOAD_PAAS_INSTANCE"])
Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
else
Storage::LocalDiskService.new
end

20
app/models/location.rb

@ -40,6 +40,7 @@ class Location < ApplicationRecord
if scopes.any?
filtered_records = filtered_records
.left_outer_joins(:location_deactivation_periods)
.joins(scheme: [:owning_organisation])
.order("location_deactivation_periods.created_at DESC")
.merge(scopes.reduce(&:or))
end
@ -53,6 +54,15 @@ class Location < ApplicationRecord
}
scope :deactivated, lambda {
deactivated_by_organisation
.or(deactivated_directly)
}
scope :deactivated_by_organisation, lambda {
merge(Organisation.filter_by_inactive)
}
scope :deactivated_directly, lambda {
merge(LocationDeactivationPeriod.deactivations_without_reactivation)
.where("location_deactivation_periods.deactivation_date <= ?", Time.zone.now)
}
@ -60,20 +70,23 @@ class Location < ApplicationRecord
scope :deactivating_soon, lambda {
merge(LocationDeactivationPeriod.deactivations_without_reactivation)
.where("location_deactivation_periods.deactivation_date > ?", Time.zone.now)
.where.not(id: joins(scheme: [:owning_organisation]).deactivated_by_organisation.pluck(:id))
}
scope :reactivating_soon, lambda {
where.not("location_deactivation_periods.reactivation_date IS NULL")
.where("location_deactivation_periods.reactivation_date > ?", Time.zone.now)
.where.not(id: joins(scheme: [:owning_organisation]).deactivated_by_organisation.pluck(:id))
}
scope :activating_soon, lambda {
where("startdate > ?", Time.zone.now)
where("locations.startdate > ?", Time.zone.now)
}
scope :active_status, lambda {
where.not(id: joins(:location_deactivation_periods).reactivating_soon.pluck(:id))
.where.not(id: joins(:location_deactivation_periods).deactivated.pluck(:id))
.where.not(id: joins(scheme: [:owning_organisation]).deactivated_by_organisation.pluck(:id))
.where.not(id: joins(:location_deactivation_periods).deactivated_directly.pluck(:id))
.where.not(id: incomplete.pluck(:id))
.where.not(id: joins(:location_deactivation_periods).deactivating_soon.pluck(:id))
.where.not(id: activating_soon.pluck(:id))
@ -142,7 +155,8 @@ class Location < ApplicationRecord
def status_at(date)
return :deleted if discarded_at.present?
return :incomplete unless confirmed
return :deactivated if open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date
return :deactivated if scheme.owning_organisation.status_at(date) == :deactivated ||
open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date
return :deactivating_soon if open_deactivation&.deactivation_date.present? && date < open_deactivation.deactivation_date
return :reactivating_soon if last_deactivation_before(date)&.reactivation_date.present? && date < last_deactivation_before(date).reactivation_date
return :activating_soon if startdate.present? && date < startdate

3
app/models/organisation.rb

@ -36,6 +36,8 @@ class Organisation < ApplicationRecord
scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") }
scope :search_by, ->(param) { search_by_name(param) }
scope :filter_by_active, -> { where(active: true) }
scope :filter_by_inactive, -> { where(active: false) }
scope :merged_during_open_collection_period, -> { where("merge_date >= ?", FormHandler.instance.start_date_of_earliest_open_for_editing_collection_period) }
has_paper_trail
@ -137,6 +139,7 @@ class Organisation < ApplicationRecord
def status_at(date)
return :merged if merge_date.present? && merge_date < date
return :deactivated unless active
:active
end

23
app/models/scheme.rb

@ -35,6 +35,7 @@ class Scheme < ApplicationRecord
if scopes.any?
filtered_records = filtered_records
.left_outer_joins(:scheme_deactivation_periods)
.joins(:owning_organisation)
.merge(scopes.reduce(&:or))
end
@ -45,12 +46,22 @@ class Scheme < ApplicationRecord
where.not(confirmed: true)
.or(where(confirmed: nil))
.or(where.not(id: Location.select(:scheme_id).where(confirmed: true).distinct))
.where.not(id: joins(:owning_organisation).deactivated_by_organisation.pluck(:id))
.where.not(id: joins(:scheme_deactivation_periods).deactivated_directly.pluck(:id))
.where.not(id: joins(:scheme_deactivation_periods).reactivating_soon.pluck(:id))
.where.not(id: joins(:scheme_deactivation_periods).deactivated.pluck(:id))
.where.not(id: joins(:scheme_deactivation_periods).deactivating_soon.pluck(:id))
}
scope :deactivated, lambda {
deactivated_by_organisation
.or(deactivated_directly)
}
scope :deactivated_by_organisation, lambda {
merge(Organisation.filter_by_inactive)
}
scope :deactivated_directly, lambda {
merge(SchemeDeactivationPeriod.deactivations_without_reactivation)
.where("scheme_deactivation_periods.deactivation_date <= ?", Time.zone.now)
}
@ -58,22 +69,25 @@ class Scheme < ApplicationRecord
scope :deactivating_soon, lambda {
merge(SchemeDeactivationPeriod.deactivations_without_reactivation)
.where("scheme_deactivation_periods.deactivation_date > ? AND scheme_deactivation_periods.deactivation_date < ? ", Time.zone.now, 6.months.from_now)
.where.not(id: joins(:owning_organisation).deactivated_by_organisation.pluck(:id))
}
scope :reactivating_soon, lambda {
where.not("scheme_deactivation_periods.reactivation_date IS NULL")
.where("scheme_deactivation_periods.reactivation_date > ?", Time.zone.now)
.where.not(id: joins(:owning_organisation).deactivated_by_organisation.pluck(:id))
}
scope :activating_soon, lambda {
where("startdate > ?", Time.zone.now)
where("schemes.startdate > ?", Time.zone.now)
}
scope :active_status, lambda {
where.not(id: joins(:scheme_deactivation_periods).reactivating_soon.pluck(:id))
.where.not(id: joins(:scheme_deactivation_periods).deactivated.pluck(:id))
.where.not(id: incomplete.pluck(:id))
.where.not(id: joins(:scheme_deactivation_periods).deactivating_soon.pluck(:id))
.where.not(id: joins(:owning_organisation).deactivated_by_organisation.pluck(:id))
.where.not(id: joins(:owning_organisation).joins(:scheme_deactivation_periods).deactivated_directly.pluck(:id))
.where.not(id: activating_soon.pluck(:id))
}
@ -268,7 +282,8 @@ class Scheme < ApplicationRecord
def status_at(date)
return :deleted if discarded_at.present?
return :incomplete unless confirmed && locations.confirmed.any?
return :deactivated if open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date
return :deactivated if owning_organisation.status_at(date) == :deactivated ||
(open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date)
return :deactivating_soon if open_deactivation&.deactivation_date.present? && date < open_deactivation.deactivation_date
return :reactivating_soon if last_deactivation_before(date)&.reactivation_date.present? && date < last_deactivation_before(date).reactivation_date
return :activating_soon if startdate.present? && date < startdate

17
app/models/user.rb

@ -134,6 +134,23 @@ class User < ApplicationRecord
update!(is_dpo: true)
end
def deactivate!(reactivate_with_organisation: false)
update!(
active: false,
confirmed_at: nil,
sign_in_count: 0,
initial_confirmation_sent: false,
reactivate_with_organisation:,
)
end
def reactivate!
update!(
active: true,
reactivate_with_organisation: false,
)
end
MFA_TEMPLATE_ID = "6bdf5ee1-8e01-4be1-b1f9-747061d8a24c".freeze
RESET_PASSWORD_TEMPLATE_ID = "2c410c19-80a7-481c-a531-2bcb3264f8e6".freeze
CONFIRMABLE_TEMPLATE_ID = "3fc2e3a7-0835-4b84-ab7a-ce51629eb614".freeze

4
app/models/validations/household_validations.rb

@ -178,10 +178,6 @@ module Validations::HouseholdValidations
record.errors.add :prevten, :internal_transfer_fixed_or_lifetime, message: I18n.t("validations.household.prevten.la_general_needs.internal_transfer")
record.errors.add :referral, :internal_transfer_fixed_or_lifetime, message: I18n.t("validations.household.referral.la_general_needs.internal_transfer")
end
if record.owning_organisation.provider_type == "LA" && record.local_housing_referral?
record.errors.add :referral, I18n.t("validations.household.referral.prp.local_housing_referral")
end
end
def validate_prevloc(record)

16
app/policies/organisation_policy.rb

@ -0,0 +1,16 @@
class OrganisationPolicy
attr_reader :user, :organisation
def initialize(user, organisation)
@user = user
@organisation = organisation
end
def deactivate?
user.support? && organisation.status == :active
end
def reactivate?
user.support? && organisation.status == :deactivated
end
end

2
app/services/bulk_update_from_csv/update_locations_from_csv_service.rb

@ -5,7 +5,7 @@ class BulkUpdateFromCsv::UpdateLocationsFromCsvService
end
def call
s3_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["CSV_DOWNLOAD_PAAS_INSTANCE"])
s3_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
original_locations_csv = csv_from_path(@original_file_name, s3_service)
updated_locations_csv = csv_from_path(@updated_file_name, s3_service)

2
app/services/bulk_update_from_csv/update_schemes_from_csv_service.rb

@ -5,7 +5,7 @@ class BulkUpdateFromCsv::UpdateSchemesFromCsvService
end
def call
s3_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["CSV_DOWNLOAD_PAAS_INSTANCE"])
s3_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
original_schemes_csv = csv_from_path(@original_file_name, s3_service)
updated_schemes_csv = csv_from_path(@updated_file_name, s3_service)

2
app/services/bulk_upload/downloader.rb

@ -37,7 +37,7 @@ private
end
def s3_storage_service
Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["CSV_DOWNLOAD_PAAS_INSTANCE"])
Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
end
def local_disk_storage_service

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

@ -517,7 +517,7 @@ private
def validate_declaration_acceptance
unless field_45 == 1
errors.add(:field_45, I18n.t("validations.declaration.missing"), category: :setup)
errors.add(:field_45, I18n.t("validations.declaration.missing.pre_2024"), category: :setup)
end
end

6
app/services/feature_toggle.rb

@ -28,14 +28,14 @@ class FeatureToggle
end
def self.delete_scheme_enabled?
!Rails.env.production?
true
end
def self.delete_location_enabled?
!Rails.env.production?
true
end
def self.delete_user_enabled?
!Rails.env.production?
true
end
end

6
app/views/form/_check_answers_summary_list.html.erb

@ -28,7 +28,11 @@
<% if @log.collection_period_open_for_editing? %>
<% row.with_action(
text: question.action_text(@log),
href: action_href(@log, question.page.id, referrer),
href: action_href(
@log,
question.page.id,
question.displayed_as_answered?(@log) || !defined?(referrer_unanswered) ? referrer : referrer_unanswered,
),
visually_hidden_text: question.check_answer_label.to_s.downcase,
) %>
<% end %>

2
app/views/form/_interruption_screen_question.html.erb

@ -19,6 +19,6 @@
<%= f.govuk_submit "Confirm and continue" %>
<%= govuk_link_to(
(@page.skip_text || "Skip for now"),
(@page.skip_href(@log) || send(@log.form.next_page_redirect_path(@page, @log, current_user), @log)),
(@page.skip_href(@log) || send(@log.form.next_page_redirect_path(@page, @log, current_user, ignore_answered: true), @log)),
) %>
</div>

1
app/views/form/check_answers.html.erb

@ -27,6 +27,7 @@
lettings_log: @log,
questions: total_applicable_questions(subsection, @log, current_user),
referrer: "check_answers",
referrer_unanswered: "check_answers_new_answer",
} %>
<% end %>

7
app/views/form/review.html.erb

@ -19,7 +19,12 @@
<h3 class="govuk-summary-card__title"><%= subsection.label %></h3>
</div>
<div class="govuk-summary-card__content">
<%= render partial: "form/check_answers_summary_list", locals: { subsection:, questions: total_applicable_questions(subsection, @log, current_user), referrer: "check_answers" } %>
<%= render partial: "form/check_answers_summary_list", locals: {
subsection:,
questions: total_applicable_questions(subsection, @log, current_user),
referrer: "check_answers",
referrer_unanswered: "check_answers_new_answer",
} %>
</div>
</div>
<% end %>

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

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

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

@ -5,19 +5,35 @@
<%= render SubNavigationComponent.new(
items: secondary_items(request.path, @organisation.id),
) %>
<% if !@organisation.active? %>
<%= govuk_notification_banner(title_text: "Important") do %>
<p class="govuk-notification-banner__heading govuk-!-width-full" style="max-width: fit-content">
This organisation is deactivated.
<p>
You cannot add any new managing agents.
<% end %>
<% end %>
<h2 class="govuk-visually-hidden">Managing Agents</h2>
<p class="govuk-body">A managing agent can submit logs for this organisation.</p>
<% if @total_count == 0 %>
<p class="govuk-body">This organisation does not currently have any managing agents.</p>
<% end %>
<% else %>
<% if !@organisation.active? %>
<%= govuk_notification_banner(title_text: "Important") do %>
<p class="govuk-notification-banner__heading govuk-!-width-full" style="max-width: fit-content">
This organisation is deactivated.
<p>
You cannot add any new managing agents.
<% end %>
<% end %>
<%= render partial: "organisations/headings", locals: { main: "Your managing agents", sub: current_user.organisation.name } %>
<p class="govuk-body">A managing agent can submit logs for this organisation.</p>
<% if @total_count == 0 %>
<p class="govuk-body">This organisation does not currently have any managing agents.</p>
<% end %>
<% end %>
<% if current_user.support? || current_user.data_coordinator? %>
<% if (current_user.support? || current_user.data_coordinator?) && @organisation.active? %>
<%= govuk_button_link_to "Add a managing agent", managing_agents_add_organisation_path, html: { method: :get } %>
<% end %>
<% if @total_count != 0 %>

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

@ -2,19 +2,35 @@
<% if current_user.support? %>
<%= render partial: "organisations/headings", locals: { main: @organisation.name, sub: nil } %>
<%= render SubNavigationComponent.new(items: secondary_items(request.path, @organisation.id)) %>
<% if !@organisation.active? %>
<%= govuk_notification_banner(title_text: "Important") do %>
<p class="govuk-notification-banner__heading govuk-!-width-full" style="max-width: fit-content">
This organisation is deactivated.
<p>
You cannot add any new stock owners.
<% end %>
<% end %>
<h2 class="govuk-visually-hidden">Stock Owners</h2>
<p class="govuk-body">This organisation can submit logs for its stock owners.</p>
<% if @total_count == 0 %>
<p class="govuk-body">This organisation does not currently have any stock owners.</p>
<% end %>
<% else %>
<% if !@organisation.active? %>
<%= govuk_notification_banner(title_text: "Important") do %>
<p class="govuk-notification-banner__heading govuk-!-width-full" style="max-width: fit-content">
This organisation is deactivated.
<p>
You cannot add any new stock owners.
<% end %>
<% end %>
<%= render partial: "organisations/headings", locals: { main: "Your stock owners", sub: current_user.organisation.name } %>
<p class="govuk-body">Your organisation can submit logs for its stock owners.</p>
<% if @total_count == 0 %>
<p class="govuk-body">You do not currently have any stock owners.</p>
<% end %>
<% end %>
<% if current_user.support? || current_user.data_coordinator? %>
<% if (current_user.support? || current_user.data_coordinator?) && @organisation.active? %>
<%= govuk_button_link_to "Add a stock owner", stock_owners_add_organisation_path, html: { method: :get } %>
<% end %>
<% if @total_count != 0 %>

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

@ -40,3 +40,12 @@
<%= render partial: "organisations/merged_organisation_details" %>
</div>
</div>
<% if OrganisationPolicy.new(current_user, @organisation).deactivate? %>
<%= govuk_button_link_to "Deactivate this organisation", deactivate_organisation_path(@organisation), warning: true %>
<% end %>
<% if OrganisationPolicy.new(current_user, @organisation).reactivate? %>
<span class="app-!-colour-muted govuk-!-margin-right-2">
<%= govuk_button_link_to "Reactivate this organisation", reactivate_organisation_path(@organisation) %>
</span>
<% end %>

29
app/views/organisations/toggle_active.html.erb

@ -0,0 +1,29 @@
<% title = "#{action.humanize} #{@organisation.name}" %>
<% content_for :title, title %>
<% content_for :before_content do %>
<%= govuk_back_link(
href: organisation_path(@organisation),
) %>
<% end %>
<%= form_for(@organisation, as: :organisation, html: { method: :patch }) do |f| %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-l">
<span class="govuk-caption-l"><%= @organisation.name %></span>
Are you sure you want to <%= action %> this organisation?
</h1>
<%= govuk_warning_text text: I18n.t("warnings.organisation.#{action}") %>
<% active_value = action != "deactivate" %>
<%= f.hidden_field :active, value: active_value %>
<%= f.govuk_submit "#{action.capitalize} this organisation" %>
<p class="govuk-body">
<%= govuk_link_to("Cancel", organisation_path(@organisation)) %>
</p>
</div>
</div>
<% end %>

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

@ -2,7 +2,7 @@
<% content_for :before_content do %>
<%= govuk_back_link(
href: request.query_parameters["check_answers"] ? "/schemes/#{@scheme.id}/check-answers" : "/schemes/#{@scheme.id}/primary-client-group",
href: request.query_parameters["check_answers"] ? "check-answers" : "primary-client-group",
) %>
<% end %>

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

@ -2,7 +2,7 @@
<% content_for :before_content do %>
<%= govuk_back_link(
href: request.query_parameters["check_answers"] ? "/schemes/#{@scheme.id}/check-answers" : "/schemes/#{@scheme.id}/confirm-secondary-client-group",
href: request.query_parameters["check_answers"] ? "check-answers" : "confirm-secondary-client-group",
) %>
<% end %>

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

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

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

@ -2,7 +2,7 @@
<% content_for :before_content do %>
<%= govuk_back_link(
href: request.query_parameters["check_answers"] ? "/schemes/#{@scheme.id}/check-answers" : "/schemes/#{@scheme.id}/secondary-client-group",
href: request.query_parameters["check_answers"] ? "check-answers" : "secondary-client-group",
) %>
<% end %>

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

@ -31,7 +31,7 @@
<% if current_user.support? %>
<% null_option = [OpenStruct.new(id: "", name: "Select an option")] %>
<% organisations = Organisation.all.map { |org| OpenStruct.new(id: org.id, name: org.name) } %>
<% organisations = Organisation.filter_by_active.map { |org| OpenStruct.new(id: org.id, name: org.name) } %>
<% answer_options = null_option + organisations %>
<% if @organisation_id %>

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

@ -127,8 +127,8 @@
end %>
<% end %>
<% if @user.organisation.active? && current_user.can_toggle_active?(@user) %>
<div class="govuk-button-group">
<% if current_user.can_toggle_active?(@user) %>
<% if @user.active? %>
<%= govuk_button_link_to "Deactivate user", deactivate_user_path(@user), warning: true %>
<% if current_user.support? && @user.last_sign_in_at.nil? %>

7
config/locales/en.yml

@ -34,6 +34,8 @@ en:
feedback_form: "https://forms.office.com/Pages/ResponsePage.aspx?id=EGg0v32c3kOociSi7zmVqC4YDsCJ3llAvEZelBFBLUBURFVUTzFDTUJPQlM4M0laTE5DTlNFSjJBQi4u"
organisation:
updated: "Organisation details updated"
reactivated: "%{organisation} has been reactivated."
deactivated: "%{organisation} has been deactivated."
user:
create_password: "Create a password to finish setting up your account"
reset_password: "Reset your password"
@ -531,8 +533,6 @@ en:
la_general_needs:
internal_transfer: "Answer cannot be internal transfer as it’s the same landlord on the tenancy agreement and the household had either a fixed-term or lifetime local authority general needs tenancy immediately before this letting"
prp_referred_by_la: "The source of the referral cannot be referred by local authority housing department for a general needs log"
prp:
local_housing_referral: "Answer cannot be ‘nominated by a local housing authority’ as a local authority is on the tenancy agreement"
homeless:
assessed:
internal_transfer: "Answer cannot be 'assessed as homeless' as this tenancy is an internal transfer"
@ -860,6 +860,9 @@ Make sure these answers are correct."
offered: "Times previously offered since becoming available"
warnings:
organisation:
deactivate: "All schemes and users at this organisation will be deactivated. All the organisation's relationships will be removed. It will no longer be possible to create logs for this organisation."
reactivate: "All schemes, users, and relationships that were active when this organisation was deactivated will be reactivated."
location:
deactivate:
existing_logs: "It will not be possible to add logs with this location if their tenancy start date is on or after the date you enter. Any existing logs may be affected."

2
config/routes.rb

@ -179,6 +179,8 @@ Rails.application.routes.draw do
post "managing-agents", to: "organisation_relationships#create_managing_agent"
delete "managing-agents", to: "organisation_relationships#delete_managing_agent"
get "merge-request", to: "organisations#merge_request"
get "deactivate", to: "organisations#deactivate"
get "reactivate", to: "organisations#reactivate"
end
end

5
db/migrate/20240304112411_add_reactivate_with_organisation_to_users.rb

@ -0,0 +1,5 @@
class AddReactivateWithOrganisationToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :reactivate_with_organisation, :boolean
end
end

12
db/migrate/20240305112507_add_default_value_to_organisation_active_field.rb

@ -0,0 +1,12 @@
class AddDefaultValueToOrganisationActiveField < ActiveRecord::Migration[7.0]
def up
change_column :organisations, :active, :boolean, default: true
execute "UPDATE organisations
SET active = true;"
end
def down
change_column :organisations, :active, :boolean
end
end

3
db/schema.rb

@ -453,7 +453,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_03_19_122706) do
t.string "managing_agents_label"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "active"
t.boolean "active", default: true
t.integer "old_association_type"
t.string "software_supplier_id"
t.string "housing_management_system"
@ -764,6 +764,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_03_19_122706) do
t.string "unconfirmed_email"
t.boolean "initial_confirmation_sent"
t.datetime "discarded_at"
t.boolean "reactivate_with_organisation"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["encrypted_otp_secret_key"], name: "index_users_on_encrypted_otp_secret_key", unique: true

2
lib/tasks/data_export.rake

@ -9,7 +9,7 @@ namespace :core do
desc "Export all data XMLs for import into Central Data System (CDS)"
task :full_data_export_xml, %i[year] => :environment do |_task, args|
collection_year = args[:year].present? ? args[:year].to_i : nil
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["EXPORT_PAAS_INSTANCE"])
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["EXPORT_BUCKET"])
export_service = Exports::LettingsLogExportService.new(storage_service)
export_service.export_xml_lettings_logs(full_update: true, collection_year:)

4
lib/tasks/import_address_from_csv.rake

@ -5,7 +5,7 @@ namespace :data_import do
raise "Usage: rake data_import:import_lettings_addresses_from_csv['csv_file_name']" if file_name.blank?
s3_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["CSV_DOWNLOAD_PAAS_INSTANCE"])
s3_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
file_io = s3_service.get_file_io(file_name)
file_io.set_encoding_by_bom
addresses_csv = CSV.parse(file_io, headers: true)
@ -69,7 +69,7 @@ namespace :data_import do
raise "Usage: rake data_import:import_sales_addresses_from_csv['csv_file_name']" if file_name.blank?
s3_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["CSV_DOWNLOAD_PAAS_INSTANCE"])
s3_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
file_io = s3_service.get_file_io(file_name)
file_io.set_encoding_by_bom
addresses_csv = CSV.parse(file_io, headers: true)

4
spec/features/form/check_answers_page_lettings_logs_spec.rb

@ -89,11 +89,11 @@ RSpec.describe "Lettings Log Check Answers Page" do
# Regex explanation: match the string "Answer" but not if it's follow by "the missing questions"
# This way only the links in the table will get picked up
it "has an answer link for questions missing an answer" do
it "has an answer link with the check_answers_new_answer referrer for questions missing an answer" do
visit("/lettings-logs/#{empty_lettings_log.id}/#{subsection}/check-answers?referrer=check_answers")
assert_selector "a", text: /Answer (?!the missing questions)/, count: 4
assert_selector "a", text: "Change", count: 0
expect(page).to have_link("Answer", href: "/lettings-logs/#{empty_lettings_log.id}/person-1-age?referrer=check_answers")
expect(page).to have_link("Answer", href: "/lettings-logs/#{empty_lettings_log.id}/person-1-age?referrer=check_answers_new_answer")
end
it "has a change link for answered questions" do

6
spec/features/form/form_navigation_spec.rb

@ -66,12 +66,12 @@ RSpec.describe "Form Navigation" do
expect(page).to have_field("lettings-log-age1-field")
end
it "a question page leads to the next question defined in the form definition" do
it "a question page leads to the next unanswered question defined in the form definition" do
pages = question_answers.map { |_key, val| val[:path] }
pages[0..-2].each_with_index do |val, index|
visit("/lettings-logs/#{id}/#{val}")
visit("/lettings-logs/#{empty_lettings_log.id}/#{val}")
click_link("Skip for now")
expect(page).to have_current_path("/lettings-logs/#{id}/#{pages[index + 1]}")
expect(page).to have_current_path("/lettings-logs/#{empty_lettings_log.id}/#{pages[index + 1]}")
end
end

10
spec/features/form/validations_spec.rb

@ -171,15 +171,17 @@ RSpec.describe "validations" do
expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success")
end
it "returns the user back to the check_your_answers after fixing a validation from check_your_anwers" do
lettings_log.update!(earnings: income_over_soft_limit, incfreq: 1)
it "returns the user back to the check_your_answers after fixing a validation from check_your_answers" do
lettings_log.update!(earnings: income_under_soft_limit, incfreq: 1, net_income_value_check: 1)
visit("/lettings-logs/#{lettings_log.id}/income-and-benefits/check-answers")
click_link("Answer", href: "/lettings-logs/#{lettings_log.id}/net-income-value-check?referrer=check_answers")
click_link("Change", href: "/lettings-logs/#{lettings_log.id}/net-income?referrer=check_answers", match: :first)
expect(page).to have_current_path("/lettings-logs/#{lettings_log.id}/net-income?referrer=check_answers")
fill_in("lettings-log-earnings-field", with: income_over_soft_limit)
click_button("Save changes")
expect(page).to have_current_path("/lettings-logs/#{lettings_log.id}/net-income-value-check?referrer=check_answers")
click_link("Change", href: "/lettings-logs/#{lettings_log.id}/net-income?referrer=interruption_screen", match: :first)
expect(page).to have_current_path("/lettings-logs/#{lettings_log.id}/net-income?referrer=interruption_screen")
fill_in("lettings-log-earnings-field", with: income_under_soft_limit)
choose("lettings-log-incfreq-1-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_current_path("/lettings-logs/#{lettings_log.id}/net-income-value-check?referrer=check_answers")
click_button("Confirm and continue")

2
spec/fixtures/files/sales_logs_csv_export_labels_23.csv vendored

@ -1,2 +1,2 @@
id,status,duplicate_set_id,created_at,updated_at,old_form_id,collection_start_year,creation_method,is_dpo,address_line1_as_entered,address_line2_as_entered,town_or_city_as_entered,county_as_entered,postcode_full_as_entered,la_as_entered,assigned_to,owning_organisation_name,managing_organisation_name,created_by,day,month,year,purchid,ownershipsch,type,othtype,companybuy,buylivein,jointpur,jointmore,beds,proptype,builtype,pcodenk,uprn,uprn_confirmed,address_line1_input,postcode_full_input,uprn_selection,address_line1,address_line2,town_or_city,county,pcode1,pcode2,la_known,la,la_label,wchair,noint,privacynotice,age1,sex1,ethnic_group,ethnic,nationality_all,national,ecstat1,buy1livein,relat2,age2,sex2,ethnic_group2,ethnicbuy2,nationality_all_buyer2,nationalbuy2,ecstat2,buy2livein,hholdcount,relat3,age3,sex3,ecstat3,relat4,age4,sex4,ecstat4,relat5,age5,sex5,ecstat5,relat6,age6,sex6,ecstat6,prevten,ppcodenk,ppostc1,ppostc2,previous_la_known,prevloc,prevloc_label,pregyrha,pregother,pregla,pregghb,pregblank,buy2living,prevtenbuy2,hhregres,hhregresstill,armedforcesspouse,disabled,wheel,income1nk,income1,inc1mort,income2nk,income2,inc2mort,hb,savingsnk,savings,prevown,prevshared,proplen,staircase,stairbought,stairowned,staircasesale,resale,exday,exmonth,exyear,hoday,homonth,hoyear,lanomagr,soctenant,frombeds,fromprop,socprevten,value,equity,mortgageused,mortgage,mortgagelender,mortgagelenderother,mortlen,extrabor,deposit,cashdis,mrent,has_mscharge,mscharge,discount,grant
,completed,,2023-12-08T00:00:00+00:00,2024-01-01T00:00:00+00:00,,2023,single log,false,address line 1 as entered,address line 2 as entered,town or city as entered,county as entered,AB1 2CD,la as entered,,DLUHC,DLUHC,billyboy@eyeklaud.com,8,12,2023,,Yes - a discounted ownership scheme,Right to Acquire (RTA),,,,Yes,Yes,2,Flat or maisonette,Purpose built,0,,,,,,Address line 1,,Town or city,,SW1A,1AA,1,E09000003,Barnet,Yes,Yes,1,30,Non-binary,Buyer prefers not to say,17,,United Kingdom,Full-time - 30 hours or more,Yes,Partner,35,Non-binary,Buyer prefers not to say,,,Buyer prefers not to say,Full-time - 30 hours or more,Yes,3,Child,14,Non-binary,,Other,Not known,Non-binary,"In government training into work, such as New Deal",Prefers not to say,Not known,Prefers not to say,Prefers not to say,,,,,Local authority tenant,No,,,No,,,1,1,1,1,,Don't know,,Yes,Yes,No,Yes,Yes,Yes,10000,Yes,Yes,10000,Yes,"Don’t know ",No,,Yes,No,10,,,,,,,,,,,,,,,,,110000.0,,Yes,20000.0,Cambridge Building Society,,10,Yes,80000.0,,,Yes,100.0,,10000.0
,completed,,2023-12-08T00:00:00+00:00,2024-01-01T00:00:00+00:00,,2023,single log,false,address line 1 as entered,address line 2 as entered,town or city as entered,county as entered,AB1 2CD,la as entered,,DLUHC,DLUHC,billyboy@eyeklaud.com,8,12,2023,,Yes - a discounted ownership scheme,Right to Acquire (RTA),,,,Yes,Yes,2,Flat or maisonette,Purpose built,0,,,,,,Address line 1,,Town or city,,SW1A,1AA,1,E09000003,Barnet,Yes,Yes,1,30,Non-binary,Buyer prefers not to say,17,,United Kingdom,Full-time - 30 hours or more,Yes,Partner,35,Non-binary,Buyer prefers not to say,,,Buyer prefers not to say,Full-time - 30 hours or more,Yes,3,Child,14,Non-binary,,Other,Not known,Non-binary,In government training into work,Prefers not to say,Not known,Prefers not to say,Prefers not to say,,,,,Local authority tenant,No,,,No,,,1,1,1,1,,Don't know,,Yes,Yes,No,Yes,Yes,Yes,10000,Yes,Yes,10000,Yes,"Don’t know ",No,,Yes,No,10,,,,,,,,,,,,,,,,,110000.0,,Yes,20000.0,Cambridge Building Society,,10,Yes,80000.0,,,Yes,100.0,,10000.0

1 id status duplicate_set_id created_at updated_at old_form_id collection_start_year creation_method is_dpo address_line1_as_entered address_line2_as_entered town_or_city_as_entered county_as_entered postcode_full_as_entered la_as_entered assigned_to owning_organisation_name managing_organisation_name created_by day month year purchid ownershipsch type othtype companybuy buylivein jointpur jointmore beds proptype builtype pcodenk uprn uprn_confirmed address_line1_input postcode_full_input uprn_selection address_line1 address_line2 town_or_city county pcode1 pcode2 la_known la la_label wchair noint privacynotice age1 sex1 ethnic_group ethnic nationality_all national ecstat1 buy1livein relat2 age2 sex2 ethnic_group2 ethnicbuy2 nationality_all_buyer2 nationalbuy2 ecstat2 buy2livein hholdcount relat3 age3 sex3 ecstat3 relat4 age4 sex4 ecstat4 relat5 age5 sex5 ecstat5 relat6 age6 sex6 ecstat6 prevten ppcodenk ppostc1 ppostc2 previous_la_known prevloc prevloc_label pregyrha pregother pregla pregghb pregblank buy2living prevtenbuy2 hhregres hhregresstill armedforcesspouse disabled wheel income1nk income1 inc1mort income2nk income2 inc2mort hb savingsnk savings prevown prevshared proplen staircase stairbought stairowned staircasesale resale exday exmonth exyear hoday homonth hoyear lanomagr soctenant frombeds fromprop socprevten value equity mortgageused mortgage mortgagelender mortgagelenderother mortlen extrabor deposit cashdis mrent has_mscharge mscharge discount grant
2 completed 2023-12-08T00:00:00+00:00 2024-01-01T00:00:00+00:00 2023 single log false address line 1 as entered address line 2 as entered town or city as entered county as entered AB1 2CD la as entered DLUHC DLUHC billyboy@eyeklaud.com 8 12 2023 Yes - a discounted ownership scheme Right to Acquire (RTA) Yes Yes 2 Flat or maisonette Purpose built 0 Address line 1 Town or city SW1A 1AA 1 E09000003 Barnet Yes Yes 1 30 Non-binary Buyer prefers not to say 17 United Kingdom Full-time - 30 hours or more Yes Partner 35 Non-binary Buyer prefers not to say Buyer prefers not to say Full-time - 30 hours or more Yes 3 Child 14 Non-binary Other Not known Non-binary In government training into work, such as New Deal In government training into work Prefers not to say Not known Prefers not to say Prefers not to say Local authority tenant No No 1 1 1 1 Don't know Yes Yes No Yes Yes Yes 10000 Yes Yes 10000 Yes Don’t know No Yes No 10 110000.0 Yes 20000.0 Cambridge Building Society 10 Yes 80000.0 Yes 100.0 10000.0

2
spec/fixtures/files/sales_logs_csv_export_labels_23_during_24_period.csv vendored

@ -1,2 +1,2 @@
id,status,duplicate_set_id,created_at,updated_at,old_form_id,collection_start_year,creation_method,is_dpo,address_line1_as_entered,address_line2_as_entered,town_or_city_as_entered,county_as_entered,postcode_full_as_entered,la_as_entered,assigned_to,owning_organisation_name,managing_organisation_name,created_by,day,month,year,purchid,ownershipsch,type,othtype,companybuy,buylivein,jointpur,jointmore,noint,privacynotice,uprn,uprn_confirmed,address_line1_input,postcode_full_input,uprn_selection,address_line1,address_line2,town_or_city,county,pcode1,pcode2,la_known,la,la_label,beds,proptype,builtype,pcodenk,wchair,age1,sex1,ethnic_group,ethnic,national,nationality_all,ecstat1,buy1livein,relat2,age2,sex2,ethnic_group2,ethnicbuy2,nationalbuy2,nationality_all_buyer2,ecstat2,buy2livein,hholdcount,relat3,age3,sex3,ecstat3,relat4,age4,sex4,ecstat4,relat5,age5,sex5,ecstat5,relat6,age6,sex6,ecstat6,prevten,ppcodenk,ppostc1,ppostc2,previous_la_known,prevloc,prevloc_label,pregyrha,pregother,pregla,pregghb,pregblank,buy2living,prevtenbuy2,hhregres,hhregresstill,armedforcesspouse,disabled,wheel,income1nk,income1,inc1mort,income2nk,income2,inc2mort,hb,savingsnk,savings,prevown,prevshared,proplen,staircase,stairbought,stairowned,staircasesale,resale,exday,exmonth,exyear,hoday,homonth,hoyear,lanomagr,soctenant,frombeds,fromprop,socprevten,value,equity,mortgageused,mortgage,mortgagelender,mortgagelenderother,mortlen,extrabor,deposit,cashdis,mrent,has_mscharge,mscharge,discount,grant
,completed,,2023-12-08T00:00:00+00:00,2024-05-01T00:00:00+01:00,,2023,single log,false,address line 1 as entered,address line 2 as entered,town or city as entered,county as entered,AB1 2CD,la as entered,,DLUHC,DLUHC,billyboy@eyeklaud.com,8,12,2023,,Yes - a discounted ownership scheme,Right to Acquire (RTA),,,,Yes,Yes,Yes,1,,,,,,Address line 1,,Town or city,,SW1A,1AA,1,E09000003,Barnet,2,Flat or maisonette,Purpose built,0,Yes,30,Non-binary,Buyer prefers not to say,17,United Kingdom,,Full-time - 30 hours or more,Yes,Partner,35,Non-binary,Buyer prefers not to say,,Buyer prefers not to say,,Full-time - 30 hours or more,Yes,3,Child,14,Non-binary,,Other,Not known,Non-binary,"In government training into work, such as New Deal",Prefers not to say,Not known,Prefers not to say,Prefers not to say,,,,,Local authority tenant,No,,,No,,,1,1,1,1,,Don't know,,Yes,Yes,No,Yes,Yes,Yes,10000,Yes,Yes,10000,Yes,"Don’t know ",No,,Yes,No,10,,,,,,,,,,,,,,,,,110000.0,,Yes,20000.0,Cambridge Building Society,,10,Yes,80000.0,,,Yes,100.0,,10000.0
,completed,,2023-12-08T00:00:00+00:00,2024-05-01T00:00:00+01:00,,2023,single log,false,address line 1 as entered,address line 2 as entered,town or city as entered,county as entered,AB1 2CD,la as entered,,DLUHC,DLUHC,billyboy@eyeklaud.com,8,12,2023,,Yes - a discounted ownership scheme,Right to Acquire (RTA),,,,Yes,Yes,Yes,1,,,,,,Address line 1,,Town or city,,SW1A,1AA,1,E09000003,Barnet,2,Flat or maisonette,Purpose built,0,Yes,30,Non-binary,Buyer prefers not to say,17,United Kingdom,,Full-time - 30 hours or more,Yes,Partner,35,Non-binary,Buyer prefers not to say,,Buyer prefers not to say,,Full-time - 30 hours or more,Yes,3,Child,14,Non-binary,,Other,Not known,Non-binary,In government training into work,Prefers not to say,Not known,Prefers not to say,Prefers not to say,,,,,Local authority tenant,No,,,No,,,1,1,1,1,,Don't know,,Yes,Yes,No,Yes,Yes,Yes,10000,Yes,Yes,10000,Yes,"Don’t know ",No,,Yes,No,10,,,,,,,,,,,,,,,,,110000.0,,Yes,20000.0,Cambridge Building Society,,10,Yes,80000.0,,,Yes,100.0,,10000.0

1 id status duplicate_set_id created_at updated_at old_form_id collection_start_year creation_method is_dpo address_line1_as_entered address_line2_as_entered town_or_city_as_entered county_as_entered postcode_full_as_entered la_as_entered assigned_to owning_organisation_name managing_organisation_name created_by day month year purchid ownershipsch type othtype companybuy buylivein jointpur jointmore noint privacynotice uprn uprn_confirmed address_line1_input postcode_full_input uprn_selection address_line1 address_line2 town_or_city county pcode1 pcode2 la_known la la_label beds proptype builtype pcodenk wchair age1 sex1 ethnic_group ethnic national nationality_all ecstat1 buy1livein relat2 age2 sex2 ethnic_group2 ethnicbuy2 nationalbuy2 nationality_all_buyer2 ecstat2 buy2livein hholdcount relat3 age3 sex3 ecstat3 relat4 age4 sex4 ecstat4 relat5 age5 sex5 ecstat5 relat6 age6 sex6 ecstat6 prevten ppcodenk ppostc1 ppostc2 previous_la_known prevloc prevloc_label pregyrha pregother pregla pregghb pregblank buy2living prevtenbuy2 hhregres hhregresstill armedforcesspouse disabled wheel income1nk income1 inc1mort income2nk income2 inc2mort hb savingsnk savings prevown prevshared proplen staircase stairbought stairowned staircasesale resale exday exmonth exyear hoday homonth hoyear lanomagr soctenant frombeds fromprop socprevten value equity mortgageused mortgage mortgagelender mortgagelenderother mortlen extrabor deposit cashdis mrent has_mscharge mscharge discount grant
2 completed 2023-12-08T00:00:00+00:00 2024-05-01T00:00:00+01:00 2023 single log false address line 1 as entered address line 2 as entered town or city as entered county as entered AB1 2CD la as entered DLUHC DLUHC billyboy@eyeklaud.com 8 12 2023 Yes - a discounted ownership scheme Right to Acquire (RTA) Yes Yes Yes 1 Address line 1 Town or city SW1A 1AA 1 E09000003 Barnet 2 Flat or maisonette Purpose built 0 Yes 30 Non-binary Buyer prefers not to say 17 United Kingdom Full-time - 30 hours or more Yes Partner 35 Non-binary Buyer prefers not to say Buyer prefers not to say Full-time - 30 hours or more Yes 3 Child 14 Non-binary Other Not known Non-binary In government training into work, such as New Deal In government training into work Prefers not to say Not known Prefers not to say Prefers not to say Local authority tenant No No 1 1 1 1 Don't know Yes Yes No Yes Yes Yes 10000 Yes Yes 10000 Yes Don’t know No Yes No 10 110000.0 Yes 20000.0 Cambridge Building Society 10 Yes 80000.0 Yes 100.0 10000.0

2
spec/fixtures/files/sales_logs_csv_export_labels_24.csv vendored

@ -1,2 +1,2 @@
id,status,duplicate_set_id,created_at,updated_at,old_form_id,collection_start_year,creation_method,is_dpo,address_line1_as_entered,address_line2_as_entered,town_or_city_as_entered,county_as_entered,postcode_full_as_entered,la_as_entered,assigned_to,owning_organisation_name,managing_organisation_name,created_by,day,month,year,purchid,ownershipsch,type,othtype,companybuy,buylivein,jointpur,jointmore,noint,privacynotice,uprn,uprn_confirmed,address_line1_input,postcode_full_input,uprn_selection,address_line1,address_line2,town_or_city,county,pcode1,pcode2,la_known,la,la_label,beds,proptype,builtype,pcodenk,wchair,age1,sex1,ethnic_group,ethnic,national,nationality_all,ecstat1,buy1livein,relat2,age2,sex2,ethnic_group2,ethnicbuy2,nationalbuy2,nationality_all_buyer2,ecstat2,buy2livein,hholdcount,relat3,age3,sex3,ecstat3,relat4,age4,sex4,ecstat4,relat5,age5,sex5,ecstat5,relat6,age6,sex6,ecstat6,prevten,ppcodenk,ppostc1,ppostc2,previous_la_known,prevloc,prevloc_label,pregyrha,pregother,pregla,pregghb,pregblank,buy2living,prevtenbuy2,hhregres,hhregresstill,armedforcesspouse,disabled,wheel,income1nk,income1,inc1mort,income2nk,income2,inc2mort,hb,savingsnk,savings,prevown,prevshared,proplen,staircase,stairbought,stairowned,staircasesale,resale,exday,exmonth,exyear,hoday,homonth,hoyear,lanomagr,soctenant,frombeds,fromprop,socprevten,value,equity,mortgageused,mortgage,mortgagelender,mortgagelenderother,mortlen,extrabor,deposit,cashdis,mrent,has_mscharge,mscharge,discount,grant
,in_progress,,2024-05-01T00:00:00+01:00,2024-05-01T00:00:00+01:00,,2024,single log,false,address line 1 as entered,address line 2 as entered,town or city as entered,county as entered,AB1 2CD,la as entered,,DLUHC,DLUHC,billyboy@eyeklaud.com,1,5,2024,,Yes - a discounted ownership scheme,Right to Acquire (RTA),,,,Yes,Yes,Yes,1,,,,,,Address line 1,,Town or city,,SW1A,1AA,1,E09000003,Barnet,2,Flat or maisonette,Purpose built,0,Yes,30,Non-binary,Buyer prefers not to say,17,,Australia,Full-time - 30 hours or more,Yes,Partner,35,Non-binary,Buyer prefers not to say,,13,,Full-time - 30 hours or more,Yes,3,Child,14,Non-binary,Child under 16,Other,Not known,Non-binary,"In government training into work, such as New Deal",Prefers not to say,Not known,Prefers not to say,Prefers not to say,,,,,Local authority tenant,Yes,SW1A,1AA,Yes,E09000003,Barnet,1,1,1,1,,Don't know,,Yes,Yes,No,Yes,Yes,Yes,10000,Yes,Yes,10000,Yes,"Don’t know ",No,,Yes,No,10,,,,,,,,,,,,,,,,,110000.0,,Yes,20000.0,Cambridge Building Society,,10,Yes,80000.0,,,Yes,100.0,,10000.0
,in_progress,,2024-05-01T00:00:00+01:00,2024-05-01T00:00:00+01:00,,2024,single log,false,address line 1 as entered,address line 2 as entered,town or city as entered,county as entered,AB1 2CD,la as entered,,DLUHC,DLUHC,billyboy@eyeklaud.com,1,5,2024,,Yes - a discounted ownership scheme,Right to Acquire (RTA),,,,Yes,Yes,Yes,1,,,,,,Address line 1,,Town or city,,SW1A,1AA,1,E09000003,Barnet,2,Flat or maisonette,Purpose built,0,Yes,30,Non-binary,Buyer prefers not to say,17,,Australia,Full-time - 30 hours or more,Yes,Partner,35,Non-binary,Buyer prefers not to say,,13,,Full-time - 30 hours or more,Yes,3,Child,14,Non-binary,Child under 16,Other,Not known,Non-binary,In government training into work,Prefers not to say,Not known,Prefers not to say,Prefers not to say,,,,,Local authority tenant,Yes,SW1A,1AA,Yes,E09000003,Barnet,1,1,1,1,,Don't know,,Yes,Yes,No,Yes,Yes,Yes,10000,Yes,Yes,10000,Yes,"Don’t know ",No,,Yes,No,10,,,,,,,,,,,,,,,,,110000.0,,Yes,20000.0,Cambridge Building Society,,10,Yes,80000.0,,,Yes,100.0,,10000.0

1 id status duplicate_set_id created_at updated_at old_form_id collection_start_year creation_method is_dpo address_line1_as_entered address_line2_as_entered town_or_city_as_entered county_as_entered postcode_full_as_entered la_as_entered assigned_to owning_organisation_name managing_organisation_name created_by day month year purchid ownershipsch type othtype companybuy buylivein jointpur jointmore noint privacynotice uprn uprn_confirmed address_line1_input postcode_full_input uprn_selection address_line1 address_line2 town_or_city county pcode1 pcode2 la_known la la_label beds proptype builtype pcodenk wchair age1 sex1 ethnic_group ethnic national nationality_all ecstat1 buy1livein relat2 age2 sex2 ethnic_group2 ethnicbuy2 nationalbuy2 nationality_all_buyer2 ecstat2 buy2livein hholdcount relat3 age3 sex3 ecstat3 relat4 age4 sex4 ecstat4 relat5 age5 sex5 ecstat5 relat6 age6 sex6 ecstat6 prevten ppcodenk ppostc1 ppostc2 previous_la_known prevloc prevloc_label pregyrha pregother pregla pregghb pregblank buy2living prevtenbuy2 hhregres hhregresstill armedforcesspouse disabled wheel income1nk income1 inc1mort income2nk income2 inc2mort hb savingsnk savings prevown prevshared proplen staircase stairbought stairowned staircasesale resale exday exmonth exyear hoday homonth hoyear lanomagr soctenant frombeds fromprop socprevten value equity mortgageused mortgage mortgagelender mortgagelenderother mortlen extrabor deposit cashdis mrent has_mscharge mscharge discount grant
2 in_progress 2024-05-01T00:00:00+01:00 2024-05-01T00:00:00+01:00 2024 single log false address line 1 as entered address line 2 as entered town or city as entered county as entered AB1 2CD la as entered DLUHC DLUHC billyboy@eyeklaud.com 1 5 2024 Yes - a discounted ownership scheme Right to Acquire (RTA) Yes Yes Yes 1 Address line 1 Town or city SW1A 1AA 1 E09000003 Barnet 2 Flat or maisonette Purpose built 0 Yes 30 Non-binary Buyer prefers not to say 17 Australia Full-time - 30 hours or more Yes Partner 35 Non-binary Buyer prefers not to say 13 Full-time - 30 hours or more Yes 3 Child 14 Non-binary Child under 16 Other Not known Non-binary In government training into work, such as New Deal In government training into work Prefers not to say Not known Prefers not to say Prefers not to say Local authority tenant Yes SW1A 1AA Yes E09000003 Barnet 1 1 1 1 Don't know Yes Yes No Yes Yes Yes 10000 Yes Yes 10000 Yes Don’t know No Yes No 10 110000.0 Yes 20000.0 Cambridge Building Society 10 Yes 80000.0 Yes 100.0 10000.0

2
spec/lib/tasks/correct_address_from_csv_spec.rb

@ -15,7 +15,7 @@ RSpec.describe "data_import" do
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(Configuration::EnvConfigurationService).to receive(:new).and_return(env_config_service)
allow(ENV).to receive(:[])
allow(ENV).to receive(:[]).with("CSV_DOWNLOAD_PAAS_INSTANCE").and_return(instance_name)
allow(ENV).to receive(:[]).with("BULK_UPLOAD_BUCKET").and_return(instance_name)
allow(ENV).to receive(:[]).with("VCAP_SERVICES").and_return("dummy")
WebMock.stub_request(:get, /api\.postcodes\.io/)

4
spec/lib/tasks/data_export_spec.rb

@ -2,7 +2,7 @@ require "rails_helper"
require "rake"
describe "rake core:data_export", type: task do
let(:export_instance) { "export_instance" }
let(:export_bucket) { "export_bucket" }
let(:storage_service) { instance_double(Storage::S3Service) }
let(:export_service) { instance_double(Exports::LettingsLogExportService) }
@ -14,7 +14,7 @@ describe "rake core:data_export", type: task do
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(Exports::LettingsLogExportService).to receive(:new).and_return(export_service)
allow(ENV).to receive(:[])
allow(ENV).to receive(:[]).with("EXPORT_PAAS_INSTANCE").and_return(export_instance)
allow(ENV).to receive(:[]).with("EXPORT_BUCKET").and_return(export_bucket)
end
context "when exporting lettings logs with no parameters" do

2
spec/lib/tasks/update_schemes_and_locations_from_csv_spec.rb

@ -22,7 +22,7 @@ RSpec.describe "bulk_update" do
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(Configuration::EnvConfigurationService).to receive(:new).and_return(env_config_service)
allow(ENV).to receive(:[])
allow(ENV).to receive(:[]).with("CSV_DOWNLOAD_PAAS_INSTANCE").and_return(instance_name)
allow(ENV).to receive(:[]).with("BULK_UPLOAD_BUCKET").and_return(instance_name)
WebMock.stub_request(:get, /api\.postcodes\.io/)
.to_return(status: 200, body: "{\"status\":404,\"error\":\"Postcode not found\"}", headers: {})

2
spec/models/form/lettings/pages/uprn_selection_spec.rb

@ -34,7 +34,7 @@ RSpec.describe Form::Lettings::Pages::UprnSelection, type: :model do
it "has the correct skip_href" do
expect(page.skip_href(log)).to eq(
"/lettings-logs/#{log.id}/address-matcher",
"address-matcher",
)
end

4
spec/models/form/lettings/pages/uprn_spec.rb

@ -50,7 +50,7 @@ RSpec.describe Form::Lettings::Pages::Uprn, type: :model do
context "with 2023/24 form" do
it "points to address page" do
expect(page.skip_href(log)).to eq(
"/lettings-logs/#{log.id}/address",
"address",
)
end
@ -66,7 +66,7 @@ RSpec.describe Form::Lettings::Pages::Uprn, type: :model do
it "points to address search page" do
expect(page.skip_href(log)).to eq(
"/lettings-logs/#{log.id}/address-matcher",
"address-matcher",
)
end

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

@ -6,7 +6,7 @@ RSpec.describe Form::Lettings::Questions::GenderIdentity1, type: :model do
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form, start_date: Time.zone.local(2023, 4, 1)) }
let(:form) { instance_double(Form, start_date: Time.zone.local(2023, 4, 1), start_year_after_2024?: false) }
before do
allow(page).to receive(:subsection).and_return(subsection)

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

@ -57,6 +57,7 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do
let(:managing_org1) { create(:organisation, name: "Managing org 1") }
let(:managing_org2) { create(:organisation, name: "Managing org 2") }
let(:managing_org3) { create(:organisation, name: "Managing org 3") }
let(:inactive_managing_org) { create(:organisation, name: "Inactive managing org", active: false) }
let(:log) { create(:lettings_log, managing_organisation: managing_org1) }
let!(:org_rel1) do
@ -76,7 +77,9 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do
}
end
it "shows current managing agent at top, followed by user's org (with hint), followed by the managing agents of the user's org" do
it "shows current managing agent at top, followed by user's org (with hint), followed by the active managing agents of the user's org" do
create(:organisation_relationship, parent_organisation: user.organisation, child_organisation: inactive_managing_org)
expect(question.displayed_answer_options(log, user)).to eq(options)
end
end
@ -100,6 +103,10 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do
create(:organisation_relationship, parent_organisation: log_owning_org, child_organisation: managing_org3)
end
before do
create(:organisation, name: "Inactive managing org", active: false)
end
context "when org owns stock" do
let(:options) do
{
@ -111,7 +118,7 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do
}
end
it "shows current managing agent at top, followed by the current owning organisation (with hint), followed by the managing agents of the current owning organisation" do
it "shows current managing agent at top, followed by the current owning organisation (with hint), followed by the active managing agents of the current owning organisation" do
log_owning_org.update!(holds_own_stock: true)
expect(question.displayed_answer_options(log, user)).to eq(options)
end
@ -133,7 +140,7 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do
org_rel2.child_organisation.update!(merge_date: Time.zone.local(2023, 8, 2), absorbing_organisation_id: log_owning_org.id)
end
it "shows current managing agent at top, followed by the current owning organisation (with hint), followed by the managing agents of the current owning organisation" do
it "shows current managing agent at top, followed by the current owning organisation (with hint), followed by the active managing agents of the current owning organisation" do
log_owning_org.update!(holds_own_stock: true)
expect(question.displayed_answer_options(log, user)).to eq(options)
end
@ -149,7 +156,7 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do
}
end
it "shows current managing agent at top, followed by the managing agents of the current owning organisation" do
it "shows current managing agent at top, followed by the active managing agents of the current owning organisation" do
log_owning_org.update!(holds_own_stock: false)
expect(question.displayed_answer_options(log, user)).to eq(options)
end

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

@ -26,7 +26,7 @@ RSpec.describe Form::Lettings::Questions::NationalityAllGroup, type: :model do
end
it "has the correct hint_text" do
expect(question.hint_text).to eq("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. If the lead tenant is a dual national of the United Kingdom and another country, enter United Kingdom. If they are a dual national of two other countries, the tenant should decide which country to enter.")
expect(question.hint_text).to eq("If the lead tenant is a dual national of the United Kingdom and another country, enter United Kingdom. If they are a dual national of two other countries, the tenant should decide which country to enter.")
end
it "has the correct answer_options" do

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

@ -24,7 +24,7 @@ RSpec.describe Form::Lettings::Questions::PersonWorkingSituation, type: :model d
"1" => { "value" => "Full-time – 30 hours or more" },
"10" => { "value" => "Person prefers not to say" },
"2" => { "value" => "Part-time – Less than 30 hours" },
"3" => { "value" => "In government training into work, such as New Deal" },
"3" => { "value" => "In government training into work" },
"4" => { "value" => "Jobseeker" },
"5" => { "value" => "Retired" },
"6" => { "value" => "Not seeking work" },

104
spec/models/form/lettings/questions/referral_prp_spec.rb

@ -0,0 +1,104 @@
require "rails_helper"
RSpec.describe Form::Lettings::Questions::ReferralPrp, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form, start_date: Time.zone.local(2023, 4, 1)) }
before do
allow(form).to receive(:start_year_after_2024?).and_return(false)
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(form)
end
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("referral")
end
it "has the correct header" do
expect(question.header).to eq("What was the source of referral for this letting?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Source of referral for letting")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(0)
end
it "has the correct hint" do
expect(question.hint_text).to eq("You told us that the needs type is general needs. We have removed some options because of this.")
end
it "is not marked as derived" do
expect(question).not_to be_derived(nil)
end
context "with 2023/24 form" do
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Internal transfer", "hint" => "Where the tenant has moved to another social property owned by the same landlord." },
"2" => { "value" => "Tenant applied directly (no referral or nomination)" },
"3" => { "value" => "Nominated by a local housing authority" },
"4" => { "value" => "Referred by local authority housing department" },
"8" => { "value" => "Re-located through official housing mobility scheme" },
"10" => { "value" => "Other social landlord" },
"9" => { "value" => "Community learning disability team" },
"14" => { "value" => "Community mental health team" },
"15" => { "value" => "Health service" },
"12" => { "value" => "Police, probation or prison" },
"7" => { "value" => "Voluntary agency" },
"13" => { "value" => "Youth offending team" },
"17" => { "value" => "Children’s Social Care" },
"16" => { "value" => "Other" },
})
end
it "has the correct question number" do
expect(question.question_number).to eq(85)
end
end
context "with 2024/25 form" do
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
before do
allow(form).to receive(:start_year_after_2024?).and_return(true)
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Internal transfer", "hint" => "Where the tenant has moved to another social property owned by the same landlord." },
"2" => { "value" => "Tenant applied directly (no referral or nomination)" },
"3" => { "value" => "Nominated by a local housing authority" },
"8" => { "value" => "Re-located through official housing mobility scheme" },
"10" => { "value" => "Other social landlord" },
"9" => { "value" => "Community learning disability team" },
"14" => { "value" => "Community mental health team" },
"15" => { "value" => "Health service" },
"18" => { "value" => "Police, probation, prison or youth offending team – tenant had custodial sentence" },
"19" => { "value" => "Police, probation, prison or youth offending team – no custodial sentence" },
"7" => { "value" => "Voluntary agency" },
"17" => { "value" => "Children’s Social Care" },
"16" => { "value" => "Other" },
})
end
it "has the correct question number" do
expect(question.question_number).to eq(84)
end
end
end

105
spec/models/form/lettings/questions/referral_supported_housing_prp_spec.rb

@ -0,0 +1,105 @@
require "rails_helper"
RSpec.describe Form::Lettings::Questions::ReferralSupportedHousingPrp, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form, start_date: Time.zone.local(2023, 4, 1)) }
before do
allow(form).to receive(:start_year_after_2024?).and_return(false)
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(form)
end
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("referral")
end
it "has the correct header" do
expect(question.header).to eq("What was the source of referral for this letting?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Source of referral for letting")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(0)
end
it "has the correct hint" do
expect(question.hint_text).to eq("")
end
it "is not marked as derived" do
expect(question).not_to be_derived(nil)
end
context "with 2023/24 form" do
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Internal transfer", "hint" => "Where the tenant has moved to another social property owned by the same landlord." },
"2" => { "value" => "Tenant applied directly (no referral or nomination)" },
"3" => { "value" => "Nominated by a local housing authority" },
"4" => { "value" => "Referred by local authority housing department" },
"8" => { "value" => "Re-located through official housing mobility scheme" },
"10" => { "value" => "Other social landlord" },
"9" => { "value" => "Community learning disability team" },
"14" => { "value" => "Community mental health team" },
"15" => { "value" => "Health service" },
"12" => { "value" => "Police, probation or prison" },
"7" => { "value" => "Voluntary agency" },
"13" => { "value" => "Youth offending team" },
"17" => { "value" => "Children’s Social Care" },
"16" => { "value" => "Other" },
})
end
it "has the correct question number" do
expect(question.question_number).to eq(85)
end
end
context "with 2024/25 form" do
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
before do
allow(form).to receive(:start_year_after_2024?).and_return(true)
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Internal transfer", "hint" => "Where the tenant has moved to another social property owned by the same landlord." },
"2" => { "value" => "Tenant applied directly (no referral or nomination)" },
"3" => { "value" => "Nominated by a local housing authority" },
"4" => { "value" => "Referred by local authority housing department" },
"8" => { "value" => "Re-located through official housing mobility scheme" },
"10" => { "value" => "Other social landlord" },
"9" => { "value" => "Community learning disability team" },
"14" => { "value" => "Community mental health team" },
"15" => { "value" => "Health service" },
"18" => { "value" => "Police, probation, prison or youth offending team – tenant had custodial sentence" },
"19" => { "value" => "Police, probation, prison or youth offending team – no custodial sentence" },
"7" => { "value" => "Voluntary agency" },
"17" => { "value" => "Children’s Social Care" },
"16" => { "value" => "Other" },
})
end
it "has the correct question number" do
expect(question.question_number).to eq(84)
end
end
end

101
spec/models/form/lettings/questions/referral_supported_housing_spec.rb

@ -0,0 +1,101 @@
require "rails_helper"
RSpec.describe Form::Lettings::Questions::ReferralSupportedHousing, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form, start_date: Time.zone.local(2023, 4, 1)) }
before do
allow(form).to receive(:start_year_after_2024?).and_return(false)
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(form)
end
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("referral")
end
it "has the correct header" do
expect(question.header).to eq("What was the source of referral for this letting?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Source of referral for letting")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(0)
end
it "has the correct hint" do
expect(question.hint_text).to eq("You told us that you are a local authority. We have removed some options because of this.")
end
it "is not marked as derived" do
expect(question).not_to be_derived(nil)
end
context "with 2023/24 form" do
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Internal transfer", "hint" => "Where the tenant has moved to another social property owned by the same landlord." },
"2" => { "value" => "Tenant applied directly (no referral or nomination)" },
"8" => { "value" => "Re-located through official housing mobility scheme" },
"10" => { "value" => "Other social landlord" },
"9" => { "value" => "Community learning disability team" },
"14" => { "value" => "Community mental health team" },
"15" => { "value" => "Health service" },
"12" => { "value" => "Police, probation or prison" },
"7" => { "value" => "Voluntary agency" },
"13" => { "value" => "Youth offending team" },
"17" => { "value" => "Children’s Social Care" },
"16" => { "value" => "Other" },
})
end
it "has the correct question number" do
expect(question.question_number).to eq(85)
end
end
context "with 2024/25 form" do
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
before do
allow(form).to receive(:start_year_after_2024?).and_return(true)
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Internal transfer", "hint" => "Where the tenant has moved to another social property owned by the same landlord." },
"2" => { "value" => "Tenant applied directly (no referral or nomination)" },
"8" => { "value" => "Re-located through official housing mobility scheme" },
"10" => { "value" => "Other social landlord" },
"9" => { "value" => "Community learning disability team" },
"14" => { "value" => "Community mental health team" },
"15" => { "value" => "Health service" },
"18" => { "value" => "Police, probation, prison or youth offending team – tenant had custodial sentence" },
"19" => { "value" => "Police, probation, prison or youth offending team – no custodial sentence" },
"7" => { "value" => "Voluntary agency" },
"17" => { "value" => "Children’s Social Care" },
"16" => { "value" => "Other" },
})
end
it "has the correct question number" do
expect(question.question_number).to eq(84)
end
end
end

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

Loading…
Cancel
Save