Browse Source

Merge branch 'main' into CLDC-3787-Autocomplete-address-search

CLDC-3787-Autocomplete-address-search
Manny Dinssa 3 days ago committed by GitHub
parent
commit
7abfc6acfe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 28
      .github/workflows/run_tests.yml
  2. 4
      Gemfile.lock
  3. 3
      app/controllers/form_controller.rb
  4. 5
      app/models/form/lettings/questions/previous_tenure.rb
  5. 8
      app/models/form/lettings/questions/previous_tenure_renewal.rb
  6. 8
      app/models/form/lettings/questions/sheltered.rb
  7. 5
      app/models/validations/setup_validations.rb
  8. 6
      app/services/bulk_upload/lettings/year2024/row_parser.rb
  9. 6
      app/services/bulk_upload/sales/year2024/row_parser.rb
  10. 4
      app/services/documentation_generator.rb
  11. 4
      app/services/feature_toggle.rb
  12. 2
      app/views/layouts/_collection_resources.html.erb
  13. 24
      app/views/start/guidance.html.erb
  14. 2
      config/locales/en.yml
  15. 8
      config/locales/forms/2025/lettings/property_information.en.yml
  16. 10
      config/locales/validations/lettings/2024/bulk_upload.en.yml
  17. 6
      config/locales/validations/sales/2024/bulk_upload.en.yml
  18. 2
      config/locales/validations/shared.en.yml
  19. 8881
      config/rent_range_data/2025.csv
  20. 1185
      config/sale_range_data/2025.csv
  21. 2
      docs/adr/index.md
  22. 47
      docs/bulk_upload.md
  23. 2
      docs/documentation_website.md
  24. BIN
      docs/images/bu_flow_diagram.png
  25. BIN
      docs/images/bu_processor.png
  26. 1
      spec/factories/sales_log.rb
  27. 2
      spec/features/form/page_routing_spec.rb
  28. 24
      spec/features/sales_log_spec.rb
  29. 2
      spec/models/form/lettings/pages/previous_housing_situation_renewal_spec.rb
  30. 31
      spec/models/form/lettings/questions/previous_tenure_renewal_spec.rb
  31. 65
      spec/models/form/lettings/questions/previous_tenure_spec.rb
  32. 4
      spec/models/form/lettings/questions/sheltered_spec.rb
  33. 6
      spec/models/location_spec.rb
  34. 2
      spec/models/sales_log_spec.rb
  35. 3
      spec/models/scheme_spec.rb
  36. 23
      spec/requests/form_controller_spec.rb
  37. 19
      spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb
  38. 28
      spec/services/bulk_upload/sales/year2024/row_parser_spec.rb

28
.github/workflows/run_tests.yml

@ -61,6 +61,11 @@ jobs:
cache: yarn
node-version: 20
# This is temporary to fix flaky parallel tests due to `secret_key_base` being read before it's set
- name: Create local secret
run: |
echo $(ruby -e "require 'securerandom'; puts SecureRandom.hex(64)") > tmp/local_secret.txt
- name: Create database
run: |
bundle exec rake parallel:setup
@ -69,11 +74,6 @@ jobs:
run: |
bundle exec rake assets:precompile
# This is temporary to fix flaky parallel tests due to `secret_key_base` being read before it's set
- name: Create local secret
run: |
echo $(ruby -e "require 'securerandom'; puts SecureRandom.hex(64)") > tmp/local_secret.txt
- name: Run tests
run: |
bundle exec rake parallel:spec['spec\/(?!features|models|requests|services)']
@ -241,6 +241,10 @@ jobs:
cache: yarn
node-version: 20
- name: Create local secret
run: |
echo $(ruby -e "require 'securerandom'; puts SecureRandom.hex(64)") > tmp/local_secret.txt
- name: Create database
run: |
bundle exec rake parallel:setup
@ -249,10 +253,6 @@ jobs:
run: |
bundle exec rake assets:precompile
- name: Create local secret
run: |
echo $(ruby -e "require 'securerandom'; puts SecureRandom.hex(64)") > tmp/local_secret.txt
- name: Run tests
run: |
bundle exec rake parallel:spec['spec/requests']
@ -304,6 +304,10 @@ jobs:
cache: yarn
node-version: 20
- name: Create local secret
run: |
echo $(ruby -e "require 'securerandom'; puts SecureRandom.hex(64)") > tmp/local_secret.txt
- name: Create database
run: |
bundle exec rake parallel:setup
@ -312,10 +316,6 @@ jobs:
run: |
bundle exec rake assets:precompile
- name: Create local secret
run: |
echo $(ruby -e "require 'securerandom'; puts SecureRandom.hex(64)") > tmp/local_secret.txt
- name: Run tests
run: |
bundle exec rake parallel:spec['spec\/services']
@ -369,7 +369,7 @@ jobs:
- name: Create database
run: |
bundle exec rake parallel:setup
bundle exec rake db:prepare
- name: Compile assets
run: |

