Browse Source

Merge branch 'main' into CLDC-4313-resolve-unhandled-errors-on-large-numbers-in-numeric-fields

# Conflicts:
#	app/models/form/sales/questions/purchase_price.rb
CLDC-4313-resolve-unhandled-errors-on-large-numbers-in-numeric-fields
samyou-softwire 4 days ago
parent
commit
fbc4544bf9
  1. 2
      .github/workflows/review_deploy.yml
  2. 27
      .github/workflows/review_teardown_pipeline.yml
  3. 38
      app/components/data_protection_confirmation_banner_component.rb
  4. 2
      app/frontend/controllers/numeric_question_controller.js
  5. 4
      app/helpers/collection_time_helper.rb
  6. 18
      app/helpers/filters_helper.rb
  7. 2
      app/models/form/lettings/questions/builtype.rb
  8. 7
      app/models/form/lettings/questions/location_id.rb
  9. 2
      app/models/form/sales/questions/buyer_still_serving.rb
  10. 2
      app/models/form/sales/questions/housing_benefits.rb
  11. 2
      app/models/form/sales/questions/property_building_type.rb
  12. 2
      app/models/form/sales/questions/purchase_price.rb
  13. 23
      app/models/user.rb
  14. 4
      app/services/feature_toggle.rb
  15. 8
      app/services/filter_manager.rb
  16. 4
      app/views/form/guidance/_address_search.html.erb
  17. 6
      app/views/form/headers/_person_2_known_page.erb
  18. 6
      app/views/form/headers/_person_3_known_page.erb
  19. 6
      app/views/form/headers/_person_4_known_page.erb
  20. 6
      app/views/form/headers/_person_5_known_page.erb
  21. 6
      app/views/form/headers/_person_6_known_page.erb
  22. 30
      app/views/users/_user_filters.html.erb
  23. 8
      aws-devcontainer/.devcontainer/Dockerfile
  24. 5
      config/locales/forms/2024/lettings/guidance.en.yml
  25. 5
      config/locales/forms/2024/sales/guidance.en.yml
  26. 5
      config/locales/forms/2025/lettings/guidance.en.yml
  27. 10
      config/locales/forms/2025/lettings/income_and_benefits.en.yml
  28. 5
      config/locales/forms/2025/sales/guidance.en.yml
  29. 16
      config/locales/forms/2025/sales/income_benefits_and_savings.en.yml
  30. 2
      config/locales/forms/2025/sales/other_household_information.en.yml
  31. 5
      config/locales/forms/2026/lettings/guidance.en.yml
  32. 10
      config/locales/forms/2026/lettings/income_and_benefits.en.yml
  33. 5
      config/locales/forms/2026/sales/guidance.en.yml
  34. 16
      config/locales/forms/2026/sales/income_benefits_and_savings.en.yml
  35. 2
      config/locales/forms/2026/sales/other_household_information.en.yml
  36. 34
      lib/tasks/delete_logs_in_collection_year_and_earlier.rake
  37. 11
      lib/tasks/round_value_for_2026_sales_logs.rake
  38. 111
      spec/components/data_protection_confirmation_banner_component_spec.rb
  39. 9
      spec/features/form/progressive_total_field_spec.rb
  40. 20
      spec/features/user_spec.rb
  41. 2
      spec/fixtures/files/lettings_log_csv_export_labels_23.csv
  42. 2
      spec/fixtures/files/lettings_log_csv_export_labels_24.csv
  43. 2
      spec/fixtures/files/lettings_log_csv_export_labels_25.csv
  44. 2
      spec/fixtures/files/lettings_log_csv_export_non_support_labels_23.csv
  45. 2
      spec/fixtures/files/lettings_log_csv_export_non_support_labels_24.csv
  46. 2
      spec/fixtures/files/lettings_log_csv_export_non_support_labels_25.csv
  47. 2
      spec/fixtures/files/sales_logs_csv_export_labels_24.csv
  48. 2
      spec/fixtures/files/sales_logs_csv_export_labels_25.csv
  49. 2
      spec/fixtures/files/sales_logs_csv_export_labels_26.csv
  50. 2
      spec/fixtures/files/sales_logs_csv_export_non_support_labels_24.csv
  51. 2
      spec/fixtures/files/sales_logs_csv_export_non_support_labels_25.csv
  52. 2
      spec/fixtures/files/sales_logs_csv_export_non_support_labels_26.csv
  53. 26
      spec/models/form/lettings/questions/location_id_spec.rb
  54. 49
      spec/models/form/sales/pages/property_building_type_spec.rb
  55. 49
      spec/models/form/sales/pages/property_wheelchair_accessible_spec.rb
  56. 14
      spec/models/form/sales/questions/buyer1_income_known_spec.rb
  57. 14
      spec/models/form/sales/questions/buyer1_income_spec.rb
  58. 2
      spec/models/form/sales/questions/buyer_still_serving_spec.rb
  59. 2
      spec/models/form/sales/questions/housing_benefits_spec.rb
  60. 2
      spec/models/form/sales/questions/property_building_type_spec.rb
  61. 39
      spec/models/lettings_log_derived_fields_spec.rb
  62. 8
      spec/models/lettings_log_spec.rb
  63. 104
      spec/models/validations/date_validations_spec.rb
  64. 51
      spec/models/validations/sales/soft_validations_spec.rb
  65. 6
      spec/requests/collection_resources_controller_spec.rb
  66. 197
      spec/services/csv/lettings_log_csv_service_spec.rb
  67. 47
      spec/services/filter_manager_spec.rb
  68. 12
      yarn.lock

2
.github/workflows/review_deploy.yml

@ -55,7 +55,7 @@ jobs:
with:
script: |
const prNumber = context.issue.number;
const msg = `Created review app at https://review.submit-social-housing-data.communities.gov.uk/${prNumber}. Note that the review app will be automatically deprovisioned after 30 days and will need the review app pipeline running again.`;
const msg = `Created review app at https://review.submit-social-housing-data.communities.gov.uk/${prNumber}. Note that the review app will be automatically deprovisioned after 30 days and will need the review app pipeline running again. To tear down the review app entirely, remove the \`review-app\` label or merge/close the PR.`;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,

27
.github/workflows/review_teardown_pipeline.yml

@ -7,6 +7,7 @@ on:
pull_request:
types:
- closed
- unlabeled
env:
app_repo_role: arn:aws:iam::815624722760:role/core-application-repo
@ -18,6 +19,9 @@ env:
jobs:
database:
name: Drop database
if: >
(github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'review-app'))
|| (github.event.action == 'unlabeled' && github.event.label.name == 'review-app')
runs-on: ubuntu-latest
permissions:
id-token: write
@ -54,6 +58,9 @@ jobs:
infra:
name: Teardown review app
if: >
(github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'review-app'))
|| (github.event.action == 'unlabeled' && github.event.label.name == 'review-app')
needs: [database]
uses: communitiesuk/submit-social-housing-lettings-and-sales-data-infrastructure/.github/workflows/destroy_review_app_infra.yml@main
with:
@ -61,3 +68,23 @@ jobs:
app_repo_role: arn:aws:iam::815624722760:role/core-application-repo
permissions:
id-token: write
comment:
name: Comment on PR
if: github.event.action == 'unlabeled' && github.event.label.name == 'review-app'
needs: [infra]
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: 'Review app has been torn down. To redeploy, reapply the `review-app` label.',
});

38
app/components/data_protection_confirmation_banner_component.rb

