Browse Source

Merge branch 'main' into CLDC-4029-add-additional-user-filters

pull/3124/head
Samuel Young 2 weeks ago committed by GitHub
parent
commit
d21ea9d5c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      .github/workflows/review_deploy.yml
  2. 27
      .github/workflows/review_teardown_pipeline.yml
  3. 5
      Gemfile
  4. 9
      app/helpers/timecop_helper.rb
  5. 3
      app/mailers/devise_notify_mailer.rb
  6. 3
      app/mailers/notify_mailer.rb
  7. 2
      app/models/form/sales/questions/buyer_still_serving.rb
  8. 2
      app/models/form/sales/questions/housing_benefits.rb
  9. 2
      app/models/form/sales/questions/value.rb
  10. 2
      app/services/feature_toggle.rb
  11. 20
      app/services/storage/s3_service.rb
  12. 4
      app/views/form/guidance/_address_search.html.erb
  13. 4
      config/initializers/timecop.rb
  14. 5
      config/locales/forms/2024/lettings/guidance.en.yml
  15. 5
      config/locales/forms/2024/sales/guidance.en.yml
  16. 5
      config/locales/forms/2025/lettings/guidance.en.yml
  17. 10
      config/locales/forms/2025/lettings/income_and_benefits.en.yml
  18. 5
      config/locales/forms/2025/sales/guidance.en.yml
  19. 16
      config/locales/forms/2025/sales/income_benefits_and_savings.en.yml
  20. 2
      config/locales/forms/2025/sales/other_household_information.en.yml
  21. 5
      config/locales/forms/2026/lettings/guidance.en.yml
  22. 10
      config/locales/forms/2026/lettings/income_and_benefits.en.yml
  23. 5
      config/locales/forms/2026/sales/guidance.en.yml
  24. 16
      config/locales/forms/2026/sales/income_benefits_and_savings.en.yml
  25. 2
      config/locales/forms/2026/sales/other_household_information.en.yml
  26. 2
      config/locales/forms/2026/sales/sale_information.en.yml
  27. 2
      config/locales/validations/sales/sale_information.en.yml
  28. 14
      spec/models/form/sales/questions/buyer1_income_known_spec.rb
  29. 14
      spec/models/form/sales/questions/buyer1_income_spec.rb
  30. 2
      spec/models/form/sales/questions/buyer_still_serving_spec.rb
  31. 2
      spec/models/form/sales/questions/housing_benefits_spec.rb
  32. 2
      spec/models/form/sales/questions/value_spec.rb
  33. 8
      spec/models/lettings_log_spec.rb
  34. 12
      yarn.lock

2
.github/workflows/review_deploy.yml

@ -55,7 +55,7 @@ jobs:
with: with:
script: | script: |
const prNumber = context.issue.number; 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({ const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,

27
.github/workflows/review_teardown_pipeline.yml

@ -7,6 +7,7 @@ on:
pull_request: pull_request:
types: types:
- closed - closed
- unlabeled
env: env:
app_repo_role: arn:aws:iam::815624722760:role/core-application-repo app_repo_role: arn:aws:iam::815624722760:role/core-application-repo
@ -18,6 +19,9 @@ env:
jobs: jobs:
database: database:
name: Drop 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 runs-on: ubuntu-latest
permissions: permissions:
id-token: write id-token: write
@ -54,6 +58,9 @@ jobs:
infra: infra:
name: Teardown review app 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] needs: [database]
uses: communitiesuk/submit-social-housing-lettings-and-sales-data-infrastructure/.github/workflows/destroy_review_app_infra.yml@main uses: communitiesuk/submit-social-housing-lettings-and-sales-data-infrastructure/.github/workflows/destroy_review_app_infra.yml@main
with: with:
@ -61,3 +68,23 @@ jobs:
app_repo_role: arn:aws:iam::815624722760:role/core-application-repo app_repo_role: arn:aws:iam::815624722760:role/core-application-repo
permissions: permissions:
id-token: write 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.',
});

5
Gemfile

@ -103,10 +103,6 @@ group :development do
gem "rubocop-rails", require: false gem "rubocop-rails", require: false
end end
group :test, :staging do
gem "timecop", "~> 0.9.4"
end
group :test do group :test do
gem "axe-core-rspec" gem "axe-core-rspec"
gem "capybara", require: false gem "capybara", require: false
@ -115,6 +111,7 @@ group :test do
gem "rspec-rails", require: false gem "rspec-rails", require: false
gem "selenium-webdriver", require: false gem "selenium-webdriver", require: false
gem "simplecov", require: false gem "simplecov", require: false
gem "timecop", "~> 0.9.4"
gem "webmock", require: false gem "webmock", require: false
end end