4
Gemfile.lock

@ -278,7 +278,7 @@ GEM
nested_form (0.3.2)
net-http (0.4.1)
uri
net-imap (0.5.1)
net-imap (0.5.6)
date
net-protocol
net-pop (0.1.2)
@ -494,7 +494,7 @@ GEM
thor (1.3.2)
thread_safe (0.3.6)
timecop (0.9.8)
timeout (0.4.2)
timeout (0.4.3)
turbo-rails (2.0.11)
actionpack (>= 6.0.0)
railties (>= 6.0.0)

3
app/controllers/form_controller.rb

@ -141,7 +141,8 @@ private
day, month, year = params[@log.log_type][question.id].split("/")
next unless [day, month, year].any?(&:present?)
result[question.id] = if Date.valid_date?(year.to_i, month.to_i, day.to_i) && year.to_i.positive?
date_matches_format = params[@log.log_type][question.id].match?(/\A\d{1,2}\/\d{1,2}\/\d{4}\z/)
result[question.id] = if date_matches_format && Date.valid_date?(year.to_i, month.to_i, day.to_i) && year.to_i.positive?
Date.new(year.to_i, month.to_i, day.to_i)
else
Date.new(0, 1, 1)

5
app/models/form/lettings/questions/previous_tenure.rb

@ -43,15 +43,14 @@ class Form::Lettings::Questions::PreviousTenure < ::Form::Question
"32" => { "value" => "Fixed-term private registered provider (PRP) general needs tenancy" },
"31" => { "value" => "Lifetime local authority general needs tenancy" },
"33" => { "value" => "Lifetime private registered provider (PRP) general needs tenancy" },
"34" => { "value" => "Specialist retirement housing" },
"36" => { "value" => "Sheltered housing for adults aged under 55 years" },
"35" => { "value" => "Extra care housing" },
"38" => { "value" => "Older people’s housing for tenants with low support needs" },
"6" => { "value" => "Other supported housing" },
"3" => { "value" => "Private sector tenancy" },
"27" => { "value" => "Owner occupation (low-cost home ownership)" },
"26" => { "value" => "Owner occupation (private)" },
"28" => { "value" => "Living with friends and family (long-term)" },
"38" => { "value" => "Sofa surfing (moving regularly between family and friends, no permanent bed)" },
"39" => { "value" => "Sofa surfing (moving regularly between family and friends, no permanent bed)" },
"14" => { "value" => "Bed and breakfast" },
"7" => { "value" => "Direct access hostel" },
"10" => { "value" => "Hospital" },

8
app/models/form/lettings/questions/previous_tenure_renewal.rb

@ -5,7 +5,7 @@ class Form::Lettings::Questions::PreviousTenureRenewal < ::Form::Question
@copy_key = "lettings.household_situation.prevten.renewal"
@type = "radio"
@check_answers_card_number = 0
@answer_options = ANSWER_OPTIONS
@answer_options = form.start_year_2025_or_later? ? ANSWER_OPTIONS_2025 : ANSWER_OPTIONS
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
@ -16,5 +16,11 @@ class Form::Lettings::Questions::PreviousTenureRenewal < ::Form::Question
"6" => { "value" => "Other supported housing" },
}.freeze
ANSWER_OPTIONS_2025 = {
"35" => { "value" => "Extra care housing" },
"38" => { "value" => "Older people’s housing for tenants with low support needs" },
"6" => { "value" => "Other supported housing" },
}.freeze
QUESTION_NUMBER_FROM_YEAR = { 2023 => 78, 2024 => 77 }.freeze
end

8
app/models/form/lettings/questions/sheltered.rb