@ -12,16 +12,16 @@ class DataProtectionConfirmationBannerComponent < ViewComponent::Base
def display_banner?
return false if user.support? && organisation.blank?
return true if org_without_dpo?
return true if show_no_dpo_message?
return false if !org_or_user_org.holds_own_stock? && org_or_user_org.stock_owners.empty? && org_or_user_org.absorbed_organisations.empty?
!org_or_user_org.data_protection_confirmed? || !org_or_user_org.organisation_or_stock_owner_signed_dsa_and_holds_own_stock?
!dsa_signed? || !org_or_user_org.organisation_or_stock_owner_signed_dsa_and_holds_own_stock?
end
def header_text
if org_without_dpo?
if show_no_dpo_message?
"To create logs your organisation must state a data protection officer. They must sign the Data Sharing Agreement."
elsif !org_or_user_org.holds_own_stock? && org_or_user_org.data_protection_confirmed?
elsif show_no_stock_owner_message?
"Your organisation does not own stock. To create logs your stock owner(s) must accept the Data Sharing Agreement on CORE."
elsif user.is_dpo?
"Your organisation must accept the Data Sharing Agreement before you can create any logs."
@ -31,7 +31,7 @@ class DataProtectionConfirmationBannerComponent < ViewComponent::Base
end
def banner_text
if org_without_dpo? || user.is_dpo? || !org_or_user_org.holds_own_stock?
if show_no_dpo_message? || user.is_dpo? || !org_or_user_org.holds_own_stock?
govuk_link_to(
link_text,
link_href,
@ -51,9 +51,9 @@ private
end
def link_text
if dpo_required?
if show_no_dpo_message?
"Contact helpdesk to assign a data protection officer"
elsif !org_or_user_org.holds_own_stock? && org_or_user_org.data_protection_confirmed?
elsif show_no_stock_owner_message?
"View or add stock owners"
else
"Read the Data Sharing Agreement"
@ -61,24 +61,32 @@ private
end
def link_href
if dpo_required?
if show_no_dpo_message?
GlobalConstants::HELPDESK_URL
elsif !org_or_user_org.holds_own_stock? && org_or_user_org.data_protection_confirmed?
elsif show_no_stock_owner_message?
stock_owners_organisation_path(org_or_user_org)
else
data_sharing_agreement_organisation_path(org_or_user_org)
end
end
def dpo_required?
org_or_user_org.data_protection_officers.empty?
def show_no_dpo_message?
# it is fine if an org has a DSA and the DPO has moved on
# CORE staff do this sometimes as a single DPO covers multiple 'orgs' that exist as branches of the same real world org
# so, they move the DPO to all the mini orgs and have them sign each DSA
# so the DSA being signed can silence this warning
org_or_user_org.data_protection_officers.empty? && !dsa_signed?
end
def org_or_user_org
organisation.presence || user.organisation
def dsa_signed?
org_or_user_org.data_protection_confirmed?
end
def show_no_stock_owner_message?
!org_or_user_org.holds_own_stock? && dsa_signed?
end
def org_without_dpo?
org_or_user_org.data_protection_officers.empty?
def org_or_user_org
organisation.presence || user.organisation
end
end

2
app/frontend/controllers/numeric_question_controller.js

@ -11,7 +11,7 @@ export default class extends Controller {
calculateFields () {
const affectedField = this.element.dataset.target
const fieldsToAdd = JSON.parse(this.element.dataset.calculated).map(x => `lettings-log-${x.replaceAll('_', '-')}-field`)
const valuesToAdd = fieldsToAdd.map(x => getFieldValue(x)).filter(x => x)
const valuesToAdd = fieldsToAdd.map(x => getFieldValue(x)).filter(x => x && !isNaN(parseFloat(x)))
const newValue = valuesToAdd.map(x => parseFloat(x)).reduce((a, b) => a + b, 0).toFixed(2)
const elementToUpdate = document.getElementById(affectedField)
elementToUpdate.value = newValue

4
app/helpers/collection_time_helper.rb

@ -29,6 +29,10 @@ module CollectionTimeHelper
Time.zone.local(current_collection_start_year, 4, 1)
end
def current_collection_after_crossover_start_date
Form::DEADLINES[current_collection_start_year][:edit_end_date] + 1.day
end
def collection_end_date(date)
Time.zone.local(collection_start_year_for_date(date) + 1, 3, 31).end_of_day
end

18
app/helpers/filters_helper.rb

@ -52,6 +52,22 @@ module FiltersHelper
}.freeze
end
def user_role_type_filters(include_support: false)
roles = {
"data_provider" => "Data provider",
"data_coordinator" => "Data coordinator",
}
roles["support"] = "Support" if include_support
roles.freeze
end
def user_additional_responsibilities_filters
{
"data_protection_officer" => "Data protection officer",
"key_contact" => "Key contact",
}.freeze
end
def scheme_status_filters
{
"incomplete" => "Incomplete",
@ -306,7 +322,7 @@ private
def filters_count(filters)
filters.each.sum do |category, category_filters|
if %w[years status needstypes bulk_upload_id].include?(category)
if %w[years status needstypes bulk_upload_id role additional_responsibilities].include?(category)
category_filters.count(&:present?)
elsif %w[user owning_organisation managing_organisation user_text_search owning_organisation_text_search managing_organisation_text_search uploading_organisation].include?(category)
1

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

@ -9,7 +9,7 @@ class Form::Lettings::Questions::Builtype < ::Form::Question
ANSWER_OPTIONS = {
"2" => { "value" => "Converted from previous residential or non-residential property" },
"1" => { "value" => "Purpose built" },
"1" => { "value" => "Purpose-built" },
}.freeze
QUESTION_NUMBER_FROM_YEAR = { 2023 => 20, 2024 => 20, 2025 => 20 }.freeze

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

@ -30,11 +30,8 @@ class Form::Lettings::Questions::LocationId < ::Form::Question
scheme_location_ids = lettings_log.scheme.locations.visible.confirmed.pluck(:id)
answer_options.select { |k, _v| scheme_location_ids.include?(k.to_i) }
.sort_by { |_, v|
name = v["hint"].match(/[a-zA-Z].*/).to_s
number = v["hint"].match(/\d+/).to_s.to_i
[name, number]
}.to_h
.sort_by { |_, v| v["value"] }
.to_h
end
def hidden_in_check_answers?(lettings_log, _current_user = nil)

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

@ -13,6 +13,8 @@ class Form::Sales::Questions::BuyerStillServing < ::Form::Question
"4" => { "value" => "Yes" },
"5" => { "value" => "No - they left up to and including 2 years ago" },
"6" => { "value" => "No - they left more than 2 years ago" },
"divider" => { "value" => true },
"9" => { "value" => "Don’t know" },
}.freeze
else
{

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

@ -12,7 +12,7 @@ class Form::Sales::Questions::HousingBenefits < ::Form::Question
ANSWER_OPTIONS = {
"2" => { "value" => "Housing benefit" },
"3" => { "value" => "Universal Credit housing element" },
"1" => { "value" => "Neither housing benefit or Universal Credit housing element" },
"1" => { "value" => "Neither" },
"divider" => { "value" => true },
"4" => { "value" => "Don’t know " },
}.freeze

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

@ -9,7 +9,7 @@ class Form::Sales::Questions::PropertyBuildingType < ::Form::Question
end
ANSWER_OPTIONS = {
"1" => { "value" => "Purpose built" },
"1" => { "value" => "Purpose-built" },
"2" => { "value" => "Converted from previous residential or non-residential property" },
}.freeze

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

@ -5,7 +5,7 @@ class Form::Sales::Questions::PurchasePrice < ::Form::Question
@type = "numeric"
@min = form.start_year_2026_or_later? ? 15_000 : 0
@max = form.start_year_2025_or_later? ? 999_999 : nil
@step = 0.01
@step = form.start_year_2026_or_later? ? 1 : 0.01 # 0.01 was a mistake that was fixed in 2026
@width = 5
@prefix = "£"
@ownership_sch = ownershipsch

23
app/models/user.rb

@ -81,6 +81,29 @@ class User < ApplicationRecord
filtered_records
}
scope :filter_by_role, ->(role, _user = nil) { where(role:) }
scope :filter_by_additional_responsibilities, lambda { |additional_responsibilities, _user|
filtered_records = all
scopes = []
additional_responsibilities.each do |responsibility|
case responsibility
when "key_contact"
scopes << is_key_contact
when "data_protection_officer"
scopes << is_data_protection_officer
end
end
if scopes.any?
filtered_records = filtered_records.merge(scopes.reduce(&:or))
end
filtered_records
}
scope :is_key_contact, -> { where(is_key_contact: true) }
scope :is_data_protection_officer, -> { where(is_dpo: true) }
scope :not_signed_in, -> { where(last_sign_in_at: nil, active: true) }
scope :deactivated, -> { where(active: false) }
scope :activated, -> { where(active: true) }

4
app/services/feature_toggle.rb

@ -1,6 +1,6 @@
class FeatureToggle
def self.allow_future_form_use?
Rails.env.development? || Rails.env.review? || Rails.env.staging?
false
end
def self.bulk_upload_duplicate_log_check_enabled?
@ -28,7 +28,7 @@ class FeatureToggle
end
def self.create_test_logs_enabled?
Rails.env.development? || Rails.env.review? || Rails.env.staging?
Rails.env.development? || Rails.env.review?
end
def self.sales_export_enabled?

8
app/services/filter_manager.rb

@ -130,6 +130,14 @@ class FilterManager
new_filters["status"] = params["status"]
end
if filter_type.include?("users") && params["role"].present?
new_filters["role"] = params["role"]
end
if filter_type.include?("users") && params["additional_responsibilities"].present?
new_filters["additional_responsibilities"] = params["additional_responsibilities"]
end
if filter_type.include?("schemes")
current_user.scheme_filters(specific_org:).each do |filter|
new_filters[filter] = params[filter] if params[filter].present?

4
app/views/form/guidance/_address_search.html.erb

@ -2,6 +2,10 @@
<%= I18n.t("forms.#{@log.form.start_date.year}.#{@log.form.type}.guidance.address_search.content").html_safe %>
<% end %>
<%= govuk_details(summary_text: I18n.t("forms.#{@log.form.start_date.year}.#{@log.form.type}.guidance.address_uprn.title")) do %>
<%= I18n.t("forms.#{@log.form.start_date.year}.#{@log.form.type}.guidance.address_uprn.content").html_safe %>
<% end %>
<div class="govuk-button-group">
<%= govuk_link_to "Enter the address manually instead", address_manual_input_path(@log.log_type, @log.id), class: "govuk-button govuk-button--secondary" %>
</div>

6
app/views/form/headers/_person_2_known_page.erb

@ -1 +1,5 @@
You have given us the details for 0 of the <%= log.hholdcount %> other people in the household
<% if log.form.start_year_2026_or_later? %>
You have given us the details for 1 of the <%= log.hholdcount %> people in the household
<% else %>
You have given us the details for 0 of the <%= log.hholdcount %> other people in the household
<% end %>

6
app/views/form/headers/_person_3_known_page.erb

@ -1 +1,5 @@
You have given us the details for <%= log.joint_purchase? ? 0 : 1 %> of the <%= log.hholdcount %> other people in the household
<% if log.form.start_year_2026_or_later? %>
You have given us the details for 2 of the <%= log.hholdcount %> people in the household
<% else %>
You have given us the details for <%= log.joint_purchase? ? 0 : 1 %> of the <%= log.hholdcount %> other people in the household
<% end %>

6
app/views/form/headers/_person_4_known_page.erb

@ -1 +1,5 @@
You have given us the details for <%= log.joint_purchase? ? 1 : 2 %> of the <%= log.hholdcount %> other people in the household
<% if log.form.start_year_2026_or_later? %>
You have given us the details for 3 of the <%= log.hholdcount %> people in the household
<% else %>
You have given us the details for <%= log.joint_purchase? ? 1 : 2 %> of the <%= log.hholdcount %> other people in the household
<% end %>

6
app/views/form/headers/_person_5_known_page.erb

@ -1 +1,5 @@
You have given us the details for <%= log.joint_purchase? ? 2 : 3 %> of the <%= log.hholdcount %> other people in the household
<% if log.form.start_year_2026_or_later? %>
You have given us the details for 4 of the <%= log.hholdcount %> people in the household
<% else %>
You have given us the details for <%= log.joint_purchase? ? 2 : 3 %> of the <%= log.hholdcount %> other people in the household
<% end %>

6
app/views/form/headers/_person_6_known_page.erb

@ -1 +1,5 @@
You have given us the details for <%= log.joint_purchase? ? 3 : 4 %> of the <%= log.hholdcount %> other people in the household
<% if log.form.start_year_2026_or_later? %>
You have given us the details for 5 of the <%= log.hholdcount %> people in the household
<% else %>
You have given us the details for <%= log.joint_purchase? ? 3 : 4 %> of the <%= log.hholdcount %> other people in the household
<% end %>

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

@ -17,12 +17,30 @@
<%= render partial: "filters/checkbox_filter",
locals: {
f:,
options: user_status_filters,
label: "Status",
category: "status",
size: "s",
} %>
f:,
options: user_status_filters,
label: "Status",
category: "status",
size: "s",
} %>
<%= render partial: "filters/checkbox_filter",
locals: {
f:,
options: user_role_type_filters(include_support: current_user.support?),
label: "Role type",
category: "role",
size: "s",
} %>
<%= render partial: "filters/checkbox_filter",
locals: {
f:,
options: user_additional_responsibilities_filters,
label: "Additional responsibilities",
category: "additional_responsibilities",
size: "s",
} %>
<% if request.params["search"].present? %>
<%= f.hidden_field :search, value: request.params["search"] %>

8
aws-devcontainer/.devcontainer/Dockerfile

@ -1,7 +1,13 @@
FROM homebrew/brew
RUN brew install aws-vault && brew install awscli
RUN curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_64bit/session-manager-plugin.deb" -o "session-manager-plugin.deb" && sudo dpkg -i session-manager-plugin.deb
RUN if [ "$(dpkg --print-architecture)" = "arm64" ]; then \
ARCH="ubuntu_arm64"; \
else \
ARCH="ubuntu_64bit"; \
fi && \
curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/${ARCH}/session-manager-plugin.deb" -o "session-manager-plugin.deb" && \
sudo dpkg -i session-manager-plugin.deb
ENV AWS_VAULT_BACKEND=file
ENV AWS_VAULT_FILE_DIR=./vault

5
config/locales/forms/2024/lettings/guidance.en.yml

@ -68,3 +68,8 @@ en:
<li>Some properties may not be available yet e.g. new builds; you might need to enter them manually instead</li>
<li>For UPRN (Unique Property Reference Number), please enter the full value exactly</li>
</ul>"
address_uprn:
title: "What is a UPRN?"
content: "<p>The Unique Property Reference Number (UPRN) is a unique number system created by Ordnance Survey and used by housing providers and various industries across the UK. An example is 0010457355.</p>
<p>The UPRN may not be the same as the property reference assigned by your organisation.</p>"

5
config/locales/forms/2024/sales/guidance.en.yml

@ -51,3 +51,8 @@ en:
<li>Some properties may not be available yet e.g. new builds; you might need to enter them manually instead</li>
<li>For UPRN (Unique Property Reference Number), please enter the full value exactly</li>
</ul>"
address_uprn:
title: "What is a UPRN?"
content: "<p>The Unique Property Reference Number (UPRN) is a unique number system created by Ordnance Survey and used by housing providers and various industries across the UK. An example is 0010457355.</p>
<p>The UPRN may not be the same as the property reference assigned by your organisation.</p>"

5
config/locales/forms/2025/lettings/guidance.en.yml

@ -67,3 +67,8 @@ en:
<li>Some properties may not be available yet e.g. new builds; you might need to enter them manually instead</li>
<li>For UPRN (Unique Property Reference Number), please enter the full value exactly</li>
</ul>"
address_uprn:
title: "What is a UPRN?"
content: "<p>The Unique Property Reference Number (UPRN) is a unique number system created by Ordnance Survey and used by housing providers and various industries across the UK. An example is 0010457355.</p>
<p>The UPRN may not be the same as the property reference assigned by your organisation.</p>"

10
config/locales/forms/2025/lettings/income_and_benefits.en.yml

@ -25,10 +25,10 @@ en:
hb:
page_header: ""
check_answer_label: "Housing related benefits received"
check_answer_prompt: "Tell us if household receives housing related benefits"
hint_text: "This is about when the tenant is in their new let. If they are unsure about the situation for their new let and their financial and working situation hasn’t changed significantly, answer based on what housing related benefits they currently receive."
question_text: "Is the household likely to be receiving any of these housing related benefits?"
check_answer_label: "Housing-related benefits received"
check_answer_prompt: "Tell us if household receives housing-related benefits"
hint_text: "This is about when the tenant is in their new let. If they are unsure about the situation for their new let and their financial and working situation hasn’t changed significantly, answer based on what housing-related benefits they currently receive."
question_text: "Is the household likely to be receiving any of these housing-related benefits?"
benefits:
page_header: ""
@ -112,7 +112,7 @@ en:
check_answer_label: "Any outstanding amount for basic rent and charges"
check_answer_prompt: "Tell us if any outstanding amount for basic rent and charges"
hint_text: "Also known as the ‘outstanding amount’."
question_text: "After the household has received any housing related benefits, will they still need to pay for rent and charges?"
question_text: "After the household has received any housing-related benefits, will they still need to pay for rent and charges?"
outstanding_amount:
page_header: ""

5
config/locales/forms/2025/sales/guidance.en.yml

@ -51,3 +51,8 @@ en:
<li>Some properties may not be available yet e.g. new builds; you might need to enter them manually instead</li>
<li>For UPRN (Unique Property Reference Number), please enter the full value exactly</li>
</ul>"
address_uprn:
title: "What is a UPRN?"
content: "<p>The Unique Property Reference Number (UPRN) is a unique number system created by Ordnance Survey and used by housing providers and various industries across the UK. An example is 0010457355.</p>
<p>The UPRN may not be the same as the property reference assigned by your organisation.</p>"

16
config/locales/forms/2025/sales/income_benefits_and_savings.en.yml

@ -8,12 +8,12 @@ en:
income1nk:
check_answer_label: "Buyer 1’s gross annual income known"
check_answer_prompt: "Enter buyer 1’s gross annual income if known"
hint_text: ""
hint_text: "Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments."
question_text: "Do you know buyer 1’s annual income?"
income1:
check_answer_label: "Buyer 1’s gross annual income"
check_answer_prompt: ""
hint_text: "Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments."
hint_text: ""
question_text: "Buyer 1’s gross annual income"
inc1mort:
@ -28,12 +28,12 @@ en:
income2nk:
check_answer_label: "Buyer 2’s gross annual income known"
check_answer_prompt: "Enter buyer 2’s gross annual income if known"
hint_text: ""
hint_text: "Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments."
question_text: "Do you know buyer 2’s annual income?"
income2:
check_answer_label: "Buyer 2’s gross annual income"
check_answer_prompt: ""
hint_text: "Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments."
hint_text: ""
question_text: "Buyer 2’s gross annual income"
inc2mort:
@ -46,16 +46,16 @@ en:
housing_benefits:
joint_purchase:
page_header: ""
check_answer_label: "Housing related benefits buyers received before buying this property"
check_answer_label: "Housing-related benefits buyers received before buying this property"
check_answer_prompt: ""
hint_text: ""
question_text: "Were the buyers receiving any of these housing related benefits immediately before buying this property?"
question_text: "Were the buyers receiving any of these housing-related benefits immediately before buying this property?"
not_joint_purchase:
page_header: ""
check_answer_label: "Housing related benefits buyer received before buying this property"
check_answer_label: "Housing-related benefits buyer received before buying this property"
check_answer_prompt: ""
hint_text: ""
question_text: "Was the buyer receiving any of these housing related benefits immediately before buying this property?"
question_text: "Was the buyer receiving any of these housing-related benefits immediately before buying this property?"
savings:
joint_purchase:

2
config/locales/forms/2025/sales/other_household_information.en.yml

@ -7,7 +7,7 @@ en:
page_header: ""
check_answer_label: "Any buyer has served as regulars in the UK armed forces"
check_answer_prompt: "Tell us if any buyer has ever served as a regular in the UK armed forces"
hint_text: "A regular is somebody who has served in the Royal Navy, the Royal Marines, the Royal Airforce or Army full time and does not include reserve forces"
hint_text: "A regular is somebody who has served in the Royal Navy, the Royal Marines, the Royal Air Force or Army full time and does not include reserve forces"
question_text: "Have any of the buyers ever served as a regular in the UK armed forces?"
hhregresstill:

5
config/locales/forms/2026/lettings/guidance.en.yml

@ -68,6 +68,11 @@ en:
<li>For UPRN (Unique Property Reference Number), please enter the full value exactly</li>
</ul>"
address_uprn:
title: "What is a UPRN?"
content: "<p>The Unique Property Reference Number (UPRN) is a unique number system created by Ordnance Survey and used by housing providers and various industries across the UK. An example is 0010457355.</p>
<p>The UPRN may not be the same as the property reference assigned by your organisation.</p>"
needs_type:
title: "What does each need type mean?"
content: "General needs housing includes both self-contained and shared housing without support or specific adaptations.<br><br>Supported housing is housing with special design facilities or features targeted at a specific client group requiring support, for example housing designed for older people, sheltered accommodation, extra care housing. It can include direct access hostels, group homes, and purpose-built self-contained housing. We do not require CORE logs for residential care or nursing homes."

10
config/locales/forms/2026/lettings/income_and_benefits.en.yml

@ -25,10 +25,10 @@ en:
hb:
page_header: ""
check_answer_label: "Housing related benefits received"
check_answer_prompt: "Tell us if household receives housing related benefits"
hint_text: "This is about when the tenant is in their new let. If they are unsure about the situation for their new let and their financial and working situation hasn’t changed significantly, answer based on what housing related benefits they currently receive."
question_text: "Is the household likely to be receiving any of these housing related benefits?"
check_answer_label: "Housing-related benefits received"
check_answer_prompt: "Tell us if household receives housing-related benefits"
hint_text: "This is about when the tenant is in their new let. If they are unsure about the situation for their new let and their financial and working situation hasn’t changed significantly, answer based on what housing-related benefits they currently receive."
question_text: "Is the household likely to be receiving any of these housing-related benefits?"
benefits:
page_header: ""
@ -112,7 +112,7 @@ en:
check_answer_label: "Any outstanding amount for basic rent and charges"
check_answer_prompt: "Tell us if any outstanding amount for basic rent and charges"
hint_text: "Also known as the ‘outstanding amount’."
question_text: "After the household has received any housing related benefits, will they still need to pay for rent and charges?"
question_text: "After the household has received any housing-related benefits, will they still need to pay for rent and charges?"
outstanding_amount:
page_header: ""

5
config/locales/forms/2026/sales/guidance.en.yml

@ -50,3 +50,8 @@ en:
<li>Some properties may not be available yet e.g. new builds; you might need to enter them manually instead</li>
<li>For UPRN (Unique Property Reference Number), please enter the full value exactly</li>
</ul>"
address_uprn:
title: "What is a UPRN?"
content: "<p>The Unique Property Reference Number (UPRN) is a unique number system created by Ordnance Survey and used by housing providers and various industries across the UK. An example is 0010457355.</p>
<p>The UPRN may not be the same as the property reference assigned by your organisation.</p>"

16
config/locales/forms/2026/sales/income_benefits_and_savings.en.yml

@ -8,12 +8,12 @@ en:
income1nk:
check_answer_label: "Buyer 1’s gross annual income known"
check_answer_prompt: "Enter buyer 1’s gross annual income if known"
hint_text: ""
hint_text: "Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments."
question_text: "Do you know buyer 1’s annual income?"
income1:
check_answer_label: "Buyer 1’s gross annual income"
check_answer_prompt: ""
hint_text: "Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments."
hint_text: ""
question_text: "Buyer 1’s gross annual income"
inc1mort:
@ -28,12 +28,12 @@ en:
income2nk:
check_answer_label: "Buyer 2’s gross annual income known"
check_answer_prompt: "Enter buyer 2’s gross annual income if known"
hint_text: ""
hint_text: "Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments."
question_text: "Do you know buyer 2’s annual income?"
income2:
check_answer_label: "Buyer 2’s gross annual income"
check_answer_prompt: ""
hint_text: "Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments."
hint_text: ""
question_text: "Buyer 2’s gross annual income"
inc2mort:
@ -46,16 +46,16 @@ en:
housing_benefits:
joint_purchase:
page_header: ""
check_answer_label: "Housing related benefits buyers received before buying this property"
check_answer_label: "Housing-related benefits buyers received before buying this property"
check_answer_prompt: ""
hint_text: ""
question_text: "Were the buyers receiving any of these housing related benefits immediately before buying this property?"
question_text: "Were the buyers receiving any of these housing-related benefits immediately before buying this property?"
not_joint_purchase:
page_header: ""
check_answer_label: "Housing related benefits buyer received before buying this property"
check_answer_label: "Housing-related benefits buyer received before buying this property"
check_answer_prompt: ""
hint_text: ""
question_text: "Was the buyer receiving any of these housing related benefits immediately before buying this property?"
question_text: "Was the buyer receiving any of these housing-related benefits immediately before buying this property?"
savings:
joint_purchase:

2
config/locales/forms/2026/sales/other_household_information.en.yml

@ -7,7 +7,7 @@ en:
page_header: ""
check_answer_label: "Any buyer has served as regulars in the UK armed forces"
check_answer_prompt: "Tell us if any buyer has ever served as a regular in the UK armed forces"
hint_text: "A regular is somebody who has served in the Royal Navy, the Royal Marines, the Royal Airforce or Army full time and does not include reserve forces"
hint_text: "A regular is somebody who has served in the Royal Navy, the Royal Marines, the Royal Air Force or Army full time and does not include reserve forces"
question_text: "Have any of the buyers ever served as a regular in the UK armed forces?"
hhregresstill:

34
lib/tasks/delete_logs_in_collection_year_and_earlier.rake

@ -0,0 +1,34 @@
desc "Deletes all logs in a given collection year and earlier. Note that this operation is PERMANENT and this will bypass callbacks/paper trail. Use only as instructed in a yearly cleanup task."
task :delete_logs_in_collection_year_and_earlier, %i[year] => :environment do |_task, args|
year = args[:year].to_i
if year < 2020
raise ArgumentError, "Year must be above 2020. Make sure you've written out the entire year"
end
if year > Time.zone.now.year - 3
raise ArgumentError, "Year cannot be the last 3 years, as these may contain visible logs"
end
puts "Deleting Logs before #{year}"
puts "Deleting Sales Logs in batches of 10000"
logs = SalesLog.filter_by_year_or_earlier(year)
logs.in_batches(of: 10_000).each_with_index do |logs, i|
puts "Deleting batch #{i + 1}"
logs.delete_all
end
puts "Done deleting Sales Logs"
puts "Deleting Lettings Logs in batches of 10000"
logs = LettingsLog.filter_by_year_or_earlier(year)
logs.in_batches(of: 10_000).each_with_index do |logs, i|
puts "Deleting batch #{i + 1}"
logs.delete_all
end
puts "Done deleting Lettings Logs"
puts "Done deleting Logs before #{year}"
end

11
lib/tasks/round_value_for_2026_sales_logs.rake

@ -0,0 +1,11 @@
desc "Rounds purchase price (the 'value' field) for sales logs in the database if not a whole number"
task round_value_for_2026_sales_logs: :environment do
logs = SalesLog.filter_by_year(2026).where("value % 1 != 0")
puts "Correcting #{logs.count} sales logs, #{logs.map(&:id)}"
logs.find_each do |log|
log.update(value: log.value.round)
end
puts "Done"
end

111
spec/components/data_protection_confirmation_banner_component_spec.rb

@ -23,13 +23,25 @@ RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
organisation.users.where(is_dpo: true).destroy_all
end
it "displays the banner" do
expect(component.display_banner?).to be(true)
expect(render).to have_link(
"Contact helpdesk to assign a data protection officer",
href: "https://mhclgdigital.atlassian.net/servicedesk/customer/portal/6/group/11",
)
expect(render).to have_selector("p", text: "To create logs your organisation must state a data protection officer. They must sign the Data Sharing Agreement.")
context "when org does not have a signed data sharing agreement" do
let(:organisation) { create(:organisation, :without_dpc) }
let(:user) { create(:user, organisation:, with_dsa: false) }
it "displays the banner" do
expect(component.display_banner?).to be(true)
expect(render).to have_link(
"Contact helpdesk to assign a data protection officer",
href: "https://mhclgdigital.atlassian.net/servicedesk/customer/portal/6/group/11",
)
expect(render).to have_selector("p", text: "To create logs your organisation must state a data protection officer. They must sign the Data Sharing Agreement.")
end
end
context "when org does have a signed data sharing agreement" do
it "does not display banner" do
expect(component.display_banner?).to be(false)
expect(render.content).to be_empty
end
end
end
@ -81,7 +93,7 @@ RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
end
end
context "when org has a signed data sharing agremeent" do
context "when org has a signed data sharing agreement" do
it "does not display banner" do
expect(component.display_banner?).to be(false)
expect(render.content).to be_empty
@ -121,88 +133,5 @@ RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
end
end
end
context "when org does not have a DPO" do
before do
organisation.users.where(is_dpo: true).destroy_all
end
it "displays the banner" do
expect(component.display_banner?).to be(true)
expect(render).to have_link(
"Contact helpdesk to assign a data protection officer",
href: "https://mhclgdigital.atlassian.net/servicedesk/customer/portal/6/group/11",
)
expect(render).to have_selector("p", text: "To create logs your organisation must state a data protection officer. They must sign the Data Sharing Agreement.")
end
end
context "when org has a DPO" do
context "when org does not have a signed data sharing agreement" do
context "when user is not a DPO" do
let(:organisation) { create(:organisation, :without_dpc) }
let(:user) { create(:user, organisation:, with_dsa: false) }
let!(:dpo) { create(:user, :data_protection_officer, organisation:, with_dsa: false) }
it "displays the banner and shows DPOs" do
expect(component.display_banner?).to be(true)
expect(render.css("a")).to be_empty
expect(render).to have_selector("p", text: "Your data protection officer must accept the Data Sharing Agreement on CORE before you can create any logs.")
expect(render).to have_selector("p", text: "You can ask: #{dpo.name}")
end
context "and has a parent organisation that owns stock and has signed DSA" do
before do
parent_organisation = create(:organisation, holds_own_stock: true)
create(:organisation_relationship, child_organisation: organisation, parent_organisation:)
end
it "displays the banner and shows DPOs" do
expect(component.display_banner?).to be(true)
expect(render.css("a")).to be_empty
expect(render).to have_selector("p", text: "Your data protection officer must accept the Data Sharing Agreement on CORE before you can create any logs.")
expect(render).to have_selector("p", text: "You can ask: #{dpo.name}")
end
end
end
context "when user is a DPO" do
let(:organisation) { create(:organisation, :without_dpc) }
let(:user) { create(:user, :data_protection_officer, organisation:, with_dsa: false) }
it "displays the banner and asks to sign" do
expect(component.display_banner?).to be(true)
expect(render).to have_link(
"Read the Data Sharing Agreement",
href: "/organisations/#{organisation.id}/data-sharing-agreement",
)
expect(render).to have_selector("p", text: "Your organisation must accept the Data Sharing Agreement before you can create any logs.")
end
context "and has a parent organisation that owns stock and has signed DSA" do
before do
parent_organisation = create(:organisation, holds_own_stock: true)
create(:organisation_relationship, child_organisation: organisation, parent_organisation:)
end
it "displays the banner and asks to sign" do
expect(component.display_banner?).to be(true)
expect(render).to have_link(
"Read the Data Sharing Agreement",
href: "/organisations/#{organisation.id}/data-sharing-agreement",
)
expect(render).to have_selector("p", text: "Your organisation must accept the Data Sharing Agreement before you can create any logs.")
end
end
end
end
context "when org has a signed data sharing agremeent" do
it "does not display banner" do
expect(component.display_banner?).to be(false)
expect(render.content).to be_empty
end
end
end
end
end

9
spec/features/form/progressive_total_field_spec.rb

@ -58,4 +58,13 @@ RSpec.describe "Accessible Autocomplete" do
fill_in("lettings-log-supcharg-field-error", with: 50)
expect(find("#lettings-log-tcharge-field").value).to eq("550.00")
end
it "does not show 'NaN' if one of the inputs is not a number", :js do
visit("/lettings-logs/#{lettings_log.id}/rent")
expect(page).to have_selector("#tcharge_div")
fill_in("lettings-log-brent-field", with: 5)
expect(find("#lettings-log-tcharge-field").value).to eq("5.00")
fill_in("lettings-log-pscharge-field", with: "something else")
expect(find("#lettings-log-tcharge-field").value).to eq("5.00")
end
end

20
spec/features/user_spec.rb

@ -282,6 +282,12 @@ RSpec.describe "User Features" do
end
end
end
it "shows correct filters" do
expect(page).to have_selector("label", text: "Data provider")
expect(page).to have_selector("label", text: "Data coordinator")
expect(page).not_to have_selector("label", text: "Support")
end
end
end
@ -619,6 +625,20 @@ RSpec.describe "User Features" do
expect(page).to have_button("Resend invite link")
end
end
context "when filtering users" do
before do
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in(user)
visit(users_path)
end
it "shows correct filters" do
expect(page).to have_selector("label", text: "Data provider")
expect(page).to have_selector("label", text: "Data coordinator")
expect(page).to have_selector("label", text: "Support")
end
end
end
context "when the user is a customer support person" do

2
spec/fixtures/files/lettings_log_csv_export_labels_23.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/lettings_log_csv_export_labels_24.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/lettings_log_csv_export_labels_25.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/lettings_log_csv_export_non_support_labels_23.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/lettings_log_csv_export_non_support_labels_24.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/lettings_log_csv_export_non_support_labels_25.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_labels_24.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_labels_25.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_labels_26.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_non_support_labels_24.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_non_support_labels_25.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_non_support_labels_26.csv vendored

File diff suppressed because one or more lines are too long

26
spec/models/form/lettings/questions/location_id_spec.rb

@ -142,27 +142,27 @@ RSpec.describe Form::Lettings::Questions::LocationId, type: :model do
context "and some locations start with numbers" do
before do
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 5), name: "2 Abe Road")
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 6), name: "1 Abe Road")
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 7), name: "1 Lake Lane")
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 8), name: "3 Abe Road")
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 9), name: "2 Lake Lane")
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 10), name: "Smith Avenue")
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 11), name: "Abacus Road")
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 12), name: "Hawthorne Road")
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 5), name: "2 Abe Road", postcode: "AA1 1AA")
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 6), name: "1 Abe Road", postcode: "AA1 2AA")
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 7), name: "1 Lake Lane", postcode: "AA1 3AA")
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 8), name: "3 Abe Road", postcode: "AA1 4AA")
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 9), name: "2 Lake Lane", postcode: "AA1 5AA")
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 10), name: "Smith Avenue", postcode: "AA1 6AA")
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 11), name: "Abacus Road", postcode: "AA1 7AA")
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 12), name: "Hawthorne Road", postcode: "AA1 8AA")
lettings_log.update!(scheme:)
end
it "orders the locations by name then numerically" do
it "orders the locations by postcode" do
expect(question.displayed_answer_options(lettings_log).values.map { |v| v["hint"] }).to eq([
"Abacus Road",
"1 Abe Road",
"2 Abe Road",
"3 Abe Road",
"Hawthorne Road",
"1 Abe Road",
"1 Lake Lane",
"3 Abe Road",
"2 Lake Lane",
"Smith Avenue",
"Abacus Road",
"Hawthorne Road",
])
end
end