9
app/helpers/timecop_helper.rb

@ -1,9 +0,0 @@
module TimecopHelper
def without_timecop(&block)
if defined?(Timecop)
Timecop.return(&block)
else
yield
end
end
end

3
app/mailers/devise_notify_mailer.rb

@ -1,5 +1,4 @@
class DeviseNotifyMailer < Devise::Mailer class DeviseNotifyMailer < Devise::Mailer
include TimecopHelper
require "notifications/client" require "notifications/client"
def notify_client def notify_client
@ -9,13 +8,11 @@ class DeviseNotifyMailer < Devise::Mailer
def send_email(email_address, template_id, personalisation) def send_email(email_address, template_id, personalisation)
return true if intercept_send?(email_address) return true if intercept_send?(email_address)
without_timecop do
notify_client.send_email( notify_client.send_email(
email_address:, email_address:,
template_id:, template_id:,
personalisation:, personalisation:,
) )
end
rescue Notifications::Client::BadRequestError => e rescue Notifications::Client::BadRequestError => e
Sentry.capture_exception(e) Sentry.capture_exception(e)

3
app/mailers/notify_mailer.rb

@ -1,5 +1,4 @@
class NotifyMailer < ApplicationMailer class NotifyMailer < ApplicationMailer
include TimecopHelper
require "notifications/client" require "notifications/client"
def notify_client def notify_client
@ -9,14 +8,12 @@ class NotifyMailer < ApplicationMailer
def send_email(email, template_id, personalisation) def send_email(email, template_id, personalisation)
return true if intercept_send?(email) return true if intercept_send?(email)
without_timecop do
notify_client.send_email( notify_client.send_email(
email_address: email, email_address: email,
template_id:, template_id:,
personalisation:, personalisation:,
) )
end end
end
def personalisation(record, token, url, username: false) def personalisation(record, token, url, username: false)
{ {

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

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

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

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

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

@ -6,7 +6,7 @@ class Form::Sales::Questions::Value < ::Form::Question
@type = "numeric" @type = "numeric"
@min = form.start_year_2026_or_later? ? 15_000 : 0 @min = form.start_year_2026_or_later? ? 15_000 : 0
@step = 1 @step = 1
@width = 5 @width = 10
@prefix = "£" @prefix = "£"
@question_number = get_question_number_from_hash(QUESTION_NUMBER_FROM_YEAR_AND_SECTION, value_key: subsection.id) @question_number = get_question_number_from_hash(QUESTION_NUMBER_FROM_YEAR_AND_SECTION, value_key: subsection.id)
@top_guidance_partial = "financial_calculations_shared_ownership" @top_guidance_partial = "financial_calculations_shared_ownership"

2
app/services/feature_toggle.rb

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

20
app/services/storage/s3_service.rb

@ -1,7 +1,5 @@
module Storage module Storage
class S3Service < StorageService class S3Service < StorageService
include TimecopHelper
attr_reader :configuration attr_reader :configuration
def initialize(config_service, instance_name) def initialize(config_service, instance_name)
@ -13,43 +11,32 @@ module Storage
end end
def list_files(folder) def list_files(folder)
without_timecop do
@client.list_objects_v2(bucket: @configuration.bucket_name, prefix: folder) @client.list_objects_v2(bucket: @configuration.bucket_name, prefix: folder)
.flat_map { |response| response.contents.map(&:key) } .flat_map { |response| response.contents.map(&:key) }
end end
end
def folder_present?(folder) def folder_present?(folder)
without_timecop do
response = @client.list_objects_v2(bucket: @configuration.bucket_name, prefix: folder, max_keys: 1) response = @client.list_objects_v2(bucket: @configuration.bucket_name, prefix: folder, max_keys: 1)
response.key_count == 1 response.key_count == 1
end end
end
def get_presigned_url(file_name, duration, response_content_disposition: nil) def get_presigned_url(file_name, duration, response_content_disposition: nil)
without_timecop do
Aws::S3::Presigner Aws::S3::Presigner
.new({ client: @client }) .new({ client: @client })
.presigned_url(:get_object, bucket: @configuration.bucket_name, key: file_name, expires_in: duration, response_content_disposition:) .presigned_url(:get_object, bucket: @configuration.bucket_name, key: file_name, expires_in: duration, response_content_disposition:)
end end
end
def get_file_io(file_name) def get_file_io(file_name)
without_timecop do
@client.get_object(bucket: @configuration.bucket_name, key: file_name) @client.get_object(bucket: @configuration.bucket_name, key: file_name)
.body .body
end end
end
def get_file(file_name) def get_file(file_name)
without_timecop do
@client.get_object(bucket: @configuration.bucket_name, key: file_name) @client.get_object(bucket: @configuration.bucket_name, key: file_name)
.body.read .body.read
end end
end
def write_file(file_name, data, content_type: nil) def write_file(file_name, data, content_type: nil)
without_timecop do
if content_type.nil? if content_type.nil?
@client.put_object( @client.put_object(
body: data, body: data,
@ -65,28 +52,21 @@ module Storage
) )
end end
end end
end
def get_file_metadata(file_name) def get_file_metadata(file_name)
without_timecop do
@client.head_object(bucket: @configuration.bucket_name, key: file_name) @client.head_object(bucket: @configuration.bucket_name, key: file_name)
end end
end
def file_exists?(file_name) def file_exists?(file_name)
without_timecop do
@client.head_object(bucket: @configuration.bucket_name, key: file_name) @client.head_object(bucket: @configuration.bucket_name, key: file_name)
true true
end
rescue Aws::S3::Errors::NotFound rescue Aws::S3::Errors::NotFound
false false
end end
def delete_file(file_name) def delete_file(file_name)
without_timecop do
@client.delete_object(bucket: @configuration.bucket_name, key: file_name) @client.delete_object(bucket: @configuration.bucket_name, key: file_name)
end end
end
private private

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 %> <%= I18n.t("forms.#{@log.form.start_date.year}.#{@log.form.type}.guidance.address_search.content").html_safe %>
<% end %> <% 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"> <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" %> <%= govuk_link_to "Enter the address manually instead", address_manual_input_path(@log.log_type, @log.id), class: "govuk-button govuk-button--secondary" %>
</div> </div>

4
config/initializers/timecop.rb

@ -1,4 +0,0 @@
if Rails.env.staging?
require "timecop"
Timecop.travel(Time.zone.local(2026, 4, 1))
end

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>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> <li>For UPRN (Unique Property Reference Number), please enter the full value exactly</li>
</ul>" </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>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> <li>For UPRN (Unique Property Reference Number), please enter the full value exactly</li>
</ul>" </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>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> <li>For UPRN (Unique Property Reference Number), please enter the full value exactly</li>
</ul>" </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: hb:
page_header: "" page_header: ""
check_answer_label: "Housing related benefits received" check_answer_label: "Housing-related benefits received"
check_answer_prompt: "Tell us if household receives housing related benefits" 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." 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?" question_text: "Is the household likely to be receiving any of these housing-related benefits?"
benefits: benefits:
page_header: "" page_header: ""
@ -112,7 +112,7 @@ en:
check_answer_label: "Any outstanding amount for basic rent and charges" 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" check_answer_prompt: "Tell us if any outstanding amount for basic rent and charges"
hint_text: "Also known as the ‘outstanding amount’." 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: outstanding_amount:
page_header: "" 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>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> <li>For UPRN (Unique Property Reference Number), please enter the full value exactly</li>
</ul>" </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: income1nk:
check_answer_label: "Buyer 1’s gross annual income known" check_answer_label: "Buyer 1’s gross annual income known"
check_answer_prompt: "Enter buyer 1’s gross annual income if 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?" question_text: "Do you know buyer 1’s annual income?"
income1: income1:
check_answer_label: "Buyer 1’s gross annual income" check_answer_label: "Buyer 1’s gross annual income"
check_answer_prompt: "" 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" question_text: "Buyer 1’s gross annual income"
inc1mort: inc1mort:
@ -28,12 +28,12 @@ en:
income2nk: income2nk:
check_answer_label: "Buyer 2’s gross annual income known" check_answer_label: "Buyer 2’s gross annual income known"
check_answer_prompt: "Enter buyer 2’s gross annual income if 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?" question_text: "Do you know buyer 2’s annual income?"
income2: income2:
check_answer_label: "Buyer 2’s gross annual income" check_answer_label: "Buyer 2’s gross annual income"
check_answer_prompt: "" 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" question_text: "Buyer 2’s gross annual income"
inc2mort: inc2mort:
@ -46,16 +46,16 @@ en:
housing_benefits: housing_benefits:
joint_purchase: joint_purchase:
page_header: "" 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: "" check_answer_prompt: ""
hint_text: "" 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: not_joint_purchase:
page_header: "" 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: "" check_answer_prompt: ""
hint_text: "" 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: savings:
joint_purchase: joint_purchase:

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

@ -7,7 +7,7 @@ en:
page_header: "" page_header: ""
check_answer_label: "Any buyer has served as regulars in the UK armed forces" 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" 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?" question_text: "Have any of the buyers ever served as a regular in the UK armed forces?"
hhregresstill: 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> <li>For UPRN (Unique Property Reference Number), please enter the full value exactly</li>
</ul>" </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: needs_type:
title: "What does each need type mean?" 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." 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: hb:
page_header: "" page_header: ""
check_answer_label: "Housing related benefits received" check_answer_label: "Housing-related benefits received"
check_answer_prompt: "Tell us if household receives housing related benefits" 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." 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?" question_text: "Is the household likely to be receiving any of these housing-related benefits?"
benefits: benefits:
page_header: "" page_header: ""
@ -112,7 +112,7 @@ en:
check_answer_label: "Any outstanding amount for basic rent and charges" 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" check_answer_prompt: "Tell us if any outstanding amount for basic rent and charges"
hint_text: "Also known as the ‘outstanding amount’." 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: outstanding_amount:
page_header: "" 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>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> <li>For UPRN (Unique Property Reference Number), please enter the full value exactly</li>
</ul>" </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: income1nk:
check_answer_label: "Buyer 1’s gross annual income known" check_answer_label: "Buyer 1’s gross annual income known"
check_answer_prompt: "Enter buyer 1’s gross annual income if 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?" question_text: "Do you know buyer 1’s annual income?"
income1: income1:
check_answer_label: "Buyer 1’s gross annual income" check_answer_label: "Buyer 1’s gross annual income"
check_answer_prompt: "" 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" question_text: "Buyer 1’s gross annual income"
inc1mort: inc1mort:
@ -28,12 +28,12 @@ en:
income2nk: income2nk:
check_answer_label: "Buyer 2’s gross annual income known" check_answer_label: "Buyer 2’s gross annual income known"
check_answer_prompt: "Enter buyer 2’s gross annual income if 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?" question_text: "Do you know buyer 2’s annual income?"
income2: income2:
check_answer_label: "Buyer 2’s gross annual income" check_answer_label: "Buyer 2’s gross annual income"
check_answer_prompt: "" 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" question_text: "Buyer 2’s gross annual income"
inc2mort: inc2mort:
@ -46,16 +46,16 @@ en:
housing_benefits: housing_benefits:
joint_purchase: joint_purchase:
page_header: "" 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: "" check_answer_prompt: ""
hint_text: "" 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: not_joint_purchase:
page_header: "" 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: "" check_answer_prompt: ""
hint_text: "" 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: savings:
joint_purchase: joint_purchase:

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

@ -7,7 +7,7 @@ en:
page_header: "" page_header: ""
check_answer_label: "Any buyer has served as regulars in the UK armed forces" 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" 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?" question_text: "Have any of the buyers ever served as a regular in the UK armed forces?"
hhregresstill: hhregresstill:

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

@ -153,7 +153,7 @@ en:
value_shared_ownership_staircase: value_shared_ownership_staircase:
check_answer_label: "Full purchase price" check_answer_label: "Full purchase price"
check_answer_prompt: "" check_answer_prompt: ""
hint_text: "Enter the full purchase price paid for the equity bought in this staircasing transaction (this is equal to the value of the share bought by the purchaser)." hint_text: "Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser in the current and all previous transactions)."
question_text: "What was the full purchase price for this staircasing transaction?" question_text: "What was the full purchase price for this staircasing transaction?"
equity: equity:

2
config/locales/validations/sales/sale_information.en.yml

@ -76,7 +76,7 @@ en:
mortgage_not_used: "The cash deposit is %{deposit}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same." mortgage_not_used: "The cash deposit is %{deposit}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same."
mortgage_used_socialhomebuy: "The mortgage amount (%{mortgage}), cash deposit (%{deposit}), and cash discount (%{cashdis}) added together is %{mortgage_deposit_and_discount_total}.</br></br>The full purchase price (%{value}) multiplied by the percentage equity stake purchased (%{equity}) is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same." mortgage_used_socialhomebuy: "The mortgage amount (%{mortgage}), cash deposit (%{deposit}), and cash discount (%{cashdis}) added together is %{mortgage_deposit_and_discount_total}.</br></br>The full purchase price (%{value}) multiplied by the percentage equity stake purchased (%{equity}) is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same."
mortgage_not_used_socialhomebuy: "The cash deposit (%{deposit}) and cash discount (%{cashdis}) added together is %{deposit_and_discount_total}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought (%{equity}) is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same." mortgage_not_used_socialhomebuy: "The cash deposit (%{deposit}) and cash discount (%{cashdis}) added together is %{deposit_and_discount_total}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought (%{equity}) is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same."
staircasing_mortgage: # this key staircasing_mortgage:
mortgage_used: "The mortgage (%{mortgage}) and cash deposit (%{deposit}) added together is %{mortgage_and_deposit_total}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought is %{stairbought_part_of_value}.</br></br>These two amounts should be the same." mortgage_used: "The mortgage (%{mortgage}) and cash deposit (%{deposit}) added together is %{mortgage_and_deposit_total}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought is %{stairbought_part_of_value}.</br></br>These two amounts should be the same."
mortgage_not_used: "The cash deposit is %{deposit}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought is %{stairbought_part_of_value}.</br></br>These two amounts should be the same." mortgage_not_used: "The cash deposit is %{deposit}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought is %{stairbought_part_of_value}.</br></br>These two amounts should be the same."
mortgage_used_socialhomebuy: "The mortgage amount (%{mortgage}), cash deposit (%{deposit}), and cash discount (%{cashdis}) added together is %{mortgage_deposit_and_discount_total}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought (%{stairbought}) is %{stairbought_part_of_value}.</br></br>These two amounts should be the same." mortgage_used_socialhomebuy: "The mortgage amount (%{mortgage}), cash deposit (%{deposit}), and cash discount (%{cashdis}) added together is %{mortgage_deposit_and_discount_total}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought (%{stairbought}) is %{stairbought_part_of_value}.</br></br>These two amounts should be the same."

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

@ -1,11 +1,19 @@
require "rails_helper" require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer1IncomeKnown, type: :model do RSpec.describe Form::Sales::Questions::Buyer1IncomeKnown, type: :model do
include CollectionTimeHelper
subject(:question) { described_class.new(question_id, question_definition, page) } subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil } let(:question_id) { nil }
let(:question_definition) { 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 it "has correct page" do
expect(question.page).to eq(page) 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 it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(1) expect(question.check_answers_card_number).to eq(1)
end 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 end

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

@ -1,11 +1,19 @@
require "rails_helper" require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer1Income, type: :model do RSpec.describe Form::Sales::Questions::Buyer1Income, type: :model do
include CollectionTimeHelper
subject(:question) { described_class.new(question_id, question_definition, page) } subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil } let(:question_id) { nil }
let(:question_definition) { 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 it "has correct page" do
expect(question.page).to eq(page) expect(question.page).to eq(page)
@ -46,4 +54,8 @@ RSpec.describe Form::Sales::Questions::Buyer1Income, type: :model do
it "has correct max" do it "has correct max" do
expect(question.max).to eq(999_999) expect(question.max).to eq(999_999)
end end
it "has the correct hint_text" do
expect(question.hint_text).to eq("")
end
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" }, "4" => { "value" => "Yes" },
"5" => { "value" => "No - they left up to and including 2 years ago" }, "5" => { "value" => "No - they left up to and including 2 years ago" },
"6" => { "value" => "No - they left more than 2 years ago" }, "6" => { "value" => "No - they left more than 2 years ago" },
"divider" => { "value" => true },
"9" => { "value" => "Don’t know" },
}) })
end end
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" }, "2" => { "value" => "Housing benefit" },
"3" => { "value" => "Universal Credit housing element" }, "3" => { "value" => "Universal Credit housing element" },
"divider" => { "value" => true }, "divider" => { "value" => true },
"1" => { "value" => "Neither housing benefit or Universal Credit housing element" }, "1" => { "value" => "Neither" },
"4" => { "value" => "Don’t know " }, "4" => { "value" => "Don’t know " },
}) })
end end

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

@ -32,7 +32,7 @@ RSpec.describe Form::Sales::Questions::Value, type: :model do
end end
it "has correct width" do it "has correct width" do
expect(question.width).to eq(5) expect(question.width).to eq(10)
end end
it "has correct prefix" do it "has correct prefix" do

8
spec/models/lettings_log_spec.rb

@ -525,7 +525,13 @@ RSpec.describe LettingsLog do
end end
context "with a current year log" do 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 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) expect { log.update!(scheme: new_scheme) }.to change(log, :location_id).from(old_location.id).to(nil)

12
yarn.lock

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

Loading…
Cancel
Save