@ -9,12 +9,14 @@ class Form::Lettings::Questions::Sheltered < ::Form::Question
def answer_options
if form.start_year_2025_or_later?
{ "1" => { "value" => "Yes – sheltered housing for tenants with low support needs" },
{
"7" => { "value" => "Yes – for tenants with low support needs" },
"2" => { "value" => "Yes – extra care housing" },
"7" => { "value" => "Yes - other" },
"8" => { "value" => "Yes other" },
"3" => { "value" => "No" },
"divider" => { "value" => true },
"4" => { "value" => "Don’t know" } }
"4" => { "value" => "Don’t know" },
}
else
{ "1" => { "value" => "Yes – specialist retirement housing" },
"2" => { "value" => "Yes – extra care housing" },

5
app/models/validations/setup_validations.rb

@ -97,6 +97,8 @@ module Validations::SetupValidations
end
def location_during_startdate_validation(record)
return unless date_valid?("startdate", record)
location_inactive_status = inactive_status(record.startdate, record.location)
if location_inactive_status.present?
@ -108,6 +110,8 @@ module Validations::SetupValidations
end
def scheme_during_startdate_validation(record)
return unless date_valid?("startdate", record)
scheme_inactive_status = inactive_status(record.startdate, record.scheme)
if scheme_inactive_status.present?
@ -120,6 +124,7 @@ module Validations::SetupValidations
def tenancy_startdate_with_scheme_locations(record)
return if record.scheme.blank? || record.startdate.blank?
return if record.scheme.has_active_locations_on_date?(record.startdate)
return unless date_valid?("startdate", record)
record.errors.add :startdate, I18n.t("validations.lettings.setup.startdate.scheme.locations_inactive.startdate", name: record.scheme.service_name)
record.errors.add :scheme_id, I18n.t("validations.lettings.setup.startdate.scheme.locations_inactive.scheme_id", name: record.scheme.service_name)

6
app/services/bulk_upload/lettings/year2024/row_parser.rb

@ -616,8 +616,10 @@ private
def validate_address_option_found
if log.uprn.nil? && field_16.blank? && key_address_fields_provided?
error_message = if log.address_options_present?
I18n.t("#{ERROR_BASE_KEY}.address.not_determined")
error_message = if log.address_options_present? && log.address_options.size > 1
I18n.t("#{ERROR_BASE_KEY}.address.not_determined.multiple")
elsif log.address_options_present?
I18n.t("#{ERROR_BASE_KEY}.address.not_determined.one")
else
I18n.t("#{ERROR_BASE_KEY}.address.not_found")
end

6
app/services/bulk_upload/sales/year2024/row_parser.rb

@ -614,8 +614,10 @@ private
def validate_address_option_found
if log.uprn.nil? && field_22.blank? && key_address_fields_provided?
error_message = if log.address_options_present?
I18n.t("#{ERROR_BASE_KEY}.address.not_determined")
error_message = if log.address_options_present? && log.address_options.size > 1
I18n.t("#{ERROR_BASE_KEY}.address.not_determined.multiple")
elsif log.address_options_present?
I18n.t("#{ERROR_BASE_KEY}.address.not_determined.one")
else
I18n.t("#{ERROR_BASE_KEY}.address.not_found")
end

4
app/services/documentation_generator.rb

@ -13,8 +13,8 @@ class DocumentationGenerator
form = FormHandler.instance.forms["current_#{log_type}"]
all_validation_methods.each do |meth|
if LogValidation.where(validation_name: meth.to_s, bulk_upload_specific: false, log_type:).exists?
Rails.logger.info("Validation #{meth} already exists")
if LogValidation.where(validation_name: meth.to_s, bulk_upload_specific: false, log_type:, collection_year: "#{form.start_date.year}/#{form.start_date.year + 1}").exists?
Rails.logger.info("Validation #{meth} already exists for #{form.start_date.year}")
next
end

4
app/services/feature_toggle.rb

@ -27,10 +27,6 @@ class FeatureToggle
!Rails.env.production? && !Rails.env.test?
end
def self.managing_resources_enabled?
!Rails.env.production?
end
def self.create_test_logs_enabled?
Rails.env.development? || Rails.env.review?
end

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

@ -23,4 +23,4 @@
<% end %>
</div>
<%= govuk_button_link_to "Manage collection resources", collection_resources_path, secondary: true, class: "govuk-!-margin-bottom-2" if current_user&.support? && FeatureToggle.managing_resources_enabled? %>
<%= govuk_button_link_to "Manage collection resources", collection_resources_path, secondary: true, class: "govuk-!-margin-bottom-2" if current_user&.support? %>

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

@ -17,13 +17,15 @@
<p class="govuk-body">For general needs, you should complete a log for each new tenancy intended to last 2 years or more if it is social rent or affordable rent, or of any length if it is intermediate rent.</p>
<p class="govuk-body">For supported housing, you should complete a log for each new letting of any length.</p>
<p class="govuk-body">If a new tenancy agreement is signed, create a new log.</p>
<p class="govuk-body">Sales logs are required after the completion date of the sale of all or part of a social property in England, of the types below.</p>
<p class="govuk-body">A Sales log should be completed for each Shared Ownership staircasing transaction(s).</p>
<% end %>
<%= accordion.with_section(heading_text: "Types of lettings you should create logs for") do %>
<p class="govuk-body">You’ll need to create a log for:</p>
<%= govuk_list [
"Tenants in general needs housing allocated a new letting. This includes tenants moving into the social rented sector from outside, existing social tenants moving between properties or landlords, and existing social tenants renewing lettings in the same property. If fixed-term and social or affordable rent, only include tenancies of 2 years or more.",
"Tenants in supported housing (social housing, sheltered accommodation and care homes) allocated a new letting. This includes tenants moving into the social rented sector from outside, existing social tenants moving between properties or landlords, and existing social tenants renewing lettings in the same property. All supported housing tenancies should be reported regardless of length.",
"Tenants in supported housing (social housing and sheltered accommodation) allocated a new letting. This includes tenants moving into the social rented sector from outside, existing social tenants moving between properties or landlords, and existing social tenants renewing lettings in the same property. All supported housing tenancies should be reported regardless of length.",
"Starter tenancies provided by local authorities (LAs) and lettings with an introductory period provided by private registered providers (PRPs) should be completed in CORE at the beginning of the starter or introductory period. The tenancy type and length entered should be based on the tenancy the tenant will roll onto once the starter or introductory period has been completed. You do not need to submit another CORE log once the period has been completed.",
"Room moves within a shared housing unit that result in a different property type or support needs – this is classed as an internal transfer of an existing social tenant to another property.",
"Existing tenants who are issued with a new tenancy agreement when stock is acquired, transferred or permanently decanted.",
@ -46,6 +48,7 @@
"Starter tenancies or lettings with an introductory period that roll onto or convert into the main tenancy. The CORE log should be completed at the beginning of this period.",
"Changes from sole to joint or joint to sole tenancies, where the number of people in the household has not changed.",
"Moves within a shared housing unit resulting in the same support needs or property type, even if a new tenancy or licence agreement is issued.",
"Supported housing lettings in a care home.",
"Lettings where no new tenancy agreement is signed.",
"Where stock is acquired, transferred or permanently decanted and the existing tenants are not issued with a new tenancy agreement.",
"Mutual exchanges including lettings where registered provider tenants have exchanged homes, for example through the national HOMESWAP system.",
@ -55,6 +58,25 @@
"Non-social lettings, including market-rented properties, employer-provided housing where the employer provides financial support, homes for staff of social landlords linked to employment, homes social landlords manage for organisations who are not social landlords, homes social landlords own but lease in entirety to organisations who are not social landlords, and freehold housing with variable charges for services and communal facilities.",
],
type: :bullet %>
<% end %>
<%= accordion.with_section(heading_text: "Types of sales you should create logs for") do %>
<p class="govuk-body">You’ll need to create a log for:</p>
<%= govuk_list [
"Shared ownership – this is when the purchaser buys an initial share of up to 75% of the property value and pays rent to the Private Registered Provider (PRP) on the remaining portion, or later staircasing transaction(s).",
"Discounted ownership – this is when the entirety of the property is sold in one transaction for less than market rent, according to one of a number of sale products. A mortgage may or may not be used. Examples of discounted ownership include, but are not limited to, Right to Acquire, Right to Buy, Preserved Right to Buy or Social HomeBuy.",
"Sales involving staircasing transactions – this is when the household purchases more shares in their property, increasing the proportion they own and decreasing the proportion the housing association owns. Once the household purchases 100% of the shares, they own the property.",
"Include sales to individuals, whether they intend to live in the property or not, as well as sales to companies.",
],
type: :bullet %>
<% end %>
<%= accordion.with_section(heading_text: "Types of sales you should not create logs for") do %>
<p class="govuk-body">You don’t need to create a log for:</p>
<%= govuk_list [
"Outright sales – this is where the buyer purchases the entirety of the home in one transaction at market price. A mortgage may or may not be used. We have previously collected this information in CORE sales, but have removed it from the 2025-26 form.",
],
type: :bullet %>
<% end %>
<%= accordion.with_section(heading_text: "What if someone is reluctant to answer any questions?") do %>

2
config/locales/en.yml

@ -124,7 +124,7 @@ en:
location:
attributes:
startdate:
invalid: "Enter a date in the correct format, for example 31 1 2022."
invalid: "Enter a date in the correct format, for example 31/1/2022."
units:
blank: "Enter the total number of units at this location."
type_of_unit:

8
config/locales/forms/2025/lettings/property_information.en.yml

@ -156,7 +156,7 @@ en:
sheltered:
page_header: ""
check_answer_label: "Letting in sheltered accommodation"
check_answer_prompt: "Tell us if letting is in sheltered accommodation"
hint_text: "Sheltered housing and special retirement housing are for tenants with low-level care and support needs. This typically provides some limited support to enable independent living, such as alarm-based assistance or a scheme manager.</br></br>Extra care housing is for tenants with medium to high care and support needs, often with 24 hour access to support staff provided by an agency registered with the Care Quality Commission."
question_text: "Is this letting in sheltered accommodation?"
check_answer_label: "Letting is older people’s housing"
check_answer_prompt: "Tell us if letting is older people’s housing"
hint_text: "This includes retirement living, sheltered housing and extra care housing. There is no national set limit for \"older people\", please answer based on your own policies.</br></br>Extra care housing is for tenants with medium to high care and support needs, often with 24 hour access to support staff provided by an agency registered with the Care Quality Commission."
question_text: "Is this property older people’s housing?"

10
config/locales/validations/lettings/2024/bulk_upload.en.yml

@ -17,7 +17,7 @@ en:
owning_organisation:
not_found: "The owning organisation code is incorrect."
not_stock_owner: "The owning organisation code provided is for an organisation that does not own stock."
not_permitted:
not_permitted:
not_support: "You do not have permission to add logs for this owning organisation."
support: "This owning organisation is not affiliated with %{org_name}."
managing_organisation:
@ -49,10 +49,12 @@ en:
age:
invalid: "Age of person %{person_num} must be a number or the letter R"
address:
not_found: "We could not find this address. Check the address data in your CSV file is correct and complete, or select the correct address using the CORE site."
not_determined: "There are multiple matches for this address. Either select the correct address manually or correct the UPRN in the CSV file."
not_found: "We could not find this address. Check the address data in your CSV file is correct and complete, or find the correct address in the service."
not_determined:
one: "There is a possible match for this address which doesn't look right. Check the address data in your CSV file is correct and complete, or confirm the address in the service."
multiple: "There are multiple matches for this address. Check the address data in your CSV file is correct and complete, or select the correct address in the service."
not_answered: "Enter either the UPRN or the full address."
nationality:
invalid: "Select a valid nationality."
charges:
missing_charges: "Please enter the %{sentence_fragment}. If there is no %{sentence_fragment}, please enter '0'."
missing_charges: "Please enter the %{sentence_fragment}. If there is no %{sentence_fragment}, please enter '0'."

6
config/locales/validations/sales/2024/bulk_upload.en.yml

@ -39,8 +39,10 @@ en:
age2:
buyer_cannot_be_over_16_and_child: "Buyer 2's age cannot be 16 or over if their working situation is child under 16."
address:
not_found: "We could not find this address. Check the address data in your CSV file is correct and complete, or select the correct address using the CORE site."
not_determined: "There are multiple matches for this address. Either select the correct address manually or correct the UPRN in the CSV file."
not_found: "We could not find this address. Check the address data in your CSV file is correct and complete, or find the correct address in the service."
not_determined:
one: "There is a possible match for this address which doesn't look right. Check the address data in your CSV file is correct and complete, or confirm the address in the service."
multiple: "There are multiple matches for this address. Check the address data in your CSV file is correct and complete, or select the correct address in the service."
not_answered: "Enter either the UPRN or the full address."
nationality:
invalid: "Select a valid nationality."

2
config/locales/validations/shared.en.yml

@ -18,4 +18,4 @@ en:
postcode: "Enter a postcode in the correct format, for example AA1 1AA."
date:
invalid_date: "Enter a date in the correct format, for example 31 1 2024."
invalid_date: "Enter a date in the correct format, for example 31/1/2024."

8881
config/rent_range_data/2025.csv

File diff suppressed because it is too large Load Diff

1185
config/sale_range_data/2025.csv

File diff suppressed because it is too large Load Diff

2
docs/adr/index.md

@ -1,6 +1,6 @@
---
has_children: true
nav_order: 11
nav_order: 12
---
# Architecture decisions

47
docs/bulk_upload.md

@ -0,0 +1,47 @@
---
nav_order: 11
---
# Bulk Upload
Bulk upload functionality allows users to upload multiple logs using a csv file.
## How Bulk Upload works
Bulk upload file can be uploaded for a specific log type (sales or lettings) for a specific year. During crossover period we ask which collection year the file is for, otherwise we assume the Bulk Upload is for the current year.
When a bulk upload file is successfully uploaded on the service, it:
- Saves a BulkUpload record in the database
- Uploads the file to S3
- Schedules `ProcessBulkUploadJob`
### Bulk upload service
There are several outcomes to a bulk upload:
- Successful upload
- Partial upload: upload has errors but partial logs can be created. Email to error report is sent to the user and the bulk upload needs a user approval
- Errors in bulk upload: errors on important fields, or in the template. Logs can't be created and an email with errors (or a link to error report) is sent to the user
![Bulk Upload Flow](https://raw.githubusercontent.com/communitiesuk/submit-social-housing-lettings-and-sales-data/main/docs/images/bu_flow_diagram.png)
### Bulk upload processing
Most of BU processing logic is in `BulkUpload::Processor`. It chooses the correct `Validator` and `LogCreator` classes for the log type and uses them to process the file.
![Bulk Upload Processing](https://raw.githubusercontent.com/communitiesuk/submit-social-housing-lettings-and-sales-data/main/docs/images/bu_processor.png)
Main differences between different collection years would be in `CsvParsers` and `RowParsers`.
#### Row parser
- Maps any values from a csv row into values saved internally
- Maps any validations into errors for bulk uploads by associating them with relevant fields
- Adds any additional validations that might only make sense in BU (for example, validation that might not relevant in single log submission due to routing)
### Csv parser
- Holds template specific information
- Header information
- Row and field information

2
docs/documentation_website.md

@ -1,5 +1,5 @@
---
nav_order: 12
nav_order: 13
---
# This documentation website

BIN
docs/images/bu_flow_diagram.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
docs/images/bu_processor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

1
spec/factories/sales_log.rb

@ -80,7 +80,6 @@ FactoryBot.define do
noint { 2 }
privacynotice { 1 }
age1_known { 0 }
staircase { 1 }
age1 { Faker::Number.within(range: 27..45) }
sex1 { %w[F M X R].sample }
national { 18 }

2
spec/features/form/page_routing_spec.rb

@ -105,7 +105,7 @@ RSpec.describe "Form Page Routing" do
it "displays the entered date if it's in a valid format" do
lettings_log.update!(startdate: "2021/10/13")
visit("/lettings-logs/#{id}/tenancy-start-date")
fill_in("lettings_log[startdate]", with: "1/12/202")
fill_in("lettings_log[startdate]", with: "1/12/0202")
click_button("Save and continue")
expect(page).to have_current_path("/lettings-logs/#{id}/tenancy-start-date")

24
spec/features/sales_log_spec.rb

@ -228,6 +228,30 @@ RSpec.describe "Sales Log Features" do
expect(page).to have_current_path("/sales-logs/csv-download?codes_only=false&search=1")
end
end
context "when displaying the question number in the page header" do
let(:sales_log) { FactoryBot.create(:sales_log, :shared_ownership_setup_complete, jointpur: 2, owning_organisation: user.organisation, assigned_to: user) }
context "when visiting the address page" do
before do
visit("/sales-logs/#{sales_log.id}/address")
end
it "displays the question number in the page header" do
expect(page).to have_content("Q16")
end
end
context "when visiting the about staircasing page" do
before do
visit("/sales-logs/#{sales_log.id}/about-staircasing-not-joint-purchase")
end
it "displays the question number in the page header" do
expect(page).to have_content(/Shared ownership scheme\s*About the staircasing transaction/)
end
end
end
end
end

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

@ -3,7 +3,7 @@ require "rails_helper"
RSpec.describe Form::Lettings::Pages::PreviousHousingSituationRenewal, type: :model do
subject(:page) { described_class.new(nil, nil, subsection) }
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2024, 4, 1))) }
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2024, 4, 1), start_year_2025_or_later?: false)) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)