49
spec/models/form/sales/pages/property_building_type_spec.rb

@ -1,12 +1,15 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::PropertyBuildingType, type: :model do
include CollectionTimeHelper
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
let(:form) { instance_double(Form, start_date: current_collection_start_date) }
let(:subsection) { instance_double(Form::Subsection, enabled?: true, form:) }
let(:saledate) { current_collection_start_date }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
@ -24,45 +27,21 @@ RSpec.describe Form::Sales::Pages::PropertyBuildingType, type: :model do
expect(page.description).to be_nil
end
context "with form year 2024" do
let(:form) { Form.new(nil, 2024, [], "sales") }
let(:saledate) { Time.zone.local(2024, 4, 1) }
context "with a staircasing log" do
let(:log) { build(:sales_log, :shared_ownership_setup_complete, staircase: 1, saledate:) }
it "is routed to" do
expect(page.routed_to?(log, nil)).to be true
end
end
context "with a non-staircasing log" do
let(:log) { build(:sales_log, staircase: nil, saledate:) }
context "with a staircasing log" do
let(:form) { Form.new(nil, current_collection_start_year, [], "sales") }
let(:log) { build(:sales_log, :shared_ownership_setup_complete, staircase: 1, saledate:) }
it "is routed to" do
expect(page.routed_to?(log, nil)).to be true
end
it "is not routed to" do
expect(page.routed_to?(log, nil)).to be false
end
end
context "with form year 2025" do
let(:form) { Form.new(nil, 2025, [], "sales") }
let(:saledate) { Time.zone.local(2025, 4, 1) }
context "with a staircasing log" do
let(:log) { build(:sales_log, :shared_ownership_setup_complete, staircase: 1, saledate:) }
it "is not routed to" do
expect(page.routed_to?(log, nil)).to be false
end
end
context "with a non-staircasing log" do
let(:log) { build(:sales_log, staircase: nil, saledate:) }
context "with a non-staircasing log" do
let(:form) { Form.new(nil, current_collection_start_year, [], "sales") }
let(:log) { build(:sales_log, staircase: nil, saledate:) }
it "is routed to" do
expect(page.routed_to?(log, nil)).to be true
end
it "is routed to" do
expect(page.routed_to?(log, nil)).to be true
end
end
end

