Browse Source

Merge branch 'main' into CLDC-3661-change-year-format

pull/2713/head
kosiakkatrina 2 years ago committed by GitHub
parent
commit
e58ccf523a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 30
      app/components/bulk_upload_error_row_component.html.erb
  2. 13
      app/components/bulk_upload_error_row_component.rb
  3. 4
      app/components/document_list_component.html.erb
  4. 153
      app/controllers/collection_resources_controller.rb
  5. 2
      app/controllers/lettings_logs_controller.rb
  6. 2
      app/controllers/sales_logs_controller.rb
  7. 2
      app/controllers/start_controller.rb
  8. 17
      app/controllers/users_controller.rb
  9. 5
      app/frontend/styles/_bulk-uploads.scss
  10. 1
      app/frontend/styles/_document-list.scss
  11. 2
      app/helpers/collection_resources_helper.rb
  12. 8
      app/helpers/log_actions_helper.rb
  13. 38
      app/models/collection_resource.rb
  14. 1
      app/models/form/sales/pages/buyer1_income.rb
  15. 3
      app/models/form/sales/pages/buyer1_income_max_value_check.rb
  16. 5
      app/models/form/sales/pages/buyer1_income_min_value_check.rb
  17. 1
      app/models/form/sales/pages/buyer2_income.rb
  18. 3
      app/models/form/sales/pages/buyer2_income_max_value_check.rb
  19. 5
      app/models/form/sales/pages/buyer2_income_min_value_check.rb
  20. 3
      app/models/form/sales/pages/combined_income_max_value_check.rb
  21. 5
      app/models/form/sales/pages/deposit_value_check.rb
  22. 4
      app/models/form/sales/pages/household_wheelchair_check.rb
  23. 1
      app/models/form/sales/pages/housing_benefits.rb
  24. 6
      app/models/form/sales/pages/mortgage_value_check.rb
  25. 1
      app/models/form/sales/pages/previous_ownership.rb
  26. 1
      app/models/form/sales/pages/savings.rb
  27. 5
      app/models/form/sales/pages/savings_value_check.rb
  28. 3
      app/models/form/sales/questions/armed_forces.rb
  29. 2
      app/models/form/sales/questions/armed_forces_spouse.rb
  30. 4
      app/models/form/sales/questions/buyer1_income.rb
  31. 3
      app/models/form/sales/questions/buyer1_income_known.rb
  32. 3
      app/models/form/sales/questions/buyer1_income_value_check.rb
  33. 2
      app/models/form/sales/questions/buyer1_mortgage.rb
  34. 4
      app/models/form/sales/questions/buyer2_income.rb
  35. 3
      app/models/form/sales/questions/buyer2_income_known.rb
  36. 3
      app/models/form/sales/questions/buyer2_income_value_check.rb
  37. 2
      app/models/form/sales/questions/buyer2_mortgage.rb
  38. 2
      app/models/form/sales/questions/buyer_still_serving.rb
  39. 3
      app/models/form/sales/questions/combined_income_value_check.rb
  40. 3
      app/models/form/sales/questions/deposit_value_check.rb
  41. 3
      app/models/form/sales/questions/household_disability.rb
  42. 2
      app/models/form/sales/questions/household_wheelchair.rb
  43. 3
      app/models/form/sales/questions/household_wheelchair_check.rb
  44. 3
      app/models/form/sales/questions/housing_benefits.rb
  45. 3
      app/models/form/sales/questions/mortgage_value_check.rb
  46. 3
      app/models/form/sales/questions/prevown.rb
  47. 3
      app/models/form/sales/questions/prevshared.rb
  48. 3
      app/models/form/sales/questions/savings.rb
  49. 3
      app/models/form/sales/questions/savings_nk.rb
  50. 3
      app/models/form/sales/questions/savings_value_check.rb
  51. 2
      app/models/form/sales/questions/uprn.rb
  52. 2
      app/models/form/sales/questions/uprn_known.rb
  53. 6
      app/models/user.rb
  54. 48
      app/models/validations/sales/household_validations.rb
  55. 15
      app/models/validations/sales/property_validations.rb
  56. 6
      app/models/validations/shared_validations.rb
  57. 6
      app/services/bulk_upload/lettings/year2023/csv_parser.rb
  58. 25
      app/services/bulk_upload/lettings/year2023/row_parser.rb
  59. 6
      app/services/bulk_upload/lettings/year2024/csv_parser.rb
  60. 25
      app/services/bulk_upload/lettings/year2024/row_parser.rb
  61. 6
      app/services/bulk_upload/sales/year2023/csv_parser.rb
  62. 13
      app/services/bulk_upload/sales/year2023/row_parser.rb
  63. 6
      app/services/bulk_upload/sales/year2024/csv_parser.rb
  64. 25
      app/services/bulk_upload/sales/year2024/row_parser.rb
  65. 7
      app/services/collection_resources_service.rb
  66. 1
      app/services/mandatory_collection_resources_service.rb
  67. 10
      app/services/storage/local_disk_service.rb
  68. 25
      app/services/storage/s3_service.rb
  69. 8
      app/services/storage/storage_service.rb
  70. 23
      app/views/collection_resources/_collection_resource_summary_list.erb
  71. 31
      app/views/collection_resources/delete_confirmation.html.erb
  72. 21
      app/views/collection_resources/edit.html.erb
  73. 9
      app/views/collection_resources/index.html.erb
  74. 36
      app/views/collection_resources/new.html.erb
  75. 4
      app/views/layouts/_collection_resources.html.erb
  76. 2
      app/views/logs/edit.html.erb
  77. 56
      config/locales/en.yml
  78. 34
      config/locales/forms/2023/sales/household_needs.en.yml
  79. 90
      config/locales/forms/2023/sales/income_benefits_and_savings.en.yml
  80. 66
      config/locales/forms/2023/sales/soft_validations.en.yml
  81. 34
      config/locales/forms/2024/sales/household_needs.en.yml
  82. 90
      config/locales/forms/2024/sales/income_benefits_and_savings.en.yml
  83. 66
      config/locales/forms/2024/sales/soft_validations.en.yml
  84. 47
      config/locales/validations/sales/household.en.yml
  85. 27
      config/locales/validations/sales/property_information.en.yml
  86. 10
      config/routes.rb
  87. 5
      db/migrate/20241011112158_add_discarded_at.rb
  88. 3
      db/schema.rb
  89. 10
      spec/factories/collection_resource.rb
  90. 73
      spec/features/collection_resources_spec.rb
  91. 12
      spec/helpers/collection_resources_helper_spec.rb
  92. 3
      spec/models/form/sales/pages/buyer1_income_max_value_check_spec.rb
  93. 3
      spec/models/form/sales/pages/buyer1_income_min_value_check_spec.rb
  94. 3
      spec/models/form/sales/pages/buyer2_income_max_value_check_spec.rb
  95. 3
      spec/models/form/sales/pages/buyer2_income_min_value_check_spec.rb
  96. 3
      spec/models/form/sales/pages/combined_income_max_value_check_spec.rb
  97. 3
      spec/models/form/sales/pages/deposit_value_check_spec.rb
  98. 3
      spec/models/form/sales/pages/household_wheelchair_check_spec.rb
  99. 3
      spec/models/form/sales/pages/mortgage_value_check_spec.rb
  100. 3
      spec/models/form/sales/pages/savings_value_check_spec.rb
  101. Some files were not shown because too many files have changed in this diff Show More

30
app/components/bulk_upload_error_row_component.html.erb

@ -13,7 +13,7 @@
<% if critical_errors.any? %>
<h2 class="govuk-heading-m">Critical errors</h2>
<p class="govuk-body">These errors must be fixed to complete your logs.</p>
<%= govuk_table do |table| %>
<%= govuk_table(html_attributes: { class: potential_errors.any? ? "" : "no-bottom-border" }) do |table| %>
<%= table.with_head do |head| %>
<% head.with_row do |row| %>
<% row.with_cell(header: true, text: "Cell") %>
@ -39,7 +39,7 @@
<% if potential_errors.any? %>
<h2 class="govuk-heading-m">Potential errors</h2>
<p class="govuk-body">The following groups of cells might have conflicting data. Check the answers and fix any incorrect data.<br><br>If the answers are correct, fix the critical errors and reupload the file. You'll need to confirm that the following data is correct when the file only contains potential errors.</p>
<%= govuk_table do |table| %>
<%= govuk_table(html_attributes: { class: "no-bottom-border" }) do |table| %>
<%= table.with_head do |head| %>
<% head.with_row do |row| %>
<% row.with_cell(header: true, text: "Cell") %>
@ -49,24 +49,24 @@
<% end %>
<% end %>
<%= table.with_body do |body| %>
<% potential_errors.group_by(&:error).each do |error_message, errors| %>
<% errors.each_with_index do |error, index| %>
<% row_class = "grouped-rows" %>
<% row_class += " first-row" if index.zero? %>
<% row_class += " last-row" if index == errors.size - 1 %>
<% body.with_row(html_attributes: { class: row_class }) do |row| %>
<% row.with_cell(text: error.cell) %>
<% row.with_cell(text: question_for_field(error.field), html_attributes: { class: "govuk-!-width-one-half" }) %>
<% if index == 0 %>
<% row.with_cell(text: error_message.html_safe, rowspan: errors.size, html_attributes: { class: "govuk-!-font-weight-bold govuk-!-width-one-half grouped-multirow-cell" }) %>
<% end %>
<% row.with_cell(text: error.field.humanize) %>
<%= table.with_body do |body| %>
<% potential_errors.group_by(&:error).each_with_index do |(error_message, errors), group_index| %>
<% total_groups = potential_errors.group_by(&:error).size %>
<% errors.each_with_index do |error, index| %>
<% row_class = row_classes(index, errors.size) %>
<% body.with_row(html_attributes: { class: row_class }) do |row| %>
<% row.with_cell(text: error.cell) %>
<% row.with_cell(text: question_for_field(error.field), html_attributes: { class: "govuk-!-width-one-half" }) %>
<% if index == 0 %>
<% cell_class = cell_classes(group_index, total_groups) %>
<% row.with_cell(text: error_message.html_safe, rowspan: errors.size, html_attributes: { class: cell_class }) %>
<% end %>
<% row.with_cell(text: error.field.humanize) %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
</div>
</div>

13
app/components/bulk_upload_error_row_component.rb

@ -62,4 +62,17 @@ class BulkUploadErrorRowComponent < ViewComponent::Base
def sales?
bulk_upload.log_type == "sales"
end
def row_classes(index, errors_size)
row_class = "grouped-rows"
row_class += " first-row" if index.zero?
row_class += " last-row" if index == errors_size - 1
row_class
end
def cell_classes(group_index, total_groups)
cell_class = "govuk-!-font-weight-bold govuk-!-width-one-half"
cell_class += " grouped-multirow-cell" unless group_index == total_groups - 1
cell_class
end
end

4
app/components/document_list_component.html.erb

@ -1,4 +1,6 @@
<h3 class="govuk-heading-m"><%= label %></h3>
<% unless label.blank? %>
<h3 class="govuk-heading-m"><%= label %></h3>
<% end %>
<dl class="app-document-list">
<% items.each do |item| %>
<div class="app-document-list__item">

153
app/controllers/collection_resources_controller.rb