31
spec/models/form/lettings/questions/previous_tenure_renewal_spec.rb

@ -3,7 +3,8 @@ require "rails_helper"
RSpec.describe Form::Lettings::Questions::PreviousTenureRenewal, type: :model do
subject(:question) { described_class.new(nil, nil, page) }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
let(:start_year_after_2025) { false }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1), start_year_2025_or_later?: start_year_after_2025))) }
it "has the correct id" do
expect(question.id).to eq("prevten")
@ -21,12 +22,26 @@ RSpec.describe Form::Lettings::Questions::PreviousTenureRenewal, type: :model do
expect(question.derived?(nil)).to be false
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"34" => { "value" => "Specialist retirement housing" },
"35" => { "value" => "Extra care housing" },
"36" => { "value" => "Sheltered housing for adults aged under 55 years" },
"6" => { "value" => "Other supported housing" },
})
context "with logs before 2025" do
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"34" => { "value" => "Specialist retirement housing" },
"35" => { "value" => "Extra care housing" },
"36" => { "value" => "Sheltered housing for adults aged under 55 years" },
"6" => { "value" => "Other supported housing" },
})
end
end
context "with logs on or after 2025" do
let(:start_year_after_2025) { true }
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"35" => { "value" => "Extra care housing" },
"38" => { "value" => "Older people’s housing for tenants with low support needs" },
"6" => { "value" => "Other supported housing" },
})
end
end
end