49
spec/models/form/sales/pages/property_wheelchair_accessible_spec.rb

@ -1,12 +1,15 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::PropertyWheelchairAccessible, type: :model do
include CollectionTimeHelper
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:form) { instance_double(Form, start_year_2024_or_later?: false, start_date: Time.zone.local(2023, 4, 1)) }
let(:form) { instance_double(Form, start_year_2024_or_later?: true, start_date: current_collection_start_date) }
let(:subsection) { instance_double(Form::Subsection, enabled?: true, form:) }
let(:saledate) { current_collection_start_date }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
@ -24,45 +27,21 @@ RSpec.describe Form::Sales::Pages::PropertyWheelchairAccessible, type: :model do
expect(page.description).to be_nil
end
context "with form year 2024" do
let(:form) { Form.new(nil, 2024, [], "sales") }
let(:saledate) { Time.zone.local(2024, 4, 1) }
context "with a staircasing log" do
let(:log) { build(:sales_log, :shared_ownership_setup_complete, staircase: 1, saledate:) }
it "is routed to" do
expect(page.routed_to?(log, nil)).to be true
end
end
context "with a non-staircasing log" do
let(:log) { build(:sales_log, :shared_ownership_setup_complete, staircase: 2, saledate:) }
context "with a staircasing log" do
let(:form) { Form.new(nil, current_collection_start_year, [], "sales") }
let(:log) { build(:sales_log, :shared_ownership_setup_complete, staircase: 1, saledate:) }
it "is routed to" do
expect(page.routed_to?(log, nil)).to be true
end
it "is not routed to" do
expect(page.routed_to?(log, nil)).to be false
end
end
context "with form year 2025" do
let(:form) { Form.new(nil, 2025, [], "sales") }
let(:saledate) { Time.zone.local(2025, 4, 1) }
context "with a staircasing log" do
let(:log) { build(:sales_log, :shared_ownership_setup_complete, staircase: 1, saledate:) }
it "is not routed to" do
expect(page.routed_to?(log, nil)).to be false
end
end
context "with a non-staircasing log" do
let(:log) { build(:sales_log, :shared_ownership_setup_complete, staircase: 2, saledate:) }
context "with a non-staircasing log" do
let(:form) { Form.new(nil, current_collection_start_year, [], "sales") }
let(:log) { build(:sales_log, :shared_ownership_setup_complete, staircase: 2, saledate:) }
it "is routed to" do
expect(page.routed_to?(log, nil)).to be true
end
it "is routed to" do
expect(page.routed_to?(log, nil)).to be true
end
end
end