@ -1,13 +1,15 @@
class CollectionResourcesController < ApplicationController
include CollectionResourcesHelper
before_action :authenticate_user!, except: %i[download_mandatory_collection_resource]
before_action :authenticate_user!, except: %i[download_mandatory_collection_resource download_additional_collection_resource]
def index
render_not_found unless current_user.support?
@mandatory_lettings_collection_resources_per_year = MandatoryCollectionResourcesService.generate_resources("lettings", editable_collection_resource_years)
@mandatory_sales_collection_resources_per_year = MandatoryCollectionResourcesService.generate_resources("sales", editable_collection_resource_years)
@additional_lettings_collection_resources_per_year = CollectionResource.visible.where(log_type: "lettings", mandatory: false).group_by(&:year)
@additional_sales_collection_resources_per_year = CollectionResource.visible.where(log_type: "sales", mandatory: false).group_by(&:year)
end
def download_mandatory_collection_resource
@ -23,8 +25,17 @@ class CollectionResourcesController < ApplicationController
download_resource(resource.download_filename)
end
def edit
return render_not_found unless current_user.support?
def download_additional_collection_resource
resource = CollectionResource.find_by(id: params[:collection_resource_id])
return render_not_found unless resource
return render_not_found unless resource_for_year_can_be_downloaded?(resource.year)
download_resource(resource.download_filename)
end
def edit_mandatory_collection_resource
return render_not_authorized unless current_user.support?
year = params[:year].to_i
resource_type = params[:resource_type]
@ -39,8 +50,19 @@ class CollectionResourcesController < ApplicationController
render "collection_resources/edit"
end
def update
return render_not_found unless current_user.support?
def edit_additional_collection_resource
return render_not_authorized unless current_user.support?
@collection_resource = CollectionResource.find_by(id: params[:collection_resource_id])
return render_not_found unless @collection_resource
return render_not_found unless resource_for_year_can_be_updated?(@collection_resource.year)
render "collection_resources/edit"
end
def update_mandatory_collection_resource
return render_not_authorized unless current_user.support?
year = resource_params[:year].to_i
resource_type = resource_params[:resource_type]
@ -52,7 +74,8 @@ class CollectionResourcesController < ApplicationController
@collection_resource = MandatoryCollectionResourcesService.generate_resource(log_type, year, resource_type)
render_not_found unless @collection_resource
validate_file(file)
@collection_resource.file = file
@collection_resource.validate_attached_file
return render "collection_resources/edit" if @collection_resource.errors.any?
@ -68,8 +91,38 @@ class CollectionResourcesController < ApplicationController
redirect_to collection_resources_path
end
def update_additional_collection_resource
return render_not_authorized unless current_user.support?
@collection_resource = CollectionResource.find_by(id: params[:collection_resource_id])
return render_not_found unless @collection_resource
return render_not_found unless resource_for_year_can_be_updated?(@collection_resource.year)
@collection_resource.file = resource_params[:file]
@collection_resource.validate_attached_file
@collection_resource.validate_short_display_name
return render "collection_resources/edit" if @collection_resource.errors.any?
@collection_resource.short_display_name = resource_params[:short_display_name]
@collection_resource.download_filename = @collection_resource.file&.original_filename
@collection_resource.display_name = "#{@collection_resource.log_type} #{@collection_resource.short_display_name} (#{text_year_range_format(@collection_resource.year)})"
if @collection_resource.save
begin
CollectionResourcesService.new.upload_collection_resource(@collection_resource.download_filename, @collection_resource.file)
flash[:notice] = "The #{@collection_resource.log_type} #{text_year_range_format(@collection_resource.year)} #{@collection_resource.short_display_name.downcase} has been updated."
redirect_to collection_resources_path
rescue StandardError
@collection_resource.errors.add(:file, :error_uploading)
render "collection_resources/edit"
end
else
render "collection_resources/edit"
end
end
def confirm_mandatory_collection_resources_release
return render_not_found unless current_user.support?
return render_not_authorized unless current_user.support?
@year = params[:year].to_i
@ -79,7 +132,7 @@ class CollectionResourcesController < ApplicationController
end
def release_mandatory_collection_resources
return render_not_found unless current_user.support?
return render_not_authorized unless current_user.support?
year = params[:year].to_i
@ -91,10 +144,73 @@ class CollectionResourcesController < ApplicationController
redirect_to collection_resources_path
end
def new
return render_not_authorized unless current_user.support?
year = params[:year].to_i
log_type = params[:log_type]
return render_not_found unless editable_collection_resource_years.include?(year)
@collection_resource = CollectionResource.new(year:, log_type:)
end
def create
return render_not_authorized unless current_user.support? && editable_collection_resource_years.include?(resource_params[:year].to_i)
@collection_resource = CollectionResource.new(resource_params)
@collection_resource.download_filename ||= @collection_resource.file&.original_filename
@collection_resource.display_name = "#{@collection_resource.log_type} #{@collection_resource.short_display_name} (#{text_year_range_format(@collection_resource.year)})"
@collection_resource.validate_attached_file
@collection_resource.validate_short_display_name
return render "collection_resources/new" if @collection_resource.errors.any?
if @collection_resource.save
begin
CollectionResourcesService.new.upload_collection_resource(@collection_resource.download_filename, @collection_resource.file)
flash[:notice] = if displayed_collection_resource_years.include?(@collection_resource.year)
"The #{@collection_resource.log_type} #{text_year_range_format(@collection_resource.year)} #{@collection_resource.short_display_name} is now available to users."
else
"The #{@collection_resource.log_type} #{text_year_range_format(@collection_resource.year)} #{@collection_resource.short_display_name} has been uploaded."
end
redirect_to collection_resources_path
rescue StandardError
@collection_resource.errors.add(:file, :error_uploading)
render "collection_resources/new"
end
else
render "collection_resources/new"
end
end
def delete_confirmation
return render_not_authorized unless current_user.support?
@collection_resource = CollectionResource.find_by(id: params[:collection_resource_id])
return render_not_found unless @collection_resource
render "collection_resources/delete_confirmation"
end
def delete
return render_not_authorized unless current_user.support?
@collection_resource = CollectionResource.find_by(id: params[:collection_resource_id])
return render_not_found unless @collection_resource
@collection_resource.discard!
flash[:notice] = "The #{@collection_resource.log_type} #{text_year_range_format(@collection_resource.year)} #{@collection_resource.short_display_name.downcase} has been deleted."
redirect_to collection_resources_path
end
private
def resource_params
params.require(:collection_resource).permit(:year, :log_type, :resource_type, :file)
params.require(:collection_resource).permit(:year, :log_type, :resource_type, :file, :mandatory, :short_display_name)
end
def download_resource(filename)
@ -113,23 +229,4 @@ private
def resource_for_year_can_be_updated?(year)
editable_collection_resource_years.include?(year)
end
def validate_file(file)
return @collection_resource.errors.add(:file, :blank) unless file
return @collection_resource.errors.add(:file, :above_100_mb) if file.size > 100.megabytes
argv = %W[file --brief --mime-type -- #{file.path}]
output = `#{argv.shelljoin}`
case @collection_resource.resource_type
when "paper_form"
unless output.match?(/application\/pdf/)
@collection_resource.errors.add(:file, :must_be_pdf)
end
when "bulk_upload_template", "bulk_upload_specification"
unless output.match?(/application\/vnd\.ms-excel|application\/vnd\.openxmlformats-officedocument\.spreadsheetml\.sheet/)
@collection_resource.errors.add(:file, :must_be_xlsx, resource: @collection_resource.short_display_name.downcase)
end
end
end
end

2
app/controllers/lettings_logs_controller.rb

@ -65,7 +65,7 @@ class LettingsLogsController < LogsController
elsif @log.collection_closed_for_editing?
redirect_to review_lettings_log_path(@log)
else
render("logs/edit", locals: { current_user: })
render("logs/edit", locals: { current_user:, bulk_upload_filter_applied: session_filters["bulk_upload_id"].present? })
end
end

2
app/controllers/sales_logs_controller.rb

@ -39,7 +39,7 @@ class SalesLogsController < LogsController
if @log.collection_closed_for_editing?
redirect_to review_sales_log_path(@log, sales_log: true)
else
render "logs/edit", locals: { current_user: }
render "logs/edit", locals: { current_user:, bulk_upload_filter_applied: session_filters["bulk_upload_id"].present? }
end
end

2
app/controllers/start_controller.rb

@ -4,6 +4,8 @@ class StartController < ApplicationController
def index
@mandatory_lettings_collection_resources_per_year = MandatoryCollectionResourcesService.generate_resources("lettings", displayed_collection_resource_years)
@mandatory_sales_collection_resources_per_year = MandatoryCollectionResourcesService.generate_resources("sales", displayed_collection_resource_years)
@additional_lettings_collection_resources_per_year = CollectionResource.visible.where(log_type: "lettings", mandatory: false, year: displayed_collection_resource_years).group_by(&:year)
@additional_sales_collection_resources_per_year = CollectionResource.visible.where(log_type: "sales", mandatory: false, year: displayed_collection_resource_years).group_by(&:year)
if current_user
@homepage_presenter = HomepagePresenter.new(current_user)
render "home/index"

17
app/controllers/users_controller.rb

@ -257,13 +257,7 @@ private
def user_params
if @user == current_user
if current_user.data_coordinator?
params.require(:user).permit(:email, :phone, :phone_extension, :name, :password, :password_confirmation, :role, :is_dpo, :is_key_contact, :initial_confirmation_sent)
elsif current_user.support?
params.require(:user).permit(:email, :phone, :phone_extension, :name, :password, :password_confirmation, :role, :is_dpo, :is_key_contact, :initial_confirmation_sent, :organisation_id)
else
params.require(:user).permit(:email, :phone, :phone_extension, :name, :password, :password_confirmation, :initial_confirmation_sent)
end
current_user_params
elsif current_user.data_coordinator?
params.require(:user).permit(:email, :phone, :phone_extension, :name, :role, :is_dpo, :is_key_contact, :active, :initial_confirmation_sent)
elsif current_user.support?
@ -271,6 +265,15 @@ private
end
end
def current_user_params
base_params = %i[email phone phone_extension name password password_confirmation initial_confirmation_sent]
return params.require(:user).permit(*(base_params + %i[role is_dpo is_key_contact])) if current_user.data_coordinator?
return params.require(:user).permit(*(base_params + %i[role is_dpo is_key_contact organisation_id])) if current_user.support?
return params.require(:user).permit(*(base_params + [:role])) if Rails.env.staging? && current_user.in_staging_role_update_email_allowlist?
params.require(:user).permit(*base_params)
end
def user_params_without_org
user_params.except(:organisation_id)
end

5
app/frontend/styles/_bulk-uploads.scss

@ -12,6 +12,11 @@
border-bottom: 1px solid #b1b4b6;
}
.no-bottom-border,
.no-bottom-border > tbody > tr:last-of-type td {
border-bottom: none;
}
.text-normal-break {
white-space: normal;
word-break: break-all;

1
app/frontend/styles/_document-list.scss

@ -1,5 +1,4 @@
.app-document-list {
margin-top: govuk-spacing(3);
margin-bottom: govuk-spacing(6);
}

2
app/helpers/collection_resources_helper.rb

@ -49,7 +49,7 @@ module CollectionResourcesHelper
def document_list_component_items(resources)
resources.map do |resource|
{
name: "Download the #{resource.display_name}",
name: "Download the #{resource.display_name.downcase}",
href: resource.download_path,
metadata: file_type_size_and_pages(resource.download_filename),
}

8
app/helpers/log_actions_helper.rb

@ -2,8 +2,8 @@ module LogActionsHelper
include GovukLinkHelper
include GovukVisuallyHiddenHelper
def edit_actions_for_log(log)
back = back_button_for(log)
def edit_actions_for_log(log, bulk_upload_filter_applied)
back = back_button_for(log, bulk_upload_filter_applied)
delete = delete_button_for_log(log)
return if back.nil? && delete.nil?
@ -15,9 +15,9 @@ module LogActionsHelper
private
def back_button_for(log)
def back_button_for(log, bulk_upload_filter_applied)
if log.completed?
if log.creation_method_bulk_upload? && log.bulk_upload.present?
if log.creation_method_bulk_upload? && log.bulk_upload.present? && bulk_upload_filter_applied
if log.lettings?
govuk_button_link_to "Back to uploaded logs", resume_bulk_upload_lettings_result_path(log.bulk_upload)
else

38
app/models/collection_resource.rb

@ -1,9 +1,45 @@
class CollectionResource < ApplicationRecord
include Rails.application.routes.url_helpers
has_paper_trail
attr_accessor :file
scope :visible, -> { where(discarded_at: nil) }
validates :short_display_name, presence: true
def download_path
download_mandatory_collection_resource_path(log_type:, year:, resource_type:)
if mandatory
download_mandatory_collection_resource_path(log_type:, year:, resource_type:)
else
collection_resource_download_path(self)
end
end
def validate_attached_file
return errors.add(:file, :blank) unless file
return errors.add(:file, :above_100_mb) if file.size > 100.megabytes
argv = %W[file --brief --mime-type -- #{file.path}]
output = `#{argv.shelljoin}`
case resource_type
when "paper_form"
unless output.match?(/application\/pdf/)
errors.add(:file, :must_be_pdf)
end
when "bulk_upload_template", "bulk_upload_specification"
unless output.match?(/application\/vnd\.ms-excel|application\/vnd\.openxmlformats-officedocument\.spreadsheetml\.sheet/)
errors.add(:file, :must_be_xlsx, resource: short_display_name.downcase)
end
end
end
def validate_short_display_name
errors.add(:short_display_name, :blank) if short_display_name.blank?
end
def discard!
CollectionResourcesService.new.delete_collection_resource(download_filename)
update!(discarded_at: Time.zone.now)
end
end

1
app/models/form/sales/pages/buyer1_income.rb

@ -2,6 +2,7 @@ class Form::Sales::Pages::Buyer1Income < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_1_income"
@copy_key = "sales.income_benefits_and_savings.buyer_1_income"
end
def questions

3
app/models/form/sales/pages/buyer1_income_max_value_check.rb

@ -6,8 +6,9 @@ class Form::Sales::Pages::Buyer1IncomeMaxValueCheck < ::Form::Page
"income1_over_soft_max?" => true,
},
]
@copy_key = "sales.soft_validations.income1_value_check.max"
@title_text = {
"translation" => "soft_validations.income.over_soft_max_for_la_buyer_1",
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",

5
app/models/form/sales/pages/buyer1_income_min_value_check.rb

@ -6,8 +6,9 @@ class Form::Sales::Pages::Buyer1IncomeMinValueCheck < ::Form::Page
"income1_under_soft_min?" => true,
},
]
@copy_key = "sales.soft_validations.income1_value_check.min"
@title_text = {
"translation" => "soft_validations.income.under_soft_min_for_economic_status.title_text",
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",
@ -22,7 +23,7 @@ class Form::Sales::Pages::Buyer1IncomeMinValueCheck < ::Form::Page
],
}
@informative_text = {
"translation" => "soft_validations.income.under_soft_min_for_economic_status.hint_text",
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.informative_text",
"arguments" => [],
}
end

1
app/models/form/sales/pages/buyer2_income.rb

@ -2,6 +2,7 @@ class Form::Sales::Pages::Buyer2Income < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_income"
@copy_key = "sales.income_benefits_and_savings.buyer_2_income"
@depends_on = [{
"joint_purchase?" => true,
}]

3
app/models/form/sales/pages/buyer2_income_max_value_check.rb

@ -6,8 +6,9 @@ class Form::Sales::Pages::Buyer2IncomeMaxValueCheck < ::Form::Page
"income2_over_soft_max?" => true,
},
]
@copy_key = "sales.soft_validations.income2_value_check.max"
@title_text = {
"translation" => "soft_validations.income.over_soft_max_for_la_buyer_2",
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",

5
app/models/form/sales/pages/buyer2_income_min_value_check.rb

@ -6,8 +6,9 @@ class Form::Sales::Pages::Buyer2IncomeMinValueCheck < ::Form::Page
"income2_under_soft_min?" => true,
},
]
@copy_key = "sales.soft_validations.income2_value_check.min"
@title_text = {
"translation" => "soft_validations.income.under_soft_min_for_economic_status.title_text",
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",
@ -22,7 +23,7 @@ class Form::Sales::Pages::Buyer2IncomeMinValueCheck < ::Form::Page
],
}
@informative_text = {
"translation" => "soft_validations.income.under_soft_min_for_economic_status.hint_text",
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.informative_text",
"arguments" => [],
}
end

3
app/models/form/sales/pages/combined_income_max_value_check.rb

@ -6,8 +6,9 @@ class Form::Sales::Pages::CombinedIncomeMaxValueCheck < ::Form::Page
"combined_income_over_soft_max?" => true,
},
]
@copy_key = "sales.soft_validations.combined_income_value_check"
@title_text = {
"translation" => "soft_validations.income.over_soft_max_for_la_combined",
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",

5
app/models/form/sales/pages/deposit_value_check.rb

@ -1,12 +1,13 @@
class Form::Sales::Pages::DepositValueCheck < ::Form::Page
def initialize(id, hsh, subsection, joint_purchase:)
super(id, hsh, subsection)
@copy_key = "sales.soft_validations.deposit_value_check.#{joint_purchase ? 'joint_purchase' : 'not_joint_purchase'}"
@informative_text = {
"translation" => "soft_validations.deposit.hint_text",
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.informative_text",
"arguments" => [],
}
@title_text = {
"translation" => "soft_validations.deposit.title_text.#{joint_purchase ? 'two' : 'one'}",
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",

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

@ -6,8 +6,8 @@ class Form::Sales::Pages::HouseholdWheelchairCheck < ::Form::Page
"wheelchair_when_not_disabled?" => true,
},
]
@informative_text = {}
@title_text = { "translation" => "soft_validations.wheelchair.title_text" }
@copy_key = "sales.soft_validations.wheel_value_check"
@title_text = { "translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text" }
end
def questions

1
app/models/form/sales/pages/housing_benefits.rb

@ -2,6 +2,7 @@ class Form::Sales::Pages::HousingBenefits < ::Form::Page
def initialize(id, hsh, subsection, joint_purchase:)
super(id, hsh, subsection)
@joint_purchase = joint_purchase
@copy_key = "sales.income_benefits_and_savings.housing_benefits.#{joint_purchase ? 'joint_purchase' : 'not_joint_purchase'}"
end
def questions