65
spec/models/form/lettings/questions/previous_tenure_spec.rb

@ -22,35 +22,37 @@ RSpec.describe Form::Lettings::Questions::PreviousTenure, type: :model do
expect(question.derived?(nil)).to be false
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"30" => { "value" => "Fixed-term local authority general needs tenancy" },
"32" => { "value" => "Fixed-term private registered provider (PRP) general needs tenancy" },
"31" => { "value" => "Lifetime local authority general needs tenancy" },
"33" => { "value" => "Lifetime private registered provider (PRP) general needs tenancy" },
"34" => { "value" => "Specialist retirement housing" },
"35" => { "value" => "Extra care housing" },
"6" => { "value" => "Other supported housing" },
"3" => { "value" => "Private sector tenancy" },
"27" => { "value" => "Owner occupation (low-cost home ownership)" },
"26" => { "value" => "Owner occupation (private)" },
"28" => { "value" => "Living with friends or family" },
"14" => { "value" => "Bed and breakfast" },
"7" => { "value" => "Direct access hostel" },
"10" => { "value" => "Hospital" },
"29" => { "value" => "Prison or approved probation hostel" },
"19" => { "value" => "Rough sleeping" },
"18" => { "value" => "Any other temporary accommodation" },
"13" => { "value" => "Children’s home or foster care" },
"24" => { "value" => "Home Office Asylum Support" },
"23" => { "value" => "Mobile home or caravan" },
"21" => { "value" => "Refuge" },
"9" => { "value" => "Residential care home" },
"4" => { "value" => "Tied housing or rented with job" },
"36" => { "value" => "Sheltered housing for adults aged under 55 years" },
"37" => { "value" => "Host family or similar refugee accommodation" },
"25" => { "value" => "Any other accommodation" },
})
context "with start year before 2025" do
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"30" => { "value" => "Fixed-term local authority general needs tenancy" },
"32" => { "value" => "Fixed-term private registered provider (PRP) general needs tenancy" },
"31" => { "value" => "Lifetime local authority general needs tenancy" },
"33" => { "value" => "Lifetime private registered provider (PRP) general needs tenancy" },
"34" => { "value" => "Specialist retirement housing" },
"35" => { "value" => "Extra care housing" },
"6" => { "value" => "Other supported housing" },
"3" => { "value" => "Private sector tenancy" },
"27" => { "value" => "Owner occupation (low-cost home ownership)" },
"26" => { "value" => "Owner occupation (private)" },
"28" => { "value" => "Living with friends or family" },
"14" => { "value" => "Bed and breakfast" },
"7" => { "value" => "Direct access hostel" },
"10" => { "value" => "Hospital" },
"29" => { "value" => "Prison or approved probation hostel" },
"19" => { "value" => "Rough sleeping" },
"18" => { "value" => "Any other temporary accommodation" },
"13" => { "value" => "Children’s home or foster care" },
"24" => { "value" => "Home Office Asylum Support" },
"23" => { "value" => "Mobile home or caravan" },
"21" => { "value" => "Refuge" },
"9" => { "value" => "Residential care home" },
"4" => { "value" => "Tied housing or rented with job" },
"36" => { "value" => "Sheltered housing for adults aged under 55 years" },
"37" => { "value" => "Host family or similar refugee accommodation" },
"25" => { "value" => "Any other accommodation" },
})
end
end
context "with 2025 logs" do
@ -62,14 +64,14 @@ RSpec.describe Form::Lettings::Questions::PreviousTenure, type: :model do
"32" => { "value" => "Fixed-term private registered provider (PRP) general needs tenancy" },
"31" => { "value" => "Lifetime local authority general needs tenancy" },
"33" => { "value" => "Lifetime private registered provider (PRP) general needs tenancy" },
"34" => { "value" => "Specialist retirement housing" },
"35" => { "value" => "Extra care housing" },
"38" => { "value" => "Older people’s housing for tenants with low support needs" },
"6" => { "value" => "Other supported housing" },
"3" => { "value" => "Private sector tenancy" },
"27" => { "value" => "Owner occupation (low-cost home ownership)" },
"26" => { "value" => "Owner occupation (private)" },
"28" => { "value" => "Living with friends and family (long-term)" },
"38" => { "value" => "Sofa surfing (moving regularly between family and friends, no permanent bed)" },
"39" => { "value" => "Sofa surfing (moving regularly between family and friends, no permanent bed)" },
"14" => { "value" => "Bed and breakfast" },
"7" => { "value" => "Direct access hostel" },
"10" => { "value" => "Hospital" },
@ -82,7 +84,6 @@ RSpec.describe Form::Lettings::Questions::PreviousTenure, type: :model do
"21" => { "value" => "Refuge" },
"9" => { "value" => "Residential care home" },
"4" => { "value" => "Tied housing or rented with job" },
"36" => { "value" => "Sheltered housing for adults aged under 55 years" },
"37" => { "value" => "Host family or similar refugee accommodation" },
"25" => { "value" => "Any other accommodation" },
})