14
spec/models/form/sales/questions/buyer1_income_known_spec.rb

@ -1,11 +1,19 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer1IncomeKnown, type: :model do
include CollectionTimeHelper
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
let(:page) { instance_double(Form::Page) }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form, start_date: current_collection_start_date) }
before do
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)
@ -39,4 +47,8 @@ RSpec.describe Form::Sales::Questions::Buyer1IncomeKnown, type: :model do
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(1)
end
it "has the correct hint_text" do
expect(question.hint_text).to eq("Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments.")
end
end

14
spec/models/form/sales/questions/buyer1_income_spec.rb

@ -1,11 +1,19 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer1Income, type: :model do
include CollectionTimeHelper
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
let(:page) { instance_double(Form::Page) }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form, start_date: current_collection_start_date) }
before do
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)
@ -46,4 +54,8 @@ RSpec.describe Form::Sales::Questions::Buyer1Income, type: :model do
it "has correct max" do
expect(question.max).to eq(999_999)
end
it "has the correct hint_text" do
expect(question.hint_text).to eq("")
end
end

2
spec/models/form/sales/questions/buyer_still_serving_spec.rb

@ -48,6 +48,8 @@ RSpec.describe Form::Sales::Questions::BuyerStillServing, type: :model do
"4" => { "value" => "Yes" },
"5" => { "value" => "No - they left up to and including 2 years ago" },
"6" => { "value" => "No - they left more than 2 years ago" },
"divider" => { "value" => true },
"9" => { "value" => "Don’t know" },
})
end
end