6
app/models/form/sales/pages/mortgage_value_check.rb

@ -2,10 +2,10 @@ class Form::Sales::Pages::MortgageValueCheck < ::Form::Page
def initialize(id, hsh, subsection, person_index = nil)
super(id, hsh, subsection)
@depends_on = depends_on
@informative_text = {}
@person_index = person_index
@copy_key = "sales.soft_validations.mortgage_value_check"
@title_text = {
"translation" => "soft_validations.mortgage.title_text",
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",
@ -15,7 +15,7 @@ class Form::Sales::Pages::MortgageValueCheck < ::Form::Page
],
}
@informative_text = {
"translation" => "soft_validations.mortgage.hint_text",
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.informative_text",
"arguments" => [],
}
end

1
app/models/form/sales/pages/previous_ownership.rb

@ -3,6 +3,7 @@ class Form::Sales::Pages::PreviousOwnership < ::Form::Page
super(id, hsh, subsection)
@joint_purchase = joint_purchase
@depends_on = [{ "joint_purchase?" => @joint_purchase }]
@copy_key = "sales.income_benefits_and_savings.prevown.#{joint_purchase ? 'joint_purchase' : 'not_joint_purchase'}"
end
def questions

1
app/models/form/sales/pages/savings.rb

@ -2,6 +2,7 @@ class Form::Sales::Pages::Savings < ::Form::Page
def initialize(id, hsh, subsection, joint_purchase:)
super(id, hsh, subsection)
@joint_purchase = joint_purchase
@copy_key = "sales.income_benefits_and_savings.savings.#{joint_purchase ? 'joint_purchase' : 'not_joint_purchase'}"
end
def questions

5
app/models/form/sales/pages/savings_value_check.rb

@ -1,8 +1,9 @@
class Form::Sales::Pages::SavingsValueCheck < ::Form::Page
def initialize(id, hsh, subsection, joint_purchase:)
super(id, hsh, subsection)
@copy_key = "sales.soft_validations.savings_value_check.#{joint_purchase ? 'joint_purchase' : 'not_joint_purchase'}"
@title_text = {
"translation" => "soft_validations.savings.title_text.#{joint_purchase ? 'two' : 'one'}",
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",
@ -12,7 +13,7 @@ class Form::Sales::Pages::SavingsValueCheck < ::Form::Page
],
}
@informative_text = {
"translation" => "soft_validations.savings.hint_text",
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.informative_text",
"arguments" => [],
}
@joint_purchase = joint_purchase

3
app/models/form/sales/questions/armed_forces.rb

@ -2,10 +2,7 @@ class Form::Sales::Questions::ArmedForces < ::Form::Question
def initialize(id, hsh, page)
super
@id = "hhregres"
@check_answer_label = "Have any of the buyers ever served as a regular in the UK armed forces?"
@header = "Have any of the buyers ever served as a regular in the UK armed forces?"
@type = "radio"
@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"
@answer_options = ANSWER_OPTIONS
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

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

@ -2,8 +2,6 @@ class Form::Sales::Questions::ArmedForcesSpouse < ::Form::Question
def initialize(id, hsh, page)
super
@id = "armedforcesspouse"
@check_answer_label = "Are any of the buyers a spouse or civil partner of a UK armed forces regular who died in service within the last 2 years?"
@header = "Are any of the buyers a spouse or civil partner of a UK armed forces regular who died in service within the last 2 years?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]

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

@ -2,9 +2,7 @@ class Form::Sales::Questions::Buyer1Income < ::Form::Question
def initialize(id, hsh, page)
super
@id = "income1"
@check_answer_label = "Buyer 1’s gross annual income"
@header = "Buyer 1’s gross annual income"
@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."
@copy_key = "sales.income_benefits_and_savings.buyer_1_income.income1"
@type = "numeric"
@min = 0
@max = 999_999

3
app/models/form/sales/questions/buyer1_income_known.rb