4
spec/models/form/lettings/questions/sheltered_spec.rb

@ -52,9 +52,9 @@ RSpec.describe Form::Lettings::Questions::Sheltered, type: :model do
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes – sheltered housing for tenants with low support needs" },
"7" => { "value" => "Yes – for tenants with low support needs" },
"2" => { "value" => "Yes – extra care housing" },
"7" => { "value" => "Yes - other" },
"8" => { "value" => "Yes other" },
"3" => { "value" => "No" },
"divider" => { "value" => true },
"4" => { "value" => "Don’t know" },

6
spec/models/location_spec.rb

@ -1016,14 +1016,16 @@ RSpec.describe Location, type: :model do
end
it "returns reactivating soon if the location has a future reactivation date" do
FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.yesterday, reactivation_date: Time.zone.tomorrow, location:)
deactivation_period = FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.yesterday, reactivation_date: Time.zone.tomorrow, location:)
location.save!
expect(deactivation_period.deactivation_date).to eq(Time.zone.yesterday)
expect(location.status).to eq(:reactivating_soon)
end
it "returns reactivating soon if the location had a deactivation during another deactivation" do
FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.today - 1.month, reactivation_date: Time.zone.today + 2.days, location:)
deactivation_period = FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.today - 1.month, reactivation_date: Time.zone.today + 2.days, location:)
location.save!
expect(deactivation_period.deactivation_date).to eq(Time.zone.today - 1.month)
expect(location.status).to eq(:reactivating_soon)
end