2
spec/models/form/sales/questions/housing_benefits_spec.rb

@ -57,7 +57,7 @@ RSpec.describe Form::Sales::Questions::HousingBenefits, type: :model do
"2" => { "value" => "Housing benefit" },
"3" => { "value" => "Universal Credit housing element" },
"divider" => { "value" => true },
"1" => { "value" => "Neither housing benefit or Universal Credit housing element" },
"1" => { "value" => "Neither" },
"4" => { "value" => "Don’t know " },
})
end

2
spec/models/form/sales/questions/property_building_type_spec.rb

@ -25,7 +25,7 @@ RSpec.describe Form::Sales::Questions::PropertyBuildingType, type: :model do
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Purpose built" },
"1" => { "value" => "Purpose-built" },
"2" => { "value" => "Converted from previous residential or non-residential property" },
})
end

39
spec/models/lettings_log_derived_fields_spec.rb

@ -111,45 +111,6 @@ RSpec.describe LettingsLog, type: :model do
end
describe "deriving household member fields" do
context "when it is 2024", metadata: { year: 24 } do
let(:startdate) { collection_start_date_for_year(2024) }
before do
log.assign_attributes(
relat2: "X",
relat3: "C",
relat4: "X",
relat5: "C",
relat7: "C",
relat8: "X",
age1: 22,
age2: 16,
age4: 60,
age6: 88,
age7: 14,
age8: 42,
)
log.set_derived_fields!
end
it "correctly derives totchild" do
expect(log.totchild).to eq 3
end
it "correctly derives totelder" do
expect(log.totelder).to eq 2
end
it "correctly derives totadult" do
expect(log.totadult).to eq 3
end
it "correctly derives economic status for tenants under 16" do
expect(log.ecstat7).to eq 9
end
end
context "when it is 2025", metadata: { year: 25 } do
let(:startdate) { collection_start_date_for_year(2025) }

8
spec/models/lettings_log_spec.rb

@ -525,7 +525,13 @@ RSpec.describe LettingsLog do
end
context "with a current year log" do
let(:log) { create(:lettings_log, :completed, :sh, :startdate_today, owning_organisation:, scheme_id: old_scheme.id, location_id: old_location.id) }
let(:log) do
built_log = create(:lettings_log, :completed, :sh, :startdate_today, owning_organisation:, scheme_id: old_scheme.id, location_id: old_location.id, postcode_full: old_location.postcode)
built_log.save!
# changing a location will reset the address details so we must set these again manually
built_log.update!(address_line1: "123 Main St", postcode_full: old_location.postcode, town_or_city: "London")
built_log
end
it "clears the location set on the log" do
expect { log.update!(scheme: new_scheme) }.to change(log, :location_id).from(old_location.id).to(nil)

104
spec/models/validations/date_validations_spec.rb