@ -2,8 +2,7 @@ class Form::Sales::Questions::Buyer1IncomeKnown < ::Form::Question
def initialize(id, hsh, page)
super
@id = "income1nk"
@check_answer_label = "Buyer 1’s gross annual income known?"
@header = "Do you know buyer 1’s annual income?"
@copy_key = "sales.income_benefits_and_savings.buyer_1_income.income1nk"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@conditional_for = {

3
app/models/form/sales/questions/buyer1_income_value_check.rb

@ -2,8 +2,7 @@ class Form::Sales::Questions::Buyer1IncomeValueCheck < ::Form::Question
def initialize(id, hsh, page, check_answers_card_number:)
super(id, hsh, page)
@id = "income1_value_check"
@check_answer_label = "Buyer 1 income confirmation"
@header = "Are you sure this is correct?"
@copy_key = "sales.soft_validations.income1_value_check"
@type = "interruption_screen"
@answer_options = {
"0" => { "value" => "Yes" },

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

@ -2,8 +2,6 @@ class Form::Sales::Questions::Buyer1Mortgage < ::Form::Question
def initialize(id, hsh, page)
super
@id = "inc1mort"
@check_answer_label = "Buyer 1’s income used for mortgage application"
@header = "Was buyer 1’s income used for a mortgage application?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@check_answers_card_number = 1

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

@ -2,10 +2,8 @@ class Form::Sales::Questions::Buyer2Income < ::Form::Question
def initialize(id, hsh, page)
super
@id = "income2"
@check_answer_label = "Buyer 2’s gross annual income"
@header = "Buyer 2’s gross annual income"
@copy_key = "sales.income_benefits_and_savings.buyer_2_income.income2"
@type = "numeric"
@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."
@min = 0
@max = 999_999
@step = 1

3
app/models/form/sales/questions/buyer2_income_known.rb

@ -2,8 +2,7 @@ class Form::Sales::Questions::Buyer2IncomeKnown < ::Form::Question
def initialize(id, hsh, page)
super
@id = "income2nk"
@check_answer_label = "Buyer 2’s gross annual income known?"
@header = "Do you know buyer 2’s annual income?"
@copy_key = "sales.income_benefits_and_savings.buyer_2_income.income2"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@conditional_for = {

3
app/models/form/sales/questions/buyer2_income_value_check.rb

@ -2,8 +2,7 @@ class Form::Sales::Questions::Buyer2IncomeValueCheck < ::Form::Question
def initialize(id, hsh, page, check_answers_card_number:)
super(id, hsh, page)
@id = "income2_value_check"
@check_answer_label = "Buyer 2 income confirmation"
@header = "Are you sure this is correct?"
@copy_key = "sales.soft_validations.income2_value_check"
@type = "interruption_screen"
@answer_options = {
"0" => { "value" => "Yes" },

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

@ -2,8 +2,6 @@ class Form::Sales::Questions::Buyer2Mortgage < ::Form::Question
def initialize(id, hsh, page)
super
@id = "inc2mort"
@check_answer_label = "Buyer 2’s income used for mortgage application"
@header = "Was buyer 2’s income used for a mortgage application?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@check_answers_card_number = 2

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

@ -2,8 +2,6 @@ class Form::Sales::Questions::BuyerStillServing < ::Form::Question
def initialize(id, hsh, page)
super
@id = "hhregresstill"
@check_answer_label = "Are they still serving in the UK armed forces?"
@header = "Is the buyer still serving in the UK armed forces?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]

3
app/models/form/sales/questions/combined_income_value_check.rb

@ -2,8 +2,7 @@ class Form::Sales::Questions::CombinedIncomeValueCheck < ::Form::Question
def initialize(id, hsh, page, check_answers_card_number:)
super(id, hsh, page)
@id = "combined_income_value_check"
@check_answer_label = "Combined income confirmation"
@header = "Are you sure this is correct?"
@copy_key = "sales.soft_validations.combined_income_value_check"
@type = "interruption_screen"
@answer_options = {
"0" => { "value" => "Yes" },

3
app/models/form/sales/questions/deposit_value_check.rb

@ -2,8 +2,7 @@ class Form::Sales::Questions::DepositValueCheck < ::Form::Question
def initialize(id, hsh, page)
super
@id = "deposit_value_check"
@check_answer_label = "Deposit confirmation"
@header = "Are you sure that the deposit is this much higher than the buyer's savings?"
@copy_key = "sales.soft_validations.deposit_value_check"
@type = "interruption_screen"
@answer_options = {
"0" => { "value" => "Yes" },

3
app/models/form/sales/questions/household_disability.rb

@ -2,11 +2,8 @@ class Form::Sales::Questions::HouseholdDisability < ::Form::Question
def initialize(id, hsh, page)
super
@id = "disabled"
@header = "Does anyone in the household consider themselves to have a disability?"
@check_answer_label = "Does anyone in the household have a disability?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@hint_text = "This includes any long-term health condition that has an impact on the person's day-to-day life"
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

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

@ -2,10 +2,8 @@ class Form::Sales::Questions::HouseholdWheelchair < ::Form::Question
def initialize(id, hsh, page)
super
@id = "wheel"
@header = "Does anyone in the household use a wheelchair?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@hint_text = "This can be inside or outside the home"
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

3
app/models/form/sales/questions/household_wheelchair_check.rb

@ -2,8 +2,7 @@ class Form::Sales::Questions::HouseholdWheelchairCheck < ::Form::Question
def initialize(id, hsh, page)
super
@id = "wheel_value_check"
@check_answer_label = "Does anyone in the household use a wheelchair?"
@header = "You told us that someone in the household uses a wheelchair."
@copy_key = "sales.soft_validations.wheel_value_check"
@type = "interruption_screen"
@answer_options = {
"0" => { "value" => "Yes" },

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

@ -2,8 +2,7 @@ class Form::Sales::Questions::HousingBenefits < ::Form::Question
def initialize(id, hsh, page, joint_purchase:)
super(id, hsh, page)
@id = "hb"
@check_answer_label = "Housing-related benefits #{joint_purchase ? 'buyers' : 'buyer'} received before buying this property"
@header = "#{joint_purchase ? 'Were the buyers' : 'Was the buyer'} receiving any of these housing-related benefits immediately before buying this property?"
@copy_key = "sales.income_benefits_and_savings.housing_benefits.#{joint_purchase ? 'joint_purchase' : 'not_joint_purchase'}"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]

3
app/models/form/sales/questions/mortgage_value_check.rb

@ -2,8 +2,7 @@ class Form::Sales::Questions::MortgageValueCheck < ::Form::Question
def initialize(id, hsh, page)
super
@id = "mortgage_value_check"
@check_answer_label = "Mortgage confirmation"
@header = "Are you sure that the mortgage is more than 5 times the income used for the mortgage application?"
@copy_key = "sales.soft_validations.mortgage_value_check"
@type = "interruption_screen"
@answer_options = {
"0" => { "value" => "Yes" },

3
app/models/form/sales/questions/prevown.rb

@ -2,8 +2,7 @@ class Form::Sales::Questions::Prevown < ::Form::Question
def initialize(id, hsh, page, joint_purchase:)
super(id, hsh, page)
@id = "prevown"
@check_answer_label = I18n.t("check_answer_labels.prevown", count: joint_purchase ? 2 : 1)
@header = I18n.t("questions.prevown", count: joint_purchase ? 2 : 1)
@copy_key = "sales.income_benefits_and_savings.prevown.#{joint_purchase ? 'joint_purchase' : 'not_joint_purchase'}"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]

3
app/models/form/sales/questions/prevshared.rb

@ -2,11 +2,8 @@ class Form::Sales::Questions::Prevshared < ::Form::Question
def initialize(id, hsh, page)
super
@id = "prevshared"
@check_answer_label = "Previous property shared ownership?"
@header = "Was the previous property under shared ownership?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@hint_text = "For any buyer"
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

3
app/models/form/sales/questions/savings.rb

@ -2,8 +2,7 @@ class Form::Sales::Questions::Savings < ::Form::Question
def initialize(id, hsh, page, joint_purchase:)
super(id, hsh, page)
@id = "savings"
@check_answer_label = "#{joint_purchase ? 'Buyers’' : 'Buyer’s'} total savings before any deposit paid"
@header = "Enter their total savings to the nearest £10"
@copy_key = "sales.income_benefits_and_savings.savings.#{joint_purchase ? 'joint_purchase' : 'not_joint_purchase'}.savings"
@type = "numeric"
@width = 5
@prefix = "£"

3
app/models/form/sales/questions/savings_nk.rb

@ -2,8 +2,7 @@ class Form::Sales::Questions::SavingsNk < ::Form::Question
def initialize(id, hsh, page, joint_purchase:)
super(id, hsh, page)
@id = "savingsnk"
@check_answer_label = "#{joint_purchase ? 'Buyers’' : 'Buyer’s'} total savings known?"
@header = "Do you know how much the #{joint_purchase ? 'buyers' : 'buyer'} had in savings before they paid any deposit for the property?"
@copy_key = "sales.income_benefits_and_savings.savings.#{joint_purchase ? 'joint_purchase' : 'not_joint_purchase'}.savingsnk"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@conditional_for = {

3
app/models/form/sales/questions/savings_value_check.rb

@ -2,8 +2,7 @@ class Form::Sales::Questions::SavingsValueCheck < ::Form::Question
def initialize(id, hsh, page)
super
@id = "savings_value_check"
@check_answer_label = "Savings confirmation"
@header = "Are you sure the savings are higher than £100,000?"
@copy_key = "sales.soft_validations.savings_value_check"
@type = "interruption_screen"
@answer_options = {
"0" => { "value" => "Yes" },

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

@ -16,7 +16,7 @@ class Form::Sales::Questions::Uprn < ::Form::Question
end
def unanswered_error_message
I18n.t("validations.property.uprn.invalid")
I18n.t("validations.sales.property_information.uprn.invalid")
end
def get_extra_check_answer_value(log)

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

@ -31,7 +31,7 @@ class Form::Sales::Questions::UprnKnown < ::Form::Question
}.freeze
def unanswered_error_message
I18n.t("validations.property.uprn_known.invalid")
I18n.t("validations.sales.property_information.uprn_known.invalid")
end
QUESTION_NUMBER_FROM_YEAR = { 2023 => 14, 2024 => 15 }.freeze

6
app/models/user.rb

@ -212,7 +212,7 @@ class User < ApplicationRecord
end
def assignable_roles
if Rails.env.staging? && Rails.application.credentials[:staging_role_update_email_allowlist].include?(email.split("@").last.downcase)
if Rails.env.staging? && in_staging_role_update_email_allowlist?
return ROLES
end
@ -222,6 +222,10 @@ class User < ApplicationRecord
ROLES.except(:support)
end
def in_staging_role_update_email_allowlist?
Rails.application.credentials[:staging_role_update_email_allowlist].include?(email.split("@").last.downcase)
end
def logs_filters(specific_org: false)
if (support? && !specific_org) || organisation.has_managing_agents? || organisation.has_stock_owners?
%w[years status needstypes assigned_to user owning_organisation managing_organisation bulk_upload_id user_text_search owning_organisation_text_search managing_organisation_text_search]

48
app/models/validations/sales/household_validations.rb

@ -9,9 +9,9 @@ module Validations::Sales::HouseholdValidations
return unless record.form.start_date.year >= 2023
if record.buyers_will_live_in? && record.buyer_one_will_not_live_in_property? && record.buyer_two_will_not_live_in_property?
record.errors.add :buylivein, I18n.t("validations.household.buylivein.buyers_will_live_in_property_values_inconsistent_setup")
record.errors.add :buy1livein, I18n.t("validations.household.buylivein.buyers_will_live_in_property_values_inconsistent")
record.errors.add :buy2livein, I18n.t("validations.household.buylivein.buyers_will_live_in_property_values_inconsistent")
record.errors.add :buylivein, I18n.t("validations.sales.household.buylivein.buyers_will_live_in_property_values_inconsistent")
record.errors.add :buy1livein, I18n.t("validations.sales.household.buy1livein.buyers_will_live_in_property_values_inconsistent")
record.errors.add :buy2livein, I18n.t("validations.sales.household.buy2livein.buyers_will_live_in_property_values_inconsistent")
end
end
@ -20,8 +20,8 @@ module Validations::Sales::HouseholdValidations
return unless record.discounted_ownership_sale? && record.prevten
if [3, 4, 5, 6, 7, 9, 0].include?(record.prevten)
record.errors.add :prevten, I18n.t("validations.household.prevten.invalid_for_discounted_sale")
record.errors.add :ownershipsch, I18n.t("validations.household.prevten.invalid_for_discounted_sale")
record.errors.add :prevten, I18n.t("validations.sales.household.prevten.prevten_invalid_for_discounted_sale")
record.errors.add :ownershipsch, I18n.t("validations.sales.household.ownershipsch.prevten_invalid_for_discounted_sale")
end
end
@ -34,11 +34,11 @@ module Validations::Sales::HouseholdValidations
next unless age && relationship
if age < 16 && !relationship_is_child_other_or_refused?(relationship)
record.errors.add "age#{person_num}", I18n.t("validations.household.age.child_under_16_relat_sales", person_num:)
record.errors.add "relat#{person_num}", I18n.t("validations.household.relat.child_under_16_sales", person_num:)
record.errors.add "age#{person_num}", I18n.t("validations.sales.household.age.child_under_16", person_num:)
record.errors.add "relat#{person_num}", I18n.t("validations.sales.household.relat.child_under_16", person_num:)
elsif age >= 20 && person_is_child?(relationship)
record.errors.add "age#{person_num}", I18n.t("validations.household.age.child_over_20")
record.errors.add "relat#{person_num}", I18n.t("validations.household.relat.child_over_20")
record.errors.add "age#{person_num}", I18n.t("validations.sales.household.age.child_over_20")
record.errors.add "relat#{person_num}", I18n.t("validations.sales.household.relat.child_over_20")
end
end
end
@ -58,16 +58,16 @@ module Validations::Sales::HouseholdValidations
child = person_is_child?(relationship)
if age_between_16_19 && !(student || economic_status_refused) && child
record.errors.add "ecstat#{person_num}", I18n.t("validations.household.ecstat.student_16_19.must_be_student")
record.errors.add "age#{person_num}", I18n.t("validations.household.age.student_16_19.cannot_be_16_19.child_not_student")
record.errors.add "relat#{person_num}", I18n.t("validations.household.relat.student_16_19.cannot_be_child.16_19_not_student")
record.errors.add "ecstat#{person_num}", I18n.t("validations.sales.household.ecstat.student_16_19.must_be_student")
record.errors.add "age#{person_num}", I18n.t("validations.sales.household.age.student_16_19.cannot_be_16_19.child_not_student")
record.errors.add "relat#{person_num}", I18n.t("validations.sales.household.relat.student_16_19.cannot_be_child.16_19_not_student")
end
next unless !age_between_16_19 && student && child
record.errors.add "age#{person_num}", I18n.t("validations.household.age.student_16_19.must_be_16_19")
record.errors.add "ecstat#{person_num}", I18n.t("validations.household.ecstat.student_16_19.cannot_be_student.child_not_16_19")
record.errors.add "relat#{person_num}", I18n.t("validations.household.relat.student_16_19.cannot_be_child.student_not_16_19")
record.errors.add "age#{person_num}", I18n.t("validations.sales.household.age.student_16_19.must_be_16_19")
record.errors.add "ecstat#{person_num}", I18n.t("validations.sales.household.ecstat.student_16_19.cannot_be_student.child_not_16_19")
record.errors.add "relat#{person_num}", I18n.t("validations.sales.household.relat.student_16_19.cannot_be_child.student_not_16_19")
end
end
@ -78,12 +78,12 @@ module Validations::Sales::HouseholdValidations
next unless age && economic_status
if age < 16 && !economic_status_is_child_other_or_refused?(economic_status) && !record.form.start_year_after_2024?
record.errors.add "ecstat#{person_num}", I18n.t("validations.household.ecstat.child_under_16", person_num:)
record.errors.add "age#{person_num}", I18n.t("validations.household.age.child_under_16_ecstat", person_num:)
record.errors.add "ecstat#{person_num}", I18n.t("validations.sales.household.ecstat.child_under_16", person_num:)
record.errors.add "age#{person_num}", I18n.t("validations.sales.household.age.child_under_16_ecstat", person_num:)
end
if person_is_economic_child?(economic_status) && age > 16
record.errors.add "ecstat#{person_num}", I18n.t("validations.household.ecstat.child_over_16", person_num:)
record.errors.add "age#{person_num}", I18n.t("validations.household.age.child_over_16", person_num:)
record.errors.add "ecstat#{person_num}", I18n.t("validations.sales.household.ecstat.child_over_16", person_num:)
record.errors.add "age#{person_num}", I18n.t("validations.sales.household.age.child_over_16", person_num:)
end
end
end
@ -99,17 +99,17 @@ module Validations::Sales::HouseholdValidations
next unless person_age > buyer_1_age - 12 && person_is_child?(relationship)
record.errors.add "age1", I18n.t("validations.household.age.child_12_years_younger")
record.errors.add "age#{person_num}", I18n.t("validations.household.age.child_12_years_younger")
record.errors.add "relat#{person_num}", I18n.t("validations.household.age.child_12_years_younger")
record.errors.add "age1", I18n.t("validations.sales.household.age1.child_12_years_younger")
record.errors.add "age#{person_num}", I18n.t("validations.sales.household.age.child_12_years_younger")
record.errors.add "relat#{person_num}", I18n.t("validations.sales.household.relat.child_12_years_younger")
end
end
def validate_buyer_not_child(record)
return unless record.saledate && record.form.start_year_after_2024?
record.errors.add "ecstat1", I18n.t("validations.household.ecstat.buyer_cannot_be_child", buyer_index: "1") if person_is_economic_child?(record.ecstat1)
record.errors.add "ecstat2", I18n.t("validations.household.ecstat.buyer_cannot_be_child", buyer_index: "2") if person_is_economic_child?(record.ecstat2) && record.joint_purchase?
record.errors.add "ecstat1", I18n.t("validations.sales.household.ecstat1.buyer_cannot_be_child") if person_is_economic_child?(record.ecstat1)
record.errors.add "ecstat2", I18n.t("validations.sales.household.ecstat2.buyer_cannot_be_child") if person_is_economic_child?(record.ecstat2) && record.joint_purchase?
end
private

15
app/models/validations/sales/property_validations.rb

@ -4,10 +4,11 @@ module Validations::Sales::PropertyValidations
return unless record.ppostcode_full.present? && record.postcode_full.present?
if record.discounted_ownership_sale? && record.ppostcode_full != record.postcode_full
record.errors.add :postcode_full, I18n.t("validations.property.postcode.must_match_previous", buyer_possessive: record.joint_purchase? ? "Buyers’" : "Buyer’s")
record.errors.add :ppostcode_full, I18n.t("validations.property.postcode.must_match_previous", buyer_possessive: record.joint_purchase? ? "Buyers’" : "Buyer’s")
record.errors.add :ownershipsch, I18n.t("validations.property.postcode.must_match_previous", buyer_possessive: record.joint_purchase? ? "Buyers’" : "Buyer’s")
record.errors.add :uprn, I18n.t("validations.property.postcode.must_match_previous", buyer_possessive: record.joint_purchase? ? "Buyers’" : "Buyer’s")
joint_purchase_id = record.joint_purchase? ? "joint_purchase" : "not_joint_purchase"
record.errors.add :postcode_full, I18n.t("validations.sales.property_information.postcode_full.postcode_must_match_previous.#{joint_purchase_id}")
record.errors.add :ppostcode_full, I18n.t("validations.sales.property_information.ppostcode_full.postcode_must_match_previous.#{joint_purchase_id}")
record.errors.add :ownershipsch, I18n.t("validations.sales.property_information.ownershipsch.postcode_must_match_previous.#{joint_purchase_id}")
record.errors.add :uprn, I18n.t("validations.sales.property_information.uprn.postcode_must_match_previous.#{joint_purchase_id}")
end
end
@ -15,8 +16,8 @@ module Validations::Sales::PropertyValidations
return unless record.proptype.present? && record.beds.present?
if record.is_bedsit? && record.beds > 1
record.errors.add :proptype, I18n.t("validations.property.proptype.bedsits_have_max_one_bedroom")
record.errors.add :beds, I18n.t("validations.property.beds.bedsits_have_max_one_bedroom")
record.errors.add :proptype, I18n.t("validations.sales.property_information.proptype.bedsits_have_max_one_bedroom")
record.errors.add :beds, I18n.t("validations.sales.property_information.beds.bedsits_have_max_one_bedroom")
end
end
@ -25,6 +26,6 @@ module Validations::Sales::PropertyValidations
return if record.uprn.match?(/^[0-9]{1,12}$/)
record.errors.add :uprn, I18n.t("validations.property.uprn.invalid")
record.errors.add :uprn, I18n.t("validations.sales.property_information.uprn.invalid")
end
end

6
app/models/validations/shared_validations.rb

@ -131,7 +131,11 @@ module Validations::SharedValidations
partner_numbers = (2..max_people).select { |n| person_is_partner?(record["relat#{n}"]) }
if partner_numbers.count > 1
partner_numbers.each do |n|
record.errors.add "relat#{n}", I18n.t("validations.household.relat.one_partner")
if record.sales?
record.errors.add "relat#{n}", I18n.t("validations.sales.household.relat.one_partner")
else
record.errors.add "relat#{n}", I18n.t("validations.household.relat.one_partner")
end
end
end
end

6
app/services/bulk_upload/lettings/year2023/csv_parser.rb

@ -109,9 +109,11 @@ private
def first_record_start_date
if with_headers?
Date.new(row_parsers.first.field_9.to_i + 2000, row_parsers.first.field_8.to_i, row_parsers.first.field_7.to_i)
year = row_parsers.first.field_9.to_s.strip.length.between?(1, 2) ? row_parsers.first.field_9.to_i + 2000 : row_parsers.first.field_9.to_i
Date.new(year, row_parsers.first.field_8.to_i, row_parsers.first.field_7.to_i)
else
Date.new(rows.first[97].to_i + 2000, rows.first[96].to_i, rows.first[95].to_i)
year = rows.first[97].to_s.strip.length.between?(1, 2) ? rows.first[97].to_i + 2000 : rows.first[97].to_i
Date.new(year, rows.first[96].to_i, rows.first[95].to_i)
end
end
end

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

@ -323,8 +323,8 @@ class BulkUpload::Lettings::Year2023::RowParser
category: :setup,
},
format: {
with: /\A\d{2}\z/,
message: I18n.t("validations.setup.startdate.year_not_two_digits"),
with: /\A(\d{2}|\d{4})\z/,
message: I18n.t("validations.setup.startdate.year_not_two_or_four_digits"),
category: :setup,
unless: -> { field_9.blank? },
},
@ -618,14 +618,6 @@ private
end
end
def start_date
return if field_7.blank? || field_8.blank? || field_9.blank?
Date.parse("20#{field_9.to_s.rjust(2, '0')}-#{field_8}-#{field_7}")
rescue StandardError
nil
end
def validate_no_and_dont_know_disabled_needs_conjunction
if field_87 == 1 && field_88 == 1
errors.add(:field_87, I18n.t("validations.household.housingneeds.no_and_dont_know_disabled_needs_conjunction"))
@ -736,9 +728,9 @@ private
end
def validate_relevant_collection_window
return if start_date.blank? || bulk_upload.form.blank?
return if startdate.blank? || bulk_upload.form.blank?
unless bulk_upload.form.valid_start_date_for_form?(start_date)
unless bulk_upload.form.valid_start_date_for_form?(startdate)
errors.add(:field_7, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
errors.add(:field_8, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
errors.add(:field_9, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
@ -1388,7 +1380,8 @@ private
end
def startdate
Date.new(field_9 + 2000, field_8, field_7) if field_9.present? && field_8.present? && field_7.present?
year = field_9.to_s.strip.length.between?(1, 2) ? field_9 + 2000 : field_9
Date.new(year, field_8, field_7) if field_9.present? && field_8.present? && field_7.present?
rescue Date::Error
Date.new
end
@ -1584,13 +1577,15 @@ private
end
def mrcdate
Date.new(field_38 + 2000, field_37, field_36) if field_38.present? && field_37.present? && field_36.present?
year = field_38.to_s.strip.length.between?(1, 2) ? field_38 + 2000 : field_38
Date.new(year, field_37, field_36) if field_38.present? && field_37.present? && field_36.present?
rescue Date::Error
Date.new
end
def voiddate
Date.new(field_35 + 2000, field_34, field_33) if field_35.present? && field_34.present? && field_33.present?
year = field_35.to_s.strip.length.between?(1, 2) ? field_35 + 2000 : field_35
Date.new(year, field_34, field_33) if field_35.present? && field_34.present? && field_33.present?
rescue Date::Error
Date.new
end

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

@ -112,9 +112,11 @@ private
def first_record_start_date
if with_headers?
Date.new(row_parsers.first.field_10.to_i + 2000, row_parsers.first.field_9.to_i, row_parsers.first.field_8.to_i)
year = row_parsers.first.field_10.to_s.strip.length.between?(1, 2) ? row_parsers.first.field_10.to_i + 2000 : row_parsers.first.field_10.to_i
Date.new(year, row_parsers.first.field_9.to_i, row_parsers.first.field_8.to_i)
else
Date.new(rows.first[9].to_i + 2000, rows.first[8].to_i, rows.first[7].to_i)
year = rows.first[9].to_s.strip.length.between?(1, 2) ? rows.first[9].to_i + 2000 : rows.first[9].to_i
Date.new(year, rows.first[8].to_i, rows.first[7].to_i)
end
end
end

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

@ -324,8 +324,8 @@ class BulkUpload::Lettings::Year2024::RowParser
category: :setup,
},
format: {
with: /\A\d{2}\z/,
message: I18n.t("validations.setup.startdate.year_not_two_digits"),
with: /\A(\d{2}|\d{4})\z/,
message: I18n.t("validations.setup.startdate.year_not_two_or_four_digits"),
category: :setup,
unless: -> { field_10.blank? },
},
@ -686,14 +686,6 @@ private
end
end
def start_date
return if field_8.blank? || field_9.blank? || field_10.blank?
Date.parse("20#{field_10.to_s.rjust(2, '0')}-#{field_9}-#{field_8}")
rescue StandardError
nil
end
def validate_no_and_dont_know_disabled_needs_conjunction
if field_83 == 1 && field_84 == 1
errors.add(:field_83, I18n.t("validations.household.housingneeds.no_and_dont_know_disabled_needs_conjunction"))
@ -790,9 +782,9 @@ private
end
def validate_relevant_collection_window
return if start_date.blank? || bulk_upload.form.blank?
return if startdate.blank? || bulk_upload.form.blank?
unless bulk_upload.form.valid_start_date_for_form?(start_date)
unless bulk_upload.form.valid_start_date_for_form?(startdate)
errors.add(:field_8, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
errors.add(:field_9, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
errors.add(:field_10, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
@ -1394,7 +1386,8 @@ private
end
def startdate
Date.new(field_10 + 2000, field_9, field_8) if field_10.present? && field_9.present? && field_8.present?
year = field_10.to_s.strip.length.between?(1, 2) ? field_10 + 2000 : field_10
Date.new(year, field_9, field_8) if field_10.present? && field_9.present? && field_8.present?
rescue Date::Error
Date.new
end
@ -1599,13 +1592,15 @@ private
end
def mrcdate
Date.new(field_35 + 2000, field_34, field_33) if field_35.present? && field_34.present? && field_33.present?
year = field_35.to_s.strip.length.between?(1, 2) ? field_35 + 2000 : field_35
Date.new(year, field_34, field_33) if field_35.present? && field_34.present? && field_33.present?
rescue Date::Error
Date.new
end
def voiddate
Date.new(field_32 + 2000, field_31, field_30) if field_32.present? && field_31.present? && field_30.present?
year = field_32.to_s.strip.length.between?(1, 2) ? field_32 + 2000 : field_32
Date.new(year, field_31, field_30) if field_32.present? && field_31.present? && field_30.present?
rescue Date::Error
Date.new
end

6
app/services/bulk_upload/sales/year2023/csv_parser.rb

@ -111,9 +111,11 @@ private
def first_record_start_date
if with_headers?
Date.new(row_parsers.first.field_5.to_i + 2000, row_parsers.first.field_4.to_i, row_parsers.first.field_3.to_i)
year = row_parsers.first.field_5.to_s.strip.length.between?(1, 2) ? row_parsers.first.field_5.to_i + 2000 : row_parsers.first.field_5.to_i
Date.new(year, row_parsers.first.field_4.to_i, row_parsers.first.field_3.to_i)
else
Date.new(rows.first[3].to_i + 2000, rows.first[2].to_i, rows.first[1].to_i)
year = rows.first[3].to_s.strip.length.between?(1, 2) ? rows.first[3].to_i + 2000 : rows.first[3].to_i
Date.new(year, rows.first[2].to_i, rows.first[1].to_i)
end
end
end

13
app/services/bulk_upload/sales/year2023/row_parser.rb

@ -328,8 +328,8 @@ class BulkUpload::Sales::Year2023::RowParser
category: :setup,
},
format: {
with: /\A\d{2}\z/,
message: I18n.t("validations.setup.saledate.year_not_two_digits"),
with: /\A(\d{2}|\d{4})\z/,
message: I18n.t("validations.setup.saledate.year_not_two_or_four_digits"),
category: :setup,
if: proc { field_5.present? },
}, on: :after_log
@ -954,19 +954,22 @@ private
end
def saledate
Date.new(field_5 + 2000, field_4, field_3) if field_5.present? && field_4.present? && field_3.present?
year = field_5.to_s.strip.length.between?(1, 2) ? field_5 + 2000 : field_5
Date.new(year, field_4, field_3) if field_5.present? && field_4.present? && field_3.present?
rescue Date::Error
Date.new
end
def hodate
Date.new(field_97 + 2000, field_96, field_95) if field_97.present? && field_96.present? && field_95.present?
year = field_97.to_s.strip.length.between?(1, 2) ? field_97 + 2000 : field_97
Date.new(year, field_96, field_95) if field_97.present? && field_96.present? && field_95.present?
rescue Date::Error
Date.new
end
def exdate
Date.new(field_94 + 2000, field_93, field_92) if field_94.present? && field_93.present? && field_92.present?
year = field_94.to_s.strip.length.between?(1, 2) ? field_94 + 2000 : field_94
Date.new(year, field_93, field_92) if field_94.present? && field_93.present? && field_92.present?
rescue Date::Error
Date.new
end

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

@ -114,9 +114,11 @@ private
def first_record_start_date
if with_headers?
Date.new(row_parsers.first.field_6.to_i + 2000, row_parsers.first.field_5.to_i, row_parsers.first.field_4.to_i)
year = row_parsers.first.field_6.to_s.strip.length.between?(1, 2) ? row_parsers.first.field_6.to_i + 2000 : row_parsers.first.field_6.to_i
Date.new(year, row_parsers.first.field_5.to_i, row_parsers.first.field_4.to_i)
else
Date.new(rows.first[5].to_i + 2000, rows.first[4].to_i, rows.first[3].to_i)
year = rows.first[5].to_s.strip.length.between?(1, 2) ? rows.first[5].to_i + 2000 : rows.first[5].to_i
Date.new(year, rows.first[4].to_i, rows.first[3].to_i)
end
end
end

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

@ -320,8 +320,8 @@ class BulkUpload::Sales::Year2024::RowParser
category: :setup,
},
format: {
with: /\A\d{2}\z/,
message: I18n.t("validations.setup.saledate.year_not_two_digits"),
with: /\A(\d{2}|\d{4})\z/,
message: I18n.t("validations.setup.saledate.year_not_two_or_four_digits"),
category: :setup,
if: proc { field_6.present? },
}, on: :after_log
@ -994,19 +994,22 @@ private
end
def saledate
Date.new(field_6 + 2000, field_5, field_4) if field_6.present? && field_5.present? && field_4.present?
year = field_6.to_s.strip.length.between?(1, 2) ? field_6 + 2000 : field_6
Date.new(year, field_5, field_4) if field_6.present? && field_5.present? && field_4.present?
rescue Date::Error
Date.new
end
def hodate
Date.new(field_96 + 2000, field_95, field_94) if field_96.present? && field_95.present? && field_94.present?
year = field_96.to_s.strip.length.between?(1, 2) ? field_96 + 2000 : field_96
Date.new(year, field_95, field_94) if field_96.present? && field_95.present? && field_94.present?
rescue Date::Error
Date.new
end
def exdate
Date.new(field_93 + 2000, field_92, field_91) if field_93.present? && field_92.present? && field_91.present?
year = field_93.to_s.strip.length.between?(1, 2) ? field_93 + 2000 : field_93
Date.new(year, field_92, field_91) if field_93.present? && field_92.present? && field_91.present?
rescue Date::Error
Date.new
end
@ -1469,10 +1472,10 @@ private
def validate_buyer1_economic_status
if field_35 == 9
if field_31.present? && field_31.to_i >= 16
errors.add(:field_35, I18n.t("validations.household.ecstat.buyer_cannot_be_over_16_and_child", buyer_index: "1"))
errors.add(:field_31, I18n.t("validations.household.ecstat.buyer_cannot_be_over_16_and_child", buyer_index: "1"))
errors.add(:field_35, I18n.t("validations.sales.household.ecstat.buyer_cannot_be_over_16_and_child", buyer_index: "1"))
errors.add(:field_31, I18n.t("validations.sales.household.ecstat.buyer_cannot_be_over_16_and_child", buyer_index: "1"))
else
errors.add(:field_35, I18n.t("validations.household.ecstat.buyer_cannot_be_child", buyer_index: "1"))
errors.add(:field_35, I18n.t("validations.sales.household.ecstat1.buyer_cannot_be_child"))
end
end
end
@ -1482,10 +1485,10 @@ private
if field_42 == 9
if field_38.present? && field_38.to_i >= 16
errors.add(:field_42, I18n.t("validations.household.ecstat.buyer_cannot_be_over_16_and_child", buyer_index: "2"))
errors.add(:field_38, I18n.t("validations.household.ecstat.buyer_cannot_be_over_16_and_child", buyer_index: "2"))
errors.add(:field_42, I18n.t("validations.sales.household.ecstat.buyer_cannot_be_over_16_and_child", buyer_index: "2"))
errors.add(:field_38, I18n.t("validations.sales.household.ecstat.buyer_cannot_be_over_16_and_child", buyer_index: "2"))
else
errors.add(:field_42, I18n.t("validations.household.ecstat.buyer_cannot_be_child", buyer_index: "2"))
errors.add(:field_42, I18n.t("validations.sales.household.ecstat2.buyer_cannot_be_child"))
end
end
end

7
app/services/collection_resources_service.rb

@ -24,6 +24,11 @@ class CollectionResourcesService
end
def upload_collection_resource(filename, file)
@storage_service.write_file(filename, file)
content_type = MiniMime.lookup_by_filename(filename)&.content_type
@storage_service.write_file(filename, file, content_type:)
end
def delete_collection_resource(filename)
@storage_service.delete_file(filename)
end
end

1
app/services/mandatory_collection_resources_service.rb

@ -27,6 +27,7 @@ class MandatoryCollectionResourcesService
year:,
log_type:,
download_filename: download_filename(resource_type, year, log_type),
mandatory: true,
)
end

10
app/services/storage/local_disk_service.rb

@ -19,7 +19,9 @@ module Storage
File.open(path, "r")
end
def write_file(filename, data)
# rubocop:disable Lint/UnusedMethodArgument
def write_file(filename, data, content_type: nil)
# rubocop:enable Lint/UnusedMethodArgument
path = Rails.root.join("tmp/storage", filename)
FileUtils.mkdir_p(path.dirname)
@ -43,5 +45,11 @@ module Storage
File.exist?(path)
end
def delete_file(filename)
path = Rails.root.join("tmp/storage", filename)
File.delete(path)
end
end
end

25
app/services/storage/s3_service.rb

@ -36,12 +36,21 @@ module Storage
.body.read
end
def write_file(file_name, data)
@client.put_object(
body: data,
bucket: @configuration.bucket_name,
key: file_name,
)
def write_file(file_name, data, content_type: nil)
if content_type.nil?
@client.put_object(
body: data,
bucket: @configuration.bucket_name,
key: file_name,
)
else
@client.put_object(
body: data,
bucket: @configuration.bucket_name,
key: file_name,
content_type:,
)
end
end
def get_file_metadata(file_name)
@ -55,6 +64,10 @@ module Storage
false
end
def delete_file(file_name)
@client.delete_object(bucket: @configuration.bucket_name, key: file_name)
end
private
def create_configuration

8
app/services/storage/storage_service.rb

@ -15,5 +15,13 @@ module Storage
def write_file(_file_name, _data)
raise NotImplementedError
end
def get_file(_file_name, _data)
raise NotImplementedError
end
def delete_file(_file_name, _data)
raise NotImplementedError
end
end
end

23
app/views/collection_resources/_collection_resource_summary_list.erb

@ -23,10 +23,27 @@
<% end %>
<% end %>
<% end %>
<% additional_resources&.each do |resource| %>
<% summary_list.with_row do |row| %>
<% row.with_key { resource.short_display_name } %>
<% row.with_value do %>
<%= render DocumentListComponent.new(items: document_list_edit_component_items([resource]), label: "") %>
<% end %>
<% row.with_action(
text: "Change",
href: collection_resource_edit_path(resource),
) %>
<% row.with_action(
text: "Delete",
href: collection_resource_delete_confirmation_path(resource),
classes: "app-!-colour-red"
) %>
<% end %>
<% end %>
<% end %>
<div class="govuk-!-margin-bottom-6">
<%= govuk_link_to "Add new #{mandatory_resources.first.log_type} #{text_year_range_format(mandatory_resources.first.year)} resource", href: "/" %>
<div class="govuk-!-margin-bottom-8">
<%= govuk_link_to "Add new #{mandatory_resources.first.log_type} #{text_year_range_format(mandatory_resources.first.year)} resource", href: new_collection_resource_path(year: mandatory_resources.first.year, log_type: mandatory_resources.first.log_type) %>
</div>
<hr class="govuk-section-break govuk-section-break--visible govuk-section-break--m">
<hr class="govuk-section-break govuk-section-break--visible govuk-section-break--m govuk-!-margin-bottom-8">
</div>
</div>

31
app/views/collection_resources/delete_confirmation.html.erb

@ -0,0 +1,31 @@
<% content_for :before_content do %>
<% content_for :title, "Are you sure you want to delete the #{@collection_resource.short_display_name.downcase}?" %>
<%= govuk_back_link href: collection_resources_path %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<span class="govuk-caption-l"><%= "#{@collection_resource.log_type.humanize} #{text_year_range_format(@collection_resource.year)}" %></span>
<h1 class="govuk-heading-xl">
<%= content_for(:title) %>
</h1>
<p class="govuk-body app-!-colour-muted">
This file will no longer be available for users to download.
</p>
<%= govuk_warning_text(text: "You will not be able to undo this action.") %>
<div class="govuk-button-group">
<%= govuk_button_to(
"Delete resource",
collection_resource_delete_path(@collection_resource),
method: :delete,
) %>
<%= govuk_button_link_to(
"Cancel",
collection_resources_path,
secondary: true,
) %>
</div>
</div>
</div>

21
app/views/collection_resources/edit.html.erb

@ -5,7 +5,7 @@
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<% resource_exists = file_exists_on_s3?(@collection_resource.download_filename) %>
<%= form_with model: @collection_resource, url: update_mandatory_collection_resource_path, method: :patch do |f| %>
<%= form_with model: @collection_resource, url: @collection_resource.mandatory ? update_mandatory_collection_resource_path : collection_resource_update_path(@collection_resource), method: :patch do |f| %>
<%= f.hidden_field :year %>
<%= f.hidden_field :log_type %>
<%= f.hidden_field :resource_type %>
@ -13,7 +13,7 @@
<%= f.govuk_error_summary %>
<span class="govuk-caption-l"><%= "#{@collection_resource.log_type.humanize} #{text_year_range_format(@collection_resource.year)}" %></span>
<h1 class="govuk-heading-l"><%= resource_exists ? "Change" : "Upload" %> the <%= @collection_resource.resource_type.humanize.downcase %></h1>
<h1 class="govuk-heading-l"><%= resource_exists ? "Change" : "Upload" %> the <%= @collection_resource.short_display_name.downcase %></h1>
<p class="govuk-body">
This file will be available for all users to download.
@ -21,9 +21,22 @@
<%= f.govuk_file_field :file,
label: { text: "Upload file", size: "m" } %>
<% if resource_exists %>
<p class="govuk-body">Current file: <%= govuk_link_to @collection_resource.download_filename, href: @collection_resource.download_path %></p>
<% end %>
<%= f.govuk_submit resource_exists ? "Save changes" : "Upload" %>
<%= govuk_button_link_to "Cancel", collection_resources_path, secondary: true %>
<% unless @collection_resource.mandatory %>
<%= f.govuk_text_field :short_display_name,
label: { text: "Resource type", size: "m" },
hint: { text: safe_join(["This will be used in the download link on the homepage. Do not include the log type or collection year.",
content_tag(:br),
"For example, if you enter “bulk upload change log”, the download link will say “Download the #{@collection_resource.log_type} bulk upload change log (#{text_year_range_format(@collection_resource.year)})”."]) } %>
<% end %>
<div class="govuk-button-group">
<%= f.govuk_submit resource_exists ? "Save changes" : "Upload" %>
<%= govuk_button_link_to "Cancel", collection_resources_path, secondary: true %>
</div>
<% end %>
</div>
</div>

9
app/views/collection_resources/index.html.erb

@ -14,16 +14,13 @@
<% end %>
<h1 class="govuk-heading-l"><%= title %></h1>
<% @mandatory_lettings_collection_resources_per_year.each do |year, mandatory_resources| %>
<% editable_collection_resource_years.each do |year| %>
<h2 class="govuk-heading-m">
Lettings <%= text_year_range_format(year) %>
</h2>
<%= render partial: "collection_resource_summary_list", locals: { mandatory_resources: } %>
<% end %>
<% @mandatory_sales_collection_resources_per_year.each do |year, mandatory_resources| %>
<%= render partial: "collection_resource_summary_list", locals: { mandatory_resources: @mandatory_lettings_collection_resources_per_year[year], additional_resources: @additional_lettings_collection_resources_per_year[year] } %>
<h2 class="govuk-heading-m">
Sales <%= text_year_range_format(year) %>
</h2>
<%= render partial: "collection_resource_summary_list", locals: { mandatory_resources: } %>
<%= render partial: "collection_resource_summary_list", locals: { mandatory_resources: @mandatory_sales_collection_resources_per_year[year], additional_resources: @additional_sales_collection_resources_per_year[year] } %>
<% end %>

36
app/views/collection_resources/new.html.erb

@ -0,0 +1,36 @@
<% content_for :before_content do %>
<%= govuk_back_link href: collection_resources_path %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= form_with model: @collection_resource, url: collection_resources_path, method: :post do |f| %>
<%= f.hidden_field :year %>
<%= f.hidden_field :log_type %>
<%= f.hidden_field :mandatory, value: false %>
<%= f.govuk_error_summary %>
<span class="govuk-caption-l"><%= "#{@collection_resource.log_type.humanize} #{text_year_range_format(@collection_resource.year)}" %></span>
<h1 class="govuk-heading-l">Add a new collection resource</h1>
<p class="govuk-body">
This file will be available for all users to download.
</p>
<%= f.govuk_file_field :file,
label: { text: "Upload file", size: "m" } %>
<%= f.govuk_text_field :short_display_name,
label: { text: "Resource type", size: "m" },
hint: { text: safe_join(["This will be used in the download link on the homepage. Do not include the log type or collection year.",
content_tag(:br),
"For example, if you enter “bulk upload change log”, the download link will say “Download the #{@collection_resource.log_type} bulk upload change log (#{text_year_range_format(@collection_resource.year)})”."]) } %>
<div class="govuk-button-group">
<%= f.govuk_submit "Add resource" %>
<%= govuk_button_link_to "Cancel", collection_resources_path, secondary: true %>
</div>
<% end %>
</div>
</div>

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

@ -12,12 +12,12 @@
<%= govuk_tabs(title: "Collection resources", classes: %w[app-tab__small-headers]) do |c| %>
<% @mandatory_lettings_collection_resources_per_year.each do |year, resources| %>
<% c.with_tab(label: "Lettings #{year_range_format(year)}") do %>
<%= render DocumentListComponent.new(items: document_list_component_items(resources), label: "Lettings #{text_year_range_format(year)}") %>
<%= render DocumentListComponent.new(items: document_list_component_items(resources.concat(@additional_lettings_collection_resources_per_year[year] || [])), label: "Lettings #{text_year_range_format(year)}") %>
<% end %>
<% end %>
<% @mandatory_sales_collection_resources_per_year.each do |year, resources| %>
<% c.with_tab(label: "Sales #{year_range_format(year)}") do %>
<%= render DocumentListComponent.new(items: document_list_component_items(resources), label: "Sales #{text_year_range_format(year)}") %>
<%= render DocumentListComponent.new(items: document_list_component_items(resources.concat(@additional_sales_collection_resources_per_year[year] || [])), label: "Sales #{text_year_range_format(year)}") %>
<% end %>
<% end %>
<% end %>

2
app/views/logs/edit.html.erb

@ -38,6 +38,6 @@
<%= render "tasklist" %>
<%= edit_actions_for_log(@log) %>
<%= edit_actions_for_log(@log, bulk_upload_filter_applied) %>
</div>
</div>

56
config/locales/en.yml

@ -122,7 +122,7 @@ en:
file:
error_uploading: There was an error uploading this file.
blank: Select which file to upload.
above_100_mb: The file is above 100MB.
above_100_mb: File must be 100MB or less.
must_be_pdf: The paper form must be a PDF.
must_be_xlsx: The %{resource} must be a Microsoft Excel file.
@ -212,9 +212,12 @@ en:
file:
error_uploading: There was an error uploading this file.
blank: Select which file to upload.
above_100_mb: The file is above 100MB.
above_100_mb: File must be 100MB or less.
must_be_pdf: The paper form must be a PDF.
must_be_xlsx: The %{resource} must be a Microsoft Excel file.
short_display_name:
blank: "You must answer resource type."
notification:
logs_deleted:
one: "%{count} log has been deleted."
@ -281,7 +284,7 @@ en:
intermediate_rent_product_name:
blank: "Enter name of other intermediate rent product."
saledate:
year_not_two_digits: "Sale completion year must be 2 digits."
year_not_two_or_four_digits: "Sale completion year must be 2 or 4 digits."
type:
percentage_bought_must_be_at_least_threshold: "The minimum increase in equity while staircasing is %{threshold}% for this shared ownership type."
@ -294,7 +297,7 @@ en:
before_scheme_end_date: "The tenancy start date must be before the end date for this supported housing scheme."
after_void_date: "Enter a tenancy start date that is after the void date."
after_major_repair_date: "Enter a tenancy start date that is after the major repair date."
year_not_two_digits: "Tenancy start year must be 2 digits."
year_not_two_or_four_digits: "Tenancy start year must be 2 or 4 digits."
ten_years_after_void_date: "Enter a tenancy start date that is no more than 10 years after the void date."
ten_years_after_mrc_date: "Enter a tenancy start date that is no more than 10 years after the major repairs completion date."
invalid_merged_organisations_start_date:
@ -382,12 +385,6 @@ en:
one_bedroom_bedsit: "A bedsit can only have one bedroom."
one_seven_bedroom_shared: "A shared house must have 1 to 7 bedrooms."
one_three_bedroom_single_tenant_shared: "A shared house with fewer than two tenants must have 1 to 3 bedrooms."
beds:
bedsits_have_max_one_bedroom: "Number of bedrooms must be 1 if the property is a bedsit."
proptype:
bedsits_have_max_one_bedroom: "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms."
postcode:
must_match_previous: "%{buyer_possessive} last accommodation and discounted ownership postcodes must match."
financial:
tshortfall:
@ -501,11 +498,8 @@ en:
retired_female: "A female tenant who is retired must be 60 or over."
retired_over_70: "Answer cannot be over 70 as person %{person_num} has economic status that is not ‘retired’."
child_under_16_relat_lettings: "Answer cannot be under 16 as person %{person_num}'s relationship to the lead tenant is ‘partner’."
child_under_16_relat_sales: "Answer cannot be under 16 as person %{person_num}'s relationship to buyer 1 is ‘partner’."
child_under_16_ecstat: "Answer cannot be under 16 as person %{person_num}’s working situation is not ‘child under 16’, ‘other’ or ‘prefers not to say’."
child_over_16: "Answer cannot be over 16 as person’s %{person_num} working situation is ‘child under 16‘."
child_over_20: "Answer cannot be 20 or over as the relationship is ‘child’."
child_12_years_younger: "A child must be at least 12 years younger than their parent."
not_student_16_19: "Answer cannot be between 16 and 19 as person %{person_num} is a child of the lead tenant but is not a full-time student."
student_16_19:
cannot_be_16_19:
@ -528,12 +522,8 @@ en:
retired_female: "Answer cannot be ‘retired’ as the female tenant is under 60."
not_child_16_19:
cannot_be_student: "Person cannot be a student if they are aged 16-19 but are not a child."
buyer_cannot_be_child: "Buyer %{buyer_index} cannot have a working situation of child under 16."
buyer_cannot_be_over_16_and_child: "Buyer %{buyer_index}'s age cannot be 16 or over if their working situation is child under 16."
relat:
child_under_16_sales: "Answer cannot be ‘partner’ as you told us person %{person_num}'s age is under 16."
child_under_16_lettings: "Answer cannot be ‘partner’ as you told us person %{person_num}'s age is under 16."
child_over_20: "Answer cannot be ‘child’ if the person's age is 20 or over."
one_partner: "Number of partners cannot be greater than 1."
not_student_16_19: "Answer cannot be ‘child’ as you told us the person %{person_num} is between 16 and 19 and is not a full-time student."
student_16_19:
@ -556,7 +546,6 @@ en:
internal_transfer: "Answer cannot be %{prevten} as this tenancy is an internal transfer."
la_general_needs:
internal_transfer: "Answer cannot be a fixed-term or lifetime local authority general needs tenancy as it’s an internal transfer and a private registered provider is on the tenancy agreement."
invalid_for_discounted_sale: "Buyer 1’s previous tenure should be “local authority tenant” or “private registered provider or housing association tenant” for discounted sales."
referral:
secure_tenancy: "Answer must be internal transfer as this is a secure tenancy."
rsnvac_non_temp: "Answer cannot be this source of referral as this is a re-let to tenant who occupied the same property as temporary accommodation."
@ -596,9 +585,6 @@ en:
no_choices: "You cannot answer this question as you told us nobody in the household has a physical or mental health condition (or other illness) expected to last 12 months or more."
postcode:
discounted_ownership: "Last settled accommodation and discounted ownership property postcodes must match."
buylivein:
buyers_will_live_in_property_values_inconsistent_setup: "You have already told us that both buyer 1 and buyer 2 will not live in the property."
buyers_will_live_in_property_values_inconsistent: "You have already told us that the buyers will live in the property. Either buyer 1 or buyer 2 must live in the property."
nationality: "Select a valid nationality."
tenancy:
@ -703,13 +689,6 @@ en:
message: "Net income is lower than expected based on the household’s working situation. Are you sure this is correct?"
in_soft_max_range:
message: "Net income is higher than expected based on the household’s working situation. Are you sure this is correct?"
income:
under_soft_min_for_economic_status:
title_text: "You told us income was %{income}."
hint_text: "This is less than we would expect for someone in this working situation."
over_soft_max_for_la_buyer_1: "You told us the income of buyer 1 is %{income}. This seems high. Are you sure this is correct?"
over_soft_max_for_la_buyer_2: "You told us the income of buyer 2 is %{income}. This seems high. Are you sure this is correct?"
over_soft_max_for_la_combined: "You told us the combined income of this household is %{combined_income}. This seems high. Are you sure this is correct?"
rent:
outside_range_title: "You told us the rent is %{brent}."
informative_text: "This is %{higher_or_lower} than we would expect."
@ -772,24 +751,9 @@ Make sure these answers are correct."
percentage_discount_value:
title_text: "You told us that the percentage discount is %{discount}."
hint_text: "This is higher than we would expect."
savings:
title_text:
one: "You told us the buyer’s savings were %{savings}."
two: "You told us the buyers’ savings were %{savings}."
hint_text: "This is higher than we would expect."
deposit:
title_text:
one: "You told us the buyer’s deposit was %{deposit} and their savings were %{savings}."
two: "You told us the buyers’ deposit was %{deposit} and their savings were %{savings}."
hint_text: "The deposit amount is higher than we would expect for the amount of savings they have."
grant:
title_text: "You told us that the grant amount is %{grant}."
hint_text: "Loans, grants and subsidies are usually between £9,000 and £16,000."
wheelchair:
title_text: "You told us that someone in the household uses a wheelchair."
mortgage:
title_text: "You told us that the mortgage amount is %{mortgage}."
hint_text: "This is more than 5 times the income, which is higher than we would expect."
referral:
title_text: "Are you sure?"
hint_text: "This is a general needs log, and this referral type is for supported housing."
@ -859,9 +823,6 @@ Make sure these answers are correct."
soctenant:
one: "Was the buyer a private registered provider, housing association or local authority tenant immediately before this sale?"
other: "Were any of the buyers private registered providers, housing association or local authority tenants immediately before this sale?"
prevown:
one: "Has the buyer previously owned a property?"
other: "Have any of the buyers previously owned a property?"
stairowned:
one: "What percentage of the property does the buyer now own in total?"
other: "What percentage of the property do the buyers now own in total?"
@ -884,9 +845,6 @@ Make sure these answers are correct."
soctenant:
one: "Buyer was a registered provider, housing association or local authority tenant immediately before this sale?"
other: "Any buyers were registered providers, housing association or local authority tenants immediately before this sale?"
prevown:
one: "Buyer previously owned a property."
other: "Buyers previously owned a property."
stairowned:
one: "Percentage the buyer now owns in total."
other: "Percentage the buyers now own in total."

34
config/locales/forms/2023/sales/household_needs.en.yml

@ -0,0 +1,34 @@
en:
forms:
2023:
sales:
household_needs:
hhregres:
page_header: ""
check_answer_label: "Have any of the buyers 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"
question_text: "Have any of the buyers ever served as a regular in the UK armed forces?"
hhregresstill:
page_header: ""
check_answer_label: "Are they still serving in the UK armed forces?"
hint_text: ""
question_text: "Is the buyer still serving in the UK armed forces?"
armedforcesspouse:
page_header: ""
check_answer_label: "Are any of the buyers a spouse or civil partner of a UK armed forces regular who died in service within the last 2 years?"
hint_text: ""
question_text: "Are any of the buyers a spouse or civil partner of a UK armed forces regular who died in service within the last 2 years?"
disabled:
page_header: ""
check_answer_label: "Does anyone in the household have a disability?"
hint_text: "This includes any long-term health condition that has an impact on the person's day-to-day life"
question_text: "Does anyone in the household consider themselves to have a disability?"
wheel:
page_header: ""
check_answer_label: "Does anyone in the household use a wheelchair?"
hint_text: "This can be inside or outside the home"
question_text: "Does anyone in the household use a wheelchair?"

90
config/locales/forms/2023/sales/income_benefits_and_savings.en.yml

@ -0,0 +1,90 @@
en:
forms:
2023:
sales:
income_benefits_and_savings:
buyer_1_income:
page_header: ""
income1nk:
check_answer_label: "Buyer 1’s gross annual income known?"
hint_text: ""
question_text: "Do you know buyer 1’s annual income?"
income1:
check_answer_label: "Buyer 1’s gross annual income"
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: "Buyer 1’s gross annual income"
inc1mort:
page_header: ""
check_answer_label: "Buyer 1’s income used for mortgage application"
hint_text: ""
question_text: "Was buyer 1’s income used for a mortgage application?"
buyer_2_income:
page_header: ""
income2nk:
check_answer_label: "Buyer 2’s gross annual income known?"
hint_text: ""
question_text: "Do you know buyer 2’s annual income?"
income2:
check_answer_label: "Buyer 2’s gross annual income"
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: "Buyer 2’s gross annual income"
inc2mort:
page_header: ""
check_answer_label: "Buyer 2’s income used for mortgage application"
hint_text: ""
question_text: "Was buyer 2’s income used for a mortgage application?"
housing_benefits:
joint_purchase:
page_header: ""
check_answer_label: "Housing-related benefits buyers received before buying this property"
hint_text: ""
question_text: "Were the buyers receiving any of these housing-related benefits immediately before buying this property?"
not_joint_purchase:
page_header: ""
check_answer_label: "Housing-related benefits buyer received before buying this property"
hint_text: ""
question_text: "Was the buyer receiving any of these housing-related benefits immediately before buying this property?"
savings:
joint_purchase:
page_header: ""
savingsnk:
check_answer_label: "Buyers’ total savings known?"
hint_text: ""
question_text: "Do you know how much the 'buyers' had in savings before they paid any deposit for the property?"
savings:
check_answer_label: "Buyers’ total savings before any deposit paid"
hint_text: "Include any savings, investments, ISAs, premium bonds, shares, or money held in a bank or building society account."
question_text: "Enter their total savings to the nearest £10"
not_joint_purchase:
page_header: ""
savingsnk:
check_answer_label: "Buyer’s total savings known?"
hint_text: ""
question_text: "Do you know how much the buyer had in savings before they paid any deposit for the property?"
savings:
check_answer_label: "Buyer’s total savings before any deposit paid"
hint_text: "Include any savings, investments, ISAs, premium bonds, shares, or money held in a bank or building society account."
question_text: "Enter their total savings to the nearest £10"
prevown:
joint_purchase:
page_header: ""
check_answer_label: "Buyers previously owned a property."
hint_text: ""
question_text: "Have any of the buyers previously owned a property?"
not_joint_purchase:
page_header: ""
check_answer_label: "Buyer previously owned a property."
hint_text: ""
question_text: "Has the buyer previously owned a property?"
prevshared:
page_header: ""
check_answer_label: "Previous property shared ownership?"
hint_text: "For any buyer"
question_text: "Was the previous property under shared ownership?"

66
config/locales/forms/2023/sales/soft_validations.en.yml

@ -0,0 +1,66 @@
en:
forms:
2023:
sales:
soft_validations:
income1_value_check:
page_header: ""
check_answer_label: "Buyer 1 income confirmation"
hint_text: ""
question_text: "Are you sure this is correct?"
min:
title_text: "You told us income was %{income}."
informative_text: "This is less than we would expect for someone in this working situation."
max:
title_text: "You told us the income of buyer 1 is %{income}. This seems high. Are you sure this is correct?"
income2_value_check:
page_header: ""
check_answer_label: "Buyer 2 income confirmation"
hint_text: ""
question_text: "Are you sure this is correct?"
min:
title_text: "You told us income was %{income}."
informative_text: "This is less than we would expect for someone in this working situation."
max:
title_text: "You told us the income of buyer 2 is %{income}. This seems high. Are you sure this is correct?"
combined_income_value_check:
page_header: ""
check_answer_label: "Combined income confirmation"
hint_text: ""
question_text: "Are you sure this is correct?"
title_text: "You told us the combined income of this household is %{combined_income}. This seems high. Are you sure this is correct?"
mortgage_value_check:
page_header: ""
check_answer_label: "Mortgage confirmation"
hint_text: ""
question_text: "Are you sure that the mortgage is more than 5 times the income used for the mortgage application?"
title_text: "You told us that the mortgage amount is %{mortgage}."
informative_text: "This is more than 5 times the income, which is higher than we would expect."
savings_value_check:
page_header: ""
check_answer_label: "Savings confirmation"
hint_text: ""
question_text: "Are you sure the savings are higher than £100,000?"
joint_purchase:
title_text: You told us the buyers’ savings were %{savings}."
informative_text: "This is higher than we would expect."
not_joint_purchase:
title_text: "You told us the buyer’s savings were %{savings}."
informative_text: "This is higher than we would expect."
deposit_value_check::
page_header: ""
check_answer_label: "Deposit confirmation"
hint_text: ""
question_text: "Are you sure that the deposit is this much higher than the buyer's savings?"
joint_purchase:
title_text: "You told us the buyers’ deposit was %{deposit} and their savings were %{savings}."
informative_text: "The deposit amount is higher than we would expect for the amount of savings they have."
not_joint_purchase:
title_text: "You told us the buyer’s deposit was %{deposit} and their savings were %{savings}."
informative_text: "The deposit amount is higher than we would expect for the amount of savings they have."
wheel_value_check:
page_header: ""
check_answer_label: "Does anyone in the household use a wheelchair?"
hint_text: ""
question_text: "You told us that someone in the household uses a wheelchair."
title_text: "You told us that someone in the household uses a wheelchair."

34
config/locales/forms/2024/sales/household_needs.en.yml

@ -0,0 +1,34 @@
en:
forms:
2024:
sales:
household_needs:
hhregres:
page_header: ""
check_answer_label: "Have any of the buyers 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"
question_text: "Have any of the buyers ever served as a regular in the UK armed forces?"
hhregresstill:
page_header: ""
check_answer_label: "Are they still serving in the UK armed forces?"
hint_text: ""
question_text: "Is the buyer still serving in the UK armed forces?"
armedforcesspouse:
page_header: ""
check_answer_label: "Are any of the buyers a spouse or civil partner of a UK armed forces regular who died in service within the last 2 years?"
hint_text: ""
question_text: "Are any of the buyers a spouse or civil partner of a UK armed forces regular who died in service within the last 2 years?"
disabled:
page_header: ""
check_answer_label: "Does anyone in the household have a disability?"
hint_text: "This includes any long-term health condition that has an impact on the person's day-to-day life"
question_text: "Does anyone in the household consider themselves to have a disability?"
wheel:
page_header: ""
check_answer_label: "Does anyone in the household use a wheelchair?"
hint_text: "This can be inside or outside the home"
question_text: "Does anyone in the household use a wheelchair?"

90
config/locales/forms/2024/sales/income_benefits_and_savings.en.yml

@ -0,0 +1,90 @@
en:
forms:
2024:
sales:
income_benefits_and_savings:
buyer_1_income:
page_header: ""
income1nk:
check_answer_label: "Buyer 1’s gross annual income known?"
hint_text: ""
question_text: "Do you know buyer 1’s annual income?"
income1:
check_answer_label: "Buyer 1’s gross annual income"
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: "Buyer 1’s gross annual income"
inc1mort:
page_header: ""
check_answer_label: "Buyer 1’s income used for mortgage application"
hint_text: ""
question_text: "Was buyer 1’s income used for a mortgage application?"
buyer_2_income:
page_header: ""
income2nk:
check_answer_label: "Buyer 2’s gross annual income known?"
hint_text: ""
question_text: "Do you know buyer 2’s annual income?"
income2:
check_answer_label: "Buyer 2’s gross annual income"
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: "Buyer 2’s gross annual income"
inc2mort:
page_header: ""
check_answer_label: "Buyer 2’s income used for mortgage application"
hint_text: ""
question_text: "Was buyer 2’s income used for a mortgage application?"
housing_benefits:
joint_purchase:
page_header: ""
check_answer_label: "Housing-related benefits buyers received before buying this property"
hint_text: ""
question_text: "Were the buyers receiving any of these housing-related benefits immediately before buying this property?"
not_joint_purchase:
page_header: ""
check_answer_label: "Housing-related benefits buyer received before buying this property"
hint_text: ""
question_text: "Was the buyer receiving any of these housing-related benefits immediately before buying this property?"
savings:
joint_purchase:
page_header: ""
savingsnk:
check_answer_label: "Buyers’ total savings known?"
hint_text: ""
question_text: "Do you know how much the 'buyers' had in savings before they paid any deposit for the property?"
savings:
check_answer_label: "Buyers’ total savings before any deposit paid"
hint_text: "Include any savings, investments, ISAs, premium bonds, shares, or money held in a bank or building society account."
question_text: "Enter their total savings to the nearest £10"
not_joint_purchase:
page_header: ""
savingsnk:
check_answer_label: "Buyer’s total savings known?"
hint_text: ""
question_text: "Do you know how much the buyer had in savings before they paid any deposit for the property?"
savings:
check_answer_label: "Buyer’s total savings before any deposit paid"
hint_text: "Include any savings, investments, ISAs, premium bonds, shares, or money held in a bank or building society account."
question_text: "Enter their total savings to the nearest £10"
prevown:
joint_purchase:
page_header: ""
check_answer_label: "Buyers previously owned a property."
hint_text: ""
question_text: "Have any of the buyers previously owned a property?"
not_joint_purchase:
page_header: ""
check_answer_label: "Buyer previously owned a property."
hint_text: ""
question_text: "Has the buyer previously owned a property?"
prevshared:
page_header: ""
check_answer_label: "Previous property shared ownership?"
hint_text: "For any buyer"
question_text: "Was the previous property under shared ownership?"

66
config/locales/forms/2024/sales/soft_validations.en.yml

@ -0,0 +1,66 @@
en:
forms:
2024:
sales:
soft_validations:
income1_value_check:
page_header: ""
check_answer_label: "Buyer 1 income confirmation"
hint_text: ""
question_text: "Are you sure this is correct?"
min:
title_text: "You told us income was %{income}."
informative_text: "This is less than we would expect for someone in this working situation."
max:
title_text: "You told us the income of buyer 1 is %{income}. This seems high. Are you sure this is correct?"
income2_value_check:
page_header: ""
check_answer_label: "Buyer 2 income confirmation"
hint_text: ""
question_text: "Are you sure this is correct?"
min:
title_text: "You told us income was %{income}."
informative_text: "This is less than we would expect for someone in this working situation."
max:
title_text: "You told us the income of buyer 2 is %{income}. This seems high. Are you sure this is correct?"
combined_income_value_check:
page_header: ""
check_answer_label: "Combined income confirmation"
hint_text: ""
question_text: "Are you sure this is correct?"
title_text: "You told us the combined income of this household is %{combined_income}. This seems high. Are you sure this is correct?"
mortgage_value_check:
page_header: ""
check_answer_label: "Mortgage confirmation"
hint_text: ""
question_text: "Are you sure that the mortgage is more than 5 times the income used for the mortgage application?"
title_text: "You told us that the mortgage amount is %{mortgage}."
informative_text: "This is more than 5 times the income, which is higher than we would expect."
savings_value_check:
page_header: ""
check_answer_label: "Savings confirmation"
hint_text: ""
question_text: "Are you sure the savings are higher than £100,000?"
joint_purchase:
title_text: You told us the buyers’ savings were %{savings}."
informative_text: "This is higher than we would expect."
not_joint_purchase:
title_text: "You told us the buyer’s savings were %{savings}."
informative_text: "This is higher than we would expect."
deposit_value_check:
page_header: ""
check_answer_label: "Deposit confirmation"
hint_text: ""
question_text: "Are you sure that the deposit is this much higher than the buyer's savings?"
joint_purchase:
title_text: "You told us the buyers’ deposit was %{deposit} and their savings were %{savings}."
informative_text: "The deposit amount is higher than we would expect for the amount of savings they have."
not_joint_purchase:
title_text: "You told us the buyer’s deposit was %{deposit} and their savings were %{savings}."
informative_text: "The deposit amount is higher than we would expect for the amount of savings they have."
wheel_value_check:
page_header: ""
check_answer_label: "Does anyone in the household use a wheelchair?"
hint_text: ""
question_text: "You told us that someone in the household uses a wheelchair."
title_text: "You told us that someone in the household uses a wheelchair."

47
config/locales/validations/sales/household.en.yml

@ -0,0 +1,47 @@
en:
validations:
sales:
household:
buylivein:
buyers_will_live_in_property_values_inconsistent: "You have already told us that both buyer 1 and buyer 2 will not live in the property."
buy1livein:
buyers_will_live_in_property_values_inconsistent: "You have already told us that the buyers will live in the property. Either buyer 1 or buyer 2 must live in the property."
buy2livein:
buyers_will_live_in_property_values_inconsistent: "You have already told us that the buyers will live in the property. Either buyer 1 or buyer 2 must live in the property."
ownershipsch:
prevten_invalid_for_discounted_sale: "Buyer 1’s previous tenure should be “local authority tenant” or “private registered provider or housing association tenant” for discounted sales."
prevten:
prevten_invalid_for_discounted_sale: "Buyer 1’s previous tenure should be “local authority tenant” or “private registered provider or housing association tenant” for discounted sales."
age1:
child_12_years_younger: "A child must be at least 12 years younger than their parent."
ecstat1:
buyer_cannot_be_child: "Buyer 1 cannot have a working situation of child under 16."
ecstat2:
buyer_cannot_be_child: "Buyer 2 cannot have a working situation of child under 16."
age:
child_12_years_younger: "A child must be at least 12 years younger than their parent."
child_under_16: "Answer cannot be under 16 as person %{person_num}'s relationship to buyer 1 is ‘partner’."
child_under_16_ecstat: "Answer cannot be under 16 as person %{person_num}’s working situation is not ‘child under 16’, ‘other’ or ‘prefers not to say’."
child_over_16: "Answer cannot be over 16 as person’s %{person_num} working situation is ‘child under 16‘."
child_over_20: "Answer cannot be 20 or over as the relationship is ‘child’."
student_16_19:
cannot_be_16_19:
child_not_student: "Person cannot be aged 16-19 if they have relationship ‘child’ but are not a student."
must_be_16_19: "Person must be aged 16-19 if they are a student and have relationship ‘child’."
relat:
one_partner: "Number of partners cannot be greater than 1."
child_12_years_younger: "A child must be at least 12 years younger than their parent."
child_under_16: "Answer cannot be ‘partner’ as you told us person %{person_num}'s age is under 16."
child_over_20: "Answer cannot be ‘child’ if the person's age is 20 or over."
student_16_19:
cannot_be_child:
student_not_16_19: "Answer cannot be ‘child’ if the person is a student but not aged 16-19."
16_19_not_student: "Answer cannot be ‘child’ if the person is aged 16-19 but not a student."
ecstat:
child_under_16: "Person %{person_num}’s working situation must be ‘child under 16’, ‘other’ or ‘prefers not to say’ as you told us they’re under 16."
child_over_16: "Answer cannot be ‘child under 16’ as you told us the person %{person_num} is older than 16."
student_16_19:
must_be_student: "Person must be a student if they are aged 16-19 and have relationship ‘child’."
cannot_be_student:
child_not_16_19: "Person cannot be a student if they are not aged 16-19 but have relationship ‘child’."
buyer_cannot_be_over_16_and_child: "Buyer %{buyer_index}'s age cannot be 16 or over if their working situation is child under 16."

27
config/locales/validations/sales/property_information.en.yml

@ -0,0 +1,27 @@
en:
validations:
sales:
property_information:
postcode_full:
postcode_must_match_previous:
joint_purchase: "Buyers’ last accommodation and discounted ownership postcodes must match."
not_joint_purchase: "Buyer’s last accommodation and discounted ownership postcodes must match."
ppostcode_full:
postcode_must_match_previous:
joint_purchase: "Buyers’ last accommodation and discounted ownership postcodes must match."
not_joint_purchase: "Buyer’s last accommodation and discounted ownership postcodes must match."
ownershipsch:
postcode_must_match_previous:
joint_purchase: "Buyers’ last accommodation and discounted ownership postcodes must match."
not_joint_purchase: "Buyer’s last accommodation and discounted ownership postcodes must match."
uprn:
postcode_must_match_previous:
joint_purchase: "Buyers’ last accommodation and discounted ownership postcodes must match."
not_joint_purchase: "Buyer’s last accommodation and discounted ownership postcodes must match."
invalid: "UPRN must be 12 digits or less."
beds:
bedsits_have_max_one_bedroom: "Number of bedrooms must be 1 if the property is a bedsit."
proptype:
bedsits_have_max_one_bedroom: "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms."
uprn_known:
invalid: "You must answer UPRN known?"

10
config/routes.rb

@ -42,13 +42,17 @@ Rails.application.routes.draw do
get "collection-resources", to: "collection_resources#index"
get "/collection-resources/:log_type/:year/:resource_type/download", to: "collection_resources#download_mandatory_collection_resource", as: :download_mandatory_collection_resource
get "/collection-resources/:log_type/:year/:resource_type/edit", to: "collection_resources#edit", as: :edit_mandatory_collection_resource
patch "/collection-resources", to: "collection_resources#update", as: :update_mandatory_collection_resource
get "/collection-resources/:log_type/:year/:resource_type/edit", to: "collection_resources#edit_mandatory_collection_resource", as: :edit_mandatory_collection_resource
patch "/collection-resources", to: "collection_resources#update_mandatory_collection_resource", as: :update_mandatory_collection_resource
get "/collection-resources/:year/release", to: "collection_resources#confirm_mandatory_collection_resources_release", as: :confirm_mandatory_collection_resources_release
patch "/collection-resources/:year/release", to: "collection_resources#release_mandatory_collection_resources", as: :release_mandatory_collection_resources
resources :collection_resources, path: "/collection-resources" do
get "/download", to: "collection_resources#download_additional_collection_resource" # when we get to adding them
get "/download", to: "collection_resources#download_additional_collection_resource"
get "/edit", to: "collection_resources#edit_additional_collection_resource"
patch "/update", to: "collection_resources#update_additional_collection_resource"
get "/delete-confirmation", to: "collection_resources#delete_confirmation"
delete "/delete", to: "collection_resources#delete"
end
get "clear-filters", to: "sessions#clear_filters"

5
db/migrate/20241011112158_add_discarded_at.rb

@ -0,0 +1,5 @@
class AddDiscardedAt < ActiveRecord::Migration[7.0]
def change
add_column :collection_resources, :discarded_at, :datetime
end
end

3
db/schema.rb

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2024_10_08_100119) do
ActiveRecord::Schema[7.0].define(version: 2024_10_11_112158) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -61,6 +61,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_10_08_100119) do
t.boolean "released_to_user"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "discarded_at"
end
create_table "csv_variable_definitions", force: :cascade do |t|

10
spec/factories/collection_resource.rb

@ -6,5 +6,15 @@ FactoryBot.define do
year { 2024 }
log_type { "lettings" }
download_filename { "24_25_lettings_paper_form.pdf" }
mandatory { true }
trait(:additional) do
resource_type { nil }
display_name { "lettings additional resource (2024 to 2025)" }
short_display_name { "additional resource" }
year { 2024 }
log_type { "lettings" }
download_filename { "additional.pdf" }
mandatory { false }
end
end
end

73
spec/features/collection_resources_spec.rb

@ -5,6 +5,9 @@ RSpec.describe "Collection resources" do
let(:collection_resources_service) { instance_double(CollectionResourcesService, file_exists_on_s3?: true) }
before do
# rubocop:disable RSpec/AnyInstance
allow_any_instance_of(CollectionResourcesHelper).to receive(:editable_collection_resource_years).and_return([2024, 2025])
# rubocop:enable RSpec/AnyInstance
allow(CollectionResourcesService).to receive(:new).and_return(collection_resources_service)
allow(collection_resources_service).to receive(:upload_collection_resource)
allow(collection_resources_service).to receive(:get_file_metadata).and_return({ "content_type" => "application/pdf", "content_length" => 1000 })
@ -165,4 +168,74 @@ RSpec.describe "Collection resources" do
expect(page).to have_content("There was an error uploading this file.")
end
end
context "when uploading an additional resource" do
it "allows valid files" do
expect(CollectionResource.count).to eq(0)
visit(new_collection_resource_path(year: 2025, log_type: "sales"))
fill_in("collection_resource[short_display_name]", with: "some file")
attach_file "file", file_fixture("pdf_file.pdf")
click_button("Add resource")
expect(collection_resources_service).to have_received(:upload_collection_resource).with("pdf_file.pdf", anything)
expect(CollectionResource.count).to eq(1)
expect(CollectionResource.first.year).to eq(2025)
expect(CollectionResource.first.log_type).to eq("sales")
expect(CollectionResource.first.resource_type).to be_nil
expect(CollectionResource.first.mandatory).to be_falsey
expect(CollectionResource.first.released_to_user).to be_nil
expect(CollectionResource.first.display_name).to eq("sales some file (2025 to 2026)")
expect(CollectionResource.first.short_display_name).to eq("some file")
expect(page).to have_content("The sales 2025 to 2026 some file has been uploaded.")
end
it "validates file is attached" do
visit(new_collection_resource_path(year: 2025, log_type: "sales"))
fill_in("collection_resource[short_display_name]", with: "some file")
click_button("Add resource")
expect(page).to have_content("Select which file to upload.")
end
it "validates resource type is given" do
visit(new_collection_resource_path(year: 2025, log_type: "sales"))
attach_file "file", file_fixture("pdf_file.pdf")
click_button("Add resource")
expect(page).to have_content("You must answer resource type.")
end
end
context "when updating an additional resource" do
let!(:collection_resource) { create(:collection_resource, :additional, year: 2025, log_type: "sales") }
it "only allows valid files" do
expect(CollectionResource.count).to eq(1)
visit(collection_resource_edit_path(collection_resource))
fill_in("collection_resource[short_display_name]", with: "some updated file")
attach_file "file", file_fixture("pdf_file.pdf")
click_button("Save changes")
expect(collection_resources_service).to have_received(:upload_collection_resource).with("pdf_file.pdf", anything)
expect(CollectionResource.count).to eq(1)
expect(CollectionResource.first.year).to eq(2025)
expect(CollectionResource.first.log_type).to eq("sales")
expect(CollectionResource.first.resource_type).to be_nil
expect(CollectionResource.first.mandatory).to be_falsey
expect(CollectionResource.first.released_to_user).to be_nil
expect(CollectionResource.first.display_name).to eq("sales some updated file (2025 to 2026)")
expect(CollectionResource.first.short_display_name).to eq("some updated file")
expect(page).to have_content("The sales 2025 to 2026 some updated file has been updated.")
end
it "validates file is attached" do
visit(collection_resource_edit_path(collection_resource))
fill_in("collection_resource[short_display_name]", with: "some file")
click_button("Save changes")
expect(page).to have_content("Select which file to upload.")
end
end
end

12
spec/helpers/collection_resources_helper_spec.rb

@ -94,9 +94,9 @@ RSpec.describe CollectionResourcesHelper do
context "and next year resources were manually released" do
before do
CollectionResource.create!(year: 2025, resource_type: "paper_form", display_name: "lettings log for tenants (2025 to 2026)", download_filename: "file.pdf", mandatory: true, released_to_user: true)
CollectionResource.create!(year: 2025, resource_type: "bulk_upload_template", display_name: "bulk upload template (2025 to 2026)", download_filename: "file.xlsx", mandatory: true, released_to_user: true)
CollectionResource.create!(year: 2025, resource_type: "bulk_upload_specification", display_name: "sales log for tenants (2025 to 2026)", download_filename: "file.xlsx", mandatory: true, released_to_user: true)
create(:collection_resource, year: 2025, resource_type: "paper_form", display_name: "lettings log for tenants (2025 to 2026)", download_filename: "file.pdf", mandatory: true, released_to_user: true)
create(:collection_resource, year: 2025, resource_type: "bulk_upload_template", display_name: "bulk upload template (2025 to 2026)", download_filename: "file.xlsx", mandatory: true, released_to_user: true)
create(:collection_resource, year: 2025, resource_type: "bulk_upload_specification", display_name: "sales log for tenants (2025 to 2026)", download_filename: "file.xlsx", mandatory: true, released_to_user: true)
end
it "reutrns current and next years" do
@ -199,9 +199,9 @@ RSpec.describe CollectionResourcesHelper do
context "and the resources have been manually released" do
before do
CollectionResource.create!(year: 2025, resource_type: "paper_form", display_name: "lettings log for tenants (2025 to 2026)", download_filename: "file.pdf", mandatory: true, released_to_user: true)
CollectionResource.create!(year: 2025, resource_type: "bulk_upload_template", display_name: "bulk upload template (2025 to 2026)", download_filename: "file.xlsx", mandatory: true, released_to_user: true)
CollectionResource.create!(year: 2025, resource_type: "bulk_upload_specification", display_name: "sales log for tenants (2025 to 2026)", download_filename: "file.xlsx", mandatory: true, released_to_user: true)
create(:collection_resource, year: 2025, resource_type: "paper_form", display_name: "lettings log for tenants (2025 to 2026)", download_filename: "file.pdf", mandatory: true, released_to_user: true)
create(:collection_resource, year: 2025, resource_type: "bulk_upload_template", display_name: "bulk upload template (2025 to 2026)", download_filename: "file.xlsx", mandatory: true, released_to_user: true)
create(:collection_resource, year: 2025, resource_type: "bulk_upload_specification", display_name: "sales log for tenants (2025 to 2026)", download_filename: "file.xlsx", mandatory: true, released_to_user: true)
end
it "returns false" do

3
spec/models/form/sales/pages/buyer1_income_max_value_check_spec.rb

@ -5,7 +5,8 @@ RSpec.describe Form::Sales::Pages::Buyer1IncomeMaxValueCheck, type: :model do
let(:page_id) { "prefix_buyer_1_income_max_value_check" }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
let(:subsection) { instance_double(Form::Subsection, form:) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)

3
spec/models/form/sales/pages/buyer1_income_min_value_check_spec.rb

@ -5,7 +5,8 @@ RSpec.describe Form::Sales::Pages::Buyer1IncomeMinValueCheck, type: :model do
let(:page_id) { "prefix_buyer_1_income_min_value_check" }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
let(:subsection) { instance_double(Form::Subsection, form:) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)

3
spec/models/form/sales/pages/buyer2_income_max_value_check_spec.rb

@ -5,7 +5,8 @@ RSpec.describe Form::Sales::Pages::Buyer2IncomeMaxValueCheck, type: :model do
let(:page_id) { "prefix_buyer_2_income_max_value_check" }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
let(:subsection) { instance_double(Form::Subsection, form:) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)

3
spec/models/form/sales/pages/buyer2_income_min_value_check_spec.rb

@ -5,7 +5,8 @@ RSpec.describe Form::Sales::Pages::Buyer2IncomeMinValueCheck, type: :model do
let(:page_id) { "prefix_buyer_2_income_min_value_check" }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
let(:subsection) { instance_double(Form::Subsection, form:) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)

3
spec/models/form/sales/pages/combined_income_max_value_check_spec.rb

@ -5,7 +5,8 @@ RSpec.describe Form::Sales::Pages::CombinedIncomeMaxValueCheck, type: :model do
let(:page_id) { "prefix_combined_income_max_value_check" }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
let(:subsection) { instance_double(Form::Subsection, form:) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)

3
spec/models/form/sales/pages/deposit_value_check_spec.rb

@ -5,7 +5,8 @@ RSpec.describe Form::Sales::Pages::DepositValueCheck, type: :model do
let(:page_id) { "deposit_value_check" }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
let(:subsection) { instance_double(Form::Subsection, form:) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)

3
spec/models/form/sales/pages/household_wheelchair_check_spec.rb

@ -5,7 +5,8 @@ RSpec.describe Form::Sales::Pages::HouseholdWheelchairCheck, type: :model do
let(:page_id) { "buyer_1_income_mortgage_value_check" }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
let(:subsection) { instance_double(Form::Subsection, form:) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)

3
spec/models/form/sales/pages/mortgage_value_check_spec.rb

@ -6,7 +6,8 @@ RSpec.describe Form::Sales::Pages::MortgageValueCheck, type: :model do
let(:page_id) { "buyer_1_income_mortgage_value_check" }
let(:page_definition) { nil }
let(:index) { 1 }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
let(:subsection) { instance_double(Form::Subsection, form:) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)

3
spec/models/form/sales/pages/savings_value_check_spec.rb

@ -5,7 +5,8 @@ RSpec.describe Form::Sales::Pages::SavingsValueCheck, type: :model do
let(:page_id) { "savings_value_check" }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
let(:subsection) { instance_double(Form::Subsection, form:) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)

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

Loading…
Cancel
Save