2
spec/models/sales_log_spec.rb

@ -886,7 +886,7 @@ RSpec.describe SalesLog, type: :model do
end
describe "expected_shared_ownership_deposit_value" do
let!(:completed_sales_log) { create(:sales_log, :completed, ownershipsch: 1, type: 2, value: 1000, equity: 50) }
let!(:completed_sales_log) { create(:sales_log, :completed, ownershipsch: 1, type: 2, value: 1000, equity: 50, staircase: 1) }
it "is set to completed for a completed sales log" do
expect(completed_sales_log.expected_shared_ownership_deposit_value).to eq(500)

3
spec/models/scheme_spec.rb

@ -433,8 +433,9 @@ RSpec.describe Scheme, type: :model do
end
it "returns reactivating soon if the scheme had a deactivation during another deactivation" do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.today - 2.months, reactivation_date: Time.zone.today + 2.days, scheme:)
deactivation_period = FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.today - 2.months, reactivation_date: Time.zone.today + 2.days, scheme:)
scheme.save!
expect(deactivation_period.deactivation_date).to eq(Time.zone.today - 2.months)
expect(scheme.status).to eq(:reactivating_soon)
end

23
spec/requests/form_controller_spec.rb

@ -619,6 +619,25 @@ RSpec.describe FormController, type: :request do
end
end
context "when the date input doesn't match the required format" do
let(:page_id) { "tenancy_start_date" }
let(:params) do
{
id: lettings_log.id,
lettings_log: {
page: page_id,
"startdate" => "31620224352342",
},
}
end
it "validates the date correctly" do
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params
follow_redirect!
expect(page).to have_content("There is a problem")
end
end
context "when allow_future_form_use? is enabled" do
before do
allow(FeatureToggle).to receive(:allow_future_form_use?).and_return(true)
@ -631,7 +650,7 @@ RSpec.describe FormController, type: :request do
id: lettings_log.id,
lettings_log: {
page: page_id,
"startdate" => "1/1/1",
"startdate" => "1/1/1000",
},
}
end
@ -652,7 +671,7 @@ RSpec.describe FormController, type: :request do
id: sales_log.id,
sales_log: {
page: page_id,
"saledate" => "1/1/1",
"saledate" => "1/1/1000",
},
}
end