@ -1,6 +1,8 @@
require "rails_helper"
RSpec.describe Validations::DateValidations do
include CollectionTimeHelper
subject(:date_validator) { validator_class.new }
let(:validator_class) { Class.new { include Validations::DateValidations } }
@ -54,44 +56,22 @@ RSpec.describe Validations::DateValidations do
expect(record.errors["mrcdate"]).to be_empty
end
context "with 2024 logs or earlier" do
it "cannot be more than 10 years before the tenancy start date" do
record.startdate = Time.zone.local(2024, 4, 1)
record.mrcdate = Time.zone.local(2014, 1, 31)
date_validator.validate_property_major_repairs(record)
expect(record.errors["mrcdate"])
.to include(match I18n.t("validations.lettings.date.mrcdate.ten_years_before_tenancy_start"))
expect(record.errors["startdate"])
.to include(match I18n.t("validations.lettings.date.startdate.ten_years_after_mrc_date"))
end
it "must be within 10 years of the tenancy start date" do
record.startdate = Time.zone.local(2024, 2, 1)
record.mrcdate = Time.zone.local(2014, 2, 1)
date_validator.validate_property_major_repairs(record)
expect(record.errors["mrcdate"]).to be_empty
expect(record.errors["startdate"]).to be_empty
end
it "cannot be more than 20 years before the tenancy start date" do
record.startdate = current_collection_start_date
record.mrcdate = current_collection_start_date - 20.years - 1.day
date_validator.validate_property_major_repairs(record)
expect(record.errors["mrcdate"])
.to include(match I18n.t("validations.lettings.date.mrcdate.twenty_years_before_tenancy_start"))
expect(record.errors["startdate"])
.to include(match I18n.t("validations.lettings.date.startdate.twenty_years_after_mrc_date"))
end
context "with 2025 logs or later" do
it "cannot be more than 20 years before the tenancy start date" do
record.startdate = Time.zone.local(2026, 2, 1)
record.mrcdate = Time.zone.local(2006, 1, 31)
date_validator.validate_property_major_repairs(record)
expect(record.errors["mrcdate"])
.to include(match I18n.t("validations.lettings.date.mrcdate.twenty_years_before_tenancy_start"))
expect(record.errors["startdate"])
.to include(match I18n.t("validations.lettings.date.startdate.twenty_years_after_mrc_date"))
end
it "must be within 20 years of the tenancy start date" do
record.startdate = Time.zone.local(2026, 2, 1)
record.mrcdate = Time.zone.local(2006, 2, 1)
date_validator.validate_property_major_repairs(record)
expect(record.errors["mrcdate"]).to be_empty
expect(record.errors["startdate"]).to be_empty
end
it "can be within 20 years of the tenancy start date" do
record.startdate = current_collection_start_date
record.mrcdate = current_collection_start_date - 20.years
date_validator.validate_property_major_repairs(record)
expect(record.errors["mrcdate"]).to be_empty
expect(record.errors["startdate"]).to be_empty
end
context "when reason for vacancy is first let of property" do
@ -148,45 +128,23 @@ RSpec.describe Validations::DateValidations do
expect(record.errors["voiddate"]).to be_empty
end
context "with 2024 logs or earlier" do
it "cannot be more than 10 years before the tenancy start date" do
record.startdate = Time.zone.local(2024, 4, 1)
record.voiddate = Time.zone.local(2014, 1, 31)
date_validator.validate_property_void_date(record)
expect(record.errors["voiddate"])
.to include(match I18n.t("validations.lettings.date.void_date.ten_years_before_tenancy_start"))
expect(record.errors["startdate"])
.to include(match I18n.t("validations.lettings.date.startdate.ten_years_after_void_date"))
end
it "must be within 10 years of the tenancy start date" do
record.startdate = Time.zone.local(2024, 2, 1)
record.voiddate = Time.zone.local(2014, 2, 1)
date_validator.validate_property_void_date(record)
expect(record.errors["voiddate"]).to be_empty
expect(record.errors["startdate"]).to be_empty
end
it "cannot be more than 20 years before the tenancy start date" do
record.startdate = current_collection_start_date
record.voiddate = current_collection_start_date - 20.years - 1.day
date_validator.validate_property_void_date(record)
date_validator.validate_startdate(record)
expect(record.errors["voiddate"])
.to include(match I18n.t("validations.lettings.date.void_date.twenty_years_before_tenancy_start"))
expect(record.errors["startdate"])
.to include(match I18n.t("validations.lettings.date.startdate.twenty_years_after_void_date"))
end
context "with 2025 logs or later" do
it "cannot be more than 20 years before the tenancy start date" do
record.startdate = Time.zone.local(2026, 2, 1)
record.voiddate = Time.zone.local(2006, 1, 31)
date_validator.validate_property_void_date(record)
date_validator.validate_startdate(record)
expect(record.errors["voiddate"])
.to include(match I18n.t("validations.lettings.date.void_date.twenty_years_before_tenancy_start"))
expect(record.errors["startdate"])
.to include(match I18n.t("validations.lettings.date.startdate.twenty_years_after_void_date"))
end
it "must be within 20 years of the tenancy start date" do
record.startdate = Time.zone.local(2026, 2, 1)
record.voiddate = Time.zone.local(2006, 2, 1)
date_validator.validate_property_void_date(record)
expect(record.errors["voiddate"]).to be_empty
expect(record.errors["startdate"]).to be_empty
end
it "can be within 20 years of the tenancy start date" do
record.startdate = current_collection_start_date
record.voiddate = current_collection_start_date - 20.years
date_validator.validate_property_void_date(record)
expect(record.errors["voiddate"]).to be_empty
expect(record.errors["startdate"]).to be_empty
end
context "when major repairs have been carried out" do

51
spec/models/validations/sales/soft_validations_spec.rb

@ -30,57 +30,8 @@ RSpec.describe Validations::Sales::SoftValidations do
expect(record).not_to be_income2_outside_soft_range_for_ecstat
end
context "when log year is before 2025" do
let(:record) { build(:sales_log, saledate: Time.zone.local(2024, 12, 25)) }
it "does not trigger for low income1 if ecstat1 has no soft min" do
record.income1 = 50
record.ecstat1 = 4
expect(record).not_to be_income1_outside_soft_range_for_ecstat
end
it "returns true if income1 is below soft min for ecstat1" do
record.income1 = 4500
record.ecstat1 = 1
expect(record).to be_income1_outside_soft_range_for_ecstat
end
it "returns false if income1 is >= soft min for ecstat1" do
record.income1 = 1500
record.ecstat1 = 2
expect(record).not_to be_income1_outside_soft_range_for_ecstat
end
it "does not trigger for income2 if ecstat2 has no soft min" do
record.income2 = 50
record.ecstat2 = 8
expect(record).not_to be_income2_outside_soft_range_for_ecstat
end
it "returns true if income2 is below soft min for ecstat2" do
record.income2 = 999
record.ecstat2 = 3
expect(record).to be_income2_outside_soft_range_for_ecstat
end
it "returns false if income2 is >= soft min for ecstat2" do
record.income2 = 2500
record.ecstat2 = 5
expect(record).not_to be_income2_outside_soft_range_for_ecstat
end
it "does not trigger for being over maxima" do
record.ecstat1 = 1
record.income1 = 200_000
record.ecstat2 = 2
record.income2 = 100_000
expect(record).not_to be_income1_outside_soft_range_for_ecstat
expect(record).not_to be_income2_outside_soft_range_for_ecstat
end
end
context "when log year is 2025" do
let(:record) { build(:sales_log, saledate: Time.zone.local(2025, 12, 25)) }
let(:record) { build(:sales_log, saledate: collection_start_date_for_year(2025)) }
it "returns true if income1 is below soft min for ecstat1" do
record.income1 = 13_399

6
spec/requests/collection_resources_controller_spec.rb

@ -734,7 +734,7 @@ RSpec.describe CollectionResourcesController, type: :request do
end
describe "GET #edit_additional_collection_resource" do
let(:collection_resource) { create(:collection_resource, :additional, year: 2025, log_type: "sales", short_display_name: "additional resource", download_filename: "additional.pdf") }
let(:collection_resource) { create(:collection_resource, :additional, year: current_collection_start_year, log_type: "sales", short_display_name: "additional resource", download_filename: "additional.pdf") }
context "when user is not signed in" do
it "redirects to the sign in page" do
@ -773,7 +773,7 @@ RSpec.describe CollectionResourcesController, type: :request do
let(:user) { create(:user, :support) }
before do
allow(Time.zone).to receive(:today).and_return(Time.zone.local(2025, 1, 8))
allow(Time.zone).to receive(:today).and_return(current_collection_after_crossover_start_date)
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in user
end
@ -786,7 +786,7 @@ RSpec.describe CollectionResourcesController, type: :request do
it "displays update collection resources page content" do
get collection_resource_edit_path(collection_resource)
expect(page).to have_content("Sales 2025 to 2026")
expect(page).to have_content("Sales #{current_collection_start_year} to #{current_collection_end_year}")
expect(page).to have_content("Change the additional resource")
expect(page).to have_content("This file will be available for all users to download.")
expect(page).to have_content("Upload file")

197
spec/services/csv/lettings_log_csv_service_spec.rb

@ -2,6 +2,8 @@ require "rails_helper"
require "rake"
RSpec.describe Csv::LettingsLogCsvService do
include CollectionTimeHelper
subject(:task) { Rake::Task["data_import:add_variable_definitions"] }
before do
@ -16,7 +18,7 @@ RSpec.describe Csv::LettingsLogCsvService do
let(:user) { create(:user, :support, email: "s.port@jeemayle.com") }
let(:service) { described_class.new(user:, export_type:, year:) }
let(:export_type) { "labels" }
let(:year) { 2024 }
let(:year) { current_collection_start_year }
let(:csv) { CSV.parse(service.prepare_csv(LettingsLog.where(id: logs.map(&:id)))) }
let(:logs) { [log] }
let(:definition_line) { csv.first }
@ -582,199 +584,6 @@ RSpec.describe Csv::LettingsLogCsvService do
end
end
end
context "when the requested log year is 2024" do
let(:year) { 2024 }
let(:organisation) { create(:organisation, provider_type: "LA", name: "MHCLG") }
let(:log) do
create(
:lettings_log,
:ignore_validation_errors,
created_by: user,
assigned_to: user,
created_at: Time.zone.local(2024, 4, 1),
updated_at: Time.zone.local(2024, 4, 1),
owning_organisation: organisation,
managing_organisation: organisation,
needstype: 1,
renewal: 0,
startdate: Time.zone.local(2024, 4, 1),
rent_type: 1,
tenancycode: "HIJKLMN",
propcode: "ABCDEFG",
declaration: 1,
address_line1: "Address line 1",
town_or_city: "London",
postcode_full: "NW9 5LL",
la: "E09000003",
is_la_inferred: false,
address_line1_as_entered: "address line 1 as entered",
address_line2_as_entered: "address line 2 as entered",
town_or_city_as_entered: "town or city as entered",
county_as_entered: "county as entered",
postcode_full_as_entered: "AB1 2CD",
la_as_entered: "la as entered",
first_time_property_let_as_social_housing: 0,
unitletas: 2,
rsnvac: 6,
unittype_gn: 7,
builtype: 1,
wchair: 1,
beds: 3,
voiddate: Time.zone.local(2024, 3, 30),
majorrepairs: 1,
mrcdate: Time.zone.local(2024, 3, 31),
joint: 3,
startertenancy: 1,
tenancy: 4,
tenancylength: 2,
hhmemb: 4,
age1_known: 0,
age1: 35,
sex1: "F",
ethnic_group: 0,
ethnic: 2,
nationality_all: 36,
ecstat1: 0,
details_known_2: 0,
relat2: "P",
age2_known: 0,
age2: 32,
sex2: "M",
ecstat2: 6,
details_known_3: 1,
details_known_4: 0,
relat4: "R",
age4_known: 1,
sex4: "R",
ecstat4: 10,
armedforces: 1,
leftreg: 4,
reservist: 1,
preg_occ: 2,
housingneeds: 1,
housingneeds_type: 0,
housingneeds_a: 1,
housingneeds_b: 0,
housingneeds_c: 0,
housingneeds_f: 0,
housingneeds_g: 0,
housingneeds_h: 0,
housingneeds_other: 0,
illness: 1,
illness_type_1: 0,
illness_type_2: 1,
illness_type_3: 0,
illness_type_4: 0,
illness_type_5: 0,
illness_type_6: 0,
illness_type_7: 0,
illness_type_8: 0,
illness_type_9: 0,
illness_type_10: 0,
layear: 2,
waityear: 7,
reason: 4,
prevten: 6,
homeless: 1,
ppcodenk: 1,
ppostcode_full: "TN23 6LZ",
previous_la_known: 1,
prevloc: "E07000105",
reasonpref: 1,
rp_homeless: 0,
rp_insan_unsat: 1,
rp_medwel: 0,
rp_hardship: 0,
rp_dontknow: 0,
cbl: 0,
chr: 1,
cap: 0,
accessible_register: 0,
referral: 2,
net_income_known: 0,
incref: 0,
incfreq: 1,
earnings: 268,
hb: 6,
has_benefits: 1,
benefits: 1,
period: 2,
brent: 200,
scharge: 50,
pscharge: 40,
supcharg: 35,
tcharge: 325,
hbrentshortfall: 1,
tshortfall_known: 1,
tshortfall: 12,
)
end
context "when exporting with human readable labels" do
let(:export_type) { "labels" }
context "when the current user is a support user" do
let(:user) { create(:user, :support, organisation:, email: "s.port@jeemayle.com") }
it "exports the CSV with 2024 ordering and all values correct" do
expected_content = CSV.read("spec/fixtures/files/lettings_log_csv_export_labels_24.csv")
values_to_delete = %w[id]
values_to_delete.each do |attribute|
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv).to eq expected_content
end
end
context "when the current user is not a support user" do
let(:user) { create(:user, :data_provider, organisation:, email: "choreographer@owtluk.com") }
it "exports the CSV with all values correct" do
expected_content = CSV.read("spec/fixtures/files/lettings_log_csv_export_non_support_labels_24.csv")
values_to_delete = %w[id]
values_to_delete.each do |attribute|
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv).to eq expected_content
end
end
end
context "when exporting values as codes" do
let(:export_type) { "codes" }
context "when the current user is a support user" do
let(:user) { create(:user, :support, organisation:, email: "s.port@jeemayle.com") }
it "exports the CSV with all values correct" do
expected_content = CSV.read("spec/fixtures/files/lettings_log_csv_export_codes_24.csv")
values_to_delete = %w[id]
values_to_delete.each do |attribute|
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv).to eq expected_content
end
end
context "when the current user is not a support user" do
let(:user) { create(:user, :data_provider, organisation:, email: "choreographer@owtluk.com") }
it "exports the CSV with all values correct" do
expected_content = CSV.read("spec/fixtures/files/lettings_log_csv_export_non_support_codes_24.csv")
values_to_delete = %w[id]
values_to_delete.each do |attribute|
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv).to eq expected_content
end
end
end
end
end
end
end

47
spec/services/filter_manager_spec.rb

@ -94,4 +94,51 @@ describe FilterManager do
expect(described_class.filter_schemes(Scheme.all, nil, {}, nil, nil)).to eq(alphabetical_order_schemes)
end
end
describe "filter_users" do
let(:data_provider_user) { FactoryBot.create(:user, role: "data_provider") }
let(:data_coordinator_user) { FactoryBot.create(:user, role: "data_coordinator") }
let(:support_user) { FactoryBot.create(:user, role: "support") }
let(:key_contact_user) { FactoryBot.create(:user, is_key_contact: true) }
let(:dpo_user) { FactoryBot.create(:user, is_dpo: true) }
let(:key_contact_dpo_user) { FactoryBot.create(:user, is_key_contact: true, is_dpo: true) }
context "when filtering by role" do
it "returns users with the role" do
filter = { "role" => %w[data_provider] }
result = described_class.filter_users(User.all, nil, filter, nil)
expect(result).to include(data_provider_user)
expect(result).not_to include(data_coordinator_user)
expect(result).not_to include(support_user)
end
it "returns users with multiple roles selected" do
filter = { "role" => %w[data_provider data_coordinator] }
result = described_class.filter_users(User.all, nil, filter, nil)
expect(result).to include(data_provider_user)
expect(result).to include(data_coordinator_user)
expect(result).not_to include(support_user)
end
end
context "when filtering by additional responsibilities" do
it "returns users with the additional responsibilities" do
filter = { "additional_responsibilities" => %w[data_protection_officer] }
result = described_class.filter_users(User.all, nil, filter, nil)
expect(result).to include(dpo_user)
expect(result).to include(key_contact_dpo_user)
expect(result).not_to include(key_contact_user)
expect(result).not_to include(support_user)
end
it "returns users with multiple additional responsibilities selected" do
filter = { "additional_responsibilities" => %w[data_protection_officer key_contact] }
result = described_class.filter_users(User.all, nil, filter, nil)
expect(result).to include(dpo_user)
expect(result).to include(key_contact_dpo_user)
expect(result).to include(key_contact_user)
expect(result).not_to include(support_user)
end
end
end
end

12
yarn.lock

@ -3677,9 +3677,9 @@ picocolors@^1.1.1:
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
version "2.3.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.2.tgz#5a942915e26b372dc0f0e6753149a16e6b1c5601"
integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==
pify@^4.0.1:
version "4.0.1"
@ -4788,9 +4788,9 @@ yallist@^3.0.2:
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yaml@^1.10.0:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
version "1.10.3"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.3.tgz#76e407ed95c42684fb8e14641e5de62fe65bbcb3"
integrity sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==
yocto-queue@^0.1.0:
version "0.1.0"

Loading…
Cancel
Save