19
spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb

@ -1710,7 +1710,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
end
end
context "when no address has a high enough match rating" do
context "when a single address with not a high enough match rating is returned" do
before do
stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/)
.to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.6, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: "AA1 1AA", UPRN: "1" } }] }.to_json, headers: {})
@ -1720,7 +1720,22 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
parser.valid?
expect(parser.errors[:field_16]).to be_empty
%i[field_17 field_18 field_19 field_20 field_21 field_22].each do |field|
expect(parser.errors[field]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_determined")])
expect(parser.errors[field]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_determined.one")])
end
end
end
context "when no addresses have a high enough match rating" do
before do
stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/)
.to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.6, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: "AA1 1AA", UPRN: "1" } }, { DPA: { MATCH: 0.8, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: "BB2 2BB", UPRN: "2" } }] }.to_json, headers: {})
end
it "adds address not found errors to address fields only" do
parser.valid?
expect(parser.errors[:field_16]).to be_empty
%i[field_17 field_18 field_19 field_20 field_21 field_22].each do |field|
expect(parser.errors[field]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_determined.multiple")])
end
end
end

28
spec/services/bulk_upload/sales/year2024/row_parser_spec.rb

@ -1094,7 +1094,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
end
end
context "when no address has a high enough match rating" do
context "when a single address with not a high enough match rating is returned" do
before do
stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/)
.to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.6, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: "AA1 1AA", UPRN: "1" } }] }.to_json, headers: {})
@ -1104,7 +1104,31 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
parser.valid?
expect(parser.errors[:field_22]).to be_empty
%i[field_23 field_24 field_25 field_26 field_27 field_28].each do |field|
expect(parser.errors[field]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_determined")])
expect(parser.errors[field]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_determined.one")])
end
end
end
context "when no addresses have a high enough match rating" do
before do
stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/)
.to_return(
status: 200,
body: {
results: [
{ DPA: { MATCH: 0.6, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: "AA1 1AA", UPRN: "1" } },
{ DPA: { MATCH: 0.8, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: "BB2 2BB", UPRN: "2" } },
],
}.to_json,
headers: {},
)
end
it "adds address not found errors to address fields only" do
parser.valid?
expect(parser.errors[:field_22]).to be_empty
%i[field_23 field_24 field_25 field_26 field_27 field_28].each do |field|
expect(parser.errors[field]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_determined.multiple")])
end
end
end

Loading…
Cancel